Skip to content
Merged
3 changes: 3 additions & 0 deletions Sources/SWBApplePlatform/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ add_library(SWBApplePlatform
CoreDataCompiler.swift
CoreMLCompiler.swift
DittoTool.swift
ExtensionPointExtractorTaskProducer.swift
ExtensionPointsCompiler.swift
EXUtil.swift
IIGCompiler.swift
InstrumentsPackageBuilderSpec.swift
IntentsCompiler.swift
Expand Down Expand Up @@ -63,6 +65,7 @@ SwiftBuild_Bundle(MODULE SWBApplePlatform FILES
Specs/Embedded-Shared.xcspec
Specs/Embedded-Simulator.xcspec
Specs/EmbeddedBinaryValidationUtility.xcspec
Specs/EXUtil.xcspec
Specs/GenerateAppPlaygroundAssetCatalog.xcspec
Specs/GenerateTextureAtlas.xcspec
Specs/IBCompiler.xcspec
Expand Down
124 changes: 124 additions & 0 deletions Sources/SWBApplePlatform/EXUtil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SWBUtil
import SWBMacro
import SWBCore
import SWBProtocol
import Foundation

final class ExtensionPointExtractorSpec: GenericCommandLineToolSpec, SpecIdentifierType, @unchecked Sendable {
public static let identifier = "com.apple.compilers.extract-appextensionpoints"

static func shouldConstructTask(scope: MacroEvaluationScope, productType: ProductTypeSpec?, isApplePlatform: Bool) -> Bool {
let isNormalVariant = scope.evaluate(BuiltinMacros.CURRENT_VARIANT) == "normal"
let buildComponents = scope.evaluate(BuiltinMacros.BUILD_COMPONENTS)
let isBuild = buildComponents.contains("build")
let indexEnableBuildArena = scope.evaluate(BuiltinMacros.INDEX_ENABLE_BUILD_ARENA)
let isAppProductType = productType?.conformsTo(identifier: "com.apple.product-type.application") ?? false
let extensionPointExtractorEnabled = scope.evaluate(BuiltinMacros.EX_ENABLE_EXTENSION_POINT_GENERATION)

let result = (
isBuild
&& isNormalVariant
&& extensionPointExtractorEnabled
&& !indexEnableBuildArena
&& isAppProductType
&& isApplePlatform
)
return result
}

override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
guard Self.shouldConstructTask(scope: cbc.scope, productType: cbc.producer.productType, isApplePlatform: cbc.producer.isApplePlatform) else {
return
}

let inputs = cbc.inputs.map { input in
return delegate.createNode(input.absolutePath)
}.filter { node in
node.path.fileExtension == "swiftconstvalues"
}
var outputs = [any PlannedNode]()

let outputPath = cbc.scope.evaluate(BuiltinMacros.EXTENSIONS_FOLDER_PATH).join(Path("\(cbc.scope.evaluate(BuiltinMacros.PRODUCT_MODULE_NAME))-generated.appexpt"))
outputs.append(delegate.createNode(outputPath))

let commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString)

delegate.createTask(type: self,
ruleInfo: defaultRuleInfo(cbc, delegate),
commandLine: commandLine,
environment: environmentFromSpec(cbc, delegate),
workingDirectory: cbc.producer.defaultWorkingDirectory,
inputs: inputs,
outputs: outputs,
action: nil,
execDescription: resolveExecutionDescription(cbc, delegate),
enableSandboxing: enableSandboxing)
}
}

