diff --git a/Sources/SWBApplePlatform/CMakeLists.txt b/Sources/SWBApplePlatform/CMakeLists.txt index e82db7a2..7dd6a466 100644 --- a/Sources/SWBApplePlatform/CMakeLists.txt +++ b/Sources/SWBApplePlatform/CMakeLists.txt @@ -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 @@ -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 diff --git a/Sources/SWBApplePlatform/EXUtil.swift b/Sources/SWBApplePlatform/EXUtil.swift new file mode 100644 index 00000000..d1a82063 --- /dev/null +++ b/Sources/SWBApplePlatform/EXUtil.swift @@ -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 + ) + } +} diff --git a/Sources/SWBApplePlatform/ExtensionPointExtractorTaskProducer.swift b/Sources/SWBApplePlatform/ExtensionPointExtractorTaskProducer.swift new file mode 100644 index 00000000..9a37cbe1 --- /dev/null +++ b/Sources/SWBApplePlatform/ExtensionPointExtractorTaskProducer.swift @@ -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 + } +} diff --git a/Sources/SWBApplePlatform/Plugin.swift b/Sources/SWBApplePlatform/Plugin.swift index 111b9578..adca6f0e 100644 --- a/Sources/SWBApplePlatform/Plugin.swift +++ b/Sources/SWBApplePlatform/Plugin.swift @@ -47,9 +47,9 @@ struct TaskProducersExtension: TaskProducerExtension { } var unorderedPostSetupTaskProducers: [any TaskProducerFactory] { - [ - StubBinaryTaskProducerFactory() - ] + [StubBinaryTaskProducerFactory(), + AppExtensionInfoPlistGeneratorTaskProducerFactory(), + ExtensionPointExtractorTaskProducerFactory()] } var unorderedPostBuildPhasesTaskProducers: [any TaskProducerFactory] { @@ -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" @@ -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, @@ -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] { [:] } diff --git a/Sources/SWBApplePlatform/Specs/EXUtil.xcspec b/Sources/SWBApplePlatform/Specs/EXUtil.xcspec new file mode 100644 index 00000000..5c7cdd4b --- /dev/null +++ b/Sources/SWBApplePlatform/Specs/EXUtil.xcspec @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +( + { + Type = Compiler; + Identifier = "com.apple.compilers.extract-appextensionpoints"; + Name = "ExtensionPoint Extractor"; + Description = "Extracts Extension Point definition"; + CommandLine = "exutil extract-extension-points --module-name $(PRODUCT_MODULE_NAME) --sdk-root $(SDKROOT) --xcode-version $(XCODE_PRODUCT_BUILD_VERSION) --platform-family $(PLATFORM_FAMILY_NAME) --deployment-target $($(DEPLOYMENT_TARGET_SETTING_NAME)) --bundle-identifier $(PRODUCT_BUNDLE_IDENTIFIER) --output $(TARGET_BUILD_DIR)/$(EXTENSIONS_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).appexpt [inputs]"; + RuleName = "ExtensionPointExtractor"; + ExecDescription = "Extract Extension Point definition"; + ProgressDescription = "Extracting Extension Point definition"; + Outputs = ( + "$(TARGET_BUILD_DIR)/$(EXTENSIONS_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).appexpt" + ); + IsArchitectureNeutral = YES; + CommandOutputParser = XCGenericCommandOutputParser; + }, + + { + Type = Compiler; + Identifier = "com.apple.compilers.appextension-plist-generator"; + Name = "AppExtension plist generator"; + Description = "Generates the required ExtensionKit plist keys"; + CommandLine = "exutil generate-appextension-plist --module-name $(PRODUCT_MODULE_NAME) --sdk-root $(SDKROOT) --xcode-version $(XCODE_PRODUCT_BUILD_VERSION) --platform-family $(PLATFORM_FAMILY_NAME) --deployment-target $($(DEPLOYMENT_TARGET_SETTING_NAME)) --bundle-identifier $(PRODUCT_BUNDLE_IDENTIFIER) --output [output] [inputs]"; + RuleName = "AppExtensionPListGenerator"; + ExecDescription = "Generate AppExtension plist"; + ProgressDescription = "Generating AppExtension plist"; + IsArchitectureNeutral = YES; + CommandOutputParser = XCGenericCommandOutputParser; + }, +) diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 34b1d5ee..04bd1c66 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -663,6 +663,8 @@ public final class BuiltinMacros { public static let DISABLE_TASK_SANDBOXING = BuiltinMacros.declareBooleanMacro("DISABLE_TASK_SANDBOXING") public static let ENABLE_USER_SCRIPT_SANDBOXING = BuiltinMacros.declareBooleanMacro("ENABLE_USER_SCRIPT_SANDBOXING") public static let ENABLE_XOJIT_PREVIEWS = BuiltinMacros.declareBooleanMacro("ENABLE_XOJIT_PREVIEWS") + public static let EX_ENABLE_EXTENSION_POINT_GENERATION = BuiltinMacros.declareBooleanMacro("EX_ENABLE_EXTENSION_POINT_GENERATION") + public static let EX_DISABLE_APPEXTENSION_ATTRIBUTES_GENERATION = BuiltinMacros.declareBooleanMacro("EX_DISABLE_APPEXTENSION_ATTRIBUTES_GENERATION") public static let EXCLUDED_EXPLICIT_TARGET_DEPENDENCIES = BuiltinMacros.declareStringListMacro("EXCLUDED_EXPLICIT_TARGET_DEPENDENCIES") public static let EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES = BuiltinMacros.declareStringListMacro("EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES") public static let EXCLUDED_SOURCE_FILE_NAMES = BuiltinMacros.declareStringListMacro("EXCLUDED_SOURCE_FILE_NAMES") @@ -1705,6 +1707,8 @@ public final class BuiltinMacros { ENTITLEMENTS_DONT_REMOVE_GET_TASK_ALLOW, ENTITLEMENTS_DESTINATION, ENTITLEMENTS_REQUIRED, + EX_ENABLE_EXTENSION_POINT_GENERATION, + EX_DISABLE_APPEXTENSION_ATTRIBUTES_GENERATION, EXCLUDED_EXPLICIT_TARGET_DEPENDENCIES, EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES, EXCLUDED_SOURCE_FILE_NAMES, diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/FilesBasedBuildPhaseTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/FilesBasedBuildPhaseTaskProducer.swift index f80e2178..0707113e 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/FilesBasedBuildPhaseTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/FilesBasedBuildPhaseTaskProducer.swift @@ -76,7 +76,7 @@ package final class BuildFilesProcessingContext: BuildFileFilteringContext { /// The resources directory for the product, if relevant. public let resourcesDir: Path /// The resources intermediates directory, if appropriate. - let tmpResourcesDir: Path + public let tmpResourcesDir: Path /// True if the build files belongs to the preferred arch among the archs we're processing. /// diff --git a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift index 38501ae7..144a07b5 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift @@ -481,7 +481,7 @@ public class TaskProducerContext: StaleFileRemovalContext, BuildFileResolution } /// Add an info plist addition generated by this target. - func addGeneratedInfoPlistContent(_ path: Path) { + public func addGeneratedInfoPlistContent(_ path: Path) { state.withLock { state in assert(!state._inDeferredMode) state._generatedInfoPlistContents.append(path) diff --git a/Sources/SWBUniversalPlatform/Specs/Swift.xcspec b/Sources/SWBUniversalPlatform/Specs/Swift.xcspec index cccdc078..a3fb17ae 100644 --- a/Sources/SWBUniversalPlatform/Specs/Swift.xcspec +++ b/Sources/SWBUniversalPlatform/Specs/Swift.xcspec @@ -936,7 +936,6 @@ { Name = "SWIFT_EMIT_CONST_VALUE_PROTOCOLS"; Type = StringList; - DefaultValue = "AppIntent EntityQuery AppEntity TransientEntity AppEnum AppShortcutProviding AppShortcutsProvider AnyResolverProviding AppIntentsPackage DynamicOptionsProvider _IntentValueRepresentable _AssistantIntentsProvider _GenerativeFunctionExtractable"; DisplayName = "Const value emission protocol list"; Description = "A list of protocol names whose conformances the Swift compiler is to emit compile-time-known values for."; }, diff --git a/Tests/SWBApplePlatformTests/EXUtilTaskConstructionTests.swift b/Tests/SWBApplePlatformTests/EXUtilTaskConstructionTests.swift new file mode 100644 index 00000000..efda1c9e --- /dev/null +++ b/Tests/SWBApplePlatformTests/EXUtilTaskConstructionTests.swift @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// 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 Foundation +import Testing +import SWBCore +import SWBProtocol +import SWBTaskConstruction +import SWBTestSupport +import SWBUtil + + +@Suite +fileprivate struct EXUtilTaskConstructionTests: CoreBasedTests { + + @Test(.requireSDKs(.iOS), .requireMinimumSDKBuildVersion(sdkName: KnownSDK.iOS.sdkName, requiredVersion: "23A213")) + func extractExtensionPoint() async throws { + try await withTemporaryDirectory { tmpDir in + let testProject = try await TestProject( + "aProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("source.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "AD_HOC_CODE_SIGNING_ALLOWED": "YES", + "ARCHS": "arm64", + "CODE_SIGN_IDENTITY": "-", + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_BUNDLE_IDENTIFIER": "com.foo.bar", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SDKROOT": "iphoneos", + "SWIFT_EXEC": swiftCompilerPath.str, + "SWIFT_VERSION": swiftVersion, + "VERSIONING_SYSTEM": "apple-generic", + "SWIFT_EMIT_CONST_VALUE_PROTOCOLS": "Foo Bar", + ]), + ], + targets: [ + TestStandardTarget( + "ExtensionPointTest", + type: .application, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "LM_ENABLE_LINK_GENERATION": "YES", + "EX_ENABLE_EXTENSION_POINT_GENERATION" : "YES" + ]), + ], + buildPhases: [ + TestSourcesBuildPhase(["source.swift"]), + ] + ) + ]) + + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + await tester.checkBuild(runDestination: .iOS) { results in + + results.checkTask(.matchRuleType("ExtensionPointExtractor")) { task in + task.checkCommandLineMatches(["exutil", "extract-extension-points", .anySequence]) + task.checkInputs(contain: [.name("source.swiftconstvalues")]) + task.checkOutputs(contain: [.namePattern(.suffix("-generated.appexpt"))]) + results.checkNoDiagnostics() + + } + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-emit-const-values"]) + } + } + } + } + + @Test(.requireSDKs(.iOS), .requireMinimumSDKBuildVersion(sdkName: KnownSDK.iOS.sdkName, requiredVersion: "23A213")) + func generateExtensionPlist() async throws { + try await withTemporaryDirectory { tmpDir in + let testProject = try await TestProject( + "aProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("source.swift"), + ]), + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "AD_HOC_CODE_SIGNING_ALLOWED": "YES", + "ARCHS": "arm64", + "CODE_SIGN_IDENTITY": "-", + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_BUNDLE_IDENTIFIER": "com.foo.bar", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SDKROOT": "iphoneos", + "SWIFT_EXEC": swiftCompilerPath.str, + "SWIFT_VERSION": swiftVersion, + "VERSIONING_SYSTEM": "apple-generic", + "SWIFT_EMIT_CONST_VALUE_PROTOCOLS": "Foo Bar", + ]), + ], + targets: [ + TestStandardTarget( + "AppExtensionPlistGeneratorTest", + type: .extensionKitExtension, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [:]), + ], + buildPhases: [ + TestSourcesBuildPhase(["source.swift"]), + ] + ) + ]) + + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + try await tester.checkBuild(runDestination: .iOS) { results in + + let generatorTask = try #require(results.checkTask(.matchRuleType("AppExtensionPListGenerator")) { task in + task.checkCommandLineMatches(["exutil", "generate-appextension-plist", .anySequence]) + task.checkInputs(contain: [.name("source.swiftconstvalues")]) + task.checkOutputs(contain: [.namePattern(.suffix("appextension-generated-info.plist"))]) + results.checkNoDiagnostics() + return task + }) + + results.checkTask(.matchRuleType("ProcessInfoPlistFile")) { task in + results.checkTaskFollows(task, antecedent: generatorTask) + } + + results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-emit-const-values"]) + } + } + } + } + +} diff --git a/Tests/SWBTaskConstructionTests/AppExtensionTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/AppExtensionTaskConstructionTests.swift index 5832096a..f84215a3 100644 --- a/Tests/SWBTaskConstructionTests/AppExtensionTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/AppExtensionTaskConstructionTests.swift @@ -69,6 +69,99 @@ fileprivate struct AppExtensionTaskConstructionTests: CoreBasedTests { ], buildPhases: [ TestSourcesBuildPhase(["Source.c"]), + TestCopyFilesBuildPhase([ + TestBuildFile(.target("Legacy"))], destinationSubfolder: .plugins, onlyForDeployment: false), + TestCopyFilesBuildPhase([ + TestBuildFile(.target("Modern"))], destinationSubfolder: .builtProductsDir, destinationSubpath: "$(EXTENSIONS_FOLDER_PATH)", onlyForDeployment: false), + ], + dependencies: ["Legacy", "Modern"]), + TestStandardTarget( + "Legacy", + type: .applicationExtension, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]) + ], + buildPhases: [ + TestSourcesBuildPhase(["Source.c"]), + ]), + TestStandardTarget( + "Modern", + type: .extensionKitExtension, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]) + ], + buildPhases: [ + TestSourcesBuildPhase(["Source.c"]), + ]), + ]) + let tester = try await TaskConstructionTester(getCore(), testProject) + + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug"), runDestination: .macOS) { results in + results.checkNoDiagnostics() + results.checkTask(.matchTargetName("Foo"), .matchRule(["Copy", "/tmp/Test/aProject/build/Debug/Foo.app/Contents/Extensions/Modern.appex", "/tmp/Test/aProject/build/Debug/Modern.appex"])) { task in } + results.checkTask(.matchTargetName("Foo"), .matchRule(["Copy", "/tmp/Test/aProject/build/Debug/Foo.app/Contents/PlugIns/Legacy.appex", "/tmp/Test/aProject/build/Debug/Legacy.appex"])) { task in } + } + + await tester.checkBuild(BuildParameters(action: .build, configuration: "Debug"), runDestination: .iOS) { results in + results.checkNoDiagnostics() + results.checkTask(.matchTargetName("Foo"), .matchRule(["Copy", "/tmp/Test/aProject/build/Debug-iphoneos/Foo.app/Extensions/Modern.appex", "/tmp/Test/aProject/build/Debug-iphoneos/Modern.appex"])) { task in } + results.checkTask(.matchTargetName("Foo"), .matchRule(["Copy", "/tmp/Test/aProject/build/Debug-iphoneos/Foo.app/PlugIns/Legacy.appex", "/tmp/Test/aProject/build/Debug-iphoneos/Legacy.appex"])) { task in } + } + } + + @Test(.requireSDKs(.macOS, .iOS)) + func appExtensionSwiftEmbedding() async throws { + let testProject = try await TestProject( + "aProject", + groupTree: TestGroup( + "Sources", children: [ + TestFile("source.swift"), + TestFile("Legacy.appex", fileType: "wrapper.app-extension", sourceTree: .buildSetting("BUILT_PRODUCTS_DIR")), + TestFile("Modern.appex", fileType: "wrapper.extensionkit-extension", sourceTree: .buildSetting("BUILT_PRODUCTS_DIR")), + ]), + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [ + "CODE_SIGNING_ALLOWED": "NO", + "GENERATE_INFOPLIST_FILE": "YES", + "PRODUCT_NAME": "$(TARGET_NAME)", + "SUPPORTED_PLATFORMS": "macosx iphoneos", + "SWIFT_EXEC": swiftCompilerPath.str, + "SWIFT_VERSION": swiftVersion, + "SWIFT_EMIT_CONST_VALUE_PROTOCOLS": "Foo Bar", + ]) + ], + targets: [ + TestStandardTarget( + "Foo", + type: .application, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]) + ], + buildPhases: [ + TestSourcesBuildPhase(["source.swift"]), + TestCopyFilesBuildPhase([ + TestBuildFile(.target("Legacy"))], destinationSubfolder: .plugins, onlyForDeployment: false), + TestCopyFilesBuildPhase([ + TestBuildFile(.target("Modern"))], destinationSubfolder: .builtProductsDir, destinationSubpath: "$(EXTENSIONS_FOLDER_PATH)", onlyForDeployment: false), + ], + dependencies: ["Legacy", "Modern"]), + TestStandardTarget( + "Legacy", + type: .applicationExtension, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]) + ], + buildPhases: [ + TestSourcesBuildPhase(["source.swift"]), + ]), + TestStandardTarget( + "Modern", + type: .extensionKitExtension, + buildConfigurations: [ + TestBuildConfiguration("Debug", buildSettings: [:]) + ], + buildPhases: [ + TestSourcesBuildPhase(["source.swift"]), ]), ]) let tester = try await TaskConstructionTester(getCore(), testProject)