From 4745007bb1833672e37fefc7e719dd921d8bd77e Mon Sep 17 00:00:00 2001 From: Dan Willemsen Date: Tue, 19 Oct 2021 20:24:49 -0700 Subject: [PATCH] Add Darwin x86_64+arm64 universal binary support This is configured from Make by setting up Darwin+Arm64 as a HOST_CROSS target (which is largely true, as binaries can't be executed on X86_64 machines). On the Soong side, it's a bit blurier, as we don't current have any other users that are the same OS but not natively executable (Linux/musl is executable, Windows/x86 is a different OS). Instead of requiring cc_binary/etc to become multi-architecture-aware and using something like common_first/MultiTarget, this defaults all non-multi-architecture-aware modules to be built with both architectures. It then adds a dependency with the DarwinUniversalVariantTag so that supporting modules can get the outputs of the other variant. Cc uses that dependency tag to run lipo on shared libraries and binaries so that the output of the x86_64 variant is actually a fat binary including both architectures. Bug: 203607969 Test: build sdk-repo targets on a Mac Change-Id: Icbddb0a177c0ba19d3e0d11f8cf568e0d1ea3245 --- android/arch.go | 47 +++++++++++++++++++++++++++++++++++++--- apex/apex.go | 2 ++ cc/binary.go | 6 +++++ cc/builder.go | 15 +++++++++++++ cc/cc.go | 9 ++++++++ cc/config/darwin_host.go | 6 +++++ cc/library.go | 6 +++++ 7 files changed, 88 insertions(+), 3 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/apex/apex.go b/apex/apex.go index 4ecb104e4..61ba15ab2 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 0650bdf39..141528d5e 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 72c2fa555..cbedf25b2 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. @@ -1060,6 +1066,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 aeb342f4f..0b59bd8f6 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 @@ -2578,6 +2582,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")