Skip to content

Commit da67fc7

Browse files
neonichuowenv
andauthored
SDK imports metadata (#75)
This provides new metadata during builds: - Use the new `-sdk_imports` linker option when appropriate and provide a post-processed version of it as a resource in each bundle. - For each codeless bundle, "stamp" its Info.plist with any clients, this allows finding the library associated with a particular codeless bundle. Both behaviors are enabled by the `ENABLE_SDK_IMPORTS` build setting which defaults to the inverse of `ASSETCATALOG_COMPILER_SKIP_APP_STORE_DEPLOYMENT`. Co-authored-by: Owen Voorhees <[email protected]>
1 parent 7da0a63 commit da67fc7

40 files changed

+613
-104
lines changed

Sources/SWBCore/PlannedTaskAction.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,29 +74,32 @@ public struct InfoPlistProcessorTaskActionContext: PlatformBuildContext, Seriali
7474
public var sdk: SDK?
7575
public var sdkVariant: SDKVariant?
7676
public var cleanupRequiredArchitectures: [String]
77+
public var clientLibrariesForCodelessBundle: [String]
7778

78-
public init(scope: MacroEvaluationScope, productType: ProductTypeSpec?, platform: Platform?, sdk: SDK?, sdkVariant: SDKVariant?, cleanupRequiredArchitectures: [String]) {
79+
public init(scope: MacroEvaluationScope, productType: ProductTypeSpec?, platform: Platform?, sdk: SDK?, sdkVariant: SDKVariant?, cleanupRequiredArchitectures: [String], clientLibrariesForCodelessBundle: [String] = []) {
7980
self.scope = scope
8081
self.productType = productType
8182
self.platform = platform
8283
self.sdk = sdk
8384
self.sdkVariant = sdkVariant
8485
self.cleanupRequiredArchitectures = cleanupRequiredArchitectures
86+
self.clientLibrariesForCodelessBundle = clientLibrariesForCodelessBundle
8587
}
8688

8789
public var targetBuildVersionPlatforms: Set<BuildVersion.Platform>? {
8890
targetBuildVersionPlatforms(in: scope)
8991
}
9092

9193
public func serialize<T: Serializer>(to serializer: T) {
92-
serializer.beginAggregate(7)
94+
serializer.beginAggregate(8)
9395
serializer.serialize(scope)
9496
serializer.serialize(platform?.identifier)
9597
serializer.serialize(sdk?.canonicalName)
9698
serializer.serialize(sdkVariant?.name)
9799
serializer.serialize(productType?.identifier)
98100
serializer.serialize(productType?.domain)
99101
serializer.serialize(cleanupRequiredArchitectures)
102+
serializer.serialize(clientLibrariesForCodelessBundle)
100103
serializer.endAggregate()
101104
}
102105

@@ -105,7 +108,7 @@ public struct InfoPlistProcessorTaskActionContext: PlatformBuildContext, Seriali
105108
// Get the platform registry to use to look up the platform from the deserializer's delegate.
106109
guard let delegate = deserializer.delegate as? (any InfoPlistProcessorTaskActionContextDeserializerDelegate) else { throw DeserializerError.invalidDelegate("delegate must be a BuildDescriptionDeserializerDelegate") }
107110

108-
try deserializer.beginAggregate(7)
111+
try deserializer.beginAggregate(8)
109112
let scope: MacroEvaluationScope = try deserializer.deserialize()
110113

111114
let platformIdentifier: String? = try deserializer.deserialize()
@@ -158,8 +161,9 @@ public struct InfoPlistProcessorTaskActionContext: PlatformBuildContext, Seriali
158161
}()
159162

160163
let cleanupRequiredArchitectures: [String] = try deserializer.deserialize()
164+
let clientLibrariesForCodelessBundle: [String] = try deserializer.deserialize()
161165

162-
self = InfoPlistProcessorTaskActionContext(scope: scope, productType: productType, platform: platform, sdk: sdk, sdkVariant: sdkVariant, cleanupRequiredArchitectures: cleanupRequiredArchitectures)
166+
self = InfoPlistProcessorTaskActionContext(scope: scope, productType: productType, platform: platform, sdk: sdk, sdkVariant: sdkVariant, cleanupRequiredArchitectures: cleanupRequiredArchitectures, clientLibrariesForCodelessBundle: clientLibrariesForCodelessBundle)
163167
}
164168
}
165169

