| package bp2build |
| |
| import ( |
| "android/soong/starlark_fmt" |
| "encoding/json" |
| "fmt" |
| "reflect" |
| "strconv" |
| "strings" |
| |
| "android/soong/android" |
| "android/soong/cc" |
| cc_config "android/soong/cc/config" |
| java_config "android/soong/java/config" |
| |
| "android/soong/apex" |
| |
| "github.com/google/blueprint/proptools" |
| ) |
| |
| type BazelFile struct { |
| Dir string |
| Basename string |
| Contents string |
| } |
| |
| // PRIVATE: Use CreateSoongInjectionDirFiles instead |
| func soongInjectionFiles(cfg android.Config, metrics CodegenMetrics) ([]BazelFile, error) { |
| var files []BazelFile |
| |
| files = append(files, newFile("android", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package. |
| files = append(files, newFile("android", "constants.bzl", android.BazelCcToolchainVars(cfg))) |
| |
| files = append(files, newFile("cc_toolchain", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package. |
| files = append(files, newFile("cc_toolchain", "config_constants.bzl", cc_config.BazelCcToolchainVars(cfg))) |
| files = append(files, newFile("cc_toolchain", "sanitizer_constants.bzl", cc.BazelCcSanitizerToolchainVars(cfg))) |
| |
| files = append(files, newFile("java_toolchain", GeneratedBuildFileName, "")) // Creates a //java_toolchain package. |
| files = append(files, newFile("java_toolchain", "constants.bzl", java_config.BazelJavaToolchainVars(cfg))) |
| |
| files = append(files, newFile("apex_toolchain", GeneratedBuildFileName, "")) // Creates a //apex_toolchain package. |
| apexToolchainVars, err := apex.BazelApexToolchainVars() |
| if err != nil { |
| return nil, err |
| } |
| files = append(files, newFile("apex_toolchain", "constants.bzl", apexToolchainVars)) |
| |
| files = append(files, newFile("metrics", "converted_modules.txt", strings.Join(metrics.Serialize().ConvertedModules, "\n"))) |
| |
| convertedModulePathMap, err := json.MarshalIndent(metrics.convertedModulePathMap, "", "\t") |
| if err != nil { |
| panic(err) |
| } |
| files = append(files, newFile("metrics", GeneratedBuildFileName, "")) // Creates a //metrics package. |
| files = append(files, newFile("metrics", "converted_modules_path_map.json", string(convertedModulePathMap))) |
| files = append(files, newFile("metrics", "converted_modules_path_map.bzl", "modules = "+strings.ReplaceAll(string(convertedModulePathMap), "\\", "\\\\"))) |
| |
| files = append(files, newFile("product_config", "soong_config_variables.bzl", cfg.Bp2buildSoongConfigDefinitions.String())) |
| |
| files = append(files, newFile("product_config", "arch_configuration.bzl", android.StarlarkArchConfigurations())) |
| |
| apiLevelsContent, err := json.Marshal(android.GetApiLevelsMap(cfg)) |
| if err != nil { |
| return nil, err |
| } |
| files = append(files, newFile("api_levels", GeneratedBuildFileName, `exports_files(["api_levels.json"])`)) |
| // TODO(b/269691302) value of apiLevelsContent is product variable dependent and should be avoided for soong injection |
| files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent))) |
| files = append(files, newFile("api_levels", "api_levels.bzl", android.StarlarkApiLevelConfigs(cfg))) |
| files = append(files, newFile("api_levels", "platform_versions.bzl", platformVersionContents(cfg))) |
| |
| files = append(files, newFile("allowlists", GeneratedBuildFileName, "")) |
| files = append(files, newFile("allowlists", "env.bzl", android.EnvironmentVarsFile(cfg))) |
| // TODO(b/262781701): Create an alternate soong_build entrypoint for writing out these files only when requested |
| files = append(files, newFile("allowlists", "mixed_build_prod_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelProdMode), "\n")+"\n")) |
| files = append(files, newFile("allowlists", "mixed_build_staging_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelStagingMode), "\n")+"\n")) |
| |
| return files, nil |
| } |
| |
| func platformVersionContents(cfg android.Config) string { |
| // Despite these coming from cfg.productVariables, they are actually hardcoded in global |
| // makefiles, not set in individual product config makesfiles, so they're safe to just export |
| // and load() directly. |
| |
| platformVersionActiveCodenames := make([]string, 0, len(cfg.PlatformVersionActiveCodenames())) |
| for _, codename := range cfg.PlatformVersionActiveCodenames() { |
| platformVersionActiveCodenames = append(platformVersionActiveCodenames, fmt.Sprintf("%q", codename)) |
| } |
| |
| platformSdkVersion := "None" |
| if cfg.RawPlatformSdkVersion() != nil { |
| platformSdkVersion = strconv.Itoa(*cfg.RawPlatformSdkVersion()) |
| } |
| |
| return fmt.Sprintf(` |
| platform_versions = struct( |
| platform_sdk_final = %s, |
| platform_sdk_version = %s, |
| platform_sdk_codename = %q, |
| platform_version_active_codenames = [%s], |
| ) |
| `, starlark_fmt.PrintBool(cfg.PlatformSdkFinal()), platformSdkVersion, cfg.PlatformSdkCodename(), strings.Join(platformVersionActiveCodenames, ", ")) |
| } |
| |
| func CreateBazelFiles( |
| cfg android.Config, |
| ruleShims map[string]RuleShim, |
| buildToTargets map[string]BazelTargets, |
| mode CodegenMode) []BazelFile { |
| |
| var files []BazelFile |
| |
| if mode == QueryView { |
| // Write top level WORKSPACE. |
| files = append(files, newFile("", "WORKSPACE", "")) |
| |
| // Used to denote that the top level directory is a package. |
| files = append(files, newFile("", GeneratedBuildFileName, "")) |
| |
| files = append(files, newFile(bazelRulesSubDir, GeneratedBuildFileName, "")) |
| |
| // These files are only used for queryview. |
| files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl)) |
| |
| for bzlFileName, ruleShim := range ruleShims { |
| files = append(files, newFile(bazelRulesSubDir, bzlFileName+".bzl", ruleShim.content)) |
| } |
| files = append(files, newFile(bazelRulesSubDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims))) |
| } |
| |
| files = append(files, createBuildFiles(buildToTargets, mode)...) |
| |
| return files |
| } |
| |
| func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile { |
| files := make([]BazelFile, 0, len(buildToTargets)) |
| for _, dir := range android.SortedKeys(buildToTargets) { |
| targets := buildToTargets[dir] |
| targets.sort() |
| |
| var content string |
| if mode == Bp2Build || mode == ApiBp2build { |
| content = `# READ THIS FIRST: |
| # This file was automatically generated by bp2build for the Bazel migration project. |
| # Feel free to edit or test it, but do *not* check it into your version control system. |
| ` |
| content += targets.LoadStatements() |
| content += "\n\n" |
| // Get package rule from the handcrafted BUILD file, otherwise emit the default one. |
| prText := "package(default_visibility = [\"//visibility:public\"])\n" |
| if pr := targets.packageRule(); pr != nil { |
| prText = pr.content |
| } |
| content += prText |
| } else if mode == QueryView { |
| content = soongModuleLoad |
| } |
| if content != "" { |
| // If there are load statements, add a couple of newlines. |
| content += "\n\n" |
| } |
| content += targets.String() |
| files = append(files, newFile(dir, GeneratedBuildFileName, content)) |
| } |
| return files |
| } |
| |
| func newFile(dir, basename, content string) BazelFile { |
| return BazelFile{ |
| Dir: dir, |
| Basename: basename, |
| Contents: content, |
| } |
| } |
| |
| const ( |
| bazelRulesSubDir = "build/bazel/queryview_rules" |
| |
| // additional files: |
| // * workspace file |
| // * base BUILD file |
| // * rules BUILD file |
| // * rules providers.bzl file |
| // * rules soong_module.bzl file |
| numAdditionalFiles = 5 |
| ) |
| |
| var ( |
| // Certain module property names are blocklisted/ignored here, for the reasons commented. |
| ignoredPropNames = map[string]bool{ |
| "name": true, // redundant, since this is explicitly generated for every target |
| "from": true, // reserved keyword |
| "in": true, // reserved keyword |
| "size": true, // reserved for tests |
| "arch": true, // interface prop type is not supported yet. |
| "multilib": true, // interface prop type is not supported yet. |
| "target": true, // interface prop type is not supported yet. |
| "visibility": true, // Bazel has native visibility semantics. Handle later. |
| "features": true, // There is already a built-in attribute 'features' which cannot be overridden. |
| "for": true, // reserved keyword, b/233579439 |
| "versions_with_info": true, // TODO(b/245730552) struct properties not fully supported |
| } |
| ) |
| |
| func shouldGenerateAttribute(prop string) bool { |
| return !ignoredPropNames[prop] |
| } |
| |
| func shouldSkipStructField(field reflect.StructField) bool { |
| if field.PkgPath != "" && !field.Anonymous { |
| // Skip unexported fields. Some properties are |
| // internal to Soong only, and these fields do not have PkgPath. |
| return true |
| } |
| // fields with tag `blueprint:"mutated"` are exported to enable modification in mutators, etc. |
| // but cannot be set in a .bp file |
| if proptools.HasTag(field, "blueprint", "mutated") { |
| return true |
| } |
| return false |
| } |
| |
| // FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as |
| // testonly = True, forcing other rules that depend on _test rules to also be |
| // marked as testonly = True. This semantic constraint is not present in Soong. |
| // To work around, rename "*_test" rules to "*_test_". |
| func canonicalizeModuleType(moduleName string) string { |
| if strings.HasSuffix(moduleName, "_test") { |
| return moduleName + "_" |
| } |
| |
| return moduleName |
| } |