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/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/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 { diff --git a/cc/cc.go b/cc/cc.go index 3bb0d6d83..b3b8f8c46 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -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..526ee52a6 100644 --- a/cc/config/darwin_host.go +++ b/cc/config/darwin_host.go @@ -87,6 +87,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 +122,7 @@ type macPlatformTools struct { sdkRoot string arPath string + lipoPath string stripPath string toolPath string } @@ -157,6 +162,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")