| // 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" |
| "strings" |
| |
| "go.starlark.net/syntax" |
| ) |
| |
| // transformer is a function type that can be used by `exprToGN` to apply |
| // special transformations to expression nodes before conversion. |
| // |
| // This is useful for rewriting certain string values, recording values during |
| // traversal, or even restructuring the syntax tree. |
| type transformer func(syntax.Expr) (syntax.Expr, error) |
| |
| // bazelVisibilityToGN converts Bazel visibility values [0] to GN [1]. |
| // |
| // NOTE: Bazel visibility is based on package groups [2], while GN visibility is |
| // based on target. However it should be possible to create matching groups in |
| // GN for the exact same visibility control in the most granular cases. |
| // |
| // [0] https://bazel.build/concepts/visibility#visibility-specifications |
| // [1] https://gn.googlesource.com/gn/+/main/docs/reference.md#var_visibility |
| // [2] https://bazel.build/reference/be/functions#package_group |
| func bazelVisibilityToGN(expr syntax.Expr) (syntax.Expr, error) { |
| lit, ok := expr.(*syntax.Literal) |
| if !ok { |
| return expr, nil |
| } |
| switch { |
| case lit.Raw == `"//visibility:public"`: |
| lit.Raw = `"*"` |
| case lit.Raw == `"//visibility:private"`: |
| lit.Raw = `":*"` |
| case lit.Raw == `":__subpackages__"`: |
| // The suffix match below would result in `"/*"`, which is equivalent to |
| // public, so we must handle this case explicitly. |
| lit.Raw = `"./*"` |
| case strings.HasSuffix(lit.Raw, `:__pkg__"`): |
| lit.Raw = strings.ReplaceAll(lit.Raw, `:__pkg__"`, `:*"`) |
| case strings.HasSuffix(lit.Raw, `:__subpackages__"`): |
| lit.Raw = strings.ReplaceAll(lit.Raw, `:__subpackages__"`, `/*"`) |
| default: |
| // This is a Bazel visibility on a package group, it should stay unchanged. |
| // In GN there should be a target matching the path of this package group. |
| } |
| return lit, nil |
| } |
| |
| // bazelCOptToGNConfig converts Bazel copts values to GN configs. |
| func bazelCOptToGNConfig(expr syntax.Expr) (syntax.Expr, error) { |
| lit, ok := expr.(*syntax.Literal) |
| if !ok { |
| return expr, nil |
| } |
| coptWithoutQuotes := lit.Raw[1 : len(lit.Raw)-1] |
| config, ok := coptToConfig[coptWithoutQuotes] |
| if !ok { |
| return nil, fmt.Errorf("unexpected copt %s", coptWithoutQuotes) |
| } |
| lit.Raw = fmt.Sprintf(`"%s"`, config) |
| return lit, nil |
| } |
| |
| // bazelDepToGN converts Bazel dependency paths to GN ones. |
| // |
| // This function is expected to encapsulate information specific to the Fuchsia |
| // build system. Ideally the problem solved here should be solved in the build |
| // system (e.g. move location of build files), so this tool packs less surprises. |
| func bazelDepToGN(expr syntax.Expr) (syntax.Expr, error) { |
| lit, ok := expr.(*syntax.Literal) |
| if !ok { |
| return expr, nil |
| } |
| |
| if dep, ok := overwrittenPath(lit); ok { |
| lit.Raw = fmt.Sprintf(`"%s"`, dep) |
| return lit, nil |
| } |
| |
| for bazelRepo, gnRepo := range thirdPartyBazelRepos { |
| quotedBazelRepo := fmt.Sprintf(`"%s"`, bazelRepo) |
| if lit.Raw == quotedBazelRepo { |
| lit.Raw = fmt.Sprintf(`"%s"`, gnRepo) |
| return lit, nil |
| } |
| } |
| |
| lit.Raw = thirdPartyRustCrateVendoredRE.ReplaceAllString( |
| lit.Raw, |
| `"//third_party/rust_crates:`, |
| ) |
| |
| lit.Raw = thirdPartyRustCrateModifiedRE.ReplaceAllString( |
| lit.Raw, |
| `"//third_party/rust_crates:$1`, |
| ) |
| |
| // Replace matching Go third-party dependencies with aggregate dependencies. |
| // |
| // For example: |
| // |
| // "//third_party/golibs:golang.org/x/crypto/ssh" -> "//third_party/golibs:golang.org/x/crypto" |
| // "//third_party/golibs:google.golang.org/protobuf/encoding/protojson" -> "//third_party/golibs:google.golang.org/protobuf" |
| // |
| // Note this can result in duplicate deps in GN, which should be fixed in |
| // later steps in `bazel2gn` by `gn format`. |
| for _, dep := range goThirdPartyAggregateDeps { |
| if strings.HasPrefix(lit.Raw, fmt.Sprintf(`"%s/`, dep)) { |
| lit.Raw = fmt.Sprintf(`"%s"`, dep) |
| break |
| } |
| } |
| |
| return lit, nil |
| } |
| |
| // overwrittenPath returns the path overwritten by comments, if any. |
| // |
| // It returns the path and true if the path is overwritten, otherwise it returns |
| // an empty string and false. |
| func overwrittenPath(lit *syntax.Literal) (string, bool) { |
| comments := lit.Comments() |
| if comments != nil { |
| for _, c := range comments.Suffix { |
| if strings.HasPrefix(c.Text, pathOverwriteAnnotationPrefix) { |
| return strings.TrimSpace(c.Text[len(pathOverwriteAnnotationPrefix):]), true |
| } |
| } |
| } |
| return "", false |
| } |
| |
| // overwrittenRaw returns the raw expression overwritten by comments, if any. |
| // |
| // It returns the raw expression and true if it is overwritten, otherwise it returns |
| // an empty string and false. |
| func overwrittenRaw(lit *syntax.Literal) (string, bool) { |
| comments := lit.Comments() |
| if comments != nil { |
| for _, c := range comments.Suffix { |
| if strings.HasPrefix(c.Text, rawOverwriteAnnotationPrefix) { |
| return strings.TrimSpace(c.Text[len(rawOverwriteAnnotationPrefix):]), true |
| } |
| } |
| } |
| return "", false |
| } |
| |
| // overwrittenValue returns the value overwritten by comments, if any. |
| // |
| // It returns the value and true if the value is overwritten, otherwise it returns |
| // an empty string and false. |
| func overwrittenValue(entry *syntax.DictEntry) (string, bool) { |
| if comments := entry.Comments(); comments != nil { |
| for _, c := range comments.Suffix { |
| if strings.HasPrefix(c.Text, valueOverwriteAnnotationPrefix) { |
| return strings.TrimSpace(c.Text[len(valueOverwriteAnnotationPrefix):]), true |
| } |
| } |
| } |
| return "", false |
| } |
| |
| // bazelFilePathsToGN converts Bazel file paths to GN file paths, handling |
| // overwritten paths. |
| func bazelFilePathsToGN(expr syntax.Expr) (syntax.Expr, error) { |
| lit, ok := expr.(*syntax.Literal) |
| if !ok { |
| return expr, nil |
| } |
| if path, ok := overwrittenPath(lit); ok { |
| lit.Raw = fmt.Sprintf(`"%s"`, path) |
| } |
| lit.Raw = strings.Replace(lit.Raw, ":", "/", 1) |
| return lit, nil |
| } |
| |
| // bazelLdflagsToGN converts Bazel ldflags to GN, supporting overwrites. |
| func bazelLdflagsToGN(expr syntax.Expr) (syntax.Expr, error) { |
| lit, ok := expr.(*syntax.Literal) |
| if !ok { |
| return expr, nil |
| } |
| if flag, ok := overwrittenRaw(lit); ok { |
| lit.Raw = flag |
| return lit, nil |
| } |
| if flag, ok := overwrittenPath(lit); ok { |
| lit.Raw = fmt.Sprintf(`"%s"`, flag) |
| } |
| return lit, nil |
| } |