@@ -333,6 +337,7 @@ public protocol TaskActionCreationDelegate
333337
func createValidateDevelopmentAssetsTaskAction() -> any PlannedTaskAction
334338
func createSignatureCollectionTaskAction() -> any PlannedTaskAction
335339
func createClangModuleVerifierInputGeneratorTaskAction() -> any PlannedTaskAction
340+
func createProcessSDKImportsTaskAction() -> any PlannedTaskAction
336341
}
337342

338343
extension TaskActionCreationDelegate {

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ public final class BuiltinMacros {
648648
/// be migrated. This is merely a stop-gap measure until we can turn on the debug
649649
/// dylib for macOS completely.
650650
public static let ENABLE_PREVIEWS_DYLIB_OVERRIDE = BuiltinMacros.declareBooleanMacro("ENABLE_PREVIEWS_DYLIB_OVERRIDE")
651+
public static let ENABLE_SDK_IMPORTS = BuiltinMacros.declareBooleanMacro("ENABLE_SDK_IMPORTS")
651652
public static let ENABLE_SIGNATURE_AGGREGATION = BuiltinMacros.declareBooleanMacro("ENABLE_SIGNATURE_AGGREGATION")
652653
public static let DISABLE_TASK_SANDBOXING = BuiltinMacros.declareBooleanMacro("DISABLE_TASK_SANDBOXING")
653654
public static let ENABLE_USER_SCRIPT_SANDBOXING = BuiltinMacros.declareBooleanMacro("ENABLE_USER_SCRIPT_SANDBOXING")
@@ -798,6 +799,7 @@ public final class BuiltinMacros {
798799
public static let LD_LTO_OBJECT_FILE = BuiltinMacros.declarePathMacro("LD_LTO_OBJECT_FILE")
799800
public static let LD_NO_PIE = BuiltinMacros.declareBooleanMacro("LD_NO_PIE")
800801
public static let LD_RUNPATH_SEARCH_PATHS = BuiltinMacros.declareStringListMacro("LD_RUNPATH_SEARCH_PATHS")
802+
public static let LD_SDK_IMPORTS_FILE = BuiltinMacros.declarePathMacro("LD_SDK_IMPORTS_FILE")
801803
public static let LD_WARN_UNUSED_DYLIBS = BuiltinMacros.declareBooleanMacro("LD_WARN_UNUSED_DYLIBS")
802804
public static let LEX = BuiltinMacros.declarePathMacro("LEX")
803805
public static let LEXFLAGS = BuiltinMacros.declareStringListMacro("LEXFLAGS")
@@ -1656,6 +1658,7 @@ public final class BuiltinMacros {
16561658
ENABLE_DEBUG_DYLIB_OVERRIDE,
16571659
ENFORCE_VALID_ARCHS,
16581660
ENABLE_PREVIEWS_DYLIB_OVERRIDE,
1661+
ENABLE_SDK_IMPORTS,
16591662
ENABLE_SIGNATURE_AGGREGATION,
16601663
ENABLE_TESTABILITY,
16611664
ENABLE_TESTING_SEARCH_PATHS,
@@ -1843,6 +1846,7 @@ public final class BuiltinMacros {
18431846
LD_LTO_OBJECT_FILE,
18441847
LD_NO_PIE,
18451848
LD_RUNPATH_SEARCH_PATHS,
1849+
LD_SDK_IMPORTS_FILE,
18461850
LD_WARN_UNUSED_DYLIBS,
18471851
LEGACY_DEVELOPER_DIR,
18481852
LEX,

Sources/SWBCore/Specs/CoreBuildSystem.xcspec

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4462,6 +4462,13 @@ When this setting is enabled:
44624462
DisplayName = "Sticker Sharing Level";
44634463
Description = "When `GENERATE_INFOPLIST_FILE` is enabled, sets the value of the NSStickerSharingLevel key in the `Info.plist` file to the value of this build setting.";
44644464
},
4465+
4466+
// Support for producing SDK imports metadata
4467+
{
4468+
Name = "ENABLE_SDK_IMPORTS";
4469+
Type = Boolean;
4470+
DefaultValue = "$(ASSETCATALOG_COMPILER_SKIP_APP_STORE_DEPLOYMENT:not)";
4471+
},
44654472
);
44664473
},
44674474
)

Sources/SWBCore/Specs/RegisterSpecs.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public struct BuiltinSpecsExtension: SpecificationsExtension {
123123
CreateAssetPackManifestToolSpec.self,
124124
CreateBuildDirectorySpec.self,
125125
MergeInfoPlistSpec.self,
126+
ProcessSDKImportsSpec.self,
126127
ProcessXCFrameworkLibrarySpec.self,
127128
RegisterExecutionPolicyExceptionToolSpec.self,
128129
SwiftHeaderToolSpec.self,

Sources/SWBCore/Specs/Tools/InfoPlistTool.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public final class InfoPlistToolSpec : GenericCommandLineToolSpec, SpecIdentifie
2020
fatalError("unexpected direct invocation")
2121
}
2222

23-
public func constructInfoPlistTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, generatedPkgInfoFile: Path? = nil, additionalContentFilePaths: [Path] = [], requiredArch: String? = nil, appPrivacyContentFiles: [Path] = []) async {
23+
public func constructInfoPlistTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, generatedPkgInfoFile: Path? = nil, additionalContentFilePaths: [Path] = [], requiredArch: String? = nil, appPrivacyContentFiles: [Path] = [], clientLibrariesForCodelessBundle: [String] = []) async {
2424
let input = cbc.input
2525
let inputPath = input.absolutePath
2626
let outputPath = cbc.output
@@ -108,7 +108,7 @@ public final class InfoPlistToolSpec : GenericCommandLineToolSpec, SpecIdentifie
108108

109109
commandLine += ["-o", outputPath.str]
110110

111-
let context = InfoPlistProcessorTaskActionContext(scope: cbc.scope, productType: cbc.producer.productType, platform: cbc.producer.platform, sdk: cbc.producer.sdk, sdkVariant: cbc.producer.sdkVariant, cleanupRequiredArchitectures: cleanupArchs.sorted())
111+
let context = InfoPlistProcessorTaskActionContext(scope: cbc.scope, productType: cbc.producer.productType, platform: cbc.producer.platform, sdk: cbc.producer.sdk, sdkVariant: cbc.producer.sdkVariant, cleanupRequiredArchitectures: cleanupArchs.sorted(), clientLibrariesForCodelessBundle: clientLibrariesForCodelessBundle)
112112
let inputs = [inputPath] + effectiveAdditionalContentFilePaths + appPrivacyContentFiles
113113
let serializer = MsgPackSerializer()
114114
serializer.serialize(context)

Sources/SWBCore/Specs/Tools/LinkerTools.swift

Lines changed: 81 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,17 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType {
556556
// Generate the command line.
557557
var commandLine = commandLineFromTemplate(cbc, delegate, optionContext: optionContext, specialArgs: specialArgs, lookup: lookup).map(\.asString)
558558

559+
// Add flags to emit SDK imports info.
560+
let sdkImportsInfoFile = cbc.scope.evaluate(BuiltinMacros.LD_SDK_IMPORTS_FILE)
561+
let supportsSDKImportsFeature = (try? optionContext?.toolVersion >= .init("1164")) == true
562+
let usesLDClassic = commandLine.contains("-r") || commandLine.contains("-ld_classic") || cbc.scope.evaluate(BuiltinMacros.CURRENT_ARCH) == "armv7k"
563+
if !usesLDClassic, supportsSDKImportsFeature, !sdkImportsInfoFile.isEmpty, cbc.scope.evaluate(BuiltinMacros.ENABLE_SDK_IMPORTS), cbc.producer.isApplePlatform {
564+
commandLine.insert(contentsOf: ["-Xlinker", "-sdk_imports", "-Xlinker", sdkImportsInfoFile.str, "-Xlinker", "-sdk_imports_each_object"], at: commandLine.count - 2) // This preserves the assumption that the last argument is the linker output which a few tests make.
565+
outputs.append(delegate.createNode(sdkImportsInfoFile))
566+
567+
await cbc.producer.processSDKImportsSpec.createTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: []), delegate, ldSDKImportsPath: sdkImportsInfoFile)
568+
}
569+
559570
// Select the driver to use based on the input file types, replacing the value computed by commandLineFromTemplate().
560571
let usedCXX = usedTools.values.contains(where: { $0.contains(where: { $0.languageDialect?.isPlusPlus ?? false }) })
561572
commandLine[0] = resolveExecutablePath(cbc, computeLinkerPath(cbc, usedCXX: usedCXX)).str
@@ -957,7 +968,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType {
957968
}
958969

