Skip to content

Commit 4f8a345

Browse files
committed
Allow command line tool specs to be extended by BuildSettingsExtension specs and an additional task producer extension point
This can allow more decoupling of platform specific concepts and the like from the core build specs.
1 parent 8196398 commit 4f8a345

22 files changed

+188
-48
lines changed

Sources/SWBApplePlatform/AssetCatalogCompiler.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,12 @@ public final class ActoolCompilerSpec : GenericCompilerSpec, SpecIdentifierType,
239239
// which it currently receives a listing of via the assetcatalog_dependencies file produced by actool.
240240
let carFiles = [cbc.resourcesDir?.join("Assets.car")].compactMap { $0 }.map(delegate.createNode)
241241

242-
let outputs = evaluatedOutputsResult + (additionalEvaluatedOutputsResult.outputs ).map(delegate.createNode)
242+
let outputs = evaluatedOutputsResult + (additionalEvaluatedOutputsResult.outputs).map { output in
243+
if let fileTypeIdentifier = output.fileType, let fileType = cbc.producer.lookupFileType(identifier: fileTypeIdentifier) {
244+
delegate.declareOutput(FileToBuild(absolutePath: output.path, fileType: fileType))
245+
}
246+
return delegate.createNode(output.path)
247+
}
243248
guard !outputs.isEmpty else { preconditionFailure("ActoolCompilerSpec.constructTasks() invoked with no outputs defined") }
244249

245250
let assetSymbolInputs = cbc.inputs

Sources/SWBApplePlatform/InterfaceBuilderCompiler.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ public class IbtoolCompilerSpec : GenericCompilerSpec, IbtoolCompilerSupport, @u
8686

8787
// Add the additional outputs defined by the spec. These are not declared as outputs but should be processed by the tool separately.
8888
let additionalEvaluatedOutputsResult = await additionalEvaluatedOutputs(cbc, delegate)
89-
outputs += additionalEvaluatedOutputsResult.outputs.map(delegate.createNode)
89+
outputs += additionalEvaluatedOutputsResult.outputs.map { output in
90+
if let fileTypeIdentifier = output.fileType, let fileType = cbc.producer.lookupFileType(identifier: fileTypeIdentifier) {
91+
delegate.declareOutput(FileToBuild(absolutePath: output.path, fileType: fileType))
92+
}
93+
return delegate.createNode(output.path)
94+
}
9095

