blob: 84ef069c97c1d090211d34397f2af581ec47d365 [file]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package bazel2gn
import (
"fmt"
"go.starlark.net/syntax"
)
// defaultBazelSelectCondition is the default match-the-rest select condition.
const defaultBazelSelectCondition = "//conditions:default"
// bazelSelectConditionToGN maps from Bazel select conditions (the keys of the
// dictionaries to select calls) to GN condition variables (the condition to
// check in if statements).
var bazelSelectConditionToGN = map[string]string{
"@platforms//os:fuchsia": "is_fuchsia",
"@platforms//os:linux": "is_linux",
"@platforms//cpu:x86_64": `current_cpu == "x64"`,
"@platforms//cpu:arm64": `current_cpu == "arm64"`,
"@platforms//cpu:riscv64": `current_cpu == "riscv64"`,
"//build/bazel/platforms:is_host_os": "is_host",
"//build/bazel/platforms:is_fuchsia_with_sdk_rules": "false", // GN build is always platform.
"//build/bazel/platforms:is_fuchsia_platform": "is_fuchsia",
"//build/bazel/platforms:is_fuchsia_x64": `is_fuchsia && current_cpu == "x64"`,
"//build/bazel/platforms:is_fuchsia_arm64": `is_fuchsia && current_cpu == "arm64"`,
"//build/bazel/platforms:is_fuchsia_riscv64": `is_fuchsia && current_cpu == "riscv64"`,
"//build/bazel/platforms:is_linux_x64": `is_linux && current_cpu == "x64"`,
"//build/bazel/platforms:is_linux_arm64": `is_linux && current_cpu == "arm64"`,
}
// Returns true iff select call or conditional expression are found in the subtree of `expr`.
func hasBranching(expr syntax.Expr) bool {
expr = unwrapParenExpr(expr)
if isSelectCall(expr) {
return true
}
if _, ok := expr.(*syntax.CondExpr); ok {
return true
}
binaryExpr, ok := expr.(*syntax.BinaryExpr)
if ok {
return hasBranching(binaryExpr.X) || hasBranching(binaryExpr.Y)
}
return false
}
// Returns true iff the input expression is a select call.
func isSelectCall(expr syntax.Expr) bool {
expr = unwrapParenExpr(expr)
fn, ok := expr.(*syntax.CallExpr)
if !ok {
return false
}
return fn.Fn.(*syntax.Ident).Name == "select"
}
// Adds additional statements needed for `sdk_headers_for_internal_use`.
// While the files in GN's `sdk_headers_for_internal_use` are included
// in `public` (or `sources`), that is not the case for Bazel's
// `hdrs_for_internal_use`. To match the GN behavior, add all the files
// specified in `hdrs_for_internal_use` to `public` in the GN target.
// For more information, see `idk_cc_source_library()`.
func handle_sdk_headers_for_internal_use(ret []string, rhs []string, indentLevel int) []string {
ret = append(ret, indent([]string{fmt.Sprintf("public += %s", rhs[0])}, indentLevel)...)
ret = append(ret, indent(rhs[1:], indentLevel)...)
return ret
}
// Converts list concatenation with select calls in them to GN.
func listConcatWithSelectToGN(attrName string, expr syntax.Expr, transformers []transformer) ([]string, error) {
for _, ts := range transformers {
var err error
expr, err = ts(expr)
if err != nil {
return nil, fmt.Errorf("applying special handler before converting list concatenation with select: %v", err)
}
}
expr = unwrapParenExpr(expr)
switch v := expr.(type) {
case *syntax.CallExpr:
return selectToGN(attrName, "+=", v, transformers)
case *syntax.CondExpr:
return condExprToGN(attrName, "+=", v, transformers)
case *syntax.ListExpr:
l, err := listExprToGN(v, transformers)
if err != nil {
return nil, fmt.Errorf("converting list expression: %v", err)
}
ret := append(
[]string{fmt.Sprintf("%s += %s", attrName, l[0])},
l[1:]...,
)
if attrName == "sdk_headers_for_internal_use" {
ret = handle_sdk_headers_for_internal_use(ret, l, 0)
}
return ret, nil
case *syntax.BinaryExpr:
if v.Op != syntax.PLUS {
return nil, fmt.Errorf("only list concatenation are support, found unexpected operator %s", v.Op)
}
// Traversal order is important here. Left-first to ensure the order of list
// items, which is important in attributes like copts.
lhs, err := listConcatWithSelectToGN(attrName, v.X, transformers)
if err != nil {
return nil, fmt.Errorf("converting lhs of concatenation: %v", err)
}
rhs, err := listConcatWithSelectToGN(attrName, v.Y, transformers)
if err != nil {
return nil, fmt.Errorf("converting rhs of concatenation: %v", err)
}
return append(lhs, rhs...), nil
case *syntax.Ident:
l, err := identToGN(v)
if err != nil {
return nil, fmt.Errorf("converting identifier: %v", err)
}
ret := []string{fmt.Sprintf("%s += %s", attrName, l[0])}
if attrName == "sdk_headers_for_internal_use" {
ret = handle_sdk_headers_for_internal_use(ret, l, 0)
}
return ret, nil
default:
return nil, fmt.Errorf("converting list concatenation with select to GN, want call expression, binary expression, list expression, or identifier, got %T", expr)
}
}
// Convert select nodes (which are syntax.CallExpr) to GN fragments.
func selectToGN(attrName string, op string, expr *syntax.CallExpr, transformers []transformer) ([]string, error) {
fn := expr.Fn.(*syntax.Ident)
if fn.Name != "select" {
return nil, fmt.Errorf("want select call, got %s", fn.Name)
}
if len(expr.Args) == 0 {
return nil, fmt.Errorf("no args found for select, expect at least one arg")
}
var ret []string
for _, entry := range expr.Args[0].(*syntax.DictExpr).List {
e := entry.(*syntax.DictEntry)
key, ok := e.Key.(*syntax.Literal)
if !ok {
return nil, fmt.Errorf("only literals are supported as keys in dictionaries in selects")
}
// key.Raw is quoted, so unquote to get the string value.
selectCondition := key.Raw[1 : len(key.Raw)-1]
valueInGN, err := exprToGN(e.Value, transformers)
if err != nil {
return nil, fmt.Errorf("converting dictionary value in select to GN: %v", err)
}
if selectCondition == defaultBazelSelectCondition {
if len(ret) == 0 {
return nil, fmt.Errorf("default select condition %q found with no other matching cases", selectCondition)
}
if op == "+=" && len(valueInGN) == 2 && valueInGN[0] == "[" && valueInGN[1] == "]" {
// The default case is just adding an empty list, so skip it.
continue
}
ret[len(ret)-1] += " else {"
} else {
gnCondition, ok := bazelSelectConditionToGN[selectCondition]
if !ok {
return nil, fmt.Errorf("unknown Bazel select condition %q in select", selectCondition)
}
if len(ret) > 0 {
ret[len(ret)-1] += fmt.Sprintf(" else if (%s) {", gnCondition)
} else {
ret = append(ret, fmt.Sprintf("if (%s) {", gnCondition))
}
}
ret = append(ret, indent(
[]string{fmt.Sprintf("%s %s %s", attrName, op, valueInGN[0])},
1,
)...)
ret = append(ret, indent(valueInGN[1:], 1)...)
if attrName == "sdk_headers_for_internal_use" {
ret = handle_sdk_headers_for_internal_use(ret, valueInGN, 1)
}
ret = append(ret, "}")
}
if len(expr.Args) > 1 {
noMatchError, ok := expr.Args[1].(*syntax.BinaryExpr)
if !ok || noMatchError.X.(*syntax.Ident).Name != "no_match_error" {
return nil, fmt.Errorf("the second arg of select must be `no_match_error`, got %q", noMatchError.X.(*syntax.Ident).Name)
}
errStr, ok := noMatchError.Y.(*syntax.Literal)
if !ok {
return nil, fmt.Errorf("value of `no_match_error` must be a literal string, got %T", noMatchError.Y)
}
ret[len(ret)-1] += " else {"
ret = append(ret, indent(
[]string{
// NOTE: raw is quoted so we don't need to quote again.
fmt.Sprintf("assert(false, %s)", errStr.Raw),
}, 1)...,
)
ret = append(ret, "}")
}
return ret, nil
}
// condExprToGN converts conditional expressions (syntax.CondExpr) to GN fragments.
func condExprToGN(attrName string, op string, expr *syntax.CondExpr, transformers []transformer) ([]string, error) {
gnConditionLines, err := exprToGN(expr.Cond, nil)
if err != nil {
return nil, fmt.Errorf("converting condition in cond expr to GN: %v", err)
}
if len(gnConditionLines) != 1 {
return nil, fmt.Errorf("expected single line for condition, got %d lines", len(gnConditionLines))
}
gnCondition := gnConditionLines[0]
var trueInGN []string
if hasBranching(expr.True) {
lines, err := listConcatWithSelectToGN(attrName, expr.True, transformers)
if err != nil {
return nil, fmt.Errorf("converting true branch in conditional to GN: %v", err)
}
if op == "=" {
trueInGN = append([]string{fmt.Sprintf("%s = []", attrName)}, lines...)
} else {
trueInGN = lines
}
} else {
lines, err := exprToGN(expr.True, transformers)
if err != nil {
return nil, fmt.Errorf("converting true branch in conditional to GN: %v", err)
}
trueInGN = append([]string{fmt.Sprintf("%s %s %s", attrName, op, lines[0])}, lines[1:]...)
if attrName == "sdk_headers_for_internal_use" {
trueInGN = handle_sdk_headers_for_internal_use(trueInGN, lines, 0)
}
}
var falseInGN []string
if hasBranching(expr.False) {
lines, err := listConcatWithSelectToGN(attrName, expr.False, transformers)
if err != nil {
return nil, fmt.Errorf("converting false branch in conditional to GN: %v", err)
}
if op == "=" {
falseInGN = append([]string{fmt.Sprintf("%s = []", attrName)}, lines...)
} else {
falseInGN = lines
}
} else {
lines, err := exprToGN(expr.False, transformers)
if err != nil {
return nil, fmt.Errorf("converting false branch in conditional to GN: %v", err)
}
if op == "+=" && len(lines) == 2 && lines[0] == "[" && lines[1] == "]" {
falseInGN = nil
} else {
falseInGN = append([]string{fmt.Sprintf("%s %s %s", attrName, op, lines[0])}, lines[1:]...)
if attrName == "sdk_headers_for_internal_use" {
falseInGN = handle_sdk_headers_for_internal_use(falseInGN, lines, 0)
}
}
}
var ret []string
ret = append(ret, fmt.Sprintf("if (%s) {", gnCondition))
ret = append(ret, indent(trueInGN, 1)...)
if falseInGN == nil {
ret = append(ret, "}")
} else {
ret = append(ret, "} else {")
ret = append(ret, indent(falseInGN, 1)...)
ret = append(ret, "}")
}
return ret, nil
}