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/AppIntentsMetadata.xcspec b/Sources/SWBApplePlatform/Specs/AppIntentsMetadata.xcspec index 91487be6..60791556 100644 --- a/Sources/SWBApplePlatform/Specs/AppIntentsMetadata.xcspec +++ b/Sources/SWBApplePlatform/Specs/AppIntentsMetadata.xcspec @@ -203,6 +203,15 @@ NO = (); }; }, + { + Name = LM_ENABLE_APP_NAME_OVERRIDE; + Type = Boolean; + DefaultValue = NO; + CommandLineArgs = { + YES = ( "--app-shortcuts-app-name-override" ); + NO = (); + }; + }, ); }, ) 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/Core.swift b/Sources/SWBCore/Core.swift index 238a2e92..7686414d 100644 --- a/Sources/SWBCore/Core.swift +++ b/Sources/SWBCore/Core.swift @@ -330,7 +330,7 @@ public final class Core: Sendable { // Search paths relative to SWBCore itself. do { func appendInferior(_ path: Path) { - if !developerPath.dirname.isAncestor(of: path) { + if !developerPath.path.dirname.isAncestor(of: path) { result.append(path) } } 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/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift b/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift index d651edfb..d2e17180 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/PrelinkedObjectLink.swift @@ -35,6 +35,13 @@ public final class PrelinkedObjectLinkSpec: CommandLineToolSpec, SpecImplementat var commandLine = [toolSpecInfo.toolPath.str] commandLine += ["-r", "-arch", arch] + if let buildPlatform = cbc.producer.sdk?.targetBuildVersionPlatform(sdkVariant: cbc.producer.sdkVariant), + let deploymentTargetMacro = cbc.producer.platform?.deploymentTargetMacro, + let minDeploymentTarget = cbc.scope.evaluate(deploymentTargetMacro).nilIfEmpty, + let sdkVersion = cbc.producer.sdk?.version { + commandLine += ["-platform_version", "\(buildPlatform.rawValue)", minDeploymentTarget, sdkVersion.canonicalDeploymentTargetForm.description] + } + // We do not pass the deployment target to the linker here. Instead the linker infers the platform and deployment target from the .o files being collected. We did briefly pass it to the linker to silence a linker warning - if we ever see issues here we should confer with the linker folks to make sure we do the right thing. See for more about the history here. let sysroot = cbc.scope.evaluate(BuiltinMacros.SDK_DIR) 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/SWBTestSupport/CoreTestSupport.swift b/Sources/SWBTestSupport/CoreTestSupport.swift index e95da70f..1bf280ea 100644 --- a/Sources/SWBTestSupport/CoreTestSupport.swift +++ b/Sources/SWBTestSupport/CoreTestSupport.swift @@ -70,6 +70,40 @@ extension Core { try POSIX.unsetenv(variable) } + // Handle Xcodes with an unbundled Metal toolchain by querying `xcodebuild` if needed. + // + // If the given environment already contains `EXTERNAL_TOOLCHAINS_DIR` and `TOOLCHAINS`, we're assuming that we do not have to obtain any toolchain information. + var environment = environment + if (try? ProcessInfo.processInfo.hostOperatingSystem()) == .macOS, !(environment.contains("EXTERNAL_TOOLCHAINS_DIR") && environment.contains("TOOLCHAINS")) { + let activeDeveloperPath: Path + if let developerPath { + activeDeveloperPath = developerPath.path + } else { + activeDeveloperPath = try await Xcode.getActiveDeveloperDirectoryPath() + } + let defaultToolchainPath = activeDeveloperPath.join("Toolchains/XcodeDefault.xctoolchain") + + if !localFS.exists(defaultToolchainPath.join("usr/metal/current")) { + struct MetalToolchainInfo: Decodable { + let buildVersion: String + let status: String + let toolchainIdentifier: String + let toolchainSearchPath: String + } + + let result = try await Process.getOutput(url: URL(fileURLWithPath: activeDeveloperPath.join("usr/bin/xcodebuild").str), arguments: ["-showComponent", "metalToolchain", "-json"], environment: ["DEVELOPER_DIR": activeDeveloperPath.str]) + if result.exitStatus != .exit(0) { + throw StubError.error("xcodebuild failed: \(String(data: result.stdout, encoding: .utf8) ?? "")\n\(String(data: result.stderr, encoding: .utf8) ?? "")") + } + + let metalToolchainInfo = try JSONDecoder().decode(MetalToolchainInfo.self, from: result.stdout) + environment.addContents(of: [ + "TOOLCHAINS": "\(metalToolchainInfo.toolchainIdentifier) $(inherited)", + "EXTERNAL_TOOLCHAINS_DIR": metalToolchainInfo.toolchainSearchPath, + ]) + } + } + // When this code is being loaded directly via unit tests *and* we detect the products directory we are running in is for Xcode, then we should run using inferior search paths. let inferiorProductsPath: Path? = self.inferiorProductsPath() 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/SWBBuildSystemTests/BuildCommandTests.swift b/Tests/SWBBuildSystemTests/BuildCommandTests.swift index 2cd6a109..c2aa61af 100644 --- a/Tests/SWBBuildSystemTests/BuildCommandTests.swift +++ b/Tests/SWBBuildSystemTests/BuildCommandTests.swift @@ -325,6 +325,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests { @Test(.requireSDKs(.macOS), .requireXcode16()) func singleFileCompileMetal() async throws { + let core = try await getCore() try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = try await TestWorkspace( "Test", @@ -337,6 +338,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests { "Debug", buildSettings: ["PRODUCT_NAME": "$(TARGET_NAME)", "SWIFT_ENABLE_EXPLICIT_MODULES": "NO", + "TOOLCHAINS": core.environment["TOOLCHAINS"] ?? "$(inherited)", "SWIFT_VERSION": swiftVersion])], targets: [ TestStandardTarget( @@ -348,7 +350,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests { ) ] ) - let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false) + let tester = try await BuildOperationTester(core, testWorkspace, simulated: false) let metalFile = testWorkspace.sourceRoot.join("aProject/Metal.metal") try await tester.fs.writeFileContents(metalFile) { stream in } diff --git a/Tests/SWBBuildSystemTests/BuildOperationTests.swift b/Tests/SWBBuildSystemTests/BuildOperationTests.swift index 61624fd0..68dd3c20 100644 --- a/Tests/SWBBuildSystemTests/BuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/BuildOperationTests.swift @@ -2928,12 +2928,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Versions/A/Resources/Info.plist", "Versions/A/Resources/ja.lproj", "Versions/A/Resources/ja.lproj/Localizable.strings", - "Versions/A/_CodeSignature", - "Versions/A/_CodeSignature/CodeDirectory", - "Versions/A/_CodeSignature/CodeRequirements", - "Versions/A/_CodeSignature/CodeRequirements-1", - "Versions/A/_CodeSignature/CodeResources", - "Versions/A/_CodeSignature/CodeSignature", "Versions/Current", ] @@ -2970,12 +2964,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Versions/A/Headers/AFwk.h", "Versions/A/Resources/AResource.plist", "Versions/A/Resources/Info.plist", - "Versions/A/_CodeSignature", - "Versions/A/_CodeSignature/CodeDirectory", - "Versions/A/_CodeSignature/CodeRequirements", - "Versions/A/_CodeSignature/CodeRequirements-1", - "Versions/A/_CodeSignature/CodeResources", - "Versions/A/_CodeSignature/CodeSignature", "Versions/Current", ]) } @@ -3061,7 +3049,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script ])) } - let sourceDynamicFrameworkFiles = try tester.fs.traverse(sourceDynamicFrameworkPath, { $0.relativeSubpath(from: sourceDynamicFrameworkPath) }).sorted() + let sourceDynamicFrameworkFiles = try tester.fs.traverse(sourceDynamicFrameworkPath, { $0.relativeSubpath(from: sourceDynamicFrameworkPath) }).sorted().filter { !$0.contains("_CodeSignature") } if runDestination.platform == "macosx" { XCTAssertEqualSequences(sourceDynamicFrameworkFiles, [ "ADynamicFwk", @@ -3075,12 +3063,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Versions/A/Resources", "Versions/A/Resources/ADynamicResource.plist", "Versions/A/Resources/Info.plist", - "Versions/A/_CodeSignature", - "Versions/A/_CodeSignature/CodeDirectory", - "Versions/A/_CodeSignature/CodeRequirements", - "Versions/A/_CodeSignature/CodeRequirements-1", - "Versions/A/_CodeSignature/CodeResources", - "Versions/A/_CodeSignature/CodeSignature", "Versions/Current", ]) } else { @@ -3090,8 +3072,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Headers", "Headers/ADynamicFwk.h", "Info.plist", - "_CodeSignature", - "_CodeSignature/CodeResources", ]) } @@ -3111,7 +3091,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script ])) } - let sourceStaticFrameworkFiles = try tester.fs.traverse(sourceStaticFrameworkPath, { $0.relativeSubpath(from: sourceStaticFrameworkPath) }).sorted() + let sourceStaticFrameworkFiles = try tester.fs.traverse(sourceStaticFrameworkPath, { $0.relativeSubpath(from: sourceStaticFrameworkPath) }).sorted().filter { !$0.contains("_CodeSignature") } if runDestination.platform == "macosx" { XCTAssertEqualSequences(sourceStaticFrameworkFiles, [ "AStaticFwk", @@ -3125,12 +3105,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Versions/A/Resources", "Versions/A/Resources/AStaticResource.plist", "Versions/A/Resources/Info.plist", - "Versions/A/_CodeSignature", - "Versions/A/_CodeSignature/CodeDirectory", - "Versions/A/_CodeSignature/CodeRequirements", - "Versions/A/_CodeSignature/CodeRequirements-1", - "Versions/A/_CodeSignature/CodeResources", - "Versions/A/_CodeSignature/CodeSignature", "Versions/Current", ]) } else { @@ -3140,12 +3114,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Headers", "Headers/AStaticFwk.h", "Info.plist", - "_CodeSignature", - "_CodeSignature/CodeDirectory", - "_CodeSignature/CodeRequirements", - "_CodeSignature/CodeRequirements-1", - "_CodeSignature/CodeResources", - "_CodeSignature/CodeSignature", ]) } @@ -3199,7 +3167,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script task.checkCommandLine(["builtin-copy", "-exclude", ".DS_Store", "-exclude", "CVS", "-exclude", ".svn", "-exclude", ".git", "-exclude", ".hg", "-exclude", "Headers", "-exclude", "PrivateHeaders", "-exclude", "Modules", "-exclude", "*.tbd", "-resolve-src-symlinks"] + (keepStaticBinary ? [] : ["-remove-static-executable"]) + ["\(tmpDirPath.str)/ADynamicFwk.framework", frameworkDestinationDir]) let destDynamicFrameworkPath = Path("\(frameworkDestinationDir)/ADynamicFwk.framework") - let delta = try Set(sourceDynamicFrameworkFiles).diff(against: tester.fs.traverse(destDynamicFrameworkPath, { $0.relativeSubpath(from: destDynamicFrameworkPath) })) + let delta = try Set(sourceDynamicFrameworkFiles).diff(against: tester.fs.traverse(destDynamicFrameworkPath, { $0.relativeSubpath(from: destDynamicFrameworkPath) }).filter{ !$0.contains("_CodeSignature") }) XCTAssertEqualSequences(delta.right, []) // Check that we removed all the headers @@ -3221,7 +3189,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script task.checkCommandLine(["builtin-copy", "-exclude", ".DS_Store", "-exclude", "CVS", "-exclude", ".svn", "-exclude", ".git", "-exclude", ".hg", "-exclude", "Headers", "-exclude", "PrivateHeaders", "-exclude", "Modules", "-exclude", "*.tbd", "-resolve-src-symlinks"] + (keepStaticBinary ? [] : ["-remove-static-executable"]) + ["\(tmpDirPath.str)/AStaticFwk.framework", frameworkDestinationDir]) let destStaticFrameworkPath = Path("\(frameworkDestinationDir)/AStaticFwk.framework") - let delta = try Set(sourceStaticFrameworkFiles).diff(against: tester.fs.traverse(destStaticFrameworkPath, { $0.relativeSubpath(from: destStaticFrameworkPath) })) + let delta = try Set(sourceStaticFrameworkFiles).diff(against: tester.fs.traverse(destStaticFrameworkPath, { $0.relativeSubpath(from: destStaticFrameworkPath) }).filter{ !$0.contains("_CodeSignature") }) XCTAssertEqualSequences(delta.right, []) // Check that we removed all the headers, as well as the binary (since it is static), if configured @@ -3240,22 +3208,10 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Headers/AStaticFwk.h", ]) } else if useAppStoreCodelessFrameworksWorkaround { - let additionalFiles: [String] - if codesign { - additionalFiles = [ - "_CodeSignature/CodeDirectory", - "_CodeSignature/CodeRequirements", - "_CodeSignature/CodeRequirements-1", - "_CodeSignature/CodeSignature", - ] - } else { - additionalFiles = [] - } - XCTAssertEqualSequences(delta.left.sorted(), [ "Headers", "Headers/AStaticFwk.h", - ] + additionalFiles) + ]) } else { XCTAssertEqualSequences(delta.left.sorted(), [ "AStaticFwk", @@ -5756,6 +5712,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script @Test(.requireSDKs(.macOS)) func incrementalMetalLinkWithCodeSign() async throws { + let core = try await getCore() try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = try await TestWorkspace( "Test", @@ -5773,6 +5730,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "CODE_SIGN_IDENTITY": "-", "INFOPLIST_FILE": "Info.plist", "CODESIGN": "/usr/bin/true", + "TOOLCHAINS": core.environment["TOOLCHAINS"] ?? "$(inherited)", "SWIFT_VERSION": swiftVersion])], targets: [ TestStandardTarget( @@ -5781,7 +5739,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script buildPhases: [ TestSourcesBuildPhase(["SwiftFile.swift", "Metal.metal"]), ])])]) - let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false, fileSystem: localFS) + let tester = try await BuildOperationTester(core, testWorkspace, simulated: false, fileSystem: localFS) let signableTargets: Set = ["aFramework"] // Create the input files. diff --git a/Tests/SWBBuildSystemTests/LinkerTests.swift b/Tests/SWBBuildSystemTests/LinkerTests.swift index 36833664..97f61702 100644 --- a/Tests/SWBBuildSystemTests/LinkerTests.swift +++ b/Tests/SWBBuildSystemTests/LinkerTests.swift @@ -360,4 +360,72 @@ fileprivate struct LinkerTests: CoreBasedTests { } } } + + @Test(.requireSDKs(.macOS)) + func prelinkingPropagatesPlatformVersion() async throws { + func createProject(_ tmpDir: Path, enableInterop: Bool) -> TestProject { + TestProject( + "TestProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("main.c"), + TestFile("lib.c"), + ]), + targets: [ + TestStandardTarget( + "cli", type: .commandLineTool, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "CODE_SIGNING_ALLOWED": "NO", + "GENERATE_PRELINK_OBJECT_FILE": "YES", + "GCC_SYMBOLS_PRIVATE_EXTERN": "NO", + "MACOSX_DEPLOYMENT_TARGET": "13.0", + ]) + ], + buildPhases: [ + TestSourcesBuildPhase(["main.c"]), + TestFrameworksBuildPhase([TestBuildFile(.target("lib"))]) + ], + dependencies: [TestTargetDependency("lib")] + ), + TestStandardTarget( + "lib", type: .staticLibrary, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "PRODUCT_NAME": "$(TARGET_NAME)", + "MACOSX_DEPLOYMENT_TARGET": "13.0", + ]) + ], + buildPhases: [ + TestSourcesBuildPhase(["lib.c"]) + ] + ), + ]) + } + + try await withTemporaryDirectory { tmpDir in + let testProject = createProject(tmpDir, enableInterop: true) + let tester = try await BuildOperationTester(getCore(), testProject, simulated: false) + let projectDir = tester.workspace.projects[0].sourceRoot + try await tester.fs.writeFileContents(projectDir.join("main.c")) { stream in + stream <<< "int foo(void); int main(void) { foo(); }" + } + try await tester.fs.writeFileContents(projectDir.join("lib.c")) { stream in + stream <<< "int foo(void) { return 42; }" + } + try await tester.checkBuild(runDestination: .macOS) { results in + results.checkNoDiagnostics() + results.checkTask(.matchRuleType("PrelinkedObjectLink")) { task in + task.checkCommandLineContainsUninterrupted(["-platform_version", "1", "13.0"]) + } + } + } + } } 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) diff --git a/Tests/SWBTaskConstructionTests/AppIntentsMetadataTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/AppIntentsMetadataTaskConstructionTests.swift index afd64f3c..cacf5453 100644 --- a/Tests/SWBTaskConstructionTests/AppIntentsMetadataTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/AppIntentsMetadataTaskConstructionTests.swift @@ -1595,6 +1595,133 @@ fileprivate struct AppIntentsMetadataTaskConstructionTests: CoreBasedTests { } } + @Test(.requireSDKs(.iOS)) + func appNameOverrideIsNotPresent() async throws { + try await withTemporaryDirectory { tmpDir in + let testProject = try await TestProject( + "aProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("source.swift"), + TestFile(appShortcutsStringsFileName) + ]), + 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( + "LinkTest", + type: .application, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "LM_ENABLE_LINK_GENERATION": "YES", + "LM_ENABLE_APP_NAME_OVERRIDE": "NO" + ]), + ], + buildPhases: [ + TestResourcesBuildPhase([TestBuildFile(appShortcutsStringsFileName)]), + TestSourcesBuildPhase(["source.swift"]), + ] + ) + ]) + + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + await tester.checkBuild(runDestination: .iOS) { results in + results.checkTask(.matchRuleType("ExtractAppIntentsMetadata")) { task in + let executableName = task.commandLine.first + if let executableName, + executableName == "appintentsmetadataprocessor" { + task.checkCommandLineDoesNotContain("--app-shortcuts-app-name-override") + } + results.checkNoDiagnostics() + } + } + } + } + + + @Test(.requireSDKs(.iOS)) + func appNameOverride() async throws { + try await withTemporaryDirectory { tmpDir in + let testProject = try await TestProject( + "aProject", + sourceRoot: tmpDir, + groupTree: TestGroup( + "SomeFiles", + children: [ + TestFile("source.swift"), + TestFile(appShortcutsStringsFileName) + ]), + 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( + "LinkTest", + type: .application, + buildConfigurations: [ + TestBuildConfiguration( + "Debug", + buildSettings: [ + "LM_ENABLE_LINK_GENERATION": "YES", + "LM_ENABLE_APP_NAME_OVERRIDE": "YES" + ]), + ], + buildPhases: [ + TestResourcesBuildPhase([TestBuildFile(appShortcutsStringsFileName)]), + TestSourcesBuildPhase(["source.swift"]), + ] + ) + ]) + + let core = try await getCore() + let tester = try TaskConstructionTester(core, testProject) + await tester.checkBuild(runDestination: .iOS) { results in + results.checkTask(.matchRuleType("ExtractAppIntentsMetadata")) { task in + let executableName = task.commandLine.first + if let executableName, + executableName == "appintentsmetadataprocessor" { + task.checkCommandLineContains(["--app-shortcuts-app-name-override"]) + } + results.checkNoDiagnostics() + } + } + } + } + @Test(.requireSDKs(.iOS)) func quietWarnings() async throws { try await withTemporaryDirectory { tmpDir in diff --git a/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift b/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift index 3c805622..45ee228d 100644 --- a/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift +++ b/Tests/SWBTaskConstructionTests/PrelinkedObjectFileTests.swift @@ -73,7 +73,7 @@ fileprivate struct PrelinkedObjectFileTests: CoreBasedTests { results.checkTarget("AllLibraries") { target in // There should be tasks to create the prelinked object file and then the static library. results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in - task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-exported_symbols_list", "Exports.exp", "-lWarningLibrary", "-lSomeLibrary", "-lAnotherLibrary", "-o", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-platform_version", "1", .any, .any, "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-exported_symbols_list", "Exports.exp", "-lWarningLibrary", "-lSomeLibrary", "-lAnotherLibrary", "-o", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) } results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", "x86_64", "-D", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("-L\(SRCROOT)/build/Debug"), "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/x86_64/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/x86_64/AllLibraries_libtool_dependency_info.dat", "-o", .equal("\(SRCROOT)/build/Debug/libAllLibraries.a")]) @@ -102,7 +102,7 @@ fileprivate struct PrelinkedObjectFileTests: CoreBasedTests { results.checkTarget("AllLibraries") { target in // There should be tasks to create the prelinked object file and then the static library. results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in - task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-exported_symbols_list", "Exports.exp", "-lWarningLibrary", "-lSomeLibrary", "-lAnotherLibrary", "-o", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-platform_version", "1", .any, .any, "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-exported_symbols_list", "Exports.exp", "-lWarningLibrary", "-lSomeLibrary", "-lAnotherLibrary", "-o", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) } results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", "x86_64", "-D", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("-L\(SRCROOT)/build/Debug/BuiltProducts"), "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/x86_64/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug/AllLibraries.build/Objects-normal/x86_64/AllLibraries_libtool_dependency_info.dat", "-o", "/tmp/aProject.dst/usr/local/lib/libAllLibraries.a"]) @@ -203,7 +203,7 @@ fileprivate struct PrelinkedObjectFileTests: CoreBasedTests { results.checkTarget("AllLibraries") { target in // There should be tasks to create the prelinked object file and then the static library. results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in - task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-platform_version", "6", .any, .any, "-syslibroot", .equal(core.loadSDK(.macOS).path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) } results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", "x86_64", "-D", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("-L\(SRCROOT)/build/Debug-maccatalyst"), "-L\(core.loadSDK(.macOS).path.str)/System/iOSSupport/usr/lib", "-L\(core.developerPath.path.str)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/maccatalyst", "-L\(core.loadSDK(.macOS).path.str)/System/iOSSupport/usr/lib", "-L\(core.developerPath.path.str)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/maccatalyst", "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/x86_64/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug-maccatalyst/AllLibraries.build/Objects-normal/x86_64/AllLibraries_libtool_dependency_info.dat", "-o", .equal("\(SRCROOT)/build/Debug-maccatalyst/libAllLibraries.a")]) @@ -272,7 +272,7 @@ fileprivate struct PrelinkedObjectFileTests: CoreBasedTests { results.checkTarget("AllLibraries") { target in // There should be tasks to create the prelinked object file and then the static library. results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in - task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "arm64", "-syslibroot", .equal(results.runDestinationSDK.path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-iphoneos/AllLibraries.build/Objects-normal/libAllLibraries.a-arm64-prelink.o")]) + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "arm64", "-platform_version", "2", .any, .any, "-syslibroot", .equal(results.runDestinationSDK.path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-iphoneos/AllLibraries.build/Objects-normal/libAllLibraries.a-arm64-prelink.o")]) } results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", "arm64", "-D", "-syslibroot", .equal(results.runDestinationSDK.path.str), .equal("-L\(SRCROOT)/build/Debug-iphoneos"), "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug-iphoneos/AllLibraries.build/Objects-normal/arm64/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug-iphoneos/AllLibraries.build/Objects-normal/arm64/AllLibraries_libtool_dependency_info.dat", "-o", .equal("\(SRCROOT)/build/Debug-iphoneos/libAllLibraries.a")]) @@ -340,7 +340,7 @@ fileprivate struct PrelinkedObjectFileTests: CoreBasedTests { results.checkTarget("AllLibraries") { target in // There should be tasks to create the prelinked object file and then the static library. results.checkTask(.matchTarget(target), .matchRuleType("PrelinkedObjectLink")) { task in - task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-syslibroot", .equal(results.runDestinationSDK.path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-iphonesimulator/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) + task.checkCommandLineMatches([.suffix("ld"), "-r", "-arch", "x86_64", "-platform_version", "7", .any, .any, "-syslibroot", .equal(results.runDestinationSDK.path.str), "-o", .equal("\(SRCROOT)/build/aProject.build/Debug-iphonesimulator/AllLibraries.build/Objects-normal/libAllLibraries.a-x86_64-prelink.o")]) } results.checkTask(.matchTarget(target), .matchRuleType("Libtool")) { task in task.checkCommandLineMatches([.suffix("libtool"), "-static", "-arch_only", "x86_64", "-D", "-syslibroot", .equal(results.runDestinationSDK.path.str), .equal("-L\(SRCROOT)/build/Debug-iphonesimulator"), "-filelist", .equal("\(SRCROOT)/build/aProject.build/Debug-iphonesimulator/AllLibraries.build/Objects-normal/x86_64/AllLibraries.LinkFileList"), "-dependency_info", "\(SRCROOT)/build/aProject.build/Debug-iphonesimulator/AllLibraries.build/Objects-normal/x86_64/AllLibraries_libtool_dependency_info.dat", "-o", .equal("\(SRCROOT)/build/Debug-iphonesimulator/libAllLibraries.a")]) diff --git a/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift index a3931342..d2c11ff1 100644 --- a/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/TaskConstructionTests.swift @@ -895,7 +895,7 @@ fileprivate struct TaskConstructionTests: CoreBasedTests { } #expect(nonUniqueObjs[0] != nonUniqueObjs[1]) - task.checkCommandLineMatches([StringPattern.suffix("ld"), "-r", "-arch", "x86_64", "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile-Matched-Excluded.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile-Matched-Included.o"), .equal(nonUniqueObjs[0]), .equal(nonUniqueObjs[1]), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile_MRR.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/Lex.yy.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/y.tab.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/Script-Output-Custom-SourceFile.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/Script-Output-Standard-SourceFile.o"), "-o", .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/AppTarget-x86_64-prelink.o")]) + task.checkCommandLineMatches([StringPattern.suffix("ld"), "-r", "-arch", "x86_64", "-platform_version", "1", .any, .any, "-syslibroot", .equal(core.loadSDK(.macOS).path.str), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile-Matched-Excluded.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile-Matched-Included.o"), .equal(nonUniqueObjs[0]), .equal(nonUniqueObjs[1]), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile_MRR.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/Lex.yy.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/y.tab.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/Script-Output-Custom-SourceFile.o"), .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/Script-Output-Standard-SourceFile.o"), "-o", .equal("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/AppTarget-x86_64-prelink.o")]) task.checkInputs([ .path("\(SRCROOT)/build/aProject.build/Release/AppTarget.build/Objects-normal/x86_64/SourceFile.o"),