final class AppExtensionPlistGeneratorSpec: GenericCommandLineToolSpec, SpecIdentifierType, @unchecked Sendable {
public static let identifier = "com.apple.compilers.appextension-plist-generator"

static func shouldConstructTask(scope: MacroEvaluationScope, productType: ProductTypeSpec?, isApplePlatform: Bool) -> Bool {
let isNormalVariant = scope.evaluate(BuiltinMacros.CURRENT_VARIANT) == "normal"
let buildComponents = scope.evaluate(BuiltinMacros.BUILD_COMPONENTS)
let isBuild = buildComponents.contains("build")
let indexEnableBuildArena = scope.evaluate(BuiltinMacros.INDEX_ENABLE_BUILD_ARENA)
let isAppExtensionProductType = productType?.conformsTo(identifier: "com.apple.product-type.extensionkit-extension") ?? false
let extensionPointAttributesGenerationEnabled = !scope.evaluate(BuiltinMacros.EX_DISABLE_APPEXTENSION_ATTRIBUTES_GENERATION)

let result = ( isBuild
&& isNormalVariant
&& extensionPointAttributesGenerationEnabled
&& !indexEnableBuildArena
&& (isAppExtensionProductType)
&& isApplePlatform )

return result
}

override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
let scope = cbc.scope
let productType = cbc.producer.productType
let isApplePlatform = cbc.producer.isApplePlatform
guard Self.shouldConstructTask(scope: scope, productType: productType, isApplePlatform: isApplePlatform) else {
return
}

let inputs = cbc.inputs.map { input in
return delegate.createNode(input.absolutePath)
}.filter { node in
node.path.fileExtension == "swiftconstvalues"
}
var outputs = [any PlannedNode]()
let outputPath = cbc.output
outputs.append(delegate.createNode(outputPath))


let commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString)

delegate.createTask(type: self,
ruleInfo: defaultRuleInfo(cbc, delegate),
commandLine: commandLine,
environment: environmentFromSpec(cbc, delegate),
workingDirectory: cbc.producer.defaultWorkingDirectory,
inputs: inputs,
outputs: outputs,
action: nil,
execDescription: resolveExecutionDescription(cbc, delegate),
enableSandboxing: enableSandboxing
)
}
}
171 changes: 171 additions & 0 deletions Sources/SWBApplePlatform/ExtensionPointExtractorTaskProducer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SWBCore
import SWBUtil
import SWBMacro
import SWBTaskConstruction

final class ExtensionPointExtractorTaskProducer: PhasedTaskProducer, TaskProducer {

override var defaultTaskOrderingOptions: TaskOrderingOptions {
return .unsignedProductRequirement
}

private func filterBuildFiles(_ buildFiles: [BuildFile]?, identifiers: [String], buildFilesProcessingContext: BuildFilesProcessingContext) -> [FileToBuild] {
guard let buildFiles else {
return []
}

let fileTypes = identifiers.compactMap { identifier in
context.lookupFileType(identifier: identifier)
}

return fileTypes.flatMap { fileType in
buildFiles.compactMap { buildFile in
guard let resolvedBuildFileInfo = try? self.context.resolveBuildFileReference(buildFile),
!buildFilesProcessingContext.isExcluded(resolvedBuildFileInfo.absolutePath, filters: buildFile.platformFilters),
resolvedBuildFileInfo.fileType.conformsTo(fileType) else {
return nil
}

return FileToBuild(absolutePath: resolvedBuildFileInfo.absolutePath, fileType: fileType)
}
}
}

func generateTasks() async -> [any PlannedTask] {

guard ExtensionPointExtractorSpec.shouldConstructTask(scope: context.settings.globalScope, productType: context.productType, isApplePlatform: context.isApplePlatform) else {
return []
}

context.addDeferredProducer {

let scope = self.context.settings.globalScope
let buildFilesProcessingContext = BuildFilesProcessingContext(scope)

let perArchConstMetadataFiles = self.context.generatedSwiftConstMetadataFiles()

let constMetadataFiles: [Path]
if let firstArch = perArchConstMetadataFiles.keys.sorted().first {
constMetadataFiles = perArchConstMetadataFiles[firstArch]!
} else {
constMetadataFiles = []
}

let constMetadataFilesToBuild = constMetadataFiles.map { absolutePath -> FileToBuild in
let fileType = self.context.workspaceContext.core.specRegistry.getSpec("file") as! FileTypeSpec
return FileToBuild(absolutePath: absolutePath, fileType: fileType)
}

let inputs = constMetadataFilesToBuild
guard inputs.isEmpty == false else {
return []
}

var deferredTasks: [any PlannedTask] = []

let cbc = CommandBuildContext(producer: self.context, scope: scope, inputs: inputs, resourcesDir: buildFilesProcessingContext.resourcesDir)
await self.appendGeneratedTasks(&deferredTasks) { delegate in
let domain = self.context.settings.platform?.name ?? ""
guard let spec = self.context.specRegistry.getSpec("com.apple.compilers.extract-appextensionpoints", domain:domain) as? ExtensionPointExtractorSpec else {
return
}
await spec.constructTasks(cbc, delegate)
}

return deferredTasks
}
return []
}
}


