Skip to content

Commit ec83172

Browse files
[#bundle support] Generate internal bundle access class for mergeable libraries (#599)
1 parent 3bc16fa commit ec83172

File tree

5 files changed

+228
-20
lines changed

5 files changed

+228
-20
lines changed

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,7 @@ public final class BuiltinMacros {
995995
public static let SWIFT_ABI_CHECKER_EXCEPTIONS_FILE = BuiltinMacros.declareStringMacro("SWIFT_ABI_CHECKER_EXCEPTIONS_FILE")
996996
public static let SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR = BuiltinMacros.declareStringMacro("SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR")
997997
public static let SWIFT_ACCESS_NOTES_PATH = BuiltinMacros.declareStringMacro("SWIFT_ACCESS_NOTES_PATH")
998+
public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS = BuiltinMacros.declareStringListMacro("SWIFT_ACTIVE_COMPILATION_CONDITIONS")
998999
public static let SWIFT_ALLOW_INSTALL_OBJC_HEADER = BuiltinMacros.declareBooleanMacro("SWIFT_ALLOW_INSTALL_OBJC_HEADER")
9991000
public static let __SWIFT_ALLOW_INSTALL_OBJC_HEADER_MESSAGE = BuiltinMacros.declareStringMacro("__SWIFT_ALLOW_INSTALL_OBJC_HEADER_MESSAGE")
10001001
public static let SWIFT_COMPILATION_MODE = BuiltinMacros.declareStringMacro("SWIFT_COMPILATION_MODE")
@@ -2164,6 +2165,7 @@ public final class BuiltinMacros {
21642165
SWIFT_ABI_CHECKER_EXCEPTIONS_FILE,
21652166
SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR,
21662167
SWIFT_ACCESS_NOTES_PATH,
2168+
SWIFT_ACTIVE_COMPILATION_CONDITIONS,
21672169
SWIFT_ALLOW_INSTALL_OBJC_HEADER,
21682170
__SWIFT_ALLOW_INSTALL_OBJC_HEADER_MESSAGE,
21692171
SWIFT_COMPILATION_MODE,

Sources/SWBCore/Settings/Settings.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,7 +1554,7 @@ private class SettingsBuilder {
15541554
}
15551555

15561556
// Push the target derived overriding settings.
1557-
addTargetDerivedSettings(self.target, boundProperties.platform, boundProperties.sdk, boundProperties.sdkVariant)
1557+
addTargetDerivedSettings(self.target, boundProperties.platform, boundProperties.sdk, boundProperties.sdkVariant, specLookupContext)
15581558

15591559
if boundDeploymentTarget.platformDeploymentTargetMacro == BuiltinMacros.DRIVERKIT_DEPLOYMENT_TARGET, let deploymentTarget = boundDeploymentTarget.platformDeploymentTarget, deploymentTarget < Version(20) {
15601560
var table = MacroValueAssignmentTable(namespace: userNamespace)
@@ -2039,13 +2039,13 @@ private class SettingsBuilder {
20392039
/// Add the derived overriding settings for the target. These are settings whose values depend on the whole stack of build settings, and include settings which are forced to a value under certain conditions, and settings whose value is wholly derived from other settings. They override settings from all lower levels, and thus cannot be overridden by (for example) xcodebuild or run destination overrides, so settings should only be assigned here when they represent true boundary conditions which users should never want to or be able to override.
20402040
///
20412041
/// These are only added if we're constructing settings for a target.
2042-
func addTargetDerivedSettings(_ target: Target?, _ platform: Platform?, _ sdk: SDK?, _ sdkVariant: SDKVariant?) {
2042+
func addTargetDerivedSettings(_ target: Target?, _ platform: Platform?, _ sdk: SDK?, _ sdkVariant: SDKVariant?, _ specLookupContext: any SpecLookupContext) {
20432043
guard target != nil else {
20442044
return
20452045
}
20462046

20472047
push(getTargetDerivedSettings(platform, sdk, sdkVariant), .exportedForNative)
2048-
addSecondaryTargetDerivedSettings(sdk)
2048+
addSecondaryTargetDerivedSettings(sdk, specLookupContext)
20492049
}
20502050

20512051
/// Add the core derived overriding settings for the target.
@@ -2138,7 +2138,7 @@ private class SettingsBuilder {
21382138
/// Add derived settings for the target which are themselves derived from the core target derived settings computed above. (Whee!)
21392139
///
21402140
/// This is called from `addTargetDerivedSettings().
2141-
func addSecondaryTargetDerivedSettings(_ sdk: SDK?) {
2141+
func addSecondaryTargetDerivedSettings(_ sdk: SDK?, _ specLookupContext: any SpecLookupContext) {
21422142
// Mergeable library/merged binary support.
21432143
do {
21442144
let scope = createScope(sdkToUse: sdk)
@@ -2178,13 +2178,22 @@ private class SettingsBuilder {
21782178
}
21792179
do {
21802180
let scope = createScope(sdkToUse: sdk)
2181+
var table = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace)
21812182

21822183
// If the product is being built as mergeable, then that overrides certain other settings.
21832184
if scope.evaluate(BuiltinMacros.MAKE_MERGEABLE) {
2184-
var table = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace)
21852185
table.push(BuiltinMacros.STRIP_INSTALLED_PRODUCT, literal: false)
2186-
push(table, .exportedForNative)
21872186
}
2187+
2188+
// Even if not being merged in this build, a mergeable library still uses a generated bundle lookup helper to power #bundle support.
2189+
if scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) {
2190+
let pathResolver = FilePathResolver(scope: scope)
2191+
if (target as? StandardTarget)?.sourcesBuildPhase?.containsSwiftSources(workspaceContext.workspace, specLookupContext, scope, pathResolver) ?? false {
2192+
table.push(BuiltinMacros.SWIFT_ACTIVE_COMPILATION_CONDITIONS, BuiltinMacros.namespace.parseStringList(["$(inherited)", "SWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]))
2193+
}
2194+
}
2195+
2196+
push(table, .exportedForNative)
21882197
}
21892198
}
21902199

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,9 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase
771771
let packageTargetBundleAccessorResult = await generatePackageTargetBundleAccessorResult(scope)
772772
tasks += packageTargetBundleAccessorResult?.tasks ?? []
773773

774+
let bundleLookupHelperResult = await generateBundleLookupHelper(scope)
775+
tasks += bundleLookupHelperResult?.tasks ?? []
776+
774777
let embedInCodeAccessorResult: GeneratedResourceAccessorResult?
775778
if scope.evaluate(BuiltinMacros.GENERATE_EMBED_IN_CODE_ACCESSORS), let configuredTarget = context.configuredTarget, buildPhase.containsSwiftSources(context.workspaceContext.workspace, context, scope, context.filePathResolver) {
776779
let ownTargetBuildFilesToEmbed = ((context.workspaceContext.workspace.target(for: configuredTarget.target.guid) as? StandardTarget)?.buildPhases.compactMap { $0 as? BuildPhaseWithBuildFiles }.flatMap { $0.buildFiles }.filter { $0.resourceRule == .embedInCode }) ?? []
@@ -868,6 +871,10 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase
868871
result.append((packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
869872
}
870873

874+
if let bundleLookupHelperResult {
875+
result.append((bundleLookupHelperResult.fileToBuild, bundleLookupHelperResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
876+
}
877+
871878
if let embedInCodeAccessorResult {
872879
result.append((embedInCodeAccessorResult.fileToBuild, embedInCodeAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
873880
}
@@ -1192,11 +1199,17 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase
11921199
let buildFilesContext = BuildFilesProcessingContext(scope, belongsToPreferredArch: preferredArch == nil || preferredArch == arch, currentArchSpec: currentArchSpec)
11931200
var perArchTasks: [any PlannedTask] = []
11941201
await groupAndAddTasksForFiles(self, buildFilesContext, scope, filterToAPIRules: isForAPI, filterToHeaderRules: isForHeaders, &perArchTasks, extraResolvedBuildFiles: {
1202+
var result: [(Path, FileTypeSpec, Bool)] = []
1203+
11951204
if let packageTargetBundleAccessorResult {
1196-
return [(packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)]
1197-
} else {
1198-
return []
1205+
result.append((packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
1206+
}
1207+
1208+
if let bundleLookupHelperResult {
1209+
result.append((bundleLookupHelperResult.fileToBuild, bundleLookupHelperResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
11991210
}
1211+
1212+
return result
12001213
}())
12011214

12021215
// Add all the collected per-arch tasks.
@@ -1725,7 +1738,43 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase
17251738
return GeneratedResourceAccessorResult(tasks: tasks, fileToBuild: filePath, fileToBuildFileType: context.lookupFileType(fileName: "sourcecode.swift")!)
17261739
}
17271740

1741+
/// Generates a task for creating the `__BundleLookupHelper` class to enable `#bundle` support in mergeable libraries.
1742+
private func generateBundleLookupHelper(_ scope: MacroEvaluationScope) async -> GeneratedResourceAccessorResult? {
1743+
// We generate a __BundleLookupHelper class that Foundation's #bundle macro can use to lookup the resource bundle.
1744+
// ld will inject a mapping of class pointers to the correct resource bundle so that BundleForClass works at runtime.
1745+
1746+
// We only need this treatment for mergeable libraries at this time.
1747+
// Package targets do something similar but they generate the Bundle.module extensions and #bundle calls that.
1748+
1749+
// We need to do this for all mergeable libraries, even if it will just be re-exported in this build.
1750+
guard scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) else {
1751+
return nil
1752+
}
1753+
1754+
let workspace = self.context.workspaceContext.workspace
1755+
1756+
// #bundle is a Swift macro, so this is only needed for Swift code.
1757+
guard buildPhase.containsSwiftSources(workspace, context, scope, context.filePathResolver) else {
1758+
return nil
1759+
}
1760+
1761+
let filePath = scope.evaluate(BuiltinMacros.DERIVED_SOURCES_DIR).join("bundle_lookup_helper.swift")
1762+
1763+
// We need one class with a relatively unique name that #bundle can use for bundle lookup.
1764+
// It cannot be less visible than internal since the #bundle expansion needs to be able to resolve it AND so ld will record the class->bundle mapping.
1765+
// We intentionally do not want a Foundation dependency in this generated code, so don't import Foundation.
1766+
let content = "internal class __BundleLookupHelper {}"
1767+
1768+
var tasks = [any PlannedTask]()
1769+
await appendGeneratedTasks(&tasks) { delegate in
1770+
context.writeFileSpec.constructFileTasks(CommandBuildContext(producer: context, scope: context.settings.globalScope, inputs: [], output: filePath), delegate, contents: ByteString(encodingAsUTF8: content), permissions: nil, preparesForIndexing: true, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering])
1771+
}
1772+
return GeneratedResourceAccessorResult(tasks: tasks, fileToBuild: filePath, fileToBuildFileType: context.lookupFileType(fileName: "sourcecode.swift")!)
1773+
}
1774+
17281775
/// Generates a task for creating the resource bundle accessor for package targets.
1776+
///
1777+
/// This produces the `Bundle.module` accessor.
17291778
private func generatePackageTargetBundleAccessorResult(_ scope: MacroEvaluationScope) async -> GeneratedResourceAccessorResult? {
17301779
let bundleName = scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_BUNDLE_NAME)
17311780
let isRegularPackage = scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_TARGET_KIND) == .regular

Tests/SWBBuildSystemTests/MergeableLibrariesBuildOperationTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests {
226226

227227
// Check mergeable framework targets.
228228
for targetName in ["FwkTarget1", "FwkTarget2"] {
229+
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in }
229230
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in }
230231
results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in
231232
task.checkCommandLineDoesNotContain("-make_mergeable")
@@ -410,6 +411,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests {
410411

411412
// Check mergeable framework targets
412413
for targetName in ["FwkTarget1", "FwkTarget2"] {
414+
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in }
413415
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in }
414416
results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in
415417
task.checkCommandLineContains("-make_mergeable")
@@ -715,6 +717,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests {
715717
// Check FwkTarget
716718
do {
717719
let targetName = "FwkTarget"
720+
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in }
718721
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in }
719722
results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in
720723
task.checkCommandLineDoesNotContain("-make_mergeable")
@@ -875,6 +878,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests {
875878
// Check FwkTarget
876879
do {
877880
let targetName = "FwkTarget"
881+
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in }
878882
results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in }
879883
results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in
880884
task.checkCommandLineContains("-make_mergeable")

0 commit comments

Comments
 (0)