959970
// Args without parameters (-Xlinker-prefixed, e.g. -Xlinker)
960-
for arg in ["-bitcode_verify", "-export_dynamic"] {
971+
for arg in ["-bitcode_verify", "-export_dynamic", "-sdk_imports_each_object"] {
961972
while let index = commandLine.firstIndex(of: arg) {
962973
guard index > 0, commandLine[index - 1] == argPrefix else { break }
963974
commandLine.removeSubrange(index - 1 ... index)
@@ -976,7 +987,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType {
976987
}
977988

978989
// Args with a parameter (-Xlinker-prefixed, e.g. -Xlinker arg -Xlinker param)
979-
for arg in ["-object_path_lto", "-add_ast_path", "-dependency_info", "-map", "-order_file", "-bitcode_symbol_map", "-final_output", "-allowable_client"] {
990+
for arg in ["-object_path_lto", "-add_ast_path", "-dependency_info", "-map", "-order_file", "-bitcode_symbol_map", "-final_output", "-allowable_client", "-sdk_imports"] {
980991
while let index = commandLine.firstIndex(of: arg) {
981992
guard index > 0,
982993
index + 2 < commandLine.count,
@@ -1228,67 +1239,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType {
12281239
return nil
12291240
}
12301241

1231-
do {
1232-
do {
1233-
let commandLine = [toolPath.str, "-version_details"]
1234-
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, commandLine, { executionResult in
1235-
let gnuLD = [
1236-
#/GNU ld version (?<version>[\d.]+)-.*/#,
1237-
#/GNU ld \(GNU Binutils.*\) (?<version>[\d.]+)/#,
1238-
]
1239-
if let match = try gnuLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1240-
return DiscoveredLdLinkerToolSpecInfo(linker: .gnuld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1241-
}
1242-
1243-
let goLD = [
1244-
#/GNU gold version (?<version>[\d.]+)-.*/#,
1245-
#/GNU gold \(GNU Binutils.*\) (?<version>[\d.]+)/#,
1246-
]
1247-
if let match = try goLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1248-
return DiscoveredLdLinkerToolSpecInfo(linker: .gold, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1249-
}
1250-
1251-
struct LDVersionDetails: Decodable {
1252-
let version: Version
1253-
let architectures: Set<String>
1254-
}
1255-
1256-
let details: LDVersionDetails
1257-
do {
1258-
details = try JSONDecoder().decode(LDVersionDetails.self, from: executionResult.stdout)
1259-
} catch {
1260-
throw CommandLineOutputJSONParsingError(commandLine: commandLine, data: executionResult.stdout)
1261-
}
1262-
1263-
return DiscoveredLdLinkerToolSpecInfo(linker: .ld64, toolPath: toolPath, toolVersion: details.version, architectures: details.architectures)
1264-
})
1265-
} catch let e as CommandLineOutputJSONParsingError {
1266-
let vCommandLine = [toolPath.str, "-v"]
1267-
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, vCommandLine, { executionResult in
1268-
let lld = [
1269-
#/LLD (?<version>[\d.]+) .*/#,
1270-
]
1271-
if let match = try lld.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1272-
return DiscoveredLdLinkerToolSpecInfo(linker: .lld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1273-
}
1274-
1275-
let versionCommandLine = [toolPath.str, "--version"]
1276-
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, versionCommandLine, { executionResult in
1277-
let lld = [
1278-
#/LLD (?<version>[\d.]+) .*/#,
1279-
]
1280-
if let match = try lld.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1281-
return DiscoveredLdLinkerToolSpecInfo(linker: .lld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1282-
}
1283-
1284-
throw e
1285-
})
1286-
})
1287-
}
1288-
} catch {
1289-
delegate.error(error)
1290-
return nil
1291-
}
1242+
return await discoveredLinkerToolsInfo(producer, delegate, at: toolPath)
12921243
}
12931244
}
12941245

@@ -1582,6 +1533,73 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType {
15821533
}
15831534
}
15841535