final class AppExtensionInfoPlistGeneratorTaskProducer: PhasedTaskProducer, TaskProducer {

override var defaultTaskOrderingOptions: TaskOrderingOptions {
return .unsignedProductRequirement
}

private func filterBuildFiles(_ buildFiles: [BuildFile]?, identifiers: [String], buildFilesProcessingContext: BuildFilesProcessingContext) -> [FileToBuild] {
guard let buildFiles else {
return []
}

let fileTypes = identifiers.compactMap { identifier in
context.lookupFileType(identifier: identifier)
}

return fileTypes.flatMap { fileType in
buildFiles.compactMap { buildFile in
guard let resolvedBuildFileInfo = try? self.context.resolveBuildFileReference(buildFile),
!buildFilesProcessingContext.isExcluded(resolvedBuildFileInfo.absolutePath, filters: buildFile.platformFilters),
resolvedBuildFileInfo.fileType.conformsTo(fileType) else {
return nil
}

return FileToBuild(absolutePath: resolvedBuildFileInfo.absolutePath, fileType: fileType)
}
}
}

func generateTasks() async -> [any PlannedTask] {

let scope = context.settings.globalScope
let productType = context.productType
let isApplePlatform = context.isApplePlatform
guard AppExtensionPlistGeneratorSpec.shouldConstructTask(scope: scope, productType: productType, isApplePlatform: isApplePlatform) else {
return []
}

let tasks: [any PlannedTask] = []
let buildFilesProcessingContext = BuildFilesProcessingContext(scope)

let moduelName = context.settings.globalScope.evaluate(BuiltinMacros.TARGET_NAME)
let plistPath = buildFilesProcessingContext.tmpResourcesDir.join(Path("\(moduelName)-appextension-generated-info.plist"))

context.addDeferredProducer {

let perArchConstMetadataFiles = self.context.generatedSwiftConstMetadataFiles()

let constMetadataFiles: [Path]
if let firstArch = perArchConstMetadataFiles.keys.sorted().first {
constMetadataFiles = perArchConstMetadataFiles[firstArch]!
} else {
constMetadataFiles = []
}

let constMetadataFilesToBuild = constMetadataFiles.map { absolutePath -> FileToBuild in
let fileType = self.context.workspaceContext.core.specRegistry.getSpec("file") as! FileTypeSpec
return FileToBuild(absolutePath: absolutePath, fileType: fileType)
}

let inputs = constMetadataFilesToBuild
var deferredTasks: [any PlannedTask] = []

let cbc = CommandBuildContext(producer: self.context, scope: scope, inputs: inputs, output: plistPath)

await self.appendGeneratedTasks(&deferredTasks) { delegate in
let domain = self.context.settings.platform?.name ?? ""
guard let spec = self.context.specRegistry.getSpec("com.apple.compilers.appextension-plist-generator",domain: domain) as? AppExtensionPlistGeneratorSpec else {
return
}
await spec.constructTasks(cbc, delegate)
}

return deferredTasks
}
self.context.addGeneratedInfoPlistContent(plistPath)
return tasks
}
}
37 changes: 32 additions & 5 deletions Sources/SWBApplePlatform/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ struct TaskProducersExtension: TaskProducerExtension {
}

var unorderedPostSetupTaskProducers: [any TaskProducerFactory] {
[
StubBinaryTaskProducerFactory()
]
[StubBinaryTaskProducerFactory(),
AppExtensionInfoPlistGeneratorTaskProducerFactory(),
ExtensionPointExtractorTaskProducerFactory()]
}

