Skip to content

Upstream: Extract Extension Point Definition and Extension's target extension point identifier from swift const values #652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading