| // 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" |
| "maps" |
| "regexp" |
| |
| "go.starlark.net/syntax" |
| ) |
| |
| // indentPrefix is the string value used to indent a line by one level. |
| // |
| // NOTE: This prefix is only used by the internal representation of this package. |
| // The final output is formatted by `gn format`. |
| const indentPrefix = "\t" |
| |
| // clearAnnotation is a comment annotation that indicates the assignment is an explicit clear. |
| const clearAnnotation = "# @bazel2gn:clear" |
| |
| // skipAnnotation is a comment annotation that indicates the statement should be skipped. |
| const skipAnnotation = "# @bazel2gn:skip" |
| |
| // pathOverwriteAnnotationPrefix is a comment annotation prefix that indicates a path should be |
| // overwritten in GN. In this case bazel2gn will ignore the value set in the BUILD.bazel file |
| // and use the value specified in the annotation in the BUILD.gn file instead. |
| const pathOverwriteAnnotationPrefix = "# @bazel2gn:path_overwrite:" |
| |
| // rawOverwriteAnnotationPrefix is a comment annotation prefix that indicates a value should be |
| // overwritten with a raw GN expression. |
| const rawOverwriteAnnotationPrefix = "# @bazel2gn:raw_overwrite:" |
| |
| // valueOverwriteAnnotationPrefix is a comment annotation prefix that indicates a value of a |
| // dictionary entry should be overwritten in GN. In this case bazel2gn will ignore the value set |
| // in the BUILD.bazel file and use the value specified in the annotation in the BUILD.gn file |
| // instead. |
| const valueOverwriteAnnotationPrefix = "# @bazel2gn:value_overwrite:" |
| |
| // transformerAnnotationPrefix is a comment annotation prefix that indicates a transformer |
| // should be applied to the next statement. |
| const transformerAnnotationPrefix = "# @bazel2gn:transformer=" |
| |
| // transformerAnnotationNames maps from transformer annotation names to the transformer functions. |
| var transformerAnnotationNames = map[string]transformer{ |
| "visibility": bazelVisibilityToGN, |
| "deps": bazelDepToGN, |
| "configs": bazelCOptToGNConfig, |
| "file_paths": bazelFilePathsToGN, |
| } |
| |
| // bazelRuleToGNTemplate maps from Bazel rule names to GN template names. They can |
| // be the same if Bazel and GN shared the same template name. |
| // |
| // This map is also used to check known Bazel rules that can be converted to GN. |
| // i.e. Bazel rules not found in this map is not supported by bazel2gn yet. |
| var bazelRuleToGNTemplate = map[string]string{ |
| // Go |
| "go_binary": "go_binary", |
| "go_library": "go_library", |
| "go_test": "go_test", |
| |
| // Python |
| "py_library": "python_library", |
| "py_binary": "python_binary", |
| |
| // Rust |
| "rust_binary": "rustc_binary", |
| "rust_library": "rustc_library", |
| "rustc_binary": "rustc_binary", |
| "rustc_library": "rustc_library", |
| "rustc_proc_macro": "rustc_macro", |
| "rustc_test": "rustc_test", |
| "rust_proc_macro": "rustc_macro", |
| |
| // C++ |
| "cc_library": "static_library", |
| "cc_binary": "executable", |
| "fx_cc_library_headers": "library_headers", |
| "fx_cc_library": "static_library", |
| |
| // C++ Zircon |
| "cc_shared_library_zx": "zx_library", // With `sdk="shared"` and `sdk_publishable` not specified. |
| "cc_source_library_zx": "zx_library", // With `sdk="source"` and `sdk_publishable` not specified. |
| "cc_static_library_zx": "zx_library", // With `sdk="static"` and `sdk_publishable` not specified. |
| |
| // FIDL |
| "fidl_library": "fidl", |
| "zither_fidl_library": "fidl", |
| |
| // Host tools |
| "cc_binary_host_tool": "executable", |
| "ffx_tool": "ffx_tool", |
| "ffx_plugin": "ffx_plugin", |
| "go_binary_host_tool": "go_binary", |
| "install_host_tools": "install_host_tools", |
| |
| // IDK |
| "idk_cc_shared_library": "sdk_shared_library", |
| "idk_cc_shared_library_zx": "zx_library", // With `sdk="shared"` and `sdk_publishable = "partner"`. |
| "idk_cc_source_library": "sdk_source_set", |
| "idk_cc_source_library_zx": "zx_library", // With `sdk="source"` and `sdk_publishable = "partner"`. |
| "idk_cc_static_library": "sdk_static_library", |
| "idk_cc_static_library_zx": "zx_library", // With `sdk="static"` and `sdk_publishable = "partner"`. |
| "idk_cc_binary_host_tool": "sdk_executable_host_tool", |
| "idk_go_binary_host_tool": "sdk_go_binary_host_tool", |
| "idk_rustc_binary_host_tool": "sdk_rustc_binary_host_tool", |
| |
| // Other |
| "fidlgentest_go_test": "fidlgentest_go_test", |
| "genrule": "action", |
| "package": "package", |
| "test_suite": "group", |
| "stamp_group": "group", |
| |
| // `exports_files()` is a concept specific to Bazel, so there is no need to convert it. |
| "exports_files": "__NO_GN_EQUIVALENT__", |
| } |
| |
| // attrsToOmitByRules stores a mapping from known Bazel rules to attributes to |
| // omit when converting them to GN. |
| var attrsToOmitByRules = map[string]map[string]bool{ |
| "go_library": { |
| // In GN we default cgo to true when compiling Go code, and explicitly disable |
| // it in very few places. However, in Bazel, cgo defaults to false, and |
| // require users to explicitly set when C sources are included. |
| "cgo": true, |
| |
| // go_library.gni doesn't allow overwriting `importpath`, and forces it to be the same as the |
| // directory path from FUCHSIA_DIR. This limits our ability to define multiple go_libraries in |
| // the same BUILD file. go_library.gni gets around this with `source_dir`, which does not |
| // exist in Bazel. So we omit syncing this attribute and let GN infer it, which handles nested |
| // directories correctly. In GN we omit `importpath` for almost all non-third-party go_libraries |
| // anyways. |
| // |
| // This is safe to do because incorrectly configured importpath would cause Go compilation to |
| // fail, so we will be alerted when importpaths are wrong in Bazel. |
| "importpath": true, |
| }, |
| "py_library": { |
| // `imports` attribute is a Bazel-only concept. Our GN python_library.gni template |
| // handles import paths differently than Bazel. |
| "imports": true, |
| }, |
| "genrule": { |
| // bazel2gn ignores the `tools` attribute of genrule, and tries to parse it out of the `cmd` |
| // attribute. It is the caller's responsibility to ensure that the `cmd` attribute contains |
| // the correct tools. Also note since `genrule` is converted to `action` in GN, the `cmd` |
| // attribute is converted to `script` and `args` in GN, so only one `tool` is supported. |
| "tools": true, |
| }, |
| // TODO(https://fxbug.dev/457605523): Support `includes` conversion to `configs` in GN. |
| // Currently the only use case is to set `includes = ["../.."]`, which is covered by |
| // `"//build/config:default_include_dirs"` in GN. |
| "cc_library": {"includes": true}, |
| "fx_cc_library": {"includes": true}, |
| "idk_cc_source_library": {"includes": true}, |
| "idk_cc_shared_library_zx": {"version_script": true}, |
| // We do not need to include the "stamp" destination in GN because it is implicitly created by |
| // the rule. |
| "stamp_group": {"stamp": true}, |
| } |
| |
| // Common Bazel attributes that use different names in GN. |
| var commonAttrMap = map[string]string{ |
| "srcs": "sources", |
| "hdrs": "public", |
| } |
| |
| // ccCommonAttrMap maps from attribute names common in Bazel CC rules to GN parameter names. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var ccCommonAttrMap = map[string]string{ |
| "copts": "configs", // Strings are converted to configs by `bazelCOptToGNConfig()`. |
| } |
| |
| // ccLibAttrMap maps from attribute names in Bazel CC library rules to GN parameter names. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var ccLibAttrMap = mustMergeMaps(ccCommonAttrMap, map[string]string{ |
| "deps": "public_deps", |
| "implementation_deps": "deps", |
| }) |
| |
| // rustCommonAttrMap maps from attribute names common in Bazel Rust rules to GN parameter names. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var rustCommonAttrMap = map[string]string{ |
| "compile_data": "inputs", |
| "data": "non_rust_deps", |
| "crate_features": "features", |
| "with_host_unit_tests": "with_unit_tests", |
| "rustc_flags": "rustflags", |
| "crate_root": "source_root", |
| "rustc_env": "rustenv", |
| } |
| |
| // rustBinAttrMap maps from attribute name in Bazel Rust binary rules to GN parameter names. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var rustBinAttrMap = mustMergeMaps(rustCommonAttrMap, map[string]string{ |
| "crate_name": "output_name", |
| }) |
| |
| // fidlAttrMap maps from attribute names in Bazel FIDL rules to GN parameter names. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var fidlAttrMap = map[string]string{ |
| "category": "sdk_category", |
| "deps": "public_deps", |
| "library_name": "name", |
| } |
| |
| // idkAttrMap maps from attribute name in Bazel IDK rules to GN parameter names. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var idkAttrMap = map[string]string{ |
| "api_area": "sdk_area", |
| "api_file_path": "api", |
| "idk_name": "sdk_name", |
| |
| // This renames the variables, but they must be made a `+=` inside a |
| // conditional block in `attrAssignmentToGN()`. The default |
| // platform-independent variable must be specified before these variables |
| // in the `BUILD.bazel` file. This may require using |
| // `# buildifier: leave-alone` above the macro. |
| "fuchsia_deps": "public_deps", |
| "fuchsia_implementation_deps": "deps", |
| "non_fuchsia_deps": "public_deps", |
| |
| // These are not identical because files in `hdrs_for_internal_use` need |
| // to be added to GN's `public` as well. This takes care of populating |
| // `sdk_headers_for_internal_use`, and `attrAssignmentToGN()` adds the files |
| // to `public`. |
| "hdrs_for_internal_use": "sdk_headers_for_internal_use", |
| } |
| |
| // Lists of attribute names that are specific to Fuchsia and non-Fuchsia builds. |
| // These are used to add appropriate conditions in the GN file. |
| var idkFuchsiaSpecificAttrs = map[string]bool{ |
| "fuchsia_deps": true, |
| "fuchsia_implementation_deps": true, |
| } |
| var idkNonFuchsiaSpecificAttrs = map[string]bool{ |
| "non_fuchsia_deps": true, |
| } |
| |
| // Maps from attribute name in Bazel IDK rules to GN `zx_library()` parameter |
| // names for attributes unique to `zx_library()` instances that are in the IDK. |
| // Use `idkZxAttrMap` instead. |
| // This map only includes attributes that have different names in Bazel and GN. |
| var zxInIDKAttrMap = map[string]string{ |
| "category": "sdk_publishable", |
| } |
| |
| // installHostToolAttrMap maps from attribute name in Bazel install host tool rules to GN parameter names. |
| var installHostToolAttrMap = map[string]string{ |
| "implementation_deps": "deps", |
| "tool_output_names": "outputs", |
| } |
| |
| // genruleAttrMap maps from attribute name in Bazel genrule to GN parameter names. |
| var genruleAttrMap = map[string]string{ |
| "outs": "outputs", |
| } |
| |
| // idkCcAttrMap maps from attribute name in Bazel IDK C++ rules to GN parameter names. |
| var idkCcAttrMap = mustMergeMaps(idkAttrMap, ccLibAttrMap) |
| |
| // idkZxAttrMap maps from attribute name in Bazel IDK C++ ZX rules to GN parameter names. |
| var idkZxAttrMap = mustMergeMaps(idkCcAttrMap, zxInIDKAttrMap) |
| |
| // idkFIDLAttrMap maps from attribute name in Bazel IDK FIDL rules to GN parameter names. |
| var idkFIDLAttrMap = mustMergeMaps(idkAttrMap, fidlAttrMap) |
| |
| // idkRustBinAttrMap maps from attribute name in Bazel IDK Rust binary rules to GN parameter names. |
| var idkRustBinAttrMap = mustMergeMaps(idkAttrMap, rustBinAttrMap) |
| |
| // pythonBinAttrMap maps from attribute name in Bazel Python binary rules to GN parameter names. |
| var pythonBinAttrMap = map[string]string{ |
| "main": "main_source", |
| } |
| |
| // A mapping from Bazel rule names to attribute mappings. |
| // Attribute mappings map from Bazel rule attributes that use different names in GN. |
| var attrMapsByRules = map[string]map[string]string{ |
| // Python |
| "py_binary": pythonBinAttrMap, |
| |
| // C++ |
| "cc_library": ccLibAttrMap, |
| "cc_binary": ccCommonAttrMap, |
| "fx_cc_library": ccLibAttrMap, |
| |
| // C++ Zircon |
| "cc_shared_library_zx": ccLibAttrMap, |
| "cc_source_library_zx": ccLibAttrMap, |
| "cc_static_library_zx": ccLibAttrMap, |
| |
| // Rust |
| "rust_binary": rustBinAttrMap, |
| "rust_library": rustCommonAttrMap, |
| "rust_proc_macro": rustCommonAttrMap, |
| "rustc_binary": rustBinAttrMap, |
| "rustc_library": rustCommonAttrMap, |
| "rustc_proc_macro": rustCommonAttrMap, |
| "rustc_test": rustCommonAttrMap, |
| |
| // FIDL |
| "fidl_library": idkFIDLAttrMap, |
| "zither_fidl_library": fidlAttrMap, |
| |
| // IDK |
| "idk_cc_shared_library": idkCcAttrMap, |
| "idk_cc_shared_library_zx": idkZxAttrMap, |
| "idk_cc_source_library": idkCcAttrMap, |
| "idk_cc_source_library_zx": idkZxAttrMap, |
| "idk_cc_static_library": idkCcAttrMap, |
| "idk_cc_static_library_zx": idkZxAttrMap, |
| "idk_cc_binary_host_tool": idkAttrMap, |
| "idk_go_binary_host_tool": idkAttrMap, |
| "idk_rustc_binary_host_tool": idkRustBinAttrMap, |
| |
| // Tools |
| "ffx_tool": rustBinAttrMap, |
| "install_host_tools": installHostToolAttrMap, |
| |
| // Others |
| "genrule": genruleAttrMap, |
| "test_suite": { |
| "tests": "deps", |
| }, |
| } |
| |
| var extraGnExpressionByRules = map[string]string{ |
| "cc_shared_library_zx": `sdk = "shared"`, |
| "cc_source_library_zx": `sdk = "source"`, |
| "cc_static_library_zx": `sdk = "static"`, |
| "idk_cc_shared_library_zx": `sdk = "shared"`, |
| "idk_cc_source_library_zx": `sdk = "source"`, |
| "idk_cc_static_library_zx": `sdk = "static"`, |
| "test_suite": `testonly = true`, |
| } |
| |
| // These identifiers with the same meanings are represented differently in Bazel |
| // and GN. specialIdentifiers maps from their Bazel representations to GN |
| // representations. |
| var specialIdentifiers = map[string]string{ |
| "True": "true", |
| "False": "false", |
| } |
| |
| // specialTokens maps from special tokens in Bazel to their GN equivalents. |
| var specialTokens = map[syntax.Token]string{ |
| syntax.AND: "&&", |
| syntax.OR: "||", |
| } |
| |
| // The following map from Bazel constraints to GN conditions. |
| // |
| // The first contains single constraints that appear in a list. |
| // The second contains variables representing an entire list. |
| // |
| // Only add new values when necessary as there are often more appropriate ways |
| // to express the same logic. For example, "HOST_CONSTRAINTS" is more |
| // appropriate than "@platforms//os:linux" in most cases. Also, |
| // "//build/bazel/platforms:fuchsia_platform_x64" may be more appropriate |
| // than using a list of "@platforms//os:fuchsia" and "@platforms//cpu:x86_64". |
| var bazelConstraintsToGNConditions = map[string]string{ |
| "@platforms//os:fuchsia": "is_fuchsia", |
| } |
| var bazelConstraintListVarsToGNConditions = map[string]string{ |
| "HOST_CONSTRAINTS": "is_host", |
| "HOST_OS_CONSTRAINTS": "is_host", |
| } |
| |
| // thirdPartyRustCrateVendoredRE matches Bazel third-party Rust crate dependency prefixes for purely |
| // vendored crates. It is used to extract the crate name from the dependency path. |
| var thirdPartyRustCrateVendoredRE = regexp.MustCompile(`^"\/\/third_party\/rust_crates\/vendor:`) |
| |
| // thirdPartyRustCrateModifiedRE matches Bazel third-party Rust crate dependency prefixes for |
| // modified crates. It is used to extract the directory name from the dependency path. |
| var thirdPartyRustCrateModifiedRE = regexp.MustCompile(`^"\/\/third_party\/rust_crates\/.+\/([a-zA-Z0-9_\.-]+):?[^"]*`) |
| |
| // thirdPartyBazelRepos maps from Bazel third-party repository names to their GN equivalent |
| // dependency paths. The key is the Bazel repository name, and the value is the GN dependency |
| // path. |
| var thirdPartyBazelRepos = map[string]string{ |
| "@re2": "//third_party/re2", |
| "@boringssl//:crypto": "//third_party/boringssl:crypto", |
| } |
| |
| // coptToConfig maps from Bazel copt values to configs to use in GN. |
| var coptToConfig = map[string]string{ |
| "-Wno-implicit-fallthrough": "//build/config:Wno-implicit-fallthrough", |
| "-Wno-vla-cxx-extension": "//build/config:Wno-vla-cxx-extension", |
| "-Wno-deprecated-pragma": "//build/config:Wno-deprecated-pragma", |
| "-Wno-conversion": "//build/config:Wno-conversion", |
| |
| // The following are GN `configs` rather than `copt` values. These must be |
| // allowed because they both appear as `configs` by the time this map is used. |
| // TODO(https://fxbug.dev/421888626): Properly specify the relevant flags in |
| // Bazel and convert those to GN configs. |
| ":ring-config": ":ring-config", |
| "//build/config/fuchsia:no_cpp_standard_library": "//build/config/fuchsia:no_cpp_standard_library", |
| "//build/config:all_source": "//build/config:all_source", |
| } |
| |
| // attrGNAssignmentOps maps from GN attribute names to the assignment operators to use in GN. |
| // |
| // NOTE: Entries in this map should be clearly documented. |
| var attrGNAssignmentOps = map[string]string{ |
| // `configs` in GN are rarely (never?) empty lists because we set them in BUILDCONFIG.gn. |
| // Trying to overwrite a non-empty list in GN with a non-empty value will fail. |
| // Simply replacing assignment with `+=` works for the initial use cases we need. |
| // More complex mechanism may be required if we need to selectively overwrite config assignments. |
| "configs": "+=", |
| } |
| |
| // goThirdPartyAggregateDeps lists known third-party GN targets that include sources of multiple Go |
| // libraries in a single target. These deps need to be handled specially because in Bazel we have a |
| // 1:1 mapping between go_library targets and Go libraries. |
| var goThirdPartyAggregateDeps = []string{ |
| "//third_party/golibs:golang.org/x/crypto", |
| "//third_party/golibs:gonum.org/v1/gonum", |
| "//third_party/golibs:google.golang.org/protobuf", |
| "//third_party/golibs:gvisor.dev/gvisor", |
| } |
| |
| // mustMergeMaps merges two input maps and return a new one with keys and values from |
| // both inputs. |
| // |
| // This function panics if m1 and m2 have duplicate keys. All these cases should be |
| // caught at build time so it's OK to panic here. |
| func mustMergeMaps(m1, m2 map[string]string) map[string]string { |
| var m = maps.Clone(m1) |
| for k, v := range m2 { |
| if _, ok := m[k]; ok { |
| panic(fmt.Sprintf("Duplicate key when merging maps: %q", k)) |
| } |
| m[k] = v |
| } |
| return m |
| } |