diff options
-rw-r--r-- | android/arch.go | 47 | ||||
-rw-r--r-- | android/config.go | 7 | ||||
-rw-r--r-- | android/defaults.go | 4 | ||||
-rw-r--r-- | apex/apex.go | 2 | ||||
-rw-r--r-- | apex/prebuilt.go | 2 | ||||
-rw-r--r-- | bp2build/metrics.go | 6 | ||||
-rw-r--r-- | bp2build/performance_test.go | 99 | ||||
-rw-r--r-- | cc/binary.go | 6 | ||||
-rw-r--r-- | cc/builder.go | 15 | ||||
-rw-r--r-- | cc/cc.go | 9 | ||||
-rw-r--r-- | cc/config/darwin_host.go | 7 | ||||
-rw-r--r-- | cc/library.go | 6 | ||||
-rw-r--r-- | cc/test.go | 13 | ||||
-rw-r--r-- | mk2rbc/expr.go | 74 | ||||
-rw-r--r-- | mk2rbc/mk2rbc.go | 60 | ||||
-rw-r--r-- | mk2rbc/mk2rbc_test.go | 40 |
16 files changed, 347 insertions, 50 deletions
diff --git a/android/arch.go b/android/arch.go index 3bf54b711..3cc5e8228 100644 --- a/android/arch.go +++ b/android/arch.go @@ -566,6 +566,8 @@ func GetOsSpecificVariantsOfCommonOSVariant(mctx BaseModuleContext) []Module { return variants } +var DarwinUniversalVariantTag = archDepTag{name: "darwin universal binary"} + // archMutator splits a module into a variant for each Target requested by the module. Target selection // for a module is in three levels, OsClass, multilib, and then Target. // OsClass selection is determined by: @@ -652,7 +654,7 @@ func archMutator(bpctx blueprint.BottomUpMutatorContext) { prefer32 := os == Windows // Determine the multilib selection for this module. - multilib, extraMultilib := decodeMultilib(base, os.Class) + multilib, extraMultilib := decodeMultilib(base, os) // Convert the multilib selection into a list of Targets. targets, err := decodeMultilibTargets(multilib, osTargets, prefer32) @@ -702,6 +704,16 @@ func archMutator(bpctx blueprint.BottomUpMutatorContext) { m.base().commonProperties.SkipInstall = true } } + + // Create a dependency for Darwin Universal binaries from the primary to secondary + // architecture. The module itself will be responsible for calling lipo to merge the outputs. + if os == Darwin { + if multilib == "darwin_universal" && len(modules) == 2 { + mctx.AddInterVariantDependency(DarwinUniversalVariantTag, modules[1], modules[0]) + } else if multilib == "darwin_universal_common_first" && len(modules) == 3 { + mctx.AddInterVariantDependency(DarwinUniversalVariantTag, modules[2], modules[1]) + } + } } // addTargetProperties annotates a variant with the Target is is being compiled for, the list @@ -717,9 +729,9 @@ func addTargetProperties(m Module, target Target, multiTargets []Target, primary // multilib from the factory's call to InitAndroidArchModule if none was set. For modules that // called InitAndroidMultiTargetsArchModule it always returns "common" for multilib, and returns // the actual multilib in extraMultilib. -func decodeMultilib(base *ModuleBase, class OsClass) (multilib, extraMultilib string) { +func decodeMultilib(base *ModuleBase, os OsType) (multilib, extraMultilib string) { // First check the "android.compile_multilib" or "host.compile_multilib" properties. - switch class { + switch os.Class { case Device: multilib = String(base.commonProperties.Target.Android.Compile_multilib) case Host: @@ -737,6 +749,26 @@ func decodeMultilib(base *ModuleBase, class OsClass) (multilib, extraMultilib st } if base.commonProperties.UseTargetVariants { + // Darwin has the concept of "universal binaries" which is implemented in Soong by + // building both x86_64 and arm64 variants, and having select module types know how to + // merge the outputs of their corresponding variants together into a final binary. Most + // module types don't need to understand this logic, as we only build a small portion + // of the tree for Darwin, and only module types writing macho files need to do the + // merging. + // + // This logic is not enabled for: + // "common", as it's not an arch-specific variant + // "32", as Darwin never has a 32-bit variant + // !UseTargetVariants, as the module has opted into handling the arch-specific logic on + // its own. + if os == Darwin && multilib != "common" && multilib != "32" { + if multilib == "common_first" { + multilib = "darwin_universal_common_first" + } else { + multilib = "darwin_universal" + } + } + return multilib, "" } else { // For app modules a single arch variant will be created per OS class which is expected to handle all the @@ -1793,6 +1825,15 @@ func decodeMultilibTargets(multilib string, targets []Target, prefer32 bool) ([] if len(buildTargets) == 0 { buildTargets = filterMultilibTargets(targets, "lib64") } + case "darwin_universal": + buildTargets = filterMultilibTargets(targets, "lib64") + // Reverse the targets so that the first architecture can depend on the second + // architecture module in order to merge the outputs. + reverseSliceInPlace(buildTargets) + case "darwin_universal_common_first": + archTargets := filterMultilibTargets(targets, "lib64") + reverseSliceInPlace(archTargets) + buildTargets = append(getCommonTargets(targets), archTargets...) default: return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", "prefer32" or "first_prefer32" found %q`, multilib) diff --git a/android/config.go b/android/config.go index 8e01e186b..4a4da080a 100644 --- a/android/config.go +++ b/android/config.go @@ -892,8 +892,13 @@ func (c *config) Eng() bool { return Bool(c.productVariables.Eng) } +// DevicePrimaryArchType returns the ArchType for the first configured device architecture, or +// Common if there are no device architectures. func (c *config) DevicePrimaryArchType() ArchType { - return c.Targets[Android][0].Arch.ArchType + if androidTargets := c.Targets[Android]; len(androidTargets) > 0 { + return androidTargets[0].Arch.ArchType + } + return Common } func (c *config) SanitizeHost() []string { diff --git a/android/defaults.go b/android/defaults.go index 9046002cc..d2b351d04 100644 --- a/android/defaults.go +++ b/android/defaults.go @@ -89,10 +89,10 @@ type DefaultableModule interface { var _ Defaultable = (*DefaultableModuleBase)(nil) func InitDefaultableModule(module DefaultableModule) { - if module.(Module).base().module == nil { + if module.base().module == nil { panic("InitAndroidModule must be called before InitDefaultableModule") } - module.setProperties(module.(Module).GetProperties(), module.(Module).base().variableProperties) + module.setProperties(module.GetProperties(), module.base().variableProperties) module.AddProperties(module.defaults()) diff --git a/apex/apex.go b/apex/apex.go index bb9207dc2..1e7ce7f6f 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -2015,6 +2015,8 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { } } else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok { // nothing + } else if depTag == android.DarwinUniversalVariantTag { + // nothing } else if am.CanHaveApexVariants() && am.IsInstallableToApex() { ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName) } diff --git a/apex/prebuilt.go b/apex/prebuilt.go index 84bdcddf7..254c90ec0 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -965,7 +965,7 @@ func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.installDir = android.PathForModuleInstall(ctx, "apex") if a.installable() { - ctx.InstallFile(a.installDir, a.installFilename, a.outputApex) + a.installedFile = ctx.InstallFile(a.installDir, a.installFilename, a.outputApex) } // in case that apex_set replaces source apex (using prefer: prop) diff --git a/bp2build/metrics.go b/bp2build/metrics.go index 1cc4143c5..b3d5afb74 100644 --- a/bp2build/metrics.go +++ b/bp2build/metrics.go @@ -1,9 +1,10 @@ package bp2build import ( - "android/soong/android" "fmt" "strings" + + "android/soong/android" ) // Simple metrics struct to collect information about a Blueprint to BUILD @@ -35,7 +36,8 @@ func (metrics *CodegenMetrics) Print() { generatedTargetCount += count } fmt.Printf( - "[bp2build] Generated %d total BUILD targets and included %d handcrafted BUILD targets from %d Android.bp modules.\n With %d modules with unconverted deps \n\t%s", + "[bp2build] Converted %d Android.bp modules to %d total generated BUILD targets. Included %d handcrafted BUILD targets. There are %d total Android.bp modules.\n%d converted modules have unconverted deps: \n\t%s", + metrics.generatedModuleCount, generatedTargetCount, metrics.handCraftedModuleCount, metrics.TotalModuleCount(), diff --git a/bp2build/performance_test.go b/bp2build/performance_test.go index 32839522a..c4bbae262 100644 --- a/bp2build/performance_test.go +++ b/bp2build/performance_test.go @@ -29,6 +29,10 @@ import ( "testing" ) +const ( + performance_test_dir = "." +) + func genCustomModule(i int, convert bool) string { var conversionString string if convert { @@ -76,34 +80,83 @@ func genCustomModuleBp(pctConverted float64) string { return strings.Join(bp, "\n\n") } +type testConfig struct { + config android.Config + ctx *android.TestContext + codegenCtx *CodegenContext +} + +func (tc testConfig) parse() []error { + _, errs := tc.ctx.ParseFileList(performance_test_dir, []string{"Android.bp"}) + return errs +} + +func (tc testConfig) resolveDependencies() []error { + _, errs := tc.ctx.ResolveDependencies(tc.config) + return errs +} + +func (tc testConfig) convert() { + generateBazelTargetsForDir(tc.codegenCtx, performance_test_dir) +} + +func setup(builddir string, tcSize float64) testConfig { + config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil) + ctx := android.NewTestContext(config) + + registerCustomModuleForBp2buildConversion(ctx) + codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build) + return testConfig{ + config, + ctx, + codegenCtx, + } +} + var pctToConvert = []float64{0.0, 0.01, 0.05, 0.10, 0.25, 0.5, 0.75, 1.0} +// This is not intended to test performance, but to verify performance infra continues to work +func TestConvertManyModulesFull(t *testing.T) { + for _, tcSize := range pctToConvert { + + t.Run(fmt.Sprintf("pctConverted %f", tcSize), func(t *testing.T) { + testConfig := setup(buildDir, tcSize) + + errs := testConfig.parse() + if len(errs) > 0 { + t.Fatalf("Unexpected errors: %s", errs) + } + + errs = testConfig.resolveDependencies() + if len(errs) > 0 { + t.Fatalf("Unexpected errors: %s", errs) + } + + testConfig.convert() + }) + } +} + func BenchmarkManyModulesFull(b *testing.B) { - dir := "." for _, tcSize := range pctToConvert { b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) { for n := 0; n < b.N; n++ { b.StopTimer() - // setup we don't want to measure - config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil) - ctx := android.NewTestContext(config) - - registerCustomModuleForBp2buildConversion(ctx) - codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build) + testConfig := setup(buildDir, tcSize) b.StartTimer() - _, errs := ctx.ParseFileList(dir, []string{"Android.bp"}) + errs := testConfig.parse() if len(errs) > 0 { b.Fatalf("Unexpected errors: %s", errs) } - _, errs = ctx.ResolveDependencies(config) + errs = testConfig.resolveDependencies() if len(errs) > 0 { b.Fatalf("Unexpected errors: %s", errs) } - generateBazelTargetsForDir(codegenCtx, dir) + testConfig.convert() b.StopTimer() } }) @@ -111,63 +164,53 @@ func BenchmarkManyModulesFull(b *testing.B) { } func BenchmarkManyModulesResolveDependencies(b *testing.B) { - dir := "." for _, tcSize := range pctToConvert { b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) { for n := 0; n < b.N; n++ { b.StopTimer() // setup we don't want to measure - config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil) - ctx := android.NewTestContext(config) - - registerCustomModuleForBp2buildConversion(ctx) - codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build) + testConfig := setup(buildDir, tcSize) - _, errs := ctx.ParseFileList(dir, []string{"Android.bp"}) + errs := testConfig.parse() if len(errs) > 0 { b.Fatalf("Unexpected errors: %s", errs) } b.StartTimer() - _, errs = ctx.ResolveDependencies(config) + errs = testConfig.resolveDependencies() b.StopTimer() if len(errs) > 0 { b.Fatalf("Unexpected errors: %s", errs) } - generateBazelTargetsForDir(codegenCtx, dir) + testConfig.convert() } }) } } func BenchmarkManyModulesGenerateBazelTargetsForDir(b *testing.B) { - dir := "." for _, tcSize := range pctToConvert { b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) { for n := 0; n < b.N; n++ { b.StopTimer() // setup we don't want to measure - config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil) - ctx := android.NewTestContext(config) - - registerCustomModuleForBp2buildConversion(ctx) - codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build) + testConfig := setup(buildDir, tcSize) - _, errs := ctx.ParseFileList(dir, []string{"Android.bp"}) + errs := testConfig.parse() if len(errs) > 0 { b.Fatalf("Unexpected errors: %s", errs) } - _, errs = ctx.ResolveDependencies(config) + errs = testConfig.resolveDependencies() if len(errs) > 0 { b.Fatalf("Unexpected errors: %s", errs) } b.StartTimer() - generateBazelTargetsForDir(codegenCtx, dir) + testConfig.convert() b.StopTimer() } }) diff --git a/cc/binary.go b/cc/binary.go index 3f951eca5..e839122f4 100644 --- a/cc/binary.go +++ b/cc/binary.go @@ -345,6 +345,12 @@ func (binary *binaryDecorator) link(ctx ModuleContext, flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-dynamic-linker") } + if ctx.Darwin() && deps.DarwinSecondArchOutput.Valid() { + fatOutputFile := outputFile + outputFile = android.PathForModuleOut(ctx, "pre-fat", fileName) + transformDarwinUniversalBinary(ctx, fatOutputFile, outputFile, deps.DarwinSecondArchOutput.Path()) + } + builderFlags := flagsToBuilderFlags(flags) stripFlags := flagsToStripFlags(flags) if binary.stripper.NeedsStrip(ctx) { diff --git a/cc/builder.go b/cc/builder.go index 7161ccf83..8af225535 100644 --- a/cc/builder.go +++ b/cc/builder.go @@ -165,6 +165,12 @@ var ( } }() + darwinLipo = pctx.AndroidStaticRule("darwinLipo", + blueprint.RuleParams{ + Command: "${config.MacLipoPath} -create -output $out $in", + CommandDeps: []string{"${config.MacLipoPath}"}, + }) + _ = pctx.SourcePathVariable("archiveRepackPath", "build/soong/scripts/archive_repack.sh") // Rule to repack an archive (.a) file with a subset of object files. @@ -1059,6 +1065,15 @@ func transformDarwinStrip(ctx android.ModuleContext, inputFile android.Path, }) } +func transformDarwinUniversalBinary(ctx android.ModuleContext, outputFile android.WritablePath, inputFiles ...android.Path) { + ctx.Build(pctx, android.BuildParams{ + Rule: darwinLipo, + Description: "lipo " + outputFile.Base(), + Output: outputFile, + Inputs: inputFiles, + }) +} + // Registers build statement to zip one or more coverage files. func transformCoverageFilesToZip(ctx android.ModuleContext, inputs Objects, baseName string) android.OptionalPath { @@ -167,6 +167,10 @@ type PathDeps struct { // Path to the dynamic linker binary DynamicLinker android.OptionalPath + + // For Darwin builds, the path to the second architecture's output that should + // be combined with this architectures's output into a FAT MachO file. + DarwinSecondArchOutput android.OptionalPath } // LocalOrGlobalFlags contains flags that need to have values set globally by the build system or locally by the module @@ -2584,6 +2588,11 @@ func (c *Module) depsToPaths(ctx android.ModuleContext) PathDeps { depName := ctx.OtherModuleName(dep) depTag := ctx.OtherModuleDependencyTag(dep) + if depTag == android.DarwinUniversalVariantTag { + depPaths.DarwinSecondArchOutput = dep.(*Module).OutputFile() + return + } + ccDep, ok := dep.(LinkableInterface) if !ok { diff --git a/cc/config/darwin_host.go b/cc/config/darwin_host.go index 318acb4e7..206bec110 100644 --- a/cc/config/darwin_host.go +++ b/cc/config/darwin_host.go @@ -54,6 +54,7 @@ var ( darwinSupportedSdkVersions = []string{ "11", + "12", } darwinAvailableLibraries = append( @@ -87,6 +88,10 @@ func init() { return getMacTools(ctx).arPath }) + pctx.VariableFunc("MacLipoPath", func(ctx android.PackageVarContext) string { + return getMacTools(ctx).lipoPath + }) + pctx.VariableFunc("MacStripPath", func(ctx android.PackageVarContext) string { return getMacTools(ctx).stripPath }) @@ -118,6 +123,7 @@ type macPlatformTools struct { sdkRoot string arPath string + lipoPath string stripPath string toolPath string } @@ -157,6 +163,7 @@ func getMacTools(ctx android.PathContext) *macPlatformTools { macTools.sdkRoot = xcrun("--show-sdk-path") macTools.arPath = xcrun("--find", "ar") + macTools.lipoPath = xcrun("--find", "lipo") macTools.stripPath = xcrun("--find", "strip") macTools.toolPath = filepath.Dir(xcrun("--find", "ld")) }) diff --git a/cc/library.go b/cc/library.go index e53aac0c6..a081c7db0 100644 --- a/cc/library.go +++ b/cc/library.go @@ -1429,6 +1429,12 @@ func (library *libraryDecorator) linkShared(ctx ModuleContext, builderFlags := flagsToBuilderFlags(flags) + if ctx.Darwin() && deps.DarwinSecondArchOutput.Valid() { + fatOutputFile := outputFile + outputFile = android.PathForModuleOut(ctx, "pre-fat", fileName) + transformDarwinUniversalBinary(ctx, fatOutputFile, outputFile, deps.DarwinSecondArchOutput.Path()) + } + // Optimize out relinking against shared libraries whose interface hasn't changed by // depending on a table of contents file instead of the library itself. tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.ShlibSuffix()[1:]+".toc") diff --git a/cc/test.go b/cc/test.go index f37fdae38..0ca96f751 100644 --- a/cc/test.go +++ b/cc/test.go @@ -105,11 +105,6 @@ type TestBinaryProperties struct { // Add RunCommandTargetPreparer to stop framework before the test and start it after the test. Disable_framework *bool - // Add ShippingApiLevelModuleController to auto generated test config. If the device properties - // for the shipping api level is less than the test_min_api_level, skip this module. - // Deprecated (b/187258404). Use test_options.min_shipping_api_level instead. - Test_min_api_level *int64 - // Flag to indicate whether or not to create test config automatically. If AndroidTest.xml // doesn't exist next to the Android.bp, this attribute doesn't need to be set to true // explicitly. @@ -432,14 +427,6 @@ func (test *testBinary) install(ctx ModuleContext, file android.Path) { var options []tradefed.Option options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_shipping_api_level), 10)}) configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options}) - } else if test.Properties.Test_min_api_level != nil { - // TODO: (b/187258404) Remove test.Properties.Test_min_api_level - if test.Properties.Test_options.Vsr_min_shipping_api_level != nil { - ctx.PropertyErrorf("test_min_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.") - } - var options []tradefed.Option - options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_min_api_level), 10)}) - configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options}) } if test.Properties.Test_options.Vsr_min_shipping_api_level != nil { var options []tradefed.Option diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go index ec0b279b9..81b31c736 100644 --- a/mk2rbc/expr.go +++ b/mk2rbc/expr.go @@ -85,6 +85,31 @@ func (s *intLiteralExpr) emitListVarCopy(gctx *generationContext) { s.emit(gctx) } +// Boolean literal +type boolLiteralExpr struct { + literal bool +} + +func (b *boolLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) { + return b, true +} + +func (b *boolLiteralExpr) emit(gctx *generationContext) { + if b.literal { + gctx.write("True") + } else { + gctx.write("False") + } +} + +func (_ *boolLiteralExpr) typ() starlarkType { + return starlarkTypeBool +} + +func (b *boolLiteralExpr) emitListVarCopy(gctx *generationContext) { + b.emit(gctx) +} + // interpolateExpr represents Starlark's interpolation operator <string> % list // we break <string> into a list of chunks, i.e., "first%second%third" % (X, Y) // will have chunks = ["first", "second", "third"] and args = [X, Y] @@ -617,6 +642,55 @@ func (cx *callExpr) emitListVarCopy(gctx *generationContext) { cx.emit(gctx) } +type ifExpr struct { + condition starlarkExpr + ifTrue starlarkExpr + ifFalse starlarkExpr +} + +func (i *ifExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) { + cond, condSame := i.condition.eval(valueMap) + t, tSame := i.ifTrue.eval(valueMap) + f, fSame := i.ifFalse.eval(valueMap) + same = condSame && tSame && fSame + if same { + return i, same + } else { + return &ifExpr{ + condition: cond, + ifTrue: t, + ifFalse: f, + }, same + } +} + +func (i *ifExpr) emit(gctx *generationContext) { + gctx.write("(") + i.ifTrue.emit(gctx) + gctx.write(" if ") + i.condition.emit(gctx) + gctx.write(" else ") + i.ifFalse.emit(gctx) + gctx.write(")") +} + +func (i *ifExpr) typ() starlarkType { + tType := i.ifTrue.typ() + fType := i.ifFalse.typ() + if tType != fType && tType != starlarkTypeUnknown && fType != starlarkTypeUnknown { + panic("Conflicting types in if expression") + } + if tType != starlarkTypeUnknown { + return tType + } else { + return fType + } +} + +func (i *ifExpr) emitListVarCopy(gctx *generationContext) { + i.emit(gctx) +} + type badExpr struct { errorLocation ErrorLocation message string diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go index cade4d20f..d5ff18105 100644 --- a/mk2rbc/mk2rbc.go +++ b/mk2rbc/mk2rbc.go @@ -112,6 +112,7 @@ var knownFunctions = map[string]struct { "filter-out": {baseName + ".filter_out", starlarkTypeList, hiddenArgNone}, "firstword": {"!firstword", starlarkTypeString, hiddenArgNone}, "get-vendor-board-platforms": {"!get-vendor-board-platforms", starlarkTypeList, hiddenArgNone}, // internal macro, used by is-board-platform, etc. + "if": {"!if", starlarkTypeUnknown, hiddenArgNone}, "info": {baseName + ".mkinfo", starlarkTypeVoid, hiddenArgNone}, "is-android-codename": {"!is-android-codename", starlarkTypeBool, hiddenArgNone}, // unused by product config "is-android-codename-in-list": {"!is-android-codename-in-list", starlarkTypeBool, hiddenArgNone}, // unused by product config @@ -1331,6 +1332,34 @@ func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeSt // TODO (asmundak): if we find many, maybe handle them. return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump) } + // Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html + if strings.Contains(refDump, ":") { + parts := strings.SplitN(refDump, ":", 2) + substParts := strings.SplitN(parts[1], "=", 2) + if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 { + return ctx.newBadExpr(node, "Invalid substitution reference") + } + if !strings.Contains(substParts[0], "%") { + if strings.Contains(substParts[1], "%") { + return ctx.newBadExpr(node, "A substitution reference must have a %% in the \"before\" part of the substitution if it has one in the \"after\" part.") + } + substParts[0] = "%" + substParts[0] + substParts[1] = "%" + substParts[1] + } + v := ctx.addVariable(parts[0]) + if v == nil { + return ctx.newBadExpr(node, "unknown variable %s", refDump) + } + return &callExpr{ + name: "patsubst", + returnType: knownFunctions["patsubst"].returnType, + args: []starlarkExpr{ + &stringLiteralExpr{literal: substParts[0]}, + &stringLiteralExpr{literal: substParts[1]}, + &variableRefExpr{v, ctx.lastAssignment(v.name()) != nil}, + }, + } + } if v := ctx.addVariable(refDump); v != nil { return &variableRefExpr{v, ctx.lastAssignment(v.name()) != nil} } @@ -1368,6 +1397,8 @@ func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeSt return ctx.newBadExpr(node, "cannot handle invoking %s", expr.name) } switch expr.name { + case "if": + return ctx.parseIfFunc(node, args) case "word": return ctx.parseWordFunc(node, args) case "firstword", "lastword": @@ -1423,6 +1454,35 @@ func (ctx *parseContext) parseSubstFunc(node mkparser.Node, fname string, args * } } +func (ctx *parseContext) parseIfFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr { + words := args.Split(",") + if len(words) != 2 && len(words) != 3 { + return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words))) + } + condition := ctx.parseMakeString(node, words[0]) + ifTrue := ctx.parseMakeString(node, words[1]) + var ifFalse starlarkExpr + if len(words) == 3 { + ifFalse = ctx.parseMakeString(node, words[2]) + } else { + switch ifTrue.typ() { + case starlarkTypeList: + ifFalse = &listExpr{items: []starlarkExpr{}} + case starlarkTypeInt: + ifFalse = &intLiteralExpr{literal: 0} + case starlarkTypeBool: + ifFalse = &boolLiteralExpr{literal: false} + default: + ifFalse = &stringLiteralExpr{literal: ""} + } + } + return &ifExpr{ + condition, + ifTrue, + ifFalse, + } +} + func (ctx *parseContext) parseWordFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr { words := args.Split(",") if len(words) != 2 { diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go index fa33e75d5..78444c9c6 100644 --- a/mk2rbc/mk2rbc_test.go +++ b/mk2rbc/mk2rbc_test.go @@ -1091,6 +1091,46 @@ def init(g, handle): pass `, }, + { + desc: "if expression", + mkname: "product.mk", + in: ` +TEST_VAR := foo +TEST_VAR_LIST := foo +TEST_VAR_LIST += bar +TEST_VAR_2 := $(if $(TEST_VAR),bar) +TEST_VAR_3 := $(if $(TEST_VAR),bar,baz) +TEST_VAR_3 := $(if $(TEST_VAR),$(TEST_VAR_LIST)) +`, + expected: `load("//build/make/core:product_config.rbc", "rblf") + +def init(g, handle): + cfg = rblf.cfg(handle) + g["TEST_VAR"] = "foo" + g["TEST_VAR_LIST"] = ["foo"] + g["TEST_VAR_LIST"] += ["bar"] + g["TEST_VAR_2"] = ("bar" if g["TEST_VAR"] else "") + g["TEST_VAR_3"] = ("bar" if g["TEST_VAR"] else "baz") + g["TEST_VAR_3"] = (g["TEST_VAR_LIST"] if g["TEST_VAR"] else []) +`, + }, + { + desc: "substitution references", + mkname: "product.mk", + in: ` +SOURCES := foo.c bar.c +OBJECTS := $(SOURCES:.c=.o) +OBJECTS2 := $(SOURCES:%.c=%.o) +`, + expected: `load("//build/make/core:product_config.rbc", "rblf") + +def init(g, handle): + cfg = rblf.cfg(handle) + g["SOURCES"] = "foo.c bar.c" + g["OBJECTS"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"]) + g["OBJECTS2"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"]) +`, + }, } var known_variables = []struct { |