9196
if let infoPlistContent = additionalEvaluatedOutputsResult.generatedInfoPlistContent {
9297
delegate.declareGeneratedInfoPlistContent(infoPlistContent)

Sources/SWBApplePlatform/Plugin.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ struct AppleDeveloperDirectoryExtension: DeveloperDirectoryExtension {
3737
}
3838

3939
struct TaskProducersExtension: TaskProducerExtension {
40-
4140
func createPreSetupTaskProducers(_ context: TaskProducerContext) -> [any TaskProducer] {
4241
[DevelopmentAssetsTaskProducer(context)]
4342
}
@@ -61,6 +60,9 @@ struct TaskProducersExtension: TaskProducerExtension {
6160
var globalTaskProducers: [any GlobalTaskProducerFactory] {
6261
[StubBinaryTaskProducerFactory()]
6362
}
63+
64+
func generateAdditionalTasks(_ tasks: inout [any SWBCore.PlannedTask], _ producer: any SWBTaskConstruction.TaskProducer) {
65+
}
6466
}
6567

6668
struct ExtensionPointExtractorTaskProducerFactory: TaskProducerFactory {

Sources/SWBCore/Core.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ public final class Core: Sendable {
590590

591591
let specs: [SpecDump] = specRegistry.domains.flatMap { domain -> [SpecDump] in
592592
let allSpecs = specRegistry.findSpecs(BuildSettingsSpec.self, domain: domain, includeInherited: false)
593+
+ specRegistry.findSpecs(BuildSettingsExtensionSpec.self, domain: domain, includeInherited: false)
593594
+ specRegistry.findSpecs(BuildSystemSpec.self, domain: domain, includeInherited: false)
594595
+ specRegistry.findSpecs(CommandLineToolSpec.self, domain: domain, includeInherited: false)
595596
return allSpecs.map { spec in

Sources/SWBCore/Settings/Settings.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fileprivate struct PreOverridesSettings {
8383
let ignoredMacros: [MacroDeclaration] = [BuiltinMacros.OutputFormat, BuiltinMacros.OutputPath]
8484

8585
for spec in specs {
86-
for option in spec.flattenedBuildOptions.values {
86+
for option in core.specRegistry.effectiveFlattenedBuildOptions(spec).values {
8787
guard !ignoredMacros.contains(option.macro) else { continue }
8888

8989
if let value = option.defaultValue {
@@ -236,11 +236,11 @@ fileprivate struct PreOverridesSettings {
236236
// Add the defaults from all the registered tools in the given domain.
237237
//
238238
// FIXME: This is somewhat wasteful, as we end up duplicating values for super specifications. However, that lets us keep the condition set required to enable a particular compiler very simple.
239-
let unionedDefaults = unionedToolDefaults(domain: domain)
239+
let unionedDefaults = unionedToolDefaults(domain: domain).table
240240
let customizedDefaults = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace)
241241
for spec in core.specRegistry.findSpecs(CompilerSpec.self, domain: domain) {
242242
// Add all the necessary defaults.
243-
for option in spec.flattenedBuildOptions.values {
243+
for option in core.specRegistry.effectiveFlattenedBuildOptions(spec).values {
244244
if let defaultValue = option.defaultValue {
245245
// Only push the default value if it diverges from the existing default.
246246
//

Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
776776
let scope = cbc.scope
777777
let inputFileType = cbc.inputs.first?.fileType
778778
let lookup = { self.lookup($0, cbc, delegate) }
779-
for buildOption in self.flattenedOrderedBuildOptions {
779+
for buildOption in cbc.producer.effectiveFlattenedOrderedBuildOptions(self) {
780780
guard let dependencyFormat = buildOption.dependencyFormat else {
781781
continue
782782
}
@@ -910,7 +910,12 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
910910

911911
// Add the additional outputs defined by the spec. These are not declared as outputs but should be processed by the tool separately.
912912
let additionalEvaluatedOutputsResult = await additionalEvaluatedOutputs(cbc, delegate)
913-
outputs.append(contentsOf: additionalEvaluatedOutputsResult.outputs.map({ delegate.createNode($0) }))
913+
outputs.append(contentsOf: additionalEvaluatedOutputsResult.outputs.map { output in
914+
if let fileTypeIdentifier = output.fileType, let fileType = cbc.producer.lookupFileType(identifier: fileTypeIdentifier) {
915+
delegate.declareOutput(FileToBuild(absolutePath: output.path, fileType: fileType))
916+
}
917+
return delegate.createNode(output.path)
918+
})
914919

915920
if let infoPlistContent = additionalEvaluatedOutputsResult.generatedInfoPlistContent {
916921
delegate.declareGeneratedInfoPlistContent(infoPlistContent)
@@ -972,7 +977,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
972977
}
973978

974979
public struct AdditionalEvaluatedOutputsResult {
975-
public var outputs = [Path]()
980+
public var outputs = [(path: Path, fileType: String?)]()
976981
public var generatedInfoPlistContent: Path? = nil
977982
}
978983

@@ -988,20 +993,25 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
988993

989994
// FIXME: In Xcode, this is also marked as an "auxiliary output", which we use in conjunction with the "MightNotEmitAllOutput" flag to determine whether or not the tool needs to rerun if the output is missing.
990995

991-
result.outputs.append(output)
996+
result.outputs.append((output, nil))
992997
}
993998

994999
let producer = cbc.producer
9951000
let scope = cbc.scope
9961001
let inputFileType = cbc.inputs.first?.fileType
9971002
let lookup = { self.lookup($0, cbc, delegate) }
9981003
let optionContext = await discoveredCommandLineToolSpecInfo(producer, scope, delegate)
999-
result.outputs.append(contentsOf: self.flattenedOrderedBuildOptions.flatMap { buildOption -> [Path] in
1004+
result.outputs.append(contentsOf: cbc.producer.effectiveFlattenedOrderedBuildOptions(self, filter: .all).flatMap { buildOption -> [(Path, String?)] in
10001005
// Check if the effective arguments for this build option were non-empty as a proxy for whether it got filtered out by architecture mismatch, etc.
10011006
guard let outputDependencies = buildOption.outputDependencies, !buildOption.getArgumentsForCommand(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, lookup: lookup).isEmpty else {
10021007
return []
10031008
}
1004-
return outputDependencies.compactMap { Path(scope.evaluate($0, lookup: lookup)).nilIfEmpty?.normalize() }
1009+
return outputDependencies.compactMap { outputDependency in
1010+
guard let path = Path(scope.evaluate(outputDependency.path, lookup: lookup)).nilIfEmpty else {
1011+
return nil
1012+
}
1013+
return (path.normalize(), outputDependency.fileType.map { scope.evaluate($0, lookup: lookup).nilIfEmpty } ?? nil)
1014+
}
10051015
})
10061016

10071017
return result
@@ -1225,7 +1235,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
12251235
return cbc.scope.evaluate(value, lookup: lookup).map { .literal(ByteString(encodingAsUTF8: $0)) }
12261236

12271237
case .options:
1228-
return self.commandLineFromOptions(cbc, delegate, optionContext: optionContext, lookup: lookup)
1238+
return self.commandLineFromOptions(cbc, delegate, optionContext: optionContext, buildOptionsFilter: .all, lookup: lookup)
12291239

12301240
case .output:
12311241
// We always resolve the Output via a recursive macro evaluation. See constructTasks() for more information.
@@ -1254,22 +1264,22 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
12541264
/// Creates and returns the command line arguments generated by the options of the specification.
12551265
///
12561266
/// - parameter lookup: An optional closure which functionally defined overriding values during build setting evaluation.
1257-
public func commandLineFromOptions(_ producer: any CommandProducer, scope: MacroEvaluationScope, inputFileType: FileTypeSpec?, optionContext: (any BuildOptionGenerationContext)?, lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [CommandLineArgument] {
1258-
return self.flattenedOrderedBuildOptions.flatMap { $0.getArgumentsForCommand(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, lookup: lookup) }
1267+
public func commandLineFromOptions(_ producer: any CommandProducer, scope: MacroEvaluationScope, inputFileType: FileTypeSpec?, optionContext: (any BuildOptionGenerationContext)?, buildOptionsFilter: BuildOptionsFilter, lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [CommandLineArgument] {
1268+
return producer.effectiveFlattenedOrderedBuildOptions(self, filter: buildOptionsFilter).flatMap { $0.getArgumentsForCommand(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, lookup: lookup) }
12591269
}
12601270

12611271
/// Creates and returns the command line arguments generated by the options of the specification.
12621272
///
12631273
/// - parameter lookup: An optional closure which functionally defined overriding values during build setting evaluation.
1264-
public func commandLineFromOptions(_ cbc: CommandBuildContext, _ delegate: any DiagnosticProducingDelegate, optionContext: (any BuildOptionGenerationContext)?, lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [CommandLineArgument] {
1265-
return commandLineFromOptions(cbc.producer, scope: cbc.scope, inputFileType: cbc.inputs.first?.fileType, optionContext: optionContext, lookup: { self.lookup($0, cbc, delegate, lookup) })
1274+
public func commandLineFromOptions(_ cbc: CommandBuildContext, _ delegate: any DiagnosticProducingDelegate, optionContext: (any BuildOptionGenerationContext)?, buildOptionsFilter: BuildOptionsFilter = .all, lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [CommandLineArgument] {
1275+
return commandLineFromOptions(cbc.producer, scope: cbc.scope, inputFileType: cbc.inputs.first?.fileType, optionContext: optionContext, buildOptionsFilter: buildOptionsFilter, lookup: { self.lookup($0, cbc, delegate, lookup) })
12661276
}
12671277

12681278
/// Creates and returns the command line arguments generated by the specification's build setting corresponding to the given macro declaration.
12691279
///
12701280
/// - parameter lookup: An optional closure which functionally defined overriding values during build setting evaluation.
12711281
func commandLineFromMacroDeclaration(_ producer: any CommandProducer, optionContext: (any BuildOptionGenerationContext)?, scope: MacroEvaluationScope, macro: MacroDeclaration, inputFileType: FileTypeSpec?, lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) -> [CommandLineArgument] {
1272-
return buildOptions.first { $0.name == macro.name }?.getArgumentsForCommand(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, lookup: lookup) ?? []
1282+
return producer.effectiveBuildOptions(self).first { $0.name == macro.name }?.getArgumentsForCommand(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, lookup: lookup) ?? []
12731283
}
12741284

12751285
/// Creates and returns the command line arguments generated by the specification's build setting corresponding to the given macro declaration.
@@ -1285,7 +1295,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
12851295
let scope = cbc.scope
12861296
let inputFileType = cbc.inputs.first?.fileType
12871297
let lookup = { self.lookup($0, cbc, delegate, lookup) }
1288-
return self.flattenedOrderedBuildOptions.flatMap { buildOption -> [Path] in
1298+
return cbc.producer.effectiveFlattenedOrderedBuildOptions(self).flatMap { buildOption -> [Path] in
12891299
// Check if the effective arguments for this build option were non-empty as a proxy for whether it got filtered out by architecture mismatch, etc.
12901300
guard let inputInclusions = buildOption.inputInclusions, !buildOption.getArgumentsForCommand(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, lookup: lookup).isEmpty else {
12911301
return []
@@ -1297,7 +1307,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
12971307
/// Compute the list of additional linker arguments to use when this tool is used for building with the given scope.
12981308
public func computeAdditionalLinkerArgs(_ producer: any CommandProducer, scope: MacroEvaluationScope, inputFileTypes: [FileTypeSpec], optionContext: (any BuildOptionGenerationContext)?, delegate: any TaskGenerationDelegate) async -> (args: [[String]], inputPaths: [Path]) {
12991309
// FIXME: Optimize the list to search here.
1300-
return (args: self.flattenedOrderedBuildOptions.map { $0.getAdditionalLinkerArgs(producer, scope: scope, inputFileTypes: inputFileTypes) }, inputPaths: [])
1310+
return (args: producer.effectiveFlattenedOrderedBuildOptions(self).map { $0.getAdditionalLinkerArgs(producer, scope: scope, inputFileTypes: inputFileTypes) }, inputPaths: [])
13011311
}
13021312

13031313
// Creates and returns the environment from the specification. This includes both the 'EnvironmentVariables' property for this tool spec, and any build options which define that their value should be exported via their 'SetValueInEnvironmentVariable' property.
@@ -1313,7 +1323,7 @@ open class CommandLineToolSpec : PropertyDomainSpec, SpecType, TaskTypeDescripti
13131323

13141324
// Add environment variables from build options which specify they should be added via a 'SetValueInEnvironmentVariable' property.
13151325
// FIXME: Optimize the list to search here.
1316-
for buildOption in self.flattenedOrderedBuildOptions {
1326+
for buildOption in cbc.producer.effectiveFlattenedOrderedBuildOptions(self) {
13171327
if let assignment = buildOption.getEnvironmentAssignmentForCommand(cbc, lookup: wrappedLookup) {
13181328
environment.append(assignment)
13191329
}

Sources/SWBCore/SpecImplementations/PropertyDomainSpec.swift

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ private let buildOptionTypes: [String: any BuildOptionType] = [
343343
let inputInclusions: [MacroStringExpression]?
344344

345345
/// Additional output dependencies to consider when this build option is active.
346-
let outputDependencies: [MacroStringExpression]?
346+
let outputDependencies: [(path: MacroStringExpression, fileType: MacroStringExpression?)]?
347347

348348
/// Helper function for extract an individual value definition from a dictionary.
349349
private static func parseBuildOptionValue(_ parser: SpecParser, _ name: String, _ type: any BuildOptionType, _ data: [String: PropertyListItem]) -> (String, BuildOptionValue)? {
@@ -804,7 +804,7 @@ private let buildOptionTypes: [String: any BuildOptionType] = [
804804
var dependencyFormat: DependencyDataFormat? = nil
805805
var featureFlags: [String]? = nil
806806
var inputInclusions: [MacroStringExpression]? = nil
807-
var outputDependencies: [MacroStringExpression]? = nil
807+
var outputDependencies: [(path: MacroStringExpression, fileType: MacroStringExpression?)]? = nil
808808
for (key, valueData) in items {
809809
switch key {
810810
case "Name":
@@ -1031,14 +1031,36 @@ private let buildOptionTypes: [String: any BuildOptionType] = [
10311031
case "OutputDependencies":
10321032
switch valueData {
10331033
case .plString(let value):
1034-
outputDependencies = [parser.delegate.internalMacroNamespace.parseString(value)]
1034+
outputDependencies = [(parser.delegate.internalMacroNamespace.parseString(value), nil)]
10351035
case .plArray(let values):
10361036
outputDependencies = values.compactMap({ data in
1037-
guard case .plString(let value) = data else {
1038-
error("expected all string values in array for '\(key)'")
1037+
switch data {
1038+
case let .plString(value):
1039+
return (parser.delegate.internalMacroNamespace.parseString(value), nil)
1040+
case let .plDict(value):
1041+
let path: MacroStringExpression
1042+
switch value["Path"] {
1043+
case let .plString(expr):
1044+
path = parser.delegate.internalMacroNamespace.parseString(expr)
1045+
default:
1046+
error("expected string value for subkey 'Path' in element of array in '\(key)'")
1047+
return nil
1048+
}
1049+
1050+
let fileType: MacroStringExpression
1051+
switch value["FileType"] {
1052+
case let .plString(expr):
1053+
fileType = parser.delegate.internalMacroNamespace.parseString(expr)
1054+
default:
1055+
error("expected string value for subkey 'FileType' in element of array in '\(key)'")
1056+
return nil
1057+
}
1058+
1059+
return (path, fileType)
1060+
default:
1061+
error("expected all string or dictionary values in array for '\(key)'")
10391062
return nil
10401063
}
1041-
return parser.delegate.internalMacroNamespace.parseString(value)
10421064
})
10431065
default:
10441066
error("expected string or array value for build option key '\(key)'")
@@ -1720,11 +1742,11 @@ open class PropertyDomainSpec : Spec, @unchecked Sendable {
17201742

17211743

17221744
/// Extensions to PropertyDomainSpec for performance testing.
1723-
public extension PropertyDomainSpec {
1745+
extension PropertyDomainSpec {
17241746

17251747
/// Creates and returns a ``MacroValueAssignmentTable`` populated with the default values of the receiver's build options.
17261748
/// The table's namespace is also returned so that the caller can add further settings to it if desired.
1727-
func macroTableForBuildOptionDefaults(_ core: Core) -> (MacroValueAssignmentTable, MacroNamespace) {
1749+
@_spi(Testing) public func macroTableForBuildOptionDefaults(_ core: Core) -> (MacroValueAssignmentTable, MacroNamespace) {
17281750
var table = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace)
17291751
for option in self.flattenedOrderedBuildOptions {
17301752
guard let value = option.defaultValue else { continue }

0 commit comments

Comments
 (0)