diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 68805db4..deae0f54 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -995,6 +995,7 @@ public final class BuiltinMacros { public static let SWIFT_ABI_CHECKER_EXCEPTIONS_FILE = BuiltinMacros.declareStringMacro("SWIFT_ABI_CHECKER_EXCEPTIONS_FILE") public static let SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR = BuiltinMacros.declareStringMacro("SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR") public static let SWIFT_ACCESS_NOTES_PATH = BuiltinMacros.declareStringMacro("SWIFT_ACCESS_NOTES_PATH") + public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS = BuiltinMacros.declareStringListMacro("SWIFT_ACTIVE_COMPILATION_CONDITIONS") public static let SWIFT_ALLOW_INSTALL_OBJC_HEADER = BuiltinMacros.declareBooleanMacro("SWIFT_ALLOW_INSTALL_OBJC_HEADER") public static let __SWIFT_ALLOW_INSTALL_OBJC_HEADER_MESSAGE = BuiltinMacros.declareStringMacro("__SWIFT_ALLOW_INSTALL_OBJC_HEADER_MESSAGE") public static let SWIFT_COMPILATION_MODE = BuiltinMacros.declareStringMacro("SWIFT_COMPILATION_MODE") @@ -2164,6 +2165,7 @@ public final class BuiltinMacros { SWIFT_ABI_CHECKER_EXCEPTIONS_FILE, SWIFT_ABI_GENERATION_TOOL_OUTPUT_DIR, SWIFT_ACCESS_NOTES_PATH, + SWIFT_ACTIVE_COMPILATION_CONDITIONS, SWIFT_ALLOW_INSTALL_OBJC_HEADER, __SWIFT_ALLOW_INSTALL_OBJC_HEADER_MESSAGE, SWIFT_COMPILATION_MODE, diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index 553f796c..12384fc0 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -1554,7 +1554,7 @@ private class SettingsBuilder { } // Push the target derived overriding settings. - addTargetDerivedSettings(self.target, boundProperties.platform, boundProperties.sdk, boundProperties.sdkVariant) + addTargetDerivedSettings(self.target, boundProperties.platform, boundProperties.sdk, boundProperties.sdkVariant, specLookupContext) if boundDeploymentTarget.platformDeploymentTargetMacro == BuiltinMacros.DRIVERKIT_DEPLOYMENT_TARGET, let deploymentTarget = boundDeploymentTarget.platformDeploymentTarget, deploymentTarget < Version(20) { var table = MacroValueAssignmentTable(namespace: userNamespace) @@ -2039,13 +2039,13 @@ private class SettingsBuilder { /// Add the derived overriding settings for the target. These are settings whose values depend on the whole stack of build settings, and include settings which are forced to a value under certain conditions, and settings whose value is wholly derived from other settings. They override settings from all lower levels, and thus cannot be overridden by (for example) xcodebuild or run destination overrides, so settings should only be assigned here when they represent true boundary conditions which users should never want to or be able to override. /// /// These are only added if we're constructing settings for a target. - func addTargetDerivedSettings(_ target: Target?, _ platform: Platform?, _ sdk: SDK?, _ sdkVariant: SDKVariant?) { + func addTargetDerivedSettings(_ target: Target?, _ platform: Platform?, _ sdk: SDK?, _ sdkVariant: SDKVariant?, _ specLookupContext: any SpecLookupContext) { guard target != nil else { return } push(getTargetDerivedSettings(platform, sdk, sdkVariant), .exportedForNative) - addSecondaryTargetDerivedSettings(sdk) + addSecondaryTargetDerivedSettings(sdk, specLookupContext) } /// Add the core derived overriding settings for the target. @@ -2138,7 +2138,7 @@ private class SettingsBuilder { /// Add derived settings for the target which are themselves derived from the core target derived settings computed above. (Whee!) /// /// This is called from `addTargetDerivedSettings(). - func addSecondaryTargetDerivedSettings(_ sdk: SDK?) { + func addSecondaryTargetDerivedSettings(_ sdk: SDK?, _ specLookupContext: any SpecLookupContext) { // Mergeable library/merged binary support. do { let scope = createScope(sdkToUse: sdk) @@ -2178,13 +2178,22 @@ private class SettingsBuilder { } do { let scope = createScope(sdkToUse: sdk) + var table = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace) // If the product is being built as mergeable, then that overrides certain other settings. if scope.evaluate(BuiltinMacros.MAKE_MERGEABLE) { - var table = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace) table.push(BuiltinMacros.STRIP_INSTALLED_PRODUCT, literal: false) - push(table, .exportedForNative) } + + // Even if not being merged in this build, a mergeable library still uses a generated bundle lookup helper to power #bundle support. + if scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) { + let pathResolver = FilePathResolver(scope: scope) + if (target as? StandardTarget)?.sourcesBuildPhase?.containsSwiftSources(workspaceContext.workspace, specLookupContext, scope, pathResolver) ?? false { + table.push(BuiltinMacros.SWIFT_ACTIVE_COMPILATION_CONDITIONS, BuiltinMacros.namespace.parseStringList(["$(inherited)", "SWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"])) + } + } + + push(table, .exportedForNative) } } diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index a225c3b0..465be808 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -771,6 +771,9 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase let packageTargetBundleAccessorResult = await generatePackageTargetBundleAccessorResult(scope) tasks += packageTargetBundleAccessorResult?.tasks ?? [] + let bundleLookupHelperResult = await generateBundleLookupHelper(scope) + tasks += bundleLookupHelperResult?.tasks ?? [] + let embedInCodeAccessorResult: GeneratedResourceAccessorResult? if scope.evaluate(BuiltinMacros.GENERATE_EMBED_IN_CODE_ACCESSORS), let configuredTarget = context.configuredTarget, buildPhase.containsSwiftSources(context.workspaceContext.workspace, context, scope, context.filePathResolver) { let ownTargetBuildFilesToEmbed = ((context.workspaceContext.workspace.target(for: configuredTarget.target.guid) as? StandardTarget)?.buildPhases.compactMap { $0 as? BuildPhaseWithBuildFiles }.flatMap { $0.buildFiles }.filter { $0.resourceRule == .embedInCode }) ?? [] @@ -868,6 +871,10 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase result.append((packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)) } + if let bundleLookupHelperResult { + result.append((bundleLookupHelperResult.fileToBuild, bundleLookupHelperResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)) + } + if let embedInCodeAccessorResult { result.append((embedInCodeAccessorResult.fileToBuild, embedInCodeAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)) } @@ -1192,11 +1199,17 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase let buildFilesContext = BuildFilesProcessingContext(scope, belongsToPreferredArch: preferredArch == nil || preferredArch == arch, currentArchSpec: currentArchSpec) var perArchTasks: [any PlannedTask] = [] await groupAndAddTasksForFiles(self, buildFilesContext, scope, filterToAPIRules: isForAPI, filterToHeaderRules: isForHeaders, &perArchTasks, extraResolvedBuildFiles: { + var result: [(Path, FileTypeSpec, Bool)] = [] + if let packageTargetBundleAccessorResult { - return [(packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)] - } else { - return [] + result.append((packageTargetBundleAccessorResult.fileToBuild, packageTargetBundleAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)) + } + + if let bundleLookupHelperResult { + result.append((bundleLookupHelperResult.fileToBuild, bundleLookupHelperResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false)) } + + return result }()) // Add all the collected per-arch tasks. @@ -1725,7 +1738,43 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase return GeneratedResourceAccessorResult(tasks: tasks, fileToBuild: filePath, fileToBuildFileType: context.lookupFileType(fileName: "sourcecode.swift")!) } + /// Generates a task for creating the `__BundleLookupHelper` class to enable `#bundle` support in mergeable libraries. + private func generateBundleLookupHelper(_ scope: MacroEvaluationScope) async -> GeneratedResourceAccessorResult? { + // We generate a __BundleLookupHelper class that Foundation's #bundle macro can use to lookup the resource bundle. + // ld will inject a mapping of class pointers to the correct resource bundle so that BundleForClass works at runtime. + + // We only need this treatment for mergeable libraries at this time. + // Package targets do something similar but they generate the Bundle.module extensions and #bundle calls that. + + // We need to do this for all mergeable libraries, even if it will just be re-exported in this build. + guard scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) else { + return nil + } + + let workspace = self.context.workspaceContext.workspace + + // #bundle is a Swift macro, so this is only needed for Swift code. + guard buildPhase.containsSwiftSources(workspace, context, scope, context.filePathResolver) else { + return nil + } + + let filePath = scope.evaluate(BuiltinMacros.DERIVED_SOURCES_DIR).join("bundle_lookup_helper.swift") + + // We need one class with a relatively unique name that #bundle can use for bundle lookup. + // It cannot be less visible than internal since the #bundle expansion needs to be able to resolve it AND so ld will record the class->bundle mapping. + // We intentionally do not want a Foundation dependency in this generated code, so don't import Foundation. + let content = "internal class __BundleLookupHelper {}" + + var tasks = [any PlannedTask]() + await appendGeneratedTasks(&tasks) { delegate in + context.writeFileSpec.constructFileTasks(CommandBuildContext(producer: context, scope: context.settings.globalScope, inputs: [], output: filePath), delegate, contents: ByteString(encodingAsUTF8: content), permissions: nil, preparesForIndexing: true, additionalTaskOrderingOptions: [.immediate, .ignorePhaseOrdering]) + } + return GeneratedResourceAccessorResult(tasks: tasks, fileToBuild: filePath, fileToBuildFileType: context.lookupFileType(fileName: "sourcecode.swift")!) + } + /// Generates a task for creating the resource bundle accessor for package targets. + /// + /// This produces the `Bundle.module` accessor. private func generatePackageTargetBundleAccessorResult(_ scope: MacroEvaluationScope) async -> GeneratedResourceAccessorResult? { let bundleName = scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_BUNDLE_NAME) let isRegularPackage = scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_TARGET_KIND) == .regular diff --git a/Tests/SWBBuildSystemTests/MergeableLibrariesBuildOperationTests.swift b/Tests/SWBBuildSystemTests/MergeableLibrariesBuildOperationTests.swift index 2c9cdc01..27f82293 100644 --- a/Tests/SWBBuildSystemTests/MergeableLibrariesBuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/MergeableLibrariesBuildOperationTests.swift @@ -226,6 +226,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests { // Check mergeable framework targets. for targetName in ["FwkTarget1", "FwkTarget2"] { + results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in task.checkCommandLineDoesNotContain("-make_mergeable") @@ -410,6 +411,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests { // Check mergeable framework targets for targetName in ["FwkTarget1", "FwkTarget2"] { + results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in task.checkCommandLineContains("-make_mergeable") @@ -715,6 +717,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests { // Check FwkTarget do { let targetName = "FwkTarget" + results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in task.checkCommandLineDoesNotContain("-make_mergeable") @@ -875,6 +878,7 @@ fileprivate struct MergeableLibrariesBuildOperationTests: CoreBasedTests { // Check FwkTarget do { let targetName = "FwkTarget" + results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile"), .matchRuleItem("Compiling bundle_lookup_helper.swift")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("SwiftCompile")) { _ in } results.checkTask(.matchTargetName(targetName), .matchRuleType("Ld")) { task in task.checkCommandLineContains("-make_mergeable") diff --git a/Tests/SWBTaskConstructionTests/MergeableLibraryTests.swift b/Tests/SWBTaskConstructionTests/MergeableLibraryTests.swift index a93e3696..4584f5d4 100644 --- a/Tests/SWBTaskConstructionTests/MergeableLibraryTests.swift +++ b/Tests/SWBTaskConstructionTests/MergeableLibraryTests.swift @@ -172,6 +172,15 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let APP_EXEC_DIR_SUBPATH = runDestination == .macOS ? "\(APP_CONTENTS_DIR_SUBPATH)MacOS/" : "" let supportsMergeableDebugHook = try await linkerSupportsMergeableDebugHook() + // This is the default set minus WriteAuxiliaryFile, since we want to check for the generated bundle lookup helper class. + let tasksToIgnore: Set = [ + "Gate", + "MkDir", + "CreateBuildDirectory", + "ClangStatCache", + "LinkAssetCatalogSignature" + ] + // Test a debug build. This will just build the merged framework to reexport the frameworks it links against. do { let buildType = "Debug" @@ -189,7 +198,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let targets = tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }) let request = BuildRequest(parameters: parameters, buildTargets: targets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: runDestination, buildRequest: request) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) // Check that the mergeable targets were *not* built to be mergeable. for targetName in ["FwkTarget1", "FwkTarget2"] { @@ -205,6 +214,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { } results.checkTask(.matchTarget(target), .matchRuleType("GenerateTAPI")) { _ in } + // Even though we aren't building to be mergeable, the re-exporting dance still means we need our custom bundle lookup class. + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } } @@ -220,6 +237,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { task.checkCommandLineDoesNotContain("-make_mergeable") // Not passed in debug builds } + // Even though we aren't building to be mergeable, the re-exporting dance still means we need our custom bundle lookup class. + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -283,6 +308,10 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { } } + // This umbrella framework is not itself being built as mergeable, so it doesn't need a bundle helper class. + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkNoTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -330,6 +359,13 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { } } + // The app target should not get a generated bundle helper class. + // #bundle should just use its default approach of lookup up the #dsohandle. + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -359,7 +395,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let targets = tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }) let request = BuildRequest(parameters: parameters, buildTargets: targets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: runDestination, buildRequest: request) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) // Check that the mergeable targets were built to be mergeable. for targetName in ["FwkTarget1", "FwkTarget2"] { @@ -379,6 +415,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { // Mergeable products should not be stripped. results.checkNoTask(.matchTarget(target), .matchRuleType("Strip")) + // We need a custom bundle lookup class to support #bundle. + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } } @@ -398,6 +442,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { // Mergeable products should not be stripped. results.checkNoTask(.matchTarget(target), .matchRuleType("Strip")) + // We need a custom bundle lookup class to support #bundle. + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -429,6 +481,10 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkNoTask(.matchTargetName(targetName), .matchRuleType("Copy"), .matchRuleItemBasename(mergeableTargetProductName)) } + // This umbrella framework is not itself being built as mergeable, so it doesn't need a bundle helper class. + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkNoTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -483,6 +539,13 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { } results.checkTask(.matchTarget(target), .matchRuleType("Strip")) { _ in } + // The app target should not get a generated bundle helper class. + // #bundle should just use its default approach of lookup up the #dsohandle. + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -1302,6 +1365,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { try fs.createDirectory(libXCFrameworkPath, recursive: true) try await XCFrameworkTestSupport.writeXCFramework(libXCFramework, fs: fs, path: libXCFrameworkPath, infoLookup: infoLookup) + let tasksToIgnore: Set = [ + "Gate", + "MkDir", + "CreateBuildDirectory", + "ClangStatCache", + "LinkAssetCatalogSignature" + ] + // Check a debug build for iOS device, where we reexport the XCFrameworks. do { let runDestination = RunDestinationInfo.iOS @@ -1323,7 +1394,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let buildTargets = [BuildRequest.BuildTargetInfo(parameters: parameters, target: tester.workspace.projects[0].targets[0])] let request = BuildRequest(parameters: parameters, buildTargets: buildTargets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: runDestination, buildRequest: request, fs: fs) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) results.checkTask(.matchRuleType("ProcessXCFramework"), .matchRuleItemBasename("\(fwkBaseName).xcframework")) { task in task.checkCommandLine(["builtin-process-xcframework", "--xcframework", "\(SRCROOT)/\(fwkBaseName).xcframework", "--platform", "ios", "--target-path", "\(BUILT_PRODUCTS_DIR)"]) @@ -1394,6 +1465,9 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkNoDiagnostics() } + + // We shouldn't be generating a bundle lookup class for #bundle since the framework/dylib itself is not actually being built here. + results.checkNoTask(.matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) } } @@ -1418,7 +1492,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let buildTargets = [BuildRequest.BuildTargetInfo(parameters: parameters, target: tester.workspace.projects[0].targets[0])] let request = BuildRequest(parameters: parameters, buildTargets: buildTargets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: runDestination, buildRequest: request, fs: fs) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) results.checkTask(.matchRuleType("ProcessXCFramework"), .matchRuleItemBasename("\(fwkBaseName).xcframework")) { task in task.checkCommandLine(["builtin-process-xcframework", "--xcframework", "\(SRCROOT)/\(fwkBaseName).xcframework", "--platform", "ios", "--environment", "simulator", "--target-path", "\(BUILT_PRODUCTS_DIR)"]) @@ -1472,6 +1546,8 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkNoDiagnostics() } + + results.checkNoTask(.matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) } } @@ -1497,7 +1573,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let buildTargets = [BuildRequest.BuildTargetInfo(parameters: parameters, target: tester.workspace.projects[0].targets[0])] let request = BuildRequest(parameters: parameters, buildTargets: buildTargets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: runDestination, buildRequest: request, fs: fs) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) results.checkTask(.matchRuleType("ProcessXCFramework"), .matchRuleItemBasename("\(fwkBaseName).xcframework")) { task in task.checkCommandLine(["builtin-process-xcframework", "--xcframework", "\(SRCROOT)/\(fwkBaseName).xcframework", "--platform", "ios", "--target-path", "\(BUILT_PRODUCTS_DIR)"]) @@ -1542,6 +1618,8 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkNoDiagnostics() } + + results.checkNoTask(.matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) } } @@ -1568,7 +1646,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let buildTargets = [BuildRequest.BuildTargetInfo(parameters: parameters, target: tester.workspace.projects[0].targets[0])] let request = BuildRequest(parameters: parameters, buildTargets: buildTargets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: runDestination, buildRequest: request, fs: fs) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) results.checkTask(.matchRuleType("ProcessXCFramework"), .matchRuleItemBasename("\(fwkBaseName).xcframework")) { task in task.checkCommandLine(["builtin-process-xcframework", "--xcframework", "\(SRCROOT)/\(fwkBaseName).xcframework", "--platform", "ios", "--target-path", "\(BUILT_PRODUCTS_DIR)"]) @@ -1621,6 +1699,8 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkNoDiagnostics() } + + results.checkNoTask(.matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) } } @@ -1698,6 +1778,8 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkNoDiagnostics() } + + results.checkNoTask(.matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) } } } @@ -2042,6 +2124,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let tester = try TaskConstructionTester(core, testProject) let supportsMergeableDebugHook = try await linkerSupportsMergeableDebugHook() + let tasksToIgnore: Set = [ + "Gate", + "MkDir", + "CreateBuildDirectory", + "ClangStatCache", + "LinkAssetCatalogSignature" + ] + // Test a debug build. This will reexport the mergeable framework and not the normal framework. do { let buildType = "Debug" @@ -2058,7 +2148,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let targets = tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }) let request = BuildRequest(parameters: parameters, buildTargets: targets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: .iOS, buildRequest: request) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) // Check that the framework targets were *not* built to be mergeable. results.checkTarget("MergeableFwkTarget") { target in @@ -2071,6 +2161,13 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { ["-o", "\(SYMROOT)/Config-iphoneos/\(targetName).framework/\(targetName)"], ].reduce([], +)) task.checkCommandLineDoesNotContain("-make_mergeable") // Not passed in debug builds + + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } } results.checkTask(.matchTarget(target), .matchRuleType("GenerateTAPI")) { _ in } @@ -2089,6 +2186,11 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { } results.checkTask(.matchTarget(target), .matchRuleType("GenerateTAPI")) { _ in } + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -2158,6 +2260,11 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { } } + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -2186,9 +2293,9 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let targets = tester.workspace.projects[0].targets.map({ BuildRequest.BuildTargetInfo(parameters: parameters, target: $0) }) let request = BuildRequest(parameters: parameters, buildTargets: targets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: .iOS, buildRequest: request) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) - // Check that the mergeable targets were *not* built to be mergeable. + // Check that the mergeable target were built to be mergeable, and the normal one was not. results.checkTarget("MergeableFwkTarget") { target in let targetName = target.target.name results.checkTask(.matchTarget(target), .matchRuleType("Ld")) { task in @@ -2205,6 +2312,13 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { // Mergeable products should not be stripped. results.checkNoTask(.matchTarget(target), .matchRuleType("Strip")) + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } results.checkTarget("NormalFwkTarget") { target in @@ -2222,6 +2336,11 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkTask(.matchTarget(target), .matchRuleType("GenerateTAPI")) { _ in } results.checkTask(.matchTarget(target), .matchRuleType("Strip")) { _ in } + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -2275,6 +2394,11 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { results.checkTask(.matchTarget(target), .matchRuleType("Strip")) { _ in } + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -2487,7 +2611,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { ]), ], targets: [ - // App target, which automatically builds its dependencies and mergeable and then merges them. + // App target, which automatically builds its dependencies as mergeable and then merges them. TestStandardTarget( "AppTarget", type: .application, @@ -2546,6 +2670,14 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let core = try await getCore() let tester = try TaskConstructionTester(core, testProject) + let tasksToIgnore: Set = [ + "Gate", + "MkDir", + "CreateBuildDirectory", + "ClangStatCache", + "LinkAssetCatalogSignature" + ] + // Test a release build. do { let buildType = "Release" @@ -2565,7 +2697,7 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { let targets = [BuildRequest.BuildTargetInfo(parameters: parameters, target: tester.workspace.projects[0].targets[0])] let request = BuildRequest(parameters: parameters, buildTargets: targets, continueBuildingAfterErrors: false, useParallelTargets: true, useImplicitDependencies: true, useDryRun: false) await tester.checkBuild(parameters, runDestination: .iOS, buildRequest: request) { results in - results.consumeTasksMatchingRuleTypes() + results.consumeTasksMatchingRuleTypes(tasksToIgnore) // Check the normal framework target. results.checkTarget("NormalFwkTarget") { target in @@ -2581,6 +2713,11 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { task.checkCommandLineDoesNotContain("-add_mergeable_debug_hook") } + results.checkNoTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineDoesNotContain("-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE") + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) } @@ -2599,6 +2736,13 @@ fileprivate struct MergeableLibraryTests: CoreBasedTests { task.checkCommandLineDoesNotContain("-add_mergeable_debug_hook") // Only passed in debug builds } + results.checkWriteAuxiliaryFileTask(.matchTarget(target), .matchRuleType("WriteAuxiliaryFile"), .matchRuleItemBasename("bundle_lookup_helper.swift")) { task, contents in + XCTAssertMatch(contents.unsafeStringValue, .contains("internal class __BundleLookupHelper {}")) + } + results.checkTask(.matchTarget(target), .matchRuleType("SwiftDriver Compilation")) { task in + task.checkCommandLineContains(["-DSWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE"]) + } + results.checkTasks(.matchTarget(target), body: { (tasks) -> Void in #expect(tasks.count > 0) }) }