var unorderedPostBuildPhasesTaskProducers: [any TaskProducerFactory] {
Expand All @@ -63,6 +63,26 @@ struct TaskProducersExtension: TaskProducerExtension {
}
}

struct ExtensionPointExtractorTaskProducerFactory: TaskProducerFactory {
var name: String {
"ExtensionPointExtractorTaskProducer"
}

func createTaskProducer(_ context: TargetTaskProducerContext, startPhaseNodes: [PlannedVirtualNode], endPhaseNode: PlannedVirtualNode) -> any TaskProducer {
ExtensionPointExtractorTaskProducer(context, phaseStartNodes: startPhaseNodes, phaseEndNode: endPhaseNode)
}
}

struct AppExtensionInfoPlistGeneratorTaskProducerFactory: TaskProducerFactory {
var name: String {
"AppExtensionInfoPlistGeneratorTaskProducer"
}

func createTaskProducer(_ context: TargetTaskProducerContext, startPhaseNodes: [PlannedVirtualNode], endPhaseNode: PlannedVirtualNode) -> any TaskProducer {
AppExtensionInfoPlistGeneratorTaskProducer(context, phaseStartNodes: startPhaseNodes, phaseEndNode: endPhaseNode)
}
}

struct StubBinaryTaskProducerFactory: TaskProducerFactory, GlobalTaskProducerFactory {
var name: String {
"StubBinaryTaskProducer"
Expand Down Expand Up @@ -100,9 +120,11 @@ struct RealityAssetsTaskProducerFactory: TaskProducerFactory {
struct ApplePlatformSpecsExtension: SpecificationsExtension {
func specificationClasses() -> [any SpecIdentifierType.Type] {
[
ActoolCompilerSpec.self,
AppExtensionPlistGeneratorSpec.self,
AppIntentsMetadataCompilerSpec.self,
AppIntentsSSUTrainingCompilerSpec.self,
ExtensionPointExtractorSpec.self,
ActoolCompilerSpec.self,
CoreDataModelCompilerSpec.self,
CoreMLCompilerSpec.self,
CopyTiffFileSpec.self,
Expand Down Expand Up @@ -232,7 +254,12 @@ struct AppleSettingsBuilderExtension: SettingsBuilderExtension {
]
}

func addBuiltinDefaults(fromEnvironment environment: [String : String], parameters: BuildParameters) throws -> [String : String] { [:] }
func addBuiltinDefaults(fromEnvironment environment: [String : String], parameters: BuildParameters) throws -> [String : String] {
let appIntentsProtocols = "AppIntent EntityQuery AppEntity TransientEntity AppEnum AppShortcutProviding AppShortcutsProvider AnyResolverProviding AppIntentsPackage DynamicOptionsProvider _IntentValueRepresentable _AssistantIntentsProvider _GenerativeFunctionExtractable IntentValueQuery Resolver"
let extensionKitProtocols = "AppExtension ExtensionPointDefining"
let constValueProtocols = [appIntentsProtocols, extensionKitProtocols].joined(separator: " ")
return ["SWIFT_EMIT_CONST_VALUE_PROTOCOLS" : constValueProtocols]
}
func addOverrides(fromEnvironment: [String : String], parameters: BuildParameters) throws -> [String : String] { [:] }
func addProductTypeDefaults(productType: ProductTypeSpec) -> [String : String] { [:] }
func addSDKOverridingSettings(_ sdk: SDK, _ variant: SDKVariant?, _ sparseSDKs: [SDK], specLookupContext: any SWBCore.SpecLookupContext) throws -> [String : String] { [:] }
Expand Down
9 changes: 9 additions & 0 deletions Sources/SWBApplePlatform/Specs/AppIntentsMetadata.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,15 @@
NO = ();
};
},
{
Name = LM_ENABLE_APP_NAME_OVERRIDE;
Type = Boolean;
DefaultValue = NO;
CommandLineArgs = {
YES = ( "--app-shortcuts-app-name-override" );
NO = ();
};
},
);
},
)
Loading