1536+
/// Consults the global cache of discovered info for the linker at `toolPath` and returns it, creating it if necessary.
1537+
///
1538+
/// This is global and public because it is used by `SWBTaskExecution` and `CoreBasedTests`, which is the basis of many of our tests (so caching this info across tests is desirable).
1539+
public func discoveredLinkerToolsInfo(_ producer: any CommandProducer, _ delegate: any CoreClientTargetDiagnosticProducingDelegate, at toolPath: Path) async -> (any DiscoveredCommandLineToolSpecInfo)? {
1540+
do {
1541+
do {
1542+
let commandLine = [toolPath.str, "-version_details"]
1543+
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, commandLine, { executionResult in
1544+
let gnuLD = [
1545+
#/GNU ld version (?<version>[\d.]+)-.*/#,
1546+
#/GNU ld \(GNU Binutils.*\) (?<version>[\d.]+)/#,
1547+
]
1548+
if let match = try gnuLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1549+
return DiscoveredLdLinkerToolSpecInfo(linker: .gnuld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1550+
}
1551+
1552+
let goLD = [
1553+
#/GNU gold version (?<version>[\d.]+)-.*/#,
1554+
#/GNU gold \(GNU Binutils.*\) (?<version>[\d.]+)/#,
1555+
]
1556+
if let match = try goLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1557+
return DiscoveredLdLinkerToolSpecInfo(linker: .gold, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1558+
}
1559+
1560+
struct LDVersionDetails: Decodable {
1561+
let version: Version
1562+
let architectures: Set<String>
1563+
}
1564+
1565+
let details: LDVersionDetails
1566+
do {
1567+
details = try JSONDecoder().decode(LDVersionDetails.self, from: executionResult.stdout)
1568+
} catch {
1569+
throw CommandLineOutputJSONParsingError(commandLine: commandLine, data: executionResult.stdout)
1570+
}
1571+
1572+
return DiscoveredLdLinkerToolSpecInfo(linker: .ld64, toolPath: toolPath, toolVersion: details.version, architectures: details.architectures)
1573+
})
1574+
} catch let e as CommandLineOutputJSONParsingError {
1575+
let vCommandLine = [toolPath.str, "-v"]
1576+
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, vCommandLine, { executionResult in
1577+
let lld = [
1578+
#/LLD (?<version>[\d.]+) .*/#,
1579+
]
1580+
if let match = try lld.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1581+
return DiscoveredLdLinkerToolSpecInfo(linker: .lld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1582+
}
1583+
1584+
let versionCommandLine = [toolPath.str, "--version"]
1585+
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, versionCommandLine, { executionResult in
1586+
let lld = [
1587+
#/LLD (?<version>[\d.]+) .*/#,
1588+
]
1589+
if let match = try lld.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1590+
return DiscoveredLdLinkerToolSpecInfo(linker: .lld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1591+
}
1592+
1593+
throw e
1594+
})
1595+
})
1596+
}
1597+
} catch {
1598+
delegate.error(error)
1599+
return nil
1600+
}
1601+
}
1602+
15851603
/// Enumerates a linker command line, calling the provided `handle` closure for each logical argument, providing the argument name as `arg` and a `value` function which returns the value of that argument at `offset` positions distance from the `arg` value based on how many arguments it takes.
15861604
///
15871605
/// The purpose of this function is to automatically abstract away handling of the `-Xlinker` and `-Wl` syntax used by compiler drivers to forward arguments to the linker which they themselves do not understand.

0 commit comments

Comments
 (0)