From 93a6090ade33865fbf18fbea0ecb1ecd7f98445d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 18 Oct 2022 19:06:49 -0400 Subject: [PATCH 001/178] [Initial] Approach 2 --- Sources/Build/BuildPlan.swift | 1610 +++++++++++++++++ Sources/Build/LLBuildManifestBuilder.swift | 94 +- .../PackageLoading/ModuleMapGenerator.swift | 20 +- Sources/PackageLoading/PackageBuilder.swift | 34 +- .../PackageLoading/TargetSourcesBuilder.swift | 5 - Sources/PackageModel/Target.swift | 99 + 6 files changed, 1817 insertions(+), 45 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index de2313cebc3..673b27bd323 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -25,6 +25,8 @@ import func TSCBasic.topologicalSort import enum TSCUtility.Diagnostics import var TSCUtility.verbosity +import PackageCollections +import CloudKit extension String { var asSwiftStringLiteralConstant: String { @@ -159,6 +161,1587 @@ extension BuildParameters { } } +/// A target description which can either be for a Swift or Clang target. +public enum TargetBuildDescription { + + /// Swift target description. + case swift(SwiftTargetBuildDescription) + + /// Clang target description. + case clang(ClangTargetBuildDescription) + + /// Mixed (Swift + Clang) target description. + case mixed(MixedTargetBuildDescription) + + /// The objects in this target. + var objects: [AbsolutePath] { + get throws { + switch self { + case .swift(let target): + return try target.objects + case .clang(let target): + return try target.objects + case .mixed(let target): + return target.swiftTargetBuildDescription.objects + target.clangTargetBuildDescription.objects + } + } + } + + /// The resources in this target. + var resources: [Resource] { + switch self { + case .swift(let target): + return target.resources + case .clang(let target): + // TODO: Clang targets should support generated resources in the future. + return target.target.underlyingTarget.resources + case .mixed(let target): + return target.resources + } + } + + /// Path to the bundle generated for this module (if any). + var bundlePath: AbsolutePath? { + switch self { + case .swift(let target): + return target.bundlePath + case .clang(let target): + return target.bundlePath + case .mixed(let target): + return target.swiftTargetBuildDescription.bundlePath + } + } + + var target: ResolvedTarget { + switch self { + case .swift(let target): + return target.target + case .clang(let target): + return target.target + case .mixed(let target): + return target.target + } + } + + /// Paths to the binary libraries the target depends on. + var libraryBinaryPaths: Set { + switch self { + case .swift(let target): + return target.libraryBinaryPaths + case .clang(let target): + return target.libraryBinaryPaths + case .mixed(let target): + return target.swiftTargetBuildDescription.libraryBinaryPaths + } + } + + var resourceBundleInfoPlistPath: AbsolutePath? { + switch self { + case .swift(let target): + return target.resourceBundleInfoPlistPath + case .clang(let target): + return target.resourceBundleInfoPlistPath + case .mixed(let target): + return target.swiftTargetBuildDescription.resourceBundleInfoPlistPath + } + } +} + +/// Target description for a Clang target i.e. C language family target. +public final class ClangTargetBuildDescription { + + /// The target described by this target. + public let target: ResolvedTarget + + /// The underlying clang target. + public var clangTarget: ClangTarget { + return target.underlyingTarget as! ClangTarget + } + + /// The tools version of the package that declared the target. This can + /// can be used to conditionalize semantically significant changes in how + /// a target is built. + public let toolsVersion: ToolsVersion + + /// The build parameters. + let buildParameters: BuildParameters + + /// The build environment. + var buildEnvironment: BuildEnvironment { + buildParameters.buildEnvironment + } + + /// Path to the bundle generated for this module (if any). + var bundlePath: AbsolutePath? { + target.underlyingTarget.bundleName.map(buildParameters.bundlePath(named:)) + } + + /// The modulemap file for this target, if any. + public private(set) var moduleMap: AbsolutePath? + + /// Path to the temporary directory for this target. + var tempsPath: AbsolutePath + + /// The directory containing derived sources of this target. + /// + /// These are the source files generated during the build. + private var derivedSources: Sources + + /// Path to the resource accessor header file, if generated. + public private(set) var resourceAccessorHeaderFile: AbsolutePath? + + /// Path to the resource Info.plist file, if generated. + public private(set) var resourceBundleInfoPlistPath: AbsolutePath? + + /// The objects in this target. + public var objects: [AbsolutePath] { + get throws { + return try compilePaths().map({ $0.object }) + } + } + + /// Paths to the binary libraries the target depends on. + fileprivate(set) var libraryBinaryPaths: Set = [] + + /// Any addition flags to be added. These flags are expected to be computed during build planning. + fileprivate var additionalFlags: [String] = [] + + /// The filesystem to operate on. + private let fileSystem: FileSystem + + /// If this target is a test target. + public var isTestTarget: Bool { + return target.type == .test + } + + /// Create a new target description with target and build parameters. + init(target: ResolvedTarget, toolsVersion: ToolsVersion, buildParameters: BuildParameters, fileSystem: FileSystem, generateSwiftHeaderInModuleMap: Bool = false) throws { + guard target.underlyingTarget is ClangTarget || target.underlyingTarget is MixedTarget else { + throw InternalError("underlying target type mismatch \(target)") + } + + self.fileSystem = fileSystem + self.target = target + self.toolsVersion = toolsVersion + self.buildParameters = buildParameters + self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") + self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) + + + // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. + if target.type == .library { + // If there's a custom module map, use it as given. + if case .custom(let path) = clangTarget.moduleMapType { + self.moduleMap = path + } + // If a generated module map is needed, generate one now in our temporary directory. + else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { + let path = tempsPath.appending(component: moduleMapFilename) + let moduleMapGenerator = ModuleMapGenerator(targetName: clangTarget.name, moduleName: clangTarget.c99name, publicHeadersDir: clangTarget.includeDir, fileSystem: fileSystem) + let swiftHeaderPath = generateSwiftHeaderInModuleMap + ? tempsPath.appending(component: "\(target.name)-Swift.h") : nil + try moduleMapGenerator.generateModuleMap( + type: generatedModuleMapType, + at: path, + swiftHeaderPath:swiftHeaderPath + ) + self.moduleMap = path + } + // Otherwise there is no module map, and we leave `moduleMap` unset. + } + + // Do nothing if we're not generating a bundle. + if bundlePath != nil { + try self.generateResourceAccessor() + + let infoPlistPath = tempsPath.appending(component: "Info.plist") + if try generateResourceInfoPlist(fileSystem: fileSystem, target: target, path: infoPlistPath) { + resourceBundleInfoPlistPath = infoPlistPath + } + } + } + + /// An array of tuple containing filename, source, object and dependency path for each of the source in this target. + public func compilePaths() + throws -> [(filename: RelativePath, source: AbsolutePath, object: AbsolutePath, deps: AbsolutePath)] + { + let sources = [ + target.sources.root: target.sources.relativePaths, + derivedSources.root: derivedSources.relativePaths, + ] + + return try sources.flatMap { (root, relativePaths) in + try relativePaths.map { source in + let path = root.appending(source) + let object = try AbsolutePath(validating: "\(source.pathString).o", relativeTo: tempsPath) + let deps = try AbsolutePath(validating: "\(source.pathString).d", relativeTo: tempsPath) + return (source, path, object, deps) + } + } + } + + /// Builds up basic compilation arguments for a source file in this target; these arguments may be different for C++ vs non-C++. + /// NOTE: The parameter to specify whether to get C++ semantics is currently optional, but this is only for revlock avoidance with clients. Callers should always specify what they want based either the user's indication or on a default value (possibly based on the filename suffix). + public func basicArguments( + isCXX isCXXOverride: Bool? = .none, + isC: Bool = false + ) throws -> [String] { + // For now fall back on the hold semantics if the C++ nature isn't specified. This is temporary until clients have been updated. + let isCXX = isCXXOverride ?? clangTarget.isCXX + + var args = [String]() + // Only enable ARC on macOS. + if buildParameters.triple.isDarwin() { + args += ["-fobjc-arc"] + } + args += try buildParameters.targetTripleArgs(for: target) + args += ["-g"] + if buildParameters.triple.isWindows() { + args += ["-gcodeview"] + } + args += optimizationArguments + args += activeCompilationConditions + args += ["-fblocks"] + + // Enable index store, if appropriate. + // + // This feature is not widely available in OSS clang. So, we only enable + // index store for Apple's clang or if explicitly asked to. + if ProcessEnv.vars.keys.contains("SWIFTPM_ENABLE_CLANG_INDEX_STORE") { + args += buildParameters.indexStoreArguments(for: target) + } else if buildParameters.triple.isDarwin(), (try? buildParameters.toolchain._isClangCompilerVendorApple()) == true { + args += buildParameters.indexStoreArguments(for: target) + } + + // Enable Clang module flags, if appropriate. + let enableModules: Bool + if toolsVersion < .v5_8 { + // For version < 5.8, we enable them except in these cases: + // 1. on Darwin when compiling for C++, because C++ modules are disabled on Apple-built Clang releases + // 2. on Windows when compiling for any language, because of issues with the Windows SDK + // 3. on Android when compiling for any language, because of issues with the Android SDK + enableModules = !(buildParameters.triple.isDarwin() && isCXX) && !buildParameters.triple.isWindows() && !buildParameters.triple.isAndroid() + } else { + // For version >= 5.8, we disable them when compiling for C++ regardless of platforms, see: + // https://github.com/llvm/llvm-project/issues/55980 for clang frontend crash when module + // enabled for C++ on c++17 standard and above. + enableModules = !isCXX && !buildParameters.triple.isWindows() && !buildParameters.triple.isAndroid() + } + + if enableModules { + // Using modules currently conflicts with the Windows and Android SDKs. + args += ["-fmodules", "-fmodule-name=" + target.c99name] + } + + // Only add the build path to the framework search path if there are binary frameworks to link against. + if !libraryBinaryPaths.isEmpty { + args += ["-F", buildParameters.buildPath.pathString] + } + + args += ["-I", clangTarget.includeDir.pathString] + args += additionalFlags + if enableModules { + args += try moduleCacheArgs + } + args += buildParameters.sanitizers.compileCFlags() + + // Add arguments from declared build settings. + args += try self.buildSettingsFlags() + + // Include the path to the resource header unless the arguments are + // being evaluated for a C file. A C file cannot depend on the resource + // accessor header due to it exporting a Foundation type (`NSBundle`). + if let resourceAccessorHeaderFile = self.resourceAccessorHeaderFile, !isC { + args += ["-include", resourceAccessorHeaderFile.pathString] + } + + args += buildParameters.toolchain.extraFlags.cCompilerFlags + // User arguments (from -Xcc and -Xcxx below) should follow generated arguments to allow user overrides + args += buildParameters.flags.cCompilerFlags + + // Add extra C++ flags if this target contains C++ files. + if clangTarget.isCXX { + args += self.buildParameters.flags.cxxCompilerFlags + } + return args + } + + /// Returns the build flags from the declared build settings. + private func buildSettingsFlags() throws -> [String] { + let scope = buildParameters.createScope(for: target) + var flags: [String] = [] + + // C defines. + let cDefines = scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) + flags += cDefines.map({ "-D" + $0 }) + + // Header search paths. + let headerSearchPaths = scope.evaluate(.HEADER_SEARCH_PATHS) + flags += try headerSearchPaths.map({ + "-I\(try AbsolutePath(validating: $0, relativeTo: target.sources.root).pathString)" + }) + + // Other C flags. + flags += scope.evaluate(.OTHER_CFLAGS) + + // Other CXX flags. + flags += scope.evaluate(.OTHER_CPLUSPLUSFLAGS) + + return flags + } + + /// Optimization arguments according to the build configuration. + private var optimizationArguments: [String] { + switch buildParameters.configuration { + case .debug: + return ["-O0"] + case .release: + return ["-O2"] + } + } + + /// A list of compilation conditions to enable for conditional compilation expressions. + private var activeCompilationConditions: [String] { + var compilationConditions = ["-DSWIFT_PACKAGE=1"] + + switch buildParameters.configuration { + case .debug: + compilationConditions += ["-DDEBUG=1"] + case .release: + break + } + + return compilationConditions + } + + /// Module cache arguments. + private var moduleCacheArgs: [String] { + get throws { + return try ["-fmodules-cache-path=\(buildParameters.moduleCache.pathString)"] + } + } + + /// Generate the resource bundle accessor, if appropriate. + private func generateResourceAccessor() throws { + // Only generate access when we have a bundle and ObjC files. + guard let bundlePath = self.bundlePath, clangTarget.sources.containsObjcFiles else { return } + + // Compute the basename of the bundle. + let bundleBasename = bundlePath.basename + + let implFileStream = BufferedOutputByteStream() + implFileStream <<< """ + #import + + NSBundle* \(target.c99name)_SWIFTPM_MODULE_BUNDLE() { + NSURL *bundleURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"\(bundleBasename)"]; + + NSBundle *preferredBundle = [NSBundle bundleWithURL:bundleURL]; + if (preferredBundle == nil) { + return [NSBundle bundleWithPath:@"\(bundlePath.pathString)"]; + } + + return preferredBundle; + } + """ + + let implFileSubpath = RelativePath("resource_bundle_accessor.m") + + // Add the file to the derived sources. + derivedSources.relativePaths.append(implFileSubpath) + + // Write this file out. + // FIXME: We should generate this file during the actual build. + try fileSystem.writeIfChanged( + path: derivedSources.root.appending(implFileSubpath), + bytes: implFileStream.bytes + ) + + let headerFileStream = BufferedOutputByteStream() + headerFileStream <<< """ + #import + + #if __cplusplus + extern "C" { + #endif + + NSBundle* \(target.c99name)_SWIFTPM_MODULE_BUNDLE(void); + + #define SWIFTPM_MODULE_BUNDLE \(target.c99name)_SWIFTPM_MODULE_BUNDLE() + + #if __cplusplus + } + #endif + """ + let headerFile = derivedSources.root.appending(component: "resource_bundle_accessor.h") + self.resourceAccessorHeaderFile = headerFile + + try fileSystem.writeIfChanged( + path: headerFile, + bytes: headerFileStream.bytes + ) + } +} + +/// Target description for a Swift target. +public final class SwiftTargetBuildDescription { + /// The package this target belongs to. + public let package: ResolvedPackage + + /// The target described by this target. + public let target: ResolvedTarget + + /// The tools version of the package that declared the target. This can + /// can be used to conditionalize semantically significant changes in how + /// a target is built. + public let toolsVersion: ToolsVersion + + /// The build parameters. + let buildParameters: BuildParameters + + /// Path to the temporary directory for this target. + let tempsPath: AbsolutePath + + /// The directory containing derived sources of this target. + /// + /// These are the source files generated during the build. + private var derivedSources: Sources + + /// These are the source files derived from plugins. + private var pluginDerivedSources: Sources + + /// These are the resource files derived from plugins. + internal var pluginDerivedResources: [Resource] + + /// Path to the bundle generated for this module (if any). + var bundlePath: AbsolutePath? { + if let bundleName = target.underlyingTarget.potentialBundleName, !resources.isEmpty { + return buildParameters.bundlePath(named: bundleName) + } else { + return .none + } + } + + /// The list of all source files in the target, including the derived ones. + public var sources: [AbsolutePath] { + target.sources.paths + derivedSources.paths + pluginDerivedSources.paths + } + + /// The list of all resource files in the target, including the derived ones. + public var resources: [Resource] { + target.underlyingTarget.resources + pluginDerivedResources + } + + /// The objects in this target. + public var objects: [AbsolutePath] { + get throws { + let relativePaths = target.sources.relativePaths + derivedSources.relativePaths + pluginDerivedSources.relativePaths + return try relativePaths.map { + try AbsolutePath(validating: "\($0.pathString).o", relativeTo: tempsPath) + } + } + } + + /// The path to the swiftmodule file after compilation. + var moduleOutputPath: AbsolutePath { + // If we're an executable and we're not allowing test targets to link against us, we hide the module. + let allowLinkingAgainstExecutables = (buildParameters.triple.isDarwin() || buildParameters.triple.isLinux() || buildParameters.triple.isWindows()) && toolsVersion >= .v5_5 + let dirPath = (target.type == .executable && !allowLinkingAgainstExecutables) ? tempsPath : buildParameters.buildPath + return dirPath.appending(component: target.c99name + ".swiftmodule") + } + + /// The path to the wrapped swift module which is created using the modulewrap tool. This is required + /// for supporting debugging on non-Darwin platforms (On Darwin, we just pass the swiftmodule to the linker + /// using the `-add_ast_path` flag). + var wrappedModuleOutputPath: AbsolutePath { + return tempsPath.appending(component: target.c99name + ".swiftmodule.o") + } + + /// The path to the swifinterface file after compilation. + var parseableModuleInterfaceOutputPath: AbsolutePath { + return buildParameters.buildPath.appending(component: target.c99name + ".swiftinterface") + } + + /// Path to the resource Info.plist file, if generated. + public private(set) var resourceBundleInfoPlistPath: AbsolutePath? + + /// Paths to the binary libraries the target depends on. + fileprivate(set) var libraryBinaryPaths: Set = [] + + /// Any addition flags to be added. These flags are expected to be computed during build planning. + fileprivate var additionalFlags: [String] = [] + + /// The swift version for this target. + var swiftVersion: SwiftLanguageVersion { + return (target.underlyingTarget as! SwiftTarget).swiftVersion + } + + /// Describes the purpose of a test target, including any special roles such as containing a list of discovered tests or + /// serving as the manifest target which contains the main entry point. + public enum TestTargetRole { + /// An ordinary test target, defined explicitly in a package, containing test code. + case `default` + + /// A test target which was synthesized automatically, containing a list of discovered tests + /// from `plain` test targets. + case discovery + + /// A test target which was either synthesized automatically and contains an entry point file configured to run all discovered + /// tests, or contains a custom entry point file. In the latter case, the custom entry point file may have been discovered in + /// the package automatically (e.g. `XCTMain.swift`) or may have been provided explicitly via a CLI flag. + case entryPoint(isSynthesized: Bool) + } + + public let testTargetRole: TestTargetRole? + + /// If this target is a test target. + public var isTestTarget: Bool { + testTargetRole != nil + } + + /// True if this module needs to be parsed as a library based on the target type and the configuration + /// of the source code + var needsToBeParsedAsLibrary: Bool { + switch self.target.type { + case .library, .test: + return true + case .executable, .snippet: + // This deactivates heuristics in the Swift compiler that treats single-file modules and source files + // named "main.swift" specially w.r.t. whether they can have an entry point. + // + // See https://bugs.swift.org/browse/SR-14488 for discussion about improvements so that SwiftPM can + // convey the intent to build an executable module to the compiler regardless of the number of files + // in the module or their names. + if self.toolsVersion < .v5_5 || self.sources.count != 1 { + return false + } + // looking into the file content to see if it is using the @main annotation which requires parse-as-library + return (try? self.containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false + default: + return false + } + } + + // looking into the file content to see if it is using the @main annotation + // this is not bullet-proof since theoretically the file can contain the @main string for other reasons + // but it is the closest to accurate we can do at this point + func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool { + let content: String = try self.fileSystem.readFileContents(path) + let lines = content.split(separator: "\n").compactMap { String($0).spm_chuzzle() } + + var multilineComment = false + for line in lines { + if line.hasPrefix("//") { + continue + } + if line.hasPrefix("/*") { + multilineComment = true + } + if line.hasSuffix("*/") { + multilineComment = false + } + if multilineComment { + continue + } + if line.hasPrefix("@main") { + return true + } + } + return false + } + + + /// The filesystem to operate on. + let fileSystem: FileSystem + + /// The modulemap file for this target, if any. + private(set) var moduleMap: AbsolutePath? + + /// The results of applying any build tool plugins to this target. + public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] + + /// The results of running any prebuild commands for this target. + public let prebuildCommandResults: [PrebuildCommandResult] + + /// ObservabilityScope with which to emit diagnostics + private let observabilityScope: ObservabilityScope + + /// Create a new target description with target and build parameters. + init( + package: ResolvedPackage, + target: ResolvedTarget, + toolsVersion: ToolsVersion, + additionalFileRules: [FileRuleDescription] = [], + buildParameters: BuildParameters, + buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], + prebuildCommandResults: [PrebuildCommandResult] = [], + testTargetRole: TestTargetRole? = nil, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + moduleMapPath: AbsolutePath? = nil + ) throws { + guard target.underlyingTarget is SwiftTarget || target.underlyingTarget is MixedTarget else { + throw InternalError("underlying target type mismatch \(target)") + } + self.package = package + self.target = target + self.toolsVersion = toolsVersion + self.buildParameters = buildParameters + // Unless mentioned explicitly, use the target type to determine if this is a test target. + if let testTargetRole = testTargetRole { + self.testTargetRole = testTargetRole + } else if target.type == .test { + self.testTargetRole = .default + } else { + self.testTargetRole = nil + } + self.fileSystem = fileSystem + self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") + self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) + self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath) + self.buildToolPluginInvocationResults = buildToolPluginInvocationResults + self.prebuildCommandResults = prebuildCommandResults + self.observabilityScope = observabilityScope + + // Add any derived files that were declared for any commands from plugin invocations. + var pluginDerivedFiles = [AbsolutePath]() + for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) { + for absPath in command.outputFiles { + pluginDerivedFiles.append(absPath) + } + } + + // Add any derived files that were discovered from output directories of prebuild commands. + for result in self.prebuildCommandResults { + for path in result.derivedFiles { + pluginDerivedFiles.append(path) + } + } + + // Let `TargetSourcesBuilder` compute the treatment of plugin generated files. + let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(for: pluginDerivedFiles, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, defaultLocalization: target.defaultLocalization, targetName: target.name, targetPath: target.underlyingTarget.path, observabilityScope: observabilityScope) + self.pluginDerivedResources = derivedResources + derivedSources.forEach { absPath in + let relPath = absPath.relative(to: self.pluginDerivedSources.root) + self.pluginDerivedSources.relativePaths.append(relPath) + } + + if shouldEmitObjCCompatibilityHeader { + self.moduleMap = moduleMapPath != nil + ? moduleMapPath : try self.generateModuleMap() + } + + // Do nothing if we're not generating a bundle. + if bundlePath != nil { + try self.generateResourceAccessor() + + let infoPlistPath = tempsPath.appending(component: "Info.plist") + if try generateResourceInfoPlist(fileSystem: self.fileSystem, target: target, path: infoPlistPath) { + resourceBundleInfoPlistPath = infoPlistPath + } + } + } + + /// Generate the resource bundle accessor, if appropriate. + private func generateResourceAccessor() throws { + // Do nothing if we're not generating a bundle. + guard let bundlePath = self.bundlePath else { return } + + let mainPathSubstitution: String + if buildParameters.triple.isWASI() { + // We prefer compile-time evaluation of the bundle path here for WASI. There's no benefit in evaluating this at runtime, + // especially as Bundle support in WASI Foundation is partial. We expect all resource paths to evaluate to + // `/\(resourceBundleName)/\(resourcePath)`, which allows us to pass this path to JS APIs like `fetch` directly, or to + // ` [String] { + var args = [String]() + args += try buildParameters.targetTripleArgs(for: target) + args += ["-swift-version", swiftVersion.rawValue] + + // Enable batch mode in debug mode. + // + // Technically, it should be enabled whenever WMO is off but we + // don't currently make that distinction in SwiftPM + switch buildParameters.configuration { + case .debug: + args += ["-enable-batch-mode"] + case .release: break + } + + args += buildParameters.indexStoreArguments(for: target) + args += optimizationArguments + args += testingArguments + args += ["-g"] + args += ["-j\(buildParameters.jobs)"] + args += activeCompilationConditions + args += additionalFlags + args += try moduleCacheArgs + args += stdlibArguments + args += buildParameters.sanitizers.compileSwiftFlags() + args += ["-parseable-output"] + + // If we're compiling the main module of an executable other than the one that + // implements a test suite, and if the package tools version indicates that we + // should, we rename the `_main` entry point to `__main`. + // + // This will allow tests to link against the module without any conflicts. And + // when we link the executable, we will ask the linker to rename the entry point + // symbol to just `_main` again (or if the linker doesn't support it, we'll + // generate a source containing a redirect). + if (target.type == .executable || target.type == .snippet) + && !isTestTarget && toolsVersion >= .v5_5 { + // We only do this if the linker supports it, as indicated by whether we + // can construct the linker flags. In the future we will use a generated + // code stub for the cases in which the linker doesn't support it, so that + // we can rename the symbol unconditionally. + // No `-` for these flags because the set of Strings in driver.supportedFrontendFlags do + // not have a leading `-` + if buildParameters.canRenameEntrypointFunctionName, + buildParameters.linkerFlagsForRenamingMainFunction(of: target) != nil { + args += ["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "\(target.c99name)_main"] + } + } + + // If the target needs to be parsed without any special semantics involving "main.swift", do so now. + if self.needsToBeParsedAsLibrary { + args += ["-parse-as-library"] + } + + // Only add the build path to the framework search path if there are binary frameworks to link against. + if !libraryBinaryPaths.isEmpty { + args += ["-F", buildParameters.buildPath.pathString] + } + + // Emit the ObjC compatibility header if enabled. + if shouldEmitObjCCompatibilityHeader { + args += ["-emit-objc-header", "-emit-objc-header-path", objCompatibilityHeaderPath.pathString] + } + + // Add arguments needed for code coverage if it is enabled. + if buildParameters.enableCodeCoverage { + args += ["-profile-coverage-mapping", "-profile-generate"] + } + + // Add arguments to colorize output if stdout is tty + if buildParameters.colorizedOutput { + args += ["-color-diagnostics"] + } + + // Add arguments from declared build settings. + args += try self.buildSettingsFlags() + + // Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other way. + if buildParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { + args += ["-emit-module-interface-path", parseableModuleInterfaceOutputPath.pathString] + } + + args += buildParameters.toolchain.extraFlags.swiftCompilerFlags + // User arguments (from -Xswiftc) should follow generated arguments to allow user overrides + args += buildParameters.swiftCompilerFlags + + // suppress warnings if the package is remote + if self.package.isRemote { + args += ["-suppress-warnings"] + // suppress-warnings and warnings-as-errors are mutually exclusive + if let index = args.firstIndex(of: "-warnings-as-errors") { + args.remove(at: index) + } + } + + return args + } + + /// When `scanInvocation` argument is set to `true`, omit the side-effect producing arguments + /// such as emitting a module or supplementary outputs. + public func emitCommandLine(scanInvocation: Bool = false) throws -> [String] { + var result: [String] = [] + result.append(buildParameters.toolchain.swiftCompilerPath.pathString) + + result.append("-module-name") + result.append(target.c99name) + + if !scanInvocation { + result.append("-emit-dependencies") + + // FIXME: Do we always have a module? + result.append("-emit-module") + result.append("-emit-module-path") + result.append(moduleOutputPath.pathString) + + result.append("-output-file-map") + // FIXME: Eliminate side effect. + result.append(try writeOutputFileMap().pathString) + } + + if buildParameters.useWholeModuleOptimization { + result.append("-whole-module-optimization") + result.append("-num-threads") + result.append(String(ProcessInfo.processInfo.activeProcessorCount)) + } else { + result.append("-incremental") + } + + result.append("-c") + result.append(contentsOf: sources.map { $0.pathString }) + + result.append("-I") + result.append(buildParameters.buildPath.pathString) + + result += try self.compileArguments() + return result + } + + /// Command-line for emitting just the Swift module. + public func emitModuleCommandLine() throws -> [String] { + guard buildParameters.emitSwiftModuleSeparately else { + throw InternalError("expecting emitSwiftModuleSeparately in build parameters") + } + + var result: [String] = [] + result.append(buildParameters.toolchain.swiftCompilerPath.pathString) + + result.append("-module-name") + result.append(target.c99name) + result.append("-emit-module") + result.append("-emit-module-path") + result.append(moduleOutputPath.pathString) + result += buildParameters.toolchain.extraFlags.swiftCompilerFlags + + result.append("-Xfrontend") + result.append("-experimental-skip-non-inlinable-function-bodies") + result.append("-force-single-frontend-invocation") + + // FIXME: Handle WMO + + for source in target.sources.paths { + result.append(source.pathString) + } + + result.append("-I") + result.append(buildParameters.buildPath.pathString) + + // FIXME: Maybe refactor these into "common args". + result += try buildParameters.targetTripleArgs(for: target) + result += ["-swift-version", swiftVersion.rawValue] + result += optimizationArguments + result += testingArguments + result += ["-g"] + result += ["-j\(buildParameters.jobs)"] + result += activeCompilationConditions + result += additionalFlags + result += try moduleCacheArgs + result += stdlibArguments + result += try self.buildSettingsFlags() + + return result + } + + /// Command-line for emitting the object files. + /// + /// Note: This doesn't emit the module. + public func emitObjectsCommandLine() throws -> [String] { + guard buildParameters.emitSwiftModuleSeparately else { + throw InternalError("expecting emitSwiftModuleSeparately in build parameters") + } + + var result: [String] = [] + result.append(buildParameters.toolchain.swiftCompilerPath.pathString) + + result.append("-module-name") + result.append(target.c99name) + result.append("-incremental") + result.append("-emit-dependencies") + + result.append("-output-file-map") + // FIXME: Eliminate side effect. + result.append(try writeOutputFileMap().pathString) + + // FIXME: Handle WMO + + result.append("-c") + for source in target.sources.paths { + result.append(source.pathString) + } + + result.append("-I") + result.append(buildParameters.buildPath.pathString) + + result += try buildParameters.targetTripleArgs(for: target) + result += ["-swift-version", swiftVersion.rawValue] + + result += buildParameters.indexStoreArguments(for: target) + result += optimizationArguments + result += testingArguments + result += ["-g"] + result += ["-j\(buildParameters.jobs)"] + result += activeCompilationConditions + result += additionalFlags + result += try moduleCacheArgs + result += stdlibArguments + result += buildParameters.sanitizers.compileSwiftFlags() + result += ["-parseable-output"] + result += try self.buildSettingsFlags() + result += buildParameters.toolchain.extraFlags.swiftCompilerFlags + result += buildParameters.swiftCompilerFlags + return result + } + + /// Returns true if ObjC compatibility header should be emitted. + private var shouldEmitObjCCompatibilityHeader: Bool { + return buildParameters.triple.isDarwin() && target.type == .library + } + + private func writeOutputFileMap() throws -> AbsolutePath { + let path = tempsPath.appending(component: "output-file-map.json") + let stream = BufferedOutputByteStream() + + stream <<< "{\n" + + let masterDepsPath = tempsPath.appending(component: "master.swiftdeps") + stream <<< " \"\": {\n" + if buildParameters.useWholeModuleOptimization { + let moduleName = target.c99name + stream <<< " \"dependencies\": \"" <<< tempsPath.appending(component: moduleName + ".d").nativePathString(escaped: true) <<< "\",\n" + // FIXME: Need to record this deps file for processing it later. + stream <<< " \"object\": \"" <<< tempsPath.appending(component: moduleName + ".o").nativePathString(escaped: true) <<< "\",\n" + } + stream <<< " \"swift-dependencies\": \"" <<< masterDepsPath.nativePathString(escaped: true) <<< "\"\n" + + stream <<< " },\n" + + // Write out the entries for each source file. + let sources = target.sources.paths + derivedSources.paths + pluginDerivedSources.paths + for (idx, source) in sources.enumerated() { + let object = try objects[idx] + let objectDir = object.parentDirectory + + let sourceFileName = source.basenameWithoutExt + + let swiftDepsPath = objectDir.appending(component: sourceFileName + ".swiftdeps") + + stream <<< " \"" <<< source.nativePathString(escaped: true) <<< "\": {\n" + + if (!buildParameters.useWholeModuleOptimization) { + let depsPath = objectDir.appending(component: sourceFileName + ".d") + stream <<< " \"dependencies\": \"" <<< depsPath.nativePathString(escaped: true) <<< "\",\n" + // FIXME: Need to record this deps file for processing it later. + } + + stream <<< " \"object\": \"" <<< object.nativePathString(escaped: true) <<< "\",\n" + + let partialModulePath = objectDir.appending(component: sourceFileName + "~partial.swiftmodule") + stream <<< " \"swiftmodule\": \"" <<< partialModulePath.nativePathString(escaped: true) <<< "\",\n" + stream <<< " \"swift-dependencies\": \"" <<< swiftDepsPath.nativePathString(escaped: true) <<< "\"\n" + stream <<< " }" <<< ((idx + 1) < sources.count ? "," : "") <<< "\n" + } + + stream <<< "}\n" + + try self.fileSystem.createDirectory(path.parentDirectory, recursive: true) + try self.fileSystem.writeFileContents(path, bytes: stream.bytes) + return path + } + + /// Generates the module map for the Swift target and returns its path. + private func generateModuleMap() throws -> AbsolutePath { + let path = tempsPath.appending(component: moduleMapFilename) + + let stream = BufferedOutputByteStream() + stream <<< "module \(target.c99name) {\n" + stream <<< " header \"" <<< objCompatibilityHeaderPath.pathString <<< "\"\n" + stream <<< " requires objc\n" + stream <<< "}\n" + + // Return early if the contents are identical. + if self.fileSystem.isFile(path), try self.fileSystem.readFileContents(path) == stream.bytes { + return path + } + + try self.fileSystem.createDirectory(path.parentDirectory, recursive: true) + try self.fileSystem.writeFileContents(path, bytes: stream.bytes) + + return path + } + + /// Returns the path to the ObjC compatibility header for this Swift target. + var objCompatibilityHeaderPath: AbsolutePath { + return tempsPath.appending(component: "\(target.name)-Swift.h") + } + + /// Returns the build flags from the declared build settings. + private func buildSettingsFlags() throws -> [String] { + let scope = buildParameters.createScope(for: target) + var flags: [String] = [] + + // Swift defines. + let swiftDefines = scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) + flags += swiftDefines.map({ "-D" + $0 }) + + // Other Swift flags. + flags += scope.evaluate(.OTHER_SWIFT_FLAGS) + + // Add C flags by prefixing them with -Xcc. + // + // C defines. + let cDefines = scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) + flags += cDefines.flatMap({ ["-Xcc", "-D" + $0] }) + + // Header search paths. + let headerSearchPaths = scope.evaluate(.HEADER_SEARCH_PATHS) + flags += try headerSearchPaths.flatMap({ path -> [String] in + return ["-Xcc", "-I\(try AbsolutePath(validating: path, relativeTo: target.sources.root).pathString)"] + }) + + // Other C flags. + flags += scope.evaluate(.OTHER_CFLAGS).flatMap({ ["-Xcc", $0] }) + + return flags + } + + /// A list of compilation conditions to enable for conditional compilation expressions. + private var activeCompilationConditions: [String] { + var compilationConditions = ["-DSWIFT_PACKAGE"] + + switch buildParameters.configuration { + case .debug: + compilationConditions += ["-DDEBUG"] + case .release: + break + } + + return compilationConditions + } + + /// Optimization arguments according to the build configuration. + private var optimizationArguments: [String] { + switch buildParameters.configuration { + case .debug: + return ["-Onone"] + case .release: + return ["-O"] + } + } + + /// Testing arguments according to the build configuration. + private var testingArguments: [String] { + if self.isTestTarget { + // test targets must be built with -enable-testing + // since its required for test discovery (the non objective-c reflection kind) + return ["-enable-testing"] + } else if buildParameters.enableTestability { + return ["-enable-testing"] + } else { + return [] + } + } + + /// Module cache arguments. + private var moduleCacheArgs: [String] { + get throws { + return ["-module-cache-path", try buildParameters.moduleCache.pathString] + } + } + + private var stdlibArguments: [String] { + if buildParameters.shouldLinkStaticSwiftStdlib && + buildParameters.triple.isSupportingStaticStdlib { + return ["-static-stdlib"] + } else { + return [] + } + } +} + +public final class MixedTargetBuildDescription { + + /// The target described by this target. + public let target: ResolvedTarget + + /// The list of all resource files in the target, including the derived ones. + public var resources: [Resource] { + target.underlyingTarget.resources + swiftTargetBuildDescription.pluginDerivedResources + } + + public var isTestTarget: Bool { + clangTargetBuildDescription.isTestTarget && + swiftTargetBuildDescription.isTestTarget + } + + /// The build description for the Clang sources. + public let clangTargetBuildDescription: ClangTargetBuildDescription + + /// The build description for the Swift sources. + public let swiftTargetBuildDescription: SwiftTargetBuildDescription + + private let fileSystem: FileSystem + + init( + package: ResolvedPackage, + target: ResolvedTarget, + toolsVersion: ToolsVersion, + additionalFileRules: [FileRuleDescription] = [], + buildParameters: BuildParameters, + buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], + prebuildCommandResults: [PrebuildCommandResult] = [], + isTestTarget: Bool? = nil, + isTestDiscoveryTarget: Bool = false, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope + ) throws { + self.target = target + + self.fileSystem = fileSystem + + let mixedTarget = target.underlyingTarget as! MixedTarget + + let clangResolvedTarget = ResolvedTarget( + target: mixedTarget.clangTarget, + dependencies: target.dependencies, + defaultLocalization: target.defaultLocalization, + platforms: target.platforms + ) + self.clangTargetBuildDescription = try ClangTargetBuildDescription( + target: clangResolvedTarget, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + fileSystem: fileSystem, + generateSwiftHeaderInModuleMap: true + ) + + let swiftResolvedTarget = ResolvedTarget( + target: mixedTarget.swiftTarget, + dependencies: target.dependencies, + defaultLocalization: target.defaultLocalization, + platforms: target.platforms + ) + self.swiftTargetBuildDescription = try SwiftTargetBuildDescription( + package: package, + target: swiftResolvedTarget, + toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, + buildParameters: buildParameters, + buildToolPluginInvocationResults: buildToolPluginInvocationResults, + prebuildCommandResults: prebuildCommandResults, + isTestTarget: isTestTarget, + isTestDiscoveryTarget: isTestDiscoveryTarget, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + moduleMapPath: self.clangTargetBuildDescription.moduleMap + ) + } +} + +/// The build description for a product. +public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription { + + /// The reference to the product. + public let package: ResolvedPackage + + /// The reference to the product. + public let product: ResolvedProduct + + /// The tools version of the package that declared the product. This can + /// can be used to conditionalize semantically significant changes in how + /// a target is built. + public let toolsVersion: ToolsVersion + + /// The build parameters. + public let buildParameters: BuildParameters + + /// All object files to link into this product. + /// + // Computed during build planning. + public fileprivate(set) var objects = SortedArray() + + /// The dynamic libraries this product needs to link with. + // Computed during build planning. + fileprivate(set) var dylibs: [ProductBuildDescription] = [] + + /// Any additional flags to be added. These flags are expected to be computed during build planning. + fileprivate var additionalFlags: [String] = [] + + /// The list of targets that are going to be linked statically in this product. + fileprivate var staticTargets: [ResolvedTarget] = [] + + /// The list of Swift modules that should be passed to the linker. This is required for debugging to work. + fileprivate var swiftASTs: SortedArray = .init() + + /// Paths to the binary libraries the product depends on. + fileprivate var libraryBinaryPaths: Set = [] + + /// Paths to tools shipped in binary dependencies + var availableTools: [String: AbsolutePath] = [:] + + /// Path to the temporary directory for this product. + var tempsPath: AbsolutePath { + return buildParameters.buildPath.appending(component: product.name + ".product") + } + + /// Path to the link filelist file. + var linkFileListPath: AbsolutePath { + return tempsPath.appending(component: "Objects.LinkFileList") + } + + /// File system reference. + private let fileSystem: FileSystem + + /// ObservabilityScope with which to emit diagnostics + private let observabilityScope: ObservabilityScope + + /// Create a build description for a product. + init( + package: ResolvedPackage, + product: ResolvedProduct, + toolsVersion: ToolsVersion, + buildParameters: BuildParameters, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope + ) throws { + guard product.type != .library(.automatic) else { + throw InternalError("Automatic type libraries should not be described.") + } + + self.package = package + self.product = product + self.toolsVersion = toolsVersion + self.buildParameters = buildParameters + self.fileSystem = fileSystem + self.observabilityScope = observabilityScope + } + + /// Strips the arguments which should *never* be passed to Swift compiler + /// when we're linking the product. + /// + /// We might want to get rid of this method once Swift driver can strip the + /// flags itself, . + private func stripInvalidArguments(_ args: [String]) -> [String] { + let invalidArguments: Set = ["-wmo", "-whole-module-optimization"] + return args.filter({ !invalidArguments.contains($0) }) + } + + private var deadStripArguments: [String] { + if !buildParameters.linkerDeadStrip { + return [] + } + + switch buildParameters.configuration { + case .debug: + return [] + case .release: + if buildParameters.triple.isDarwin() { + return ["-Xlinker", "-dead_strip"] + } else if buildParameters.triple.isWindows() { + return ["-Xlinker", "/OPT:REF"] + } else if buildParameters.triple.arch == .wasm32 { + // FIXME: wasm-ld strips data segments referenced through __start/__stop symbols + // during GC, and it removes Swift metadata sections like swift5_protocols + // We should add support of SHF_GNU_RETAIN-like flag for __attribute__((retain)) + // to LLVM and wasm-ld + // This workaround is required for not only WASI but also all WebAssembly archs + // using wasm-ld (e.g. wasm32-unknown-unknown). So this branch is conditioned by + // arch == .wasm32 + return [] + } else { + return ["-Xlinker", "--gc-sections"] + } + } + } + + /// The arguments to the librarian to create a static library. + public func archiveArguments() throws -> [String] { + let librarian = buildParameters.toolchain.librarianPath.pathString + let triple = buildParameters.triple + if triple.isWindows(), librarian.hasSuffix("link") || librarian.hasSuffix("link.exe") { + return [librarian, "/LIB", "/OUT:\(binaryPath.pathString)", "@\(linkFileListPath.pathString)"] + } + if triple.isDarwin(), librarian.hasSuffix("libtool") { + return [librarian, "-static", "-o", binaryPath.pathString, "@\(linkFileListPath.pathString)"] + } + return [librarian, "crs", binaryPath.pathString, "@\(linkFileListPath.pathString)"] + } + + /// The arguments to link and create this product. + public func linkArguments() throws -> [String] { + var args = [buildParameters.toolchain.swiftCompilerPath.pathString] + args += buildParameters.sanitizers.linkSwiftFlags() + args += additionalFlags + + // Pass `-g` during a *release* build so the Swift driver emits a dSYM file for the binary. + if buildParameters.configuration == .release { + if buildParameters.triple.isWindows() { + args += ["-Xlinker", "-debug"] + } else { + args += ["-g"] + } + } + + // Only add the build path to the framework search path if there are binary frameworks to link against. + if !libraryBinaryPaths.isEmpty { + args += ["-F", buildParameters.buildPath.pathString] + } + + args += ["-L", buildParameters.buildPath.pathString] + args += ["-o", binaryPath.pathString] + args += ["-module-name", product.name.spm_mangledToC99ExtendedIdentifier()] + args += dylibs.map({ "-l" + $0.product.name }) + + // Add arguments needed for code coverage if it is enabled. + if buildParameters.enableCodeCoverage { + args += ["-profile-coverage-mapping", "-profile-generate"] + } + + let containsSwiftTargets = product.containsSwiftTargets + + switch product.type { + case .library(.automatic): + throw InternalError("automatic library not supported") + case .library(.static): + // No arguments for static libraries. + return [] + case .test: + // Test products are bundle when using objectiveC, executable when using test entry point. + switch buildParameters.testProductStyle { + case .loadableBundle: + args += ["-Xlinker", "-bundle"] + case .entryPointExecutable: + args += ["-emit-executable"] + } + args += deadStripArguments + case .library(.dynamic): + args += ["-emit-library"] + if buildParameters.triple.isDarwin() { + let relativePath = "@rpath/\(buildParameters.binaryRelativePath(for: product).pathString)" + args += ["-Xlinker", "-install_name", "-Xlinker", relativePath] + } + args += deadStripArguments + case .executable, .snippet: + // Link the Swift stdlib statically, if requested. + if buildParameters.shouldLinkStaticSwiftStdlib { + if buildParameters.triple.isDarwin() { + self.observabilityScope.emit(.swiftBackDeployError) + } else if buildParameters.triple.isSupportingStaticStdlib { + args += ["-static-stdlib"] + } + } + args += ["-emit-executable"] + args += deadStripArguments + + // If we're linking an executable whose main module is implemented in Swift, + // we rename the `__main` entry point symbol to `_main` again. + // This is because executable modules implemented in Swift are compiled with + // a main symbol named that way to allow tests to link against it without + // conflicts. If we're using a linker that doesn't support symbol renaming, + // we will instead have generated a source file containing the redirect. + // Support for linking tests against executables is conditional on the tools + // version of the package that defines the executable product. + let executableTarget = try product.executableTarget + if executableTarget.underlyingTarget is SwiftTarget, toolsVersion >= .v5_5, + buildParameters.canRenameEntrypointFunctionName { + if let flags = buildParameters.linkerFlagsForRenamingMainFunction(of: executableTarget) { + args += flags + } + } + case .plugin: + throw InternalError("unexpectedly asked to generate linker arguments for a plugin product") + } + + // Set rpath such that dynamic libraries are looked up + // adjacent to the product. + if buildParameters.triple.isLinux() { + args += ["-Xlinker", "-rpath=$ORIGIN"] + } else if buildParameters.triple.isDarwin() { + let rpath = product.type == .test ? "@loader_path/../../../" : "@loader_path" + args += ["-Xlinker", "-rpath", "-Xlinker", rpath] + } + args += ["@\(linkFileListPath.pathString)"] + + // Embed the swift stdlib library path inside tests and executables on Darwin. + if containsSwiftTargets { + let useStdlibRpath: Bool + switch product.type { + case .library(let type): + useStdlibRpath = type == .dynamic + case .test, .executable, .snippet: + useStdlibRpath = true + case .plugin: + throw InternalError("unexpectedly asked to generate linker arguments for a plugin product") + } + + // When deploying to macOS prior to macOS 12, add an rpath to the + // back-deployed concurrency libraries. + if useStdlibRpath, buildParameters.triple.isDarwin(), + let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS), + macOSSupportedPlatform.version.major < 12 { + let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib + .parentDirectory + .parentDirectory + .appending(component: "swift-5.5") + .appending(component: "macosx") + args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString] + } + } + + // Don't link runtime compatibility patch libraries if there are no + // Swift sources in the target. + if !containsSwiftTargets { + args += ["-runtime-compatibility-version", "none"] + } + + // Add the target triple from the first target in the product. + // + // We can just use the first target of the product because the deployment target + // setting is the package-level right now. We might need to figure out a better + // answer for libraries if/when we support specifying deployment target at the + // target-level. + args += try buildParameters.targetTripleArgs(for: product.targets[0]) + + // Add arguments from declared build settings. + args += self.buildSettingsFlags() + + // Add AST paths to make the product debuggable. This array is only populated when we're + // building for Darwin in debug configuration. + args += swiftASTs.flatMap{ ["-Xlinker", "-add_ast_path", "-Xlinker", $0.pathString] } + + args += buildParameters.toolchain.extraFlags.swiftCompilerFlags + // User arguments (from -Xlinker and -Xswiftc) should follow generated arguments to allow user overrides + args += buildParameters.linkerFlags + args += stripInvalidArguments(buildParameters.swiftCompilerFlags) + + // Add toolchain's libdir at the very end (even after the user -Xlinker arguments). + // + // This will allow linking to libraries shipped in the toolchain. + let toolchainLibDir = try buildParameters.toolchain.toolchainLibDir + if self.fileSystem.isDirectory(toolchainLibDir) { + args += ["-L", toolchainLibDir.pathString] + } + + return args + } + + /// Writes link filelist to the filesystem. + func writeLinkFilelist(_ fs: FileSystem) throws { + let stream = BufferedOutputByteStream() + + for object in objects { + stream <<< object.pathString.spm_shellEscaped() <<< "\n" + } + + try fs.createDirectory(linkFileListPath.parentDirectory, recursive: true) + try fs.writeFileContents(linkFileListPath, bytes: stream.bytes) + } + + /// Returns the build flags from the declared build settings. + private func buildSettingsFlags() -> [String] { + var flags: [String] = [] + + // Linked libraries. + let libraries = OrderedSet(staticTargets.reduce([]) { + $0 + buildParameters.createScope(for: $1).evaluate(.LINK_LIBRARIES) + }) + flags += libraries.map({ "-l" + $0 }) + + // Linked frameworks. + let frameworks = OrderedSet(staticTargets.reduce([]) { + $0 + buildParameters.createScope(for: $1).evaluate(.LINK_FRAMEWORKS) + }) + flags += frameworks.flatMap({ ["-framework", $0] }) + + // Other linker flags. + for target in staticTargets { + let scope = buildParameters.createScope(for: target) + flags += scope.evaluate(.OTHER_LDFLAGS) + } + + return flags + } +} + +/// Description for a plugin target. This is treated a bit differently from the +/// regular kinds of targets, and is not included in the LLBuild description. +/// But because the package graph and build plan are not loaded for incremental +/// builds, this information is included in the BuildDescription, and the plugin +/// targets are compiled directly. +public final class PluginDescription: Codable { + + /// The identity of the package in which the plugin is defined. + public let package: PackageIdentity + + /// The name of the plugin target in that package (this is also the name of + /// the plugin). + public let targetName: String + + /// The names of any plugin products in that package that vend the plugin + /// to other packages. + public let productNames: [String] + + /// The tools version of the package that declared the target. This affects + /// the API that is available in the PackagePlugin module. + public let toolsVersion: ToolsVersion + + /// Swift source files that comprise the plugin. + public let sources: Sources + + /// Initialize a new plugin target description. The target is expected to be + /// a `PluginTarget`. + init( + target: ResolvedTarget, + products: [ResolvedProduct], + package: ResolvedPackage, + toolsVersion: ToolsVersion, + testDiscoveryTarget: Bool = false, + fileSystem: FileSystem + ) throws { + guard target.underlyingTarget is PluginTarget else { + throw InternalError("underlying target type mismatch \(target)") + } + + self.package = package.identity + self.targetName = target.name + self.productNames = products.map{ $0.name } + self.toolsVersion = toolsVersion + self.sources = target.sources + } +} + /// A build plan for a package graph. public class BuildPlan: SPMBuildCore.BuildPlan { @@ -503,6 +2086,17 @@ public class BuildPlan: SPMBuildCore.BuildPlan { fileSystem: fileSystem, observabilityScope: observabilityScope) ) + case is MixedTarget: + guard let package = graph.package(for: target) else { + throw InternalError("package not found for \(target)") + } + targetMap[target] = try .mixed(MixedTargetBuildDescription( + package: package, + target: target, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + fileSystem: fileSystem, + observabilityScope: observabilityScope)) case is PluginTarget: guard let package = graph.package(for: target) else { throw InternalError("package not found for \(target)") @@ -591,6 +2185,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { try self.plan(swiftTarget: target) case .clang(let target): try self.plan(clangTarget: target) + case .mixed(let target): + try self.plan(mixedTarget: target) } } @@ -912,6 +2508,12 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } } + /// Plan a Mixed target. + private func plan(mixedTarget: MixedTargetBuildDescription) throws { + try plan(clangTarget: mixedTarget.clangTargetBuildDescription) + try plan(swiftTarget: mixedTarget.swiftTargetBuildDescription) + } + public func createAPIToolCommonArgs(includeLibrarySearchPaths: Bool) throws -> [String] { let buildPath = buildParameters.buildPath.pathString var arguments = ["-I", buildPath] @@ -941,6 +2543,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { if let includeDir = targetDescription.moduleMap?.parentDirectory { arguments += ["-I", includeDir.pathString] } + case .mixed(let targetDescription): + if let includeDir = targetDescription.clangTargetBuildDescription.moduleMap?.parentDirectory { + arguments += ["-I", includeDir.pathString] + } } } @@ -977,6 +2583,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { if let includeDir = targetDescription.moduleMap?.parentDirectory { arguments += ["-I\(includeDir.pathString)"] } + case .mixed(let targetDescription): + if let includeDir = targetDescription.clangTargetBuildDescription.moduleMap?.parentDirectory { + arguments += ["-I\(includeDir.pathString)"] + } } } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index b3f3d487922..d9c7183a7e1 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -92,6 +92,8 @@ public class LLBuildManifestBuilder { try self.createSwiftCompileCommand(desc) case .clang(let desc): try self.createClangCompileCommand(desc) + case .mixed(let desc): + try self.createMixedCompileCommand(desc) } } } @@ -246,10 +248,12 @@ extension LLBuildManifestBuilder { // MARK: - Compile Swift extension LLBuildManifestBuilder { - /// Create a llbuild target for a Swift target description. + /// Create a llbuild target for a Swift target description and returns the Swift targets outputs. + @discardableResult private func createSwiftCompileCommand( - _ target: SwiftTargetBuildDescription - ) throws { + _ target: SwiftTargetBuildDescription, + addTargetCmd: Bool = true + ) throws -> [Node] { // Inputs. let inputs = try self.computeSwiftCompileCmdInputs(target) @@ -269,8 +273,16 @@ extension LLBuildManifestBuilder { try self.addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs) } - self.addTargetCmd(target, cmdOutputs: cmdOutputs) + if addTargetCmd { + self.addTargetCmd( + target: target.target, + isTestTarget: target.isTestTarget, + inputs: cmdOutputs + ) + } try self.addModuleWrapCmd(target) + + return cmdOutputs } private func addSwiftCmdsViaIntegratedDriver( @@ -450,6 +462,8 @@ extension LLBuildManifestBuilder { ) case .clang(let desc): try self.createClangCompileCommand(desc) + case .mixed(let desc): + try self.createMixedCompileCommand(desc) } } } @@ -475,7 +489,11 @@ extension LLBuildManifestBuilder { explicitDependencyJobTracker: explicitDependencyJobTracker ) - self.addTargetCmd(description, cmdOutputs: cmdOutputs) + self.addTargetCmd( + target: description.target, + isTestTarget: description.isTestTarget, + inputs: cmdOutputs + ) try self.addModuleWrapCmd(description) } @@ -646,6 +664,13 @@ extension LLBuildManifestBuilder { for object in try target.objects { inputs.append(file: object) } + case .mixed(let target)?: + inputs.append(file: target.swiftTargetBuildDescription.moduleOutputPath) + + for object in target.clangTargetBuildDescription.objects { + inputs.append(file: object) + } + case nil: throw InternalError("unexpected: target \(target) not in target map \(self.plan.targetMap)") } @@ -697,20 +722,24 @@ extension LLBuildManifestBuilder { } /// Adds a top-level phony command that builds the entire target. - private func addTargetCmd(_ target: SwiftTargetBuildDescription, cmdOutputs: [Node]) { + private func addTargetCmd( + target: ResolvedTarget, + isTestTarget: Bool, + inputs: [Node] + ) { // Create a phony node to represent the entire target. - let targetName = target.target.getLLBuildTargetName(config: self.buildConfig) + let targetName = target.getLLBuildTargetName(config: buildConfig) let targetOutput: Node = .virtual(targetName) self.manifest.addNode(targetOutput, toTarget: targetName) self.manifest.addPhonyCmd( name: targetOutput.name, - inputs: cmdOutputs, + inputs: inputs, outputs: [targetOutput] ) - if self.plan.graph.isInRootPackages(target.target, satisfying: self.buildEnvironment) { - if !target.isTestTarget { - self.addNode(targetOutput, toTarget: .main) + if plan.graph.isInRootPackages(target, satisfying: self.buildEnvironment) { + if !isTestTarget { + addNode(targetOutput, toTarget: .main) } self.addNode(targetOutput, toTarget: .test) } @@ -764,10 +793,12 @@ private class UniqueExplicitDependencyJobTracker { // MARK: - Compile C-family extension LLBuildManifestBuilder { - /// Create a llbuild target for a Clang target description. + /// Create a llbuild target for a Clang target description and returns the Clang target's outputs. + @discardableResult private func createClangCompileCommand( - _ target: ClangTargetBuildDescription - ) throws { + _ target: ClangTargetBuildDescription, + addTargetCmd: Bool = true + ) throws -> [Node] { let standards = [ (target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), (target.clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions), @@ -860,25 +891,28 @@ extension LLBuildManifestBuilder { ) } - try addBuildToolPlugins(.clang(target)) + if addTargetCmd { + self.addTargetCmd( + target: target.target, + isTestTarget: target.isTestTarget, + inputs: objectFileNodes + ) + } - // Create a phony node to represent the entire target. - let targetName = target.target.getLLBuildTargetName(config: self.buildConfig) - let output: Node = .virtual(targetName) + return objectFileNodes + } +} - self.manifest.addNode(output, toTarget: targetName) - self.manifest.addPhonyCmd( - name: output.name, - inputs: objectFileNodes, - outputs: [output] - ) +// MARK: - Compile Mixed Languages - if self.plan.graph.isInRootPackages(target.target, satisfying: self.buildEnvironment) { - if !target.isTestTarget { - self.addNode(output, toTarget: .main) - } - self.addNode(output, toTarget: .test) - } +extension LLBuildManifestBuilder { + /// Create a llbuild target for a mixed target description. + private func createMixedCompileCommand( + _ target: MixedTargetBuildDescription + ) throws { + let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false) + let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false) + self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: clangOutputs + swiftOutputs) } } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 4b15d1c3883..17885526349 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -174,21 +174,23 @@ public struct ModuleMapGenerator { } /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any diagnostics are added to the receiver's diagnostics engine. - public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath) throws { - var moduleMap = "module \(moduleName) {\n" + public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath, swiftHeaderPath: AbsolutePath? = nil) throws { + let stream = BufferedOutputByteStream() + stream <<< "module \(moduleName) {\n" switch type { case .umbrellaHeader(let hdr): moduleMap.append(" umbrella header \"\(hdr.moduleEscapedPathString)\"\n") case .umbrellaDirectory(let dir): moduleMap.append(" umbrella \"\(dir.moduleEscapedPathString)\"\n") } - moduleMap.append( - """ - export * - } - - """ - ) + stream <<< " export *\n" + stream <<< "}\n" + if let swiftHeaderPath = swiftHeaderPath { + stream <<< "module \(moduleName).Swift {\n" + stream <<< " header \"" <<< swiftHeaderPath.pathString <<< "\"\n" + stream <<< " requires objc\n" + stream <<< "}\n" + } // FIXME: This doesn't belong here. try fileSystem.createDirectory(path.parentDirectory, recursive: true) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 46cada4972b..bce3636f9b4 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -955,7 +955,39 @@ public final class PackageBuilder { } // Create and return the right kind of target depending on what kind of sources we found. - if sources.hasSwiftSources { + if sources.hasSwiftSources && sources.hasClangSources { + // First determine the type of module map that will be appropriate for the target based on its header layout. + let moduleMapType: ModuleMapType + + if fileSystem.exists(publicHeadersPath) { + let moduleMapGenerator = ModuleMapGenerator(targetName: potentialModule.name, moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), publicHeadersDir: publicHeadersPath, fileSystem: fileSystem) + moduleMapType = moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) + } else if targetType == .library, manifest.toolsVersion >= .v5_5 { + // If this clang target is a library, it must contain "include" directory. + throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) + } else { + moduleMapType = .none + } + + return try MixedTarget( + name: potentialModule.name, + potentialBundleName: potentialBundleName, + cLanguageStandard: manifest.cLanguageStandard, + cxxLanguageStandard: manifest.cxxLanguageStandard, + includeDir: publicHeadersPath, + moduleMapType: moduleMapType, + headers: headers, + type: targetType, + path: potentialModule.path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + swiftVersion: try swiftVersion(), + buildSettings: buildSettings) + + } else if sources.hasSwiftSources { return SwiftTarget( name: potentialModule.name, potentialBundleName: potentialBundleName, diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index 8c27651ef59..1f8d763c254 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -179,11 +179,6 @@ public struct TargetSourcesBuilder { try diagnoseInfoPlistConflicts(in: resources) diagnoseInvalidResource(in: target.resources) - // It's an error to contain mixed language source files. - if sources.containsMixedLanguage { - throw Target.Error.mixedSources(targetPath) - } - return (sources, resources, headers, ignored, others) } diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index ef6201e62b9..2bb6308dc77 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -19,6 +19,7 @@ public class Target: PolymorphicCodableProtocol { public static var implementations: [PolymorphicCodableProtocol.Type] = [ SwiftTarget.self, ClangTarget.self, + MixedTarget.self, SystemLibraryTarget.self, BinaryTarget.self, PluginTarget.self, @@ -606,6 +607,104 @@ public final class ClangTarget: Target { } } +public final class MixedTarget: Target { + + // The Clang target for the mixed target's Clang sources. + public let clangTarget: ClangTarget + + // The Swift target for the mixed target's Swift sources. + public let swiftTarget: SwiftTarget + + public init( + name: String, + potentialBundleName: String? = nil, + cLanguageStandard: String?, + cxxLanguageStandard: String?, + includeDir: AbsolutePath, + moduleMapType: ModuleMapType, + headers: [AbsolutePath] = [], + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency] = [], + swiftVersion: SwiftLanguageVersion, + buildSettings: BuildSettings.AssignmentTable = .init(), + pluginUsages: [PluginUsage] = [] + ) throws { + + let swiftSources = Sources( + paths: sources.paths.filter { $0.extension == "swift" }, + root: sources.root + ) + + self.swiftTarget = SwiftTarget( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: swiftSources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + swiftVersion: swiftVersion, + buildSettings: buildSettings, + pluginUsages: pluginUsages + ) + + let clangSources = Sources( + paths: sources.paths.filter { $0.extension != "swift" }, + root: sources.root + ) + + self.clangTarget = try ClangTarget( + name: name, + cLanguageStandard: cLanguageStandard, + cxxLanguageStandard: cxxLanguageStandard, + includeDir: includeDir, + moduleMapType: moduleMapType, + type: type, + path: path, + sources: clangSources + ) + + super.init( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + buildSettings: buildSettings, + pluginUsages: pluginUsages + ) + } + + private enum CodingKeys: String, CodingKey { + case clangTarget, swiftTarget + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(clangTarget, forKey: .clangTarget) + try container.encode(swiftTarget, forKey: .swiftTarget) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.clangTarget = try container.decode(ClangTarget.self, forKey: .clangTarget) + self.swiftTarget = try container.decode(SwiftTarget.self, forKey: .swiftTarget) + try super.init(from: decoder) + } +} + public final class BinaryTarget: Target { /// The kind of binary artifact. public let kind: Kind From bda2a8af82f20780ed99c09c9d18f4e62b4f926e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 7 Nov 2022 14:05:12 -0500 Subject: [PATCH 002/178] Leaf mixed packages build --- Sources/Build/BuildPlan.swift | 15 ++++++++- Sources/Build/LLBuildManifestBuilder.swift | 36 +++++++++++++++++++--- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 673b27bd323..7e2ed5c05c6 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -2502,6 +2502,19 @@ public class BuildPlan: SPMBuildCore.BuildPlan { swiftTarget.libraryBinaryPaths.insert(library.libraryPath) } } + case let underlyingTarget as MixedTarget where underlyingTarget.type == .library: + guard case let .mixed(target)? = targetMap[dependency] else { + throw InternalError("unexpected mixed target \(underlyingTarget)") + } + // Add the path to modulemap of the dependency. Currently we require that all Clang targets have a + // modulemap but we may want to remove that requirement since it is valid for a target to exist without + // one. However, in that case it will not be importable in Swift targets. We may want to emit a warning + // in that case from here. + guard let moduleMap = target.clangTargetBuildDescription.moduleMap else { break } + swiftTarget.additionalFlags += [ + "-Xcc", "-fmodule-map-file=\(moduleMap.pathString)", + "-Xcc", "-I", "-Xcc", target.clangTargetBuildDescription.clangTarget.includeDir.pathString + ] default: break } @@ -2510,8 +2523,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Plan a Mixed target. private func plan(mixedTarget: MixedTargetBuildDescription) throws { - try plan(clangTarget: mixedTarget.clangTargetBuildDescription) try plan(swiftTarget: mixedTarget.swiftTargetBuildDescription) + try plan(clangTarget: mixedTarget.clangTargetBuildDescription) } public func createAPIToolCommonArgs(includeLibrarySearchPaths: Bool) throws -> [String] { diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index d9c7183a7e1..855f32c4701 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -252,7 +252,8 @@ extension LLBuildManifestBuilder { @discardableResult private func createSwiftCompileCommand( _ target: SwiftTargetBuildDescription, - addTargetCmd: Bool = true + addTargetCmd: Bool = true, + mixedTarget: Bool = false ) throws -> [Node] { // Inputs. let inputs = try self.computeSwiftCompileCmdInputs(target) @@ -270,7 +271,7 @@ extension LLBuildManifestBuilder { moduleNode: moduleNode ) } else { - try self.addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs) + try self.addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs, mixedTarget: mixedTarget) } if addTargetCmd { @@ -595,7 +596,8 @@ extension LLBuildManifestBuilder { private func addCmdWithBuiltinSwiftTool( _ target: SwiftTargetBuildDescription, inputs: [Node], - cmdOutputs: [Node] + cmdOutputs: [Node], + mixedTarget: Bool = false ) throws { let isLibrary = target.target.type == .library || target.target.type == .test let cmdName = target.target.getCommandName(config: self.buildConfig) @@ -604,7 +606,7 @@ extension LLBuildManifestBuilder { self.manifest.addSwiftCmd( name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], - outputs: cmdOutputs, + outputs: mixedTarget ? cmdOutputs.dropLast() : cmdOutputs, executable: target.buildParameters.toolchain.swiftCompilerPath, moduleName: target.target.c99name, moduleAliases: target.target.moduleAliases, @@ -619,6 +621,30 @@ extension LLBuildManifestBuilder { wholeModuleOptimization: target.buildParameters.configuration == .release, outputFileMapPath: try target.writeOutputFileMap() // FIXME: Eliminate side effect. ) + + if mixedTarget { + // Add a successive command to built the underlying Objective-C + // module. I'm breaking it into a successive compile command because + //when I mix the `-emit-object-header` flag with the + // `-import-underlying-module`, the $(ModuleName)-Swift.h does not + // get created. + manifest.addSwiftCmd( + name: "\(cmdName)-2", + inputs: inputs, + outputs: [cmdOutputs.last!], + executable: buildParameters.toolchain.swiftCompilerPath, + moduleName: target.target.c99name, + moduleAliases: target.target.moduleAliases, + moduleOutputPath: target.moduleOutputPath, + importPath: buildParameters.buildPath, + tempsPath: target.tempsPath, + objects: target.objects, + otherArguments: try target.compileArguments() + ["-import-underlying-module"], + sources: target.sources, + isLibrary: isLibrary, + wholeModuleOptimization: buildParameters.configuration == .release + ) + } } private func computeSwiftCompileCmdInputs( @@ -911,7 +937,7 @@ extension LLBuildManifestBuilder { _ target: MixedTargetBuildDescription ) throws { let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false) - let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false) + let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false, mixedTarget: true) self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: clangOutputs + swiftOutputs) } } From e8deac0ac3d1188fbe4bdd9e7070ac16e5c7ba3b Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 9 Nov 2022 13:28:53 -0500 Subject: [PATCH 003/178] Various fixes - Fix importing mixed package into ObjC project - Fix importing API from the Swift half of a mixed package into its Objc half - Fix needing two separate Swift compile commands to build the mixed module --- Sources/Build/BuildPlan.swift | 7 +++ Sources/Build/LLBuildManifestBuilder.swift | 55 +++++++++++----------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 7e2ed5c05c6..e27baf4464d 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -2452,6 +2452,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan { clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] } } + case let target as MixedTarget where target.type == .library: + // Add the modulemap of the dependency if it has one. + if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { + if let moduleMap = dependencyTargetDescription.clangTargetBuildDescription.moduleMap { + clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] + } + } case let target as SystemLibraryTarget: clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"] clangTarget.additionalFlags += try pkgConfig(for: target).cFlags diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 855f32c4701..b1142dc4653 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -603,6 +603,22 @@ extension LLBuildManifestBuilder { let cmdName = target.target.getCommandName(config: self.buildConfig) self.manifest.addWriteSourcesFileListCommand(sources: target.sources, sourcesFileListPath: target.sourcesFileListPath) + + var otherArguments = try target.compileArguments() + if mixedTarget { + otherArguments += [ + "-import-underlying-module", + "-Xcc", + "-ivfsoverlay", + "-Xcc", + "\(target.tempsPath)/all-product-headers.yaml", + "-Xcc", + "-ivfsoverlay", + "-Xcc", + "\(target.tempsPath)/unextended-module-overlay.yaml" + ] + } + self.manifest.addSwiftCmd( name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], @@ -613,38 +629,14 @@ extension LLBuildManifestBuilder { moduleOutputPath: target.moduleOutputPath, importPath: target.buildParameters.buildPath, tempsPath: target.tempsPath, - objects: try target.objects, - otherArguments: try target.compileArguments(), + objects: target.objects, + otherArguments: otherArguments, sources: target.sources, fileList: target.sourcesFileListPath, isLibrary: isLibrary, wholeModuleOptimization: target.buildParameters.configuration == .release, outputFileMapPath: try target.writeOutputFileMap() // FIXME: Eliminate side effect. ) - - if mixedTarget { - // Add a successive command to built the underlying Objective-C - // module. I'm breaking it into a successive compile command because - //when I mix the `-emit-object-header` flag with the - // `-import-underlying-module`, the $(ModuleName)-Swift.h does not - // get created. - manifest.addSwiftCmd( - name: "\(cmdName)-2", - inputs: inputs, - outputs: [cmdOutputs.last!], - executable: buildParameters.toolchain.swiftCompilerPath, - moduleName: target.target.c99name, - moduleAliases: target.target.moduleAliases, - moduleOutputPath: target.moduleOutputPath, - importPath: buildParameters.buildPath, - tempsPath: target.tempsPath, - objects: target.objects, - otherArguments: try target.compileArguments() + ["-import-underlying-module"], - sources: target.sources, - isLibrary: isLibrary, - wholeModuleOptimization: buildParameters.configuration == .release - ) - } } private func computeSwiftCompileCmdInputs( @@ -823,7 +815,8 @@ extension LLBuildManifestBuilder { @discardableResult private func createClangCompileCommand( _ target: ClangTargetBuildDescription, - addTargetCmd: Bool = true + addTargetCmd: Bool = true, + mixedTarget: Bool = false ) throws -> [Node] { let standards = [ (target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), @@ -843,6 +836,8 @@ extension LLBuildManifestBuilder { func addStaticTargetInputs(_ target: ResolvedTarget) { if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { inputs.append(file: desc.moduleOutputPath) + } else if case .mixed(let desc)? = plan.targetMap[target], target.type == .library { + inputs.append(file: desc.swiftTargetBuildDescription.moduleOutputPath) } } @@ -888,6 +883,10 @@ extension LLBuildManifestBuilder { var args = try target.basicArguments(isCXX: isCXX, isC: isC) + if mixedTarget { + args += ["-I\(target.tempsPath)"] + } + args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString] // Add language standard flag if needed. @@ -936,7 +935,7 @@ extension LLBuildManifestBuilder { private func createMixedCompileCommand( _ target: MixedTargetBuildDescription ) throws { - let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false) + let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false, mixedTarget: true) let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false, mixedTarget: true) self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: clangOutputs + swiftOutputs) } From 455f4bcf830b948f016d880351393fa087311716 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 9 Nov 2022 15:22:18 -0500 Subject: [PATCH 004/178] Resolve issues post rebase --- Sources/Build/BuildPlan.swift | 7 ++----- Sources/Build/LLBuildManifestBuilder.swift | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index e27baf4464d..e4636ad6b1f 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -25,8 +25,6 @@ import func TSCBasic.topologicalSort import enum TSCUtility.Diagnostics import var TSCUtility.verbosity -import PackageCollections -import CloudKit extension String { var asSwiftStringLiteralConstant: String { @@ -182,7 +180,7 @@ public enum TargetBuildDescription { case .clang(let target): return try target.objects case .mixed(let target): - return target.swiftTargetBuildDescription.objects + target.clangTargetBuildDescription.objects + return try target.swiftTargetBuildDescription.objects + target.clangTargetBuildDescription.objects } } } @@ -775,6 +773,7 @@ public final class SwiftTargetBuildDescription { buildParameters: BuildParameters, buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], + isTestTarget: Bool? = nil, testTargetRole: TestTargetRole? = nil, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1323,7 +1322,6 @@ public final class MixedTargetBuildDescription { buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], isTestTarget: Bool? = nil, - isTestDiscoveryTarget: Bool = false, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { @@ -1362,7 +1360,6 @@ public final class MixedTargetBuildDescription { buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, isTestTarget: isTestTarget, - isTestDiscoveryTarget: isTestDiscoveryTarget, fileSystem: fileSystem, observabilityScope: observabilityScope, moduleMapPath: self.clangTargetBuildDescription.moduleMap diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index b1142dc4653..2768bbe52c0 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -629,7 +629,7 @@ extension LLBuildManifestBuilder { moduleOutputPath: target.moduleOutputPath, importPath: target.buildParameters.buildPath, tempsPath: target.tempsPath, - objects: target.objects, + objects: try target.objects, otherArguments: otherArguments, sources: target.sources, fileList: target.sourcesFileListPath, @@ -685,7 +685,7 @@ extension LLBuildManifestBuilder { case .mixed(let target)?: inputs.append(file: target.swiftTargetBuildDescription.moduleOutputPath) - for object in target.clangTargetBuildDescription.objects { + for object in try target.clangTargetBuildDescription.objects { inputs.append(file: object) } From 6e961d176529143f466ee5e08fd58c5ce01685dd Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 9 Nov 2022 22:23:16 -0500 Subject: [PATCH 005/178] Generate unextended-module-overlay.yaml --- Sources/Basics/FileSystem/VFSOverlay.swift | 50 +++++++++++++++++---- Sources/Build/BuildPlan.swift | 26 +++++++++++ Sources/PackageLoading/ManifestLoader.swift | 3 ++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index bbe902ec631..28f3d95f522 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -13,31 +13,65 @@ import class Foundation.JSONEncoder public struct VFSOverlay: Encodable { - public struct File: Encodable { - enum CodingKeys: String, CodingKey { + + public class Resource: Encodable { + private let name: String + private let type: String + + fileprivate init(name: String, type: String) { + self.name = name + self.type = type + } + } + + public class File: Resource { + private enum CodingKeys: String, CodingKey { case externalContents = "external-contents" - case name - case type } private let externalContents: String - private let name: String - private let type = "file" public init(name: String, externalContents: String) { - self.name = name self.externalContents = externalContents + super.init(name: name, type: "file") + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(externalContents, forKey: .externalContents) + try super.encode(to: encoder) + } + } + + public class Directory: Resource { + private enum CodingKeys: CodingKey { + case contents + } + + private let contents: [File] + + public init(name: String, contents: [File]) { + self.contents = contents + super.init(name: name, type: "directory") + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(contents, forKey: .contents) + try super.encode(to: encoder) } } enum CodingKeys: String, CodingKey { case roots case useExternalNames = "use-external-names" + case caseSensitive = "case-sensitive" case version } - private let roots: [File] + private let roots: [Resource] private let useExternalNames = false + private let caseSensitive = false private let version = 0 public init(roots: [File]) { diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index e4636ad6b1f..9cf7d85bce1 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -327,10 +327,14 @@ public final class ClangTargetBuildDescription { // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. + // TODO(ncooke3): Will non-library mixed language targets be supported? + // If so, they may need a module map if the Objc implementation uses Swift. if target.type == .library { // If there's a custom module map, use it as given. if case .custom(let path) = clangTarget.moduleMapType { self.moduleMap = path + // TODO(ncooke3): We need to ensure that custom modulemaps + // expose the generated Swift header. } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { @@ -1364,6 +1368,28 @@ public final class MixedTargetBuildDescription { observabilityScope: observabilityScope, moduleMapPath: self.clangTargetBuildDescription.moduleMap ) + + // We need to generate some auxiliary files for the Clang and Swift + // sources to later be compiled successfully. + + // unextended-module-overlay.yaml + let buildArtifactDirectory = self.swiftTargetBuildDescription.tempsPath + let unextendedModuleOverlayPath = buildArtifactDirectory + .appending(component: "unextended-module-overlay.yaml") + + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: buildArtifactDirectory.pathString, + contents: [ + VFSOverlay.File( + name: "module.modulemap", + externalContents: + buildArtifactDirectory.appending(component: "module.modulemap").pathString + ) + ] + ) + ]).write(to: unextendedModuleOverlayPath, fileSystem: fileSystem) + } } diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index 623684a40e0..a69210eff16 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -425,6 +425,9 @@ public final class ManifestLoader: ManifestLoaderProtocol { } do { + // Ran into an issue where my cache became invalidated. Adding this + // for development. + try? cache?.remove(key: key.sha256Checksum) // try to get it from the cache if let result = try cache?.get(key: key.sha256Checksum), let manifestJSON = result.manifestJSON, !manifestJSON.isEmpty { observabilityScope.emit(debug: "loading manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown") from cache") From 57f3c5222f906e180efbc927d12c0dd87f99d693 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 10 Nov 2022 13:59:53 -0500 Subject: [PATCH 006/178] Generate unextended-module.modulemap and all-product-headers.yaml --- Sources/Basics/FileSystem/VFSOverlay.swift | 4 ++ Sources/Build/BuildPlan.swift | 64 +++++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index 28f3d95f522..d4b94c74e1a 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -78,6 +78,10 @@ public struct VFSOverlay: Encodable { self.roots = roots } + public init(roots: [Directory]) { + self.roots = roots + } + public func write(to path: AbsolutePath, fileSystem: FileSystem) throws { // VFS overlay files are YAML, but ours is simple enough that it works when being written using `JSONEncoder`. try JSONEncoder.makeWithDefaults(prettified: false).encode(path: path, fileSystem: fileSystem, self) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 9cf7d85bce1..5a1fa7f83fb 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1372,24 +1372,82 @@ public final class MixedTargetBuildDescription { // We need to generate some auxiliary files for the Clang and Swift // sources to later be compiled successfully. - // unextended-module-overlay.yaml let buildArtifactDirectory = self.swiftTargetBuildDescription.tempsPath + + // unextended-module-overlay.yaml let unextendedModuleOverlayPath = buildArtifactDirectory .appending(component: "unextended-module-overlay.yaml") try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, + name: buildArtifactDirectory.nativePathString(escaped: false), contents: [ VFSOverlay.File( name: "module.modulemap", externalContents: - buildArtifactDirectory.appending(component: "module.modulemap").pathString + buildArtifactDirectory.appending(component: "unextended-module.modulemap").nativePathString(escaped: false) ) ] ) ]).write(to: unextendedModuleOverlayPath, fileSystem: fileSystem) + // all-product-headers.yaml + let allProductHeadersPath = buildArtifactDirectory + .appending(component: "all-product-headers.yaml") + + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: clangTargetBuildDescription.clangTarget.includeDir.pathString, + contents: + // TODO(ncooke3): Why is clangTargetBuildDescription.clangTarget.headers empty? + try Set(fileSystem.getDirectoryContents(clangTargetBuildDescription.clangTarget.includeDir) + .map(clangTargetBuildDescription.clangTarget.includeDir.appending(component:))) + .filter { headerPath in + headerPath.pathString.hasPrefix(clangTargetBuildDescription.clangTarget.includeDir.pathString) + }.map { headerPath in + VFSOverlay.File( + name: headerPath.basename, + externalContents: headerPath.nativePathString(escaped: false) + ) + } + ), + VFSOverlay.Directory( + name: buildArtifactDirectory.nativePathString(escaped: false), + contents: [ + VFSOverlay.File( + name: "module.modulemap", + externalContents: + buildArtifactDirectory.appending(component: "module.modulemap").nativePathString(escaped: false) + ) + ] + ), + VFSOverlay.Directory( + name: buildArtifactDirectory.nativePathString(escaped: false), + contents: [ + VFSOverlay.File( + name: "\(target.c99name)-Swift.h", + externalContents: buildArtifactDirectory.appending(component: "\(target.c99name)-Swift.h").nativePathString(escaped: false) + ) + ] + ), + ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + + /// unextended-module.modulemap + // TODO(ncooke3): The umbrella header is hardcoded. The below block needs + // to probabaly move into the module map generation phase. + let stream = BufferedOutputByteStream() + stream <<< """ + module \(target.c99name) { + umbrella header "\(clangTargetBuildDescription.clangTarget.includeDir)/MixedPackage.h" + + export * + } + + module \(target.c99name).__Swift { + exclude header "\(buildArtifactDirectory)/\(target.c99name)-Swift.h" + } + """ + try fileSystem.writeFileContents(buildArtifactDirectory.appending(component: "unextended-module.modulemap"), bytes: stream.bytes) } } From 67a05da9c8785d65cbca53ba2cbc5459c22d10de Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 10 Nov 2022 17:40:03 -0500 Subject: [PATCH 007/178] Fix build when Objc target depends on mixed target --- Sources/Build/LLBuildManifestBuilder.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 2768bbe52c0..4f17d60316b 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -261,7 +261,11 @@ extension LLBuildManifestBuilder { // Outputs. let objectNodes = try target.objects.map(Node.file) let moduleNode = Node.file(target.moduleOutputPath) - let cmdOutputs = objectNodes + [moduleNode] + var cmdOutputs = objectNodes + [moduleNode] + + if mixedTarget { + cmdOutputs += [Node.file(target.objCompatibilityHeaderPath)] + } if self.buildParameters.useIntegratedSwiftDriver { try self.addSwiftCmdsViaIntegratedDriver( @@ -833,6 +837,15 @@ extension LLBuildManifestBuilder { inputs.append(resourcesNode) } + // If it's a mixed target, add the Objective-C compatibility header that + // the Swift half of the mixed target generates. This header acts as an + // input to the Clang compile command, which therefore forces the + // Swift half of the mixed target to be built first. + if mixedTarget { + // TODO(ncooke3): Maybe the header should be passed into this API? + inputs.append(Node.file(target.tempsPath.appending(component: "\(target.target.name)-Swift.h"))) + } + func addStaticTargetInputs(_ target: ResolvedTarget) { if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { inputs.append(file: desc.moduleOutputPath) @@ -935,9 +948,9 @@ extension LLBuildManifestBuilder { private func createMixedCompileCommand( _ target: MixedTargetBuildDescription ) throws { - let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false, mixedTarget: true) let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false, mixedTarget: true) - self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: clangOutputs + swiftOutputs) + let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false, mixedTarget: true) + self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: swiftOutputs + clangOutputs) } } From 680cf52cabbb6f2e0c27fd695c009ef6ce58903a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 11 Nov 2022 19:10:34 -0500 Subject: [PATCH 008/178] Add unit test to test mixed sources - Found one potential? bug where I needed to pass headers into the MixedTarget initializer --- Sources/PackageModel/Target.swift | 1 + .../PackageBuilderTests.swift | 34 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 2bb6308dc77..1a88d486730 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -666,6 +666,7 @@ public final class MixedTarget: Target { cxxLanguageStandard: cxxLanguageStandard, includeDir: includeDir, moduleMapType: moduleMapType, + headers: headers, type: type, path: path, sources: clangSources diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index c615f9f28ad..9c37e7ea57e 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -45,8 +45,11 @@ class PackageBuilderTests: XCTestCase { let foo: AbsolutePath = "/Sources/foo" let fs = InMemoryFileSystem(emptyFiles: - foo.appending(components: "main.swift").pathString, - foo.appending(components: "main.c").pathString + foo.appending(components: "Foo.swift").pathString, + foo.appending(components: "include", "Bar.h").pathString, + foo.appending(components: "Bar.m").pathString, + foo.appending(components: "include", "baz.h").pathString, + foo.appending(components: "baz.c").pathString ) let manifest = Manifest.createRootManifest( @@ -56,8 +59,13 @@ class PackageBuilderTests: XCTestCase { try TargetDescription(name: "foo"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in - diagnostics.check(diagnostic: "target at '\(foo)' contains mixed language source files; feature not supported", severity: .error) + PackageBuilderTester(manifest, in: fs) { package, _ in + package.checkModule("foo") { module in + module.check(c99name: "foo", type: .library) + module.checkSources(root: foo.pathString, paths: "Foo.swift", "Bar.m", "baz.c") + module.check(includeDir: foo.appending(component: "include").pathString) + module.check(moduleMapType: .umbrellaDirectory(foo.appending(component: "include"))) + } } } @@ -3135,17 +3143,23 @@ final class PackageBuilderTester { } func check(includeDir: String, file: StaticString = #file, line: UInt = #line) { - guard case let target as ClangTarget = target else { - return XCTFail("Include directory is being checked on a non clang target", file: file, line: line) + if case let target as ClangTarget = target { + XCTAssertEqual(target.includeDir.pathString, includeDir, file: file, line: line) + } else if case let target as MixedTarget = target { + XCTAssertEqual(target.clangTarget.includeDir.pathString, includeDir, file: file, line: line) + } else { + return XCTFail("Include directory is being checked on a non-clang or mixed target", file: file, line: line) } - XCTAssertEqual(target.includeDir.pathString, includeDir, file: file, line: line) } func check(moduleMapType: ModuleMapType, file: StaticString = #file, line: UInt = #line) { - guard case let target as ClangTarget = target else { - return XCTFail("Module map type is being checked on a non-Clang target", file: file, line: line) + if case let target as ClangTarget = target { + XCTAssertEqual(target.moduleMapType, moduleMapType, file: file, line: line) + } else if case let target as MixedTarget = target { + XCTAssertEqual(target.clangTarget.moduleMapType, moduleMapType, file: file, line: line) + } else { + return XCTFail("Module map type is being checked on a non-clang or mixed target", file: file, line: line) } - XCTAssertEqual(target.moduleMapType, moduleMapType, file: file, line: line) } func check(c99name: String? = nil, type: PackageModel.Target.Kind? = nil, file: StaticString = #file, line: UInt = #line) { From ccaf69f09bc9fb7b4569eb8f6b7eeadecea216ca Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 11 Nov 2022 19:16:04 -0500 Subject: [PATCH 009/178] Remove newline --- Sources/Build/BuildPlan.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 5a1fa7f83fb..6e3d6c8a024 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -325,7 +325,6 @@ public final class ClangTargetBuildDescription { self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) - // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. // TODO(ncooke3): Will non-library mixed language targets be supported? // If so, they may need a module map if the Objc implementation uses Swift. From 2c73c236b4f1d932e73c2e6b5f418cb1f3dbfb4c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Nov 2022 11:04:32 -0500 Subject: [PATCH 010/178] Consolidate 'finding module map type' logic --- Sources/PackageLoading/PackageBuilder.swift | 53 +++++++++------------ Sources/PackageModel/Sources.swift | 2 +- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index bce3636f9b4..973a656db50 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -955,19 +955,10 @@ public final class PackageBuilder { } // Create and return the right kind of target depending on what kind of sources we found. + // TODO(ncooke3): Figure out if we should restrict Swift + Cxx. if sources.hasSwiftSources && sources.hasClangSources { - // First determine the type of module map that will be appropriate for the target based on its header layout. - let moduleMapType: ModuleMapType - - if fileSystem.exists(publicHeadersPath) { - let moduleMapGenerator = ModuleMapGenerator(targetName: potentialModule.name, moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), publicHeadersDir: publicHeadersPath, fileSystem: fileSystem) - moduleMapType = moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) - } else if targetType == .library, manifest.toolsVersion >= .v5_5 { - // If this clang target is a library, it must contain "include" directory. - throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) - } else { - moduleMapType = .none - } + + let moduleMapType = findModuleMapType(for: potentialModule, publicHeadersPath: publicHeadersPath) return try MixedTarget( name: potentialModule.name, @@ -1004,25 +995,9 @@ public final class PackageBuilder { usesUnsafeFlags: manifestTarget.usesUnsafeFlags ) } else { - // It's not a Swift target, so it's a Clang target (those are the only two types of source target currently supported). - - // First determine the type of module map that will be appropriate for the target based on its header layout. - let moduleMapType: ModuleMapType + // It's not a Mixed or Swift target, so it's a Clang target. - if self.fileSystem.exists(publicHeadersPath) { - let moduleMapGenerator = ModuleMapGenerator( - targetName: potentialModule.name, - moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), - publicHeadersDir: publicHeadersPath, - fileSystem: self.fileSystem - ) - moduleMapType = moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) - } else if targetType == .library, self.manifest.toolsVersion >= .v5_5 { - // If this clang target is a library, it must contain "include" directory. - throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) - } else { - moduleMapType = .none - } + let moduleMapType = findModuleMapType(for: potentialModule, publicHeadersPath: publicHeadersPath) if resources.contains(where: { $0.rule == .embedInCode }) { throw ModuleError.embedInCodeNotSupported(target: potentialModule.name) @@ -1242,6 +1217,24 @@ public final class PackageBuilder { } } + /// Determines the type of module map that will be appropriate for a potential target based on its header layout. + private func findModuleMapType(for potentialModule: PotentialModule, publicHeadersPath: AbsolutePath) -> ModuleMapType { + if fileSystem.exists(publicHeadersPath) { + let moduleMapGenerator = ModuleMapGenerator( + targetName: potentialModule.name, + moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), + publicHeadersDir: publicHeadersPath, + fileSystem: self.fileSystem + ) + return moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) + } else if targetType == .library, manifest.toolsVersion >= .v5_5 { + // If this clang target is a library, it must contain "include" directory. + throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) + } else { + return .none + } + } + /// Find the test entry point file for the package. private func findTestEntryPoint(in testTargets: [Target]) throws -> AbsolutePath? { if let testEntryPointPath { diff --git a/Sources/PackageModel/Sources.swift b/Sources/PackageModel/Sources.swift index efe55605920..d984e3becc3 100644 --- a/Sources/PackageModel/Sources.swift +++ b/Sources/PackageModel/Sources.swift @@ -41,7 +41,7 @@ public struct Sources: Codable { }) } - /// Returns true if the sources contain C++ files. + /// Returns true if the sources contain Objective-C files. public var containsObjcFiles: Bool { return paths.contains(where: { guard let ext = $0.extension else { From bcceb79de556517076bb54068b9c2551fc86beab Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Nov 2022 11:27:32 -0500 Subject: [PATCH 011/178] Consolidate 'finding module map type' logic --- Sources/PackageLoading/PackageBuilder.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 973a656db50..063b9a5aefc 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -958,7 +958,11 @@ public final class PackageBuilder { // TODO(ncooke3): Figure out if we should restrict Swift + Cxx. if sources.hasSwiftSources && sources.hasClangSources { - let moduleMapType = findModuleMapType(for: potentialModule, publicHeadersPath: publicHeadersPath) + let moduleMapType = try findModuleMapType( + for: potentialModule, + targetType: targetType, + publicHeadersPath: publicHeadersPath + ) return try MixedTarget( name: potentialModule.name, @@ -997,7 +1001,11 @@ public final class PackageBuilder { } else { // It's not a Mixed or Swift target, so it's a Clang target. - let moduleMapType = findModuleMapType(for: potentialModule, publicHeadersPath: publicHeadersPath) + let moduleMapType = try findModuleMapType( + for: potentialModule, + targetType: targetType, + publicHeadersPath: publicHeadersPath + ) if resources.contains(where: { $0.rule == .embedInCode }) { throw ModuleError.embedInCodeNotSupported(target: potentialModule.name) @@ -1218,7 +1226,7 @@ public final class PackageBuilder { } /// Determines the type of module map that will be appropriate for a potential target based on its header layout. - private func findModuleMapType(for potentialModule: PotentialModule, publicHeadersPath: AbsolutePath) -> ModuleMapType { + private func findModuleMapType(for potentialModule: PotentialModule, targetType: Target.Kind, publicHeadersPath: AbsolutePath) throws -> ModuleMapType { if fileSystem.exists(publicHeadersPath) { let moduleMapGenerator = ModuleMapGenerator( targetName: potentialModule.name, From 19bae8a243b8547b7f15517ccae3e856271bf691 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Nov 2022 17:25:32 -0500 Subject: [PATCH 012/178] Refactor unextended module map generation --- Sources/Build/BuildPlan.swift | 52 +++++++++---------- .../PackageLoading/ModuleMapGenerator.swift | 18 +++++-- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 6e3d6c8a024..7f99b3d03cc 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -313,7 +313,7 @@ public final class ClangTargetBuildDescription { } /// Create a new target description with target and build parameters. - init(target: ResolvedTarget, toolsVersion: ToolsVersion, buildParameters: BuildParameters, fileSystem: FileSystem, generateSwiftHeaderInModuleMap: Bool = false) throws { + init(target: ResolvedTarget, toolsVersion: ToolsVersion, buildParameters: BuildParameters, fileSystem: FileSystem, includesInteropHeaderInModuleMap: Bool = false) throws { guard target.underlyingTarget is ClangTarget || target.underlyingTarget is MixedTarget else { throw InternalError("underlying target type mismatch \(target)") } @@ -337,18 +337,35 @@ public final class ClangTargetBuildDescription { } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { - let path = tempsPath.appending(component: moduleMapFilename) - let moduleMapGenerator = ModuleMapGenerator(targetName: clangTarget.name, moduleName: clangTarget.c99name, publicHeadersDir: clangTarget.includeDir, fileSystem: fileSystem) - let swiftHeaderPath = generateSwiftHeaderInModuleMap - ? tempsPath.appending(component: "\(target.name)-Swift.h") : nil + let moduleMapGenerator = ModuleMapGenerator( + targetName: clangTarget.name, + moduleName: clangTarget.c99name, + publicHeadersDir: clangTarget.includeDir, + fileSystem: fileSystem + ) + + let generatedInteropHeaderPath = includesInteropHeaderInModuleMap + ? tempsPath.appending(component: "\(target.c99name)-Swift.h") : nil + + let moduleMapPath = tempsPath.appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, - at: path, - swiftHeaderPath:swiftHeaderPath + at: moduleMapPath, + interopHeaderPath: generatedInteropHeaderPath ) - self.moduleMap = path + + if includesInteropHeaderInModuleMap { + let unextendedModuleMapPath = tempsPath.appending(component: "unextended-module.modulemap") + try moduleMapGenerator.generateModuleMap( + type: generatedModuleMapType, + at: unextendedModuleMapPath + ) + } + + self.moduleMap = moduleMapPath } // Otherwise there is no module map, and we leave `moduleMap` unset. + // TODO(ncooke3): Investigate implications here for mixed targets. } // Do nothing if we're not generating a bundle. @@ -1345,7 +1362,7 @@ public final class MixedTargetBuildDescription { toolsVersion: toolsVersion, buildParameters: buildParameters, fileSystem: fileSystem, - generateSwiftHeaderInModuleMap: true + includesInteropHeaderInModuleMap: true ) let swiftResolvedTarget = ResolvedTarget( @@ -1430,23 +1447,6 @@ public final class MixedTargetBuildDescription { ] ), ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - - /// unextended-module.modulemap - // TODO(ncooke3): The umbrella header is hardcoded. The below block needs - // to probabaly move into the module map generation phase. - let stream = BufferedOutputByteStream() - stream <<< """ - module \(target.c99name) { - umbrella header "\(clangTargetBuildDescription.clangTarget.includeDir)/MixedPackage.h" - - export * - } - - module \(target.c99name).__Swift { - exclude header "\(buildArtifactDirectory)/\(target.c99name)-Swift.h" - } - """ - try fileSystem.writeFileContents(buildArtifactDirectory.appending(component: "unextended-module.modulemap"), bytes: stream.bytes) } } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 17885526349..65a3d15e558 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -50,7 +50,7 @@ extension ClangTarget: ModuleMapProtocol { } } -/// A module map generator for Clang targets. Module map generation consists of two steps: +/// A module map generator for Clang and Mixed language targets. Module map generation consists of two steps: /// 1. Examining a target's public-headers directory to determine the appropriate module map type /// 2. Generating a module map for any target that doesn't have a custom module map file /// @@ -66,6 +66,9 @@ extension ClangTarget: ModuleMapProtocol { /// These rules are documented at https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#creating-c-language-targets. To avoid breaking existing packages, do not change the semantics here without making any change conditional on the tools version of the package that defines the target. /// /// Note that a module map generator doesn't require a target to already have been instantiated; it can operate on information that will later be used to instantiate a target. +/// +/// For Mixed language targets, the module map generator will generate a module map that includes a +/// module declaration for the Mixed target's C-language API and a submodule declaration for the Swift API. public struct ModuleMapGenerator { /// The name of the Clang target (for diagnostics). @@ -173,8 +176,13 @@ public struct ModuleMapGenerator { return .umbrellaDirectory(publicHeadersDir) } - /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any diagnostics are added to the receiver's diagnostics engine. - public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath, swiftHeaderPath: AbsolutePath? = nil) throws { + /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any + /// diagnostics are added to the receiver's diagnostics engine. + /// + /// The `interopHeaderPath` is the path to the generated interop header used to access a + /// module's Swift API in an Objective-C context (`$(ModuleName)-Swift.h`). If non-`nil`, the + /// created module map will include a submodule to access interop header's API. + public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath, interopHeaderPath: AbsolutePath? = nil) throws { let stream = BufferedOutputByteStream() stream <<< "module \(moduleName) {\n" switch type { @@ -185,9 +193,9 @@ public struct ModuleMapGenerator { } stream <<< " export *\n" stream <<< "}\n" - if let swiftHeaderPath = swiftHeaderPath { + if let interopHeaderPath = interopHeaderPath { stream <<< "module \(moduleName).Swift {\n" - stream <<< " header \"" <<< swiftHeaderPath.pathString <<< "\"\n" + stream <<< " header \"" <<< interopHeaderPath.pathString <<< "\"\n" stream <<< " requires objc\n" stream <<< "}\n" } From 37bf34a33ed656260172d967f379c88dc0844c64 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Nov 2022 17:40:33 -0500 Subject: [PATCH 013/178] Add doc note wrt unextended module map --- Sources/Build/BuildPlan.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 7f99b3d03cc..0e2955dac3b 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -355,6 +355,14 @@ public final class ClangTargetBuildDescription { ) if includesInteropHeaderInModuleMap { + // The underlying Clang target is building within a Mixed + // language target and needs an auxiliary module map that + // doesn't include the generated interop header from the + // Swift half of the mixed target. This will later allow the + // Clang half of the module to be built when compiling the + // Swift part without the generated header being considered + // an input (because it won't exist yet and is an output of + // that compilation command). let unextendedModuleMapPath = tempsPath.appending(component: "unextended-module.modulemap") try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, From 8bee64ad6f7d1ba231beded604b80829a3074b71 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Nov 2022 17:48:14 -0500 Subject: [PATCH 014/178] Remove unnecessary path string formatting --- Sources/Build/BuildPlan.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 0e2955dac3b..b6a680a5a19 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1404,12 +1404,12 @@ public final class MixedTargetBuildDescription { try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactDirectory.nativePathString(escaped: false), + name: buildArtifactDirectory.pathString, contents: [ VFSOverlay.File( name: "module.modulemap", externalContents: - buildArtifactDirectory.appending(component: "unextended-module.modulemap").nativePathString(escaped: false) + buildArtifactDirectory.appending(component: "unextended-module.modulemap").pathString ) ] ) @@ -1423,7 +1423,6 @@ public final class MixedTargetBuildDescription { VFSOverlay.Directory( name: clangTargetBuildDescription.clangTarget.includeDir.pathString, contents: - // TODO(ncooke3): Why is clangTargetBuildDescription.clangTarget.headers empty? try Set(fileSystem.getDirectoryContents(clangTargetBuildDescription.clangTarget.includeDir) .map(clangTargetBuildDescription.clangTarget.includeDir.appending(component:))) .filter { headerPath in @@ -1431,26 +1430,26 @@ public final class MixedTargetBuildDescription { }.map { headerPath in VFSOverlay.File( name: headerPath.basename, - externalContents: headerPath.nativePathString(escaped: false) + externalContents: headerPath.pathString ) } ), VFSOverlay.Directory( - name: buildArtifactDirectory.nativePathString(escaped: false), + name: buildArtifactDirectory.pathString, contents: [ VFSOverlay.File( name: "module.modulemap", externalContents: - buildArtifactDirectory.appending(component: "module.modulemap").nativePathString(escaped: false) + buildArtifactDirectory.appending(component: "module.modulemap").pathString ) ] ), VFSOverlay.Directory( - name: buildArtifactDirectory.nativePathString(escaped: false), + name: buildArtifactDirectory.pathString, contents: [ VFSOverlay.File( name: "\(target.c99name)-Swift.h", - externalContents: buildArtifactDirectory.appending(component: "\(target.c99name)-Swift.h").nativePathString(escaped: false) + externalContents: buildArtifactDirectory.appending(component: "\(target.c99name)-Swift.h").pathString ) ] ), From ce699906ae3acf34fe5a93a5aa724397e107eb18 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 14 Nov 2022 14:56:15 -0500 Subject: [PATCH 015/178] Add doc comment wrt -I usage --- Sources/Build/LLBuildManifestBuilder.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 4f17d60316b..cf63c41cd32 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -897,6 +897,12 @@ extension LLBuildManifestBuilder { var args = try target.basicArguments(isCXX: isCXX, isC: isC) if mixedTarget { + // For mixed targets, the Swift half of the target will generate + // an Objective-C compatibility header in the build folder. + // Compiling the Objective-C half of the target may require this + // generated header if the Objective-C half uses any APIs from + // the Swift half. For successful compilation, the directory + // with the generated header is added as a header search path. args += ["-I\(target.tempsPath)"] } From 54a71bd49bc7622e00ac250c81150673d538bf6e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 14 Nov 2022 21:16:17 -0500 Subject: [PATCH 016/178] Refactor approach - Move build logic out of LLBUILD manifest builder to the target build description files --- Sources/Build/BuildPlan.swift | 129 +++++++++++++-------- Sources/Build/LLBuildManifestBuilder.swift | 28 ++--- 2 files changed, 94 insertions(+), 63 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index b6a680a5a19..9468e4618fb 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -307,14 +307,19 @@ public final class ClangTargetBuildDescription { /// The filesystem to operate on. private let fileSystem: FileSystem + /// Whether or not the target belongs to a mixed language target. + /// + /// Mixed language targets consist of an underlying Swift and Clang target. + let isWithinMixedTarget: Bool + /// If this target is a test target. public var isTestTarget: Bool { return target.type == .test } /// Create a new target description with target and build parameters. - init(target: ResolvedTarget, toolsVersion: ToolsVersion, buildParameters: BuildParameters, fileSystem: FileSystem, includesInteropHeaderInModuleMap: Bool = false) throws { - guard target.underlyingTarget is ClangTarget || target.underlyingTarget is MixedTarget else { + init(target: ResolvedTarget, toolsVersion: ToolsVersion, buildParameters: BuildParameters, fileSystem: FileSystem, isWithinMixedTarget: Bool = false) throws { + guard target.underlyingTarget is ClangTarget else { throw InternalError("underlying target type mismatch \(target)") } @@ -324,6 +329,7 @@ public final class ClangTargetBuildDescription { self.buildParameters = buildParameters self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) + self.isWithinMixedTarget = isWithinMixedTarget // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. // TODO(ncooke3): Will non-library mixed language targets be supported? @@ -344,7 +350,7 @@ public final class ClangTargetBuildDescription { fileSystem: fileSystem ) - let generatedInteropHeaderPath = includesInteropHeaderInModuleMap + let generatedInteropHeaderPath = isWithinMixedTarget ? tempsPath.appending(component: "\(target.c99name)-Swift.h") : nil let moduleMapPath = tempsPath.appending(component: moduleMapFilename) @@ -354,7 +360,7 @@ public final class ClangTargetBuildDescription { interopHeaderPath: generatedInteropHeaderPath ) - if includesInteropHeaderInModuleMap { + if isWithinMixedTarget { // The underlying Clang target is building within a Mixed // language target and needs an auxiliary module map that // doesn't include the generated interop header from the @@ -363,17 +369,35 @@ public final class ClangTargetBuildDescription { // Swift part without the generated header being considered // an input (because it won't exist yet and is an output of // that compilation command). - let unextendedModuleMapPath = tempsPath.appending(component: "unextended-module.modulemap") + let unextendedModuleMapFilename = "unextended-module.modulemap" + let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: unextendedModuleMapPath ) + + // The auxilliary module map is passed to the Clang compiler + // via a VFS overlay, represented by the below YAML file. + let unextendedModuleMapOverlayPath = tempsPath + .appending(component: "unextended-module-overlay.yaml") + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: tempsPath.pathString, + contents: [ + VFSOverlay.File( + name: moduleMapFilename, + externalContents: + tempsPath.appending(component: unextendedModuleMapFilename).pathString + ) + ] + ) + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) } self.moduleMap = moduleMapPath } // Otherwise there is no module map, and we leave `moduleMap` unset. - // TODO(ncooke3): Investigate implications here for mixed targets. + // TODO(ncooke3): Mixed targets need a module map. } // Do nothing if we're not generating a bundle. @@ -697,6 +721,11 @@ public final class SwiftTargetBuildDescription { /// Any addition flags to be added. These flags are expected to be computed during build planning. fileprivate var additionalFlags: [String] = [] + /// Whether or not the target belongs to a mixed language target. + /// + /// Mixed language targets consist of an underlying Swift and Clang target. + let isWithinMixedTarget: Bool + /// The swift version for this target. var swiftVersion: SwiftLanguageVersion { return (target.underlyingTarget as! SwiftTarget).swiftVersion @@ -805,9 +834,9 @@ public final class SwiftTargetBuildDescription { testTargetRole: TestTargetRole? = nil, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - moduleMapPath: AbsolutePath? = nil + isWithinMixedTarget: Bool = false ) throws { - guard target.underlyingTarget is SwiftTarget || target.underlyingTarget is MixedTarget else { + guard target.underlyingTarget is SwiftTarget else { throw InternalError("underlying target type mismatch \(target)") } self.package = package @@ -829,6 +858,7 @@ public final class SwiftTargetBuildDescription { self.buildToolPluginInvocationResults = buildToolPluginInvocationResults self.prebuildCommandResults = prebuildCommandResults self.observabilityScope = observabilityScope + self.isWithinMixedTarget = isWithinMixedTarget // Add any derived files that were declared for any commands from plugin invocations. var pluginDerivedFiles = [AbsolutePath]() @@ -853,9 +883,11 @@ public final class SwiftTargetBuildDescription { self.pluginDerivedSources.relativePaths.append(relPath) } - if shouldEmitObjCCompatibilityHeader { - self.moduleMap = moduleMapPath != nil - ? moduleMapPath : try self.generateModuleMap() + // If building for a mixed target, the Objective-C portion of the build + // description will create the module map and include the Swift + // interoptability header. + if shouldEmitObjCCompatibilityHeader && !isWithinMixedTarget { + self.moduleMap = try self.generateModuleMap() } // Do nothing if we're not generating a bundle. @@ -983,6 +1015,20 @@ public final class SwiftTargetBuildDescription { args += ["-emit-objc-header", "-emit-objc-header-path", objCompatibilityHeaderPath.pathString] } + if isWithinMixedTarget { + args += [ + "-import-underlying-module", + "-Xcc", + "-ivfsoverlay", + "-Xcc", + "\(tempsPath)/all-product-headers.yaml", + "-Xcc", + "-ivfsoverlay", + "-Xcc", + "\(tempsPath)/unextended-module-overlay.yaml" + ] + } + // Add arguments needed for code coverage if it is enabled. if buildParameters.enableCodeCoverage { args += ["-profile-coverage-mapping", "-profile-generate"] @@ -1353,12 +1399,13 @@ public final class MixedTargetBuildDescription { fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { - self.target = target + guard let mixedTarget = target.underlyingTarget as? MixedTarget else { + throw InternalError("underlying target type mismatch \(target)") + } + self.target = target self.fileSystem = fileSystem - let mixedTarget = target.underlyingTarget as! MixedTarget - let clangResolvedTarget = ResolvedTarget( target: mixedTarget.clangTarget, dependencies: target.dependencies, @@ -1370,7 +1417,7 @@ public final class MixedTargetBuildDescription { toolsVersion: toolsVersion, buildParameters: buildParameters, fileSystem: fileSystem, - includesInteropHeaderInModuleMap: true + isWithinMixedTarget: true ) let swiftResolvedTarget = ResolvedTarget( @@ -1390,44 +1437,26 @@ public final class MixedTargetBuildDescription { isTestTarget: isTestTarget, fileSystem: fileSystem, observabilityScope: observabilityScope, - moduleMapPath: self.clangTargetBuildDescription.moduleMap + isWithinMixedTarget: true ) - // We need to generate some auxiliary files for the Clang and Swift - // sources to later be compiled successfully. - - let buildArtifactDirectory = self.swiftTargetBuildDescription.tempsPath - - // unextended-module-overlay.yaml - let unextendedModuleOverlayPath = buildArtifactDirectory - .appending(component: "unextended-module-overlay.yaml") - - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, - contents: [ - VFSOverlay.File( - name: "module.modulemap", - externalContents: - buildArtifactDirectory.appending(component: "unextended-module.modulemap").pathString - ) - ] - ) - ]).write(to: unextendedModuleOverlayPath, fileSystem: fileSystem) - - // all-product-headers.yaml + // Compiling the mixed target will require a Clang VFS overlay file + // with mappings to the target's module map and public headers. + let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir + let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath + let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath let allProductHeadersPath = buildArtifactDirectory .appending(component: "all-product-headers.yaml") try VFSOverlay(roots: [ VFSOverlay.Directory( - name: clangTargetBuildDescription.clangTarget.includeDir.pathString, + name: publicHeadersPath.pathString, contents: - try Set(fileSystem.getDirectoryContents(clangTargetBuildDescription.clangTarget.includeDir) - .map(clangTargetBuildDescription.clangTarget.includeDir.appending(component:))) - .filter { headerPath in - headerPath.pathString.hasPrefix(clangTargetBuildDescription.clangTarget.includeDir.pathString) - }.map { headerPath in + // Public headers + try Set(fileSystem.getDirectoryContents(publicHeadersPath) + .map(publicHeadersPath.appending(component:))) + .filter(publicHeadersPath.isAncestor(of:)) + .map { headerPath in VFSOverlay.File( name: headerPath.basename, externalContents: headerPath.pathString @@ -1437,19 +1466,21 @@ public final class MixedTargetBuildDescription { VFSOverlay.Directory( name: buildArtifactDirectory.pathString, contents: [ + // Module map VFSOverlay.File( - name: "module.modulemap", + name: moduleMapFilename, externalContents: - buildArtifactDirectory.appending(component: "module.modulemap").pathString + buildArtifactDirectory.appending(component: moduleMapFilename).pathString ) ] ), VFSOverlay.Directory( name: buildArtifactDirectory.pathString, contents: [ + // Generated $(ModuleName)-Swift.h header VFSOverlay.File( - name: "\(target.c99name)-Swift.h", - externalContents: buildArtifactDirectory.appending(component: "\(target.c99name)-Swift.h").pathString + name: generatedInteropHeaderPath.basename, + externalContents: generatedInteropHeaderPath.pathString ) ] ), diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index cf63c41cd32..2320e2d3b88 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -252,8 +252,7 @@ extension LLBuildManifestBuilder { @discardableResult private func createSwiftCompileCommand( _ target: SwiftTargetBuildDescription, - addTargetCmd: Bool = true, - mixedTarget: Bool = false + addTargetCmd: Bool = true ) throws -> [Node] { // Inputs. let inputs = try self.computeSwiftCompileCmdInputs(target) @@ -263,7 +262,7 @@ extension LLBuildManifestBuilder { let moduleNode = Node.file(target.moduleOutputPath) var cmdOutputs = objectNodes + [moduleNode] - if mixedTarget { + if target.isWithinMixedTarget { cmdOutputs += [Node.file(target.objCompatibilityHeaderPath)] } @@ -275,7 +274,7 @@ extension LLBuildManifestBuilder { moduleNode: moduleNode ) } else { - try self.addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs, mixedTarget: mixedTarget) + try self.addCmdWithBuiltinSwiftTool(target, inputs: inputs, cmdOutputs: cmdOutputs) } if addTargetCmd { @@ -600,12 +599,12 @@ extension LLBuildManifestBuilder { private func addCmdWithBuiltinSwiftTool( _ target: SwiftTargetBuildDescription, inputs: [Node], - cmdOutputs: [Node], - mixedTarget: Bool = false + cmdOutputs: [Node] ) throws { let isLibrary = target.target.type == .library || target.target.type == .test let cmdName = target.target.getCommandName(config: self.buildConfig) +<<<<<<< HEAD self.manifest.addWriteSourcesFileListCommand(sources: target.sources, sourcesFileListPath: target.sourcesFileListPath) var otherArguments = try target.compileArguments() @@ -624,6 +623,9 @@ extension LLBuildManifestBuilder { } self.manifest.addSwiftCmd( +======= + manifest.addSwiftCmd( +>>>>>>> 136a03bc1 (Refactor approach) name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], outputs: mixedTarget ? cmdOutputs.dropLast() : cmdOutputs, @@ -634,7 +636,7 @@ extension LLBuildManifestBuilder { importPath: target.buildParameters.buildPath, tempsPath: target.tempsPath, objects: try target.objects, - otherArguments: otherArguments, + otherArguments: try target.compileArguments(), sources: target.sources, fileList: target.sourcesFileListPath, isLibrary: isLibrary, @@ -819,8 +821,7 @@ extension LLBuildManifestBuilder { @discardableResult private func createClangCompileCommand( _ target: ClangTargetBuildDescription, - addTargetCmd: Bool = true, - mixedTarget: Bool = false + addTargetCmd: Bool = true ) throws -> [Node] { let standards = [ (target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), @@ -841,8 +842,7 @@ extension LLBuildManifestBuilder { // the Swift half of the mixed target generates. This header acts as an // input to the Clang compile command, which therefore forces the // Swift half of the mixed target to be built first. - if mixedTarget { - // TODO(ncooke3): Maybe the header should be passed into this API? + if target.isWithinMixedTarget { inputs.append(Node.file(target.tempsPath.appending(component: "\(target.target.name)-Swift.h"))) } @@ -896,7 +896,7 @@ extension LLBuildManifestBuilder { var args = try target.basicArguments(isCXX: isCXX, isC: isC) - if mixedTarget { + if target.isWithinMixedTarget { // For mixed targets, the Swift half of the target will generate // an Objective-C compatibility header in the build folder. // Compiling the Objective-C half of the target may require this @@ -954,8 +954,8 @@ extension LLBuildManifestBuilder { private func createMixedCompileCommand( _ target: MixedTargetBuildDescription ) throws { - let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false, mixedTarget: true) - let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false, mixedTarget: true) + let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false) + let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false) self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: swiftOutputs + clangOutputs) } } From 5929d4611f508527ae508bf00c6a68c8165afc37 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 14 Nov 2022 21:26:28 -0500 Subject: [PATCH 017/178] Remove unneeded param --- Sources/Build/BuildPlan.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 9468e4618fb..6c598b918e9 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -830,7 +830,6 @@ public final class SwiftTargetBuildDescription { buildParameters: BuildParameters, buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], - isTestTarget: Bool? = nil, testTargetRole: TestTargetRole? = nil, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1395,7 +1394,6 @@ public final class MixedTargetBuildDescription { buildParameters: BuildParameters, buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], - isTestTarget: Bool? = nil, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { @@ -1434,7 +1432,6 @@ public final class MixedTargetBuildDescription { buildParameters: buildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, - isTestTarget: isTestTarget, fileSystem: fileSystem, observabilityScope: observabilityScope, isWithinMixedTarget: true From de336ed2b824ac6863ae707e3d710bc16e1da83d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 11:13:46 -0500 Subject: [PATCH 018/178] Consolidate build argument additions --- Sources/Build/BuildPlan.swift | 133 ++++++++++++--------- Sources/Build/LLBuildManifestBuilder.swift | 10 -- 2 files changed, 74 insertions(+), 69 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 6c598b918e9..4eca9aefc42 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1014,20 +1014,6 @@ public final class SwiftTargetBuildDescription { args += ["-emit-objc-header", "-emit-objc-header-path", objCompatibilityHeaderPath.pathString] } - if isWithinMixedTarget { - args += [ - "-import-underlying-module", - "-Xcc", - "-ivfsoverlay", - "-Xcc", - "\(tempsPath)/all-product-headers.yaml", - "-Xcc", - "-ivfsoverlay", - "-Xcc", - "\(tempsPath)/unextended-module-overlay.yaml" - ] - } - // Add arguments needed for code coverage if it is enabled. if buildParameters.enableCodeCoverage { args += ["-profile-coverage-mapping", "-profile-generate"] @@ -1437,51 +1423,80 @@ public final class MixedTargetBuildDescription { isWithinMixedTarget: true ) - // Compiling the mixed target will require a Clang VFS overlay file - // with mappings to the target's module map and public headers. - let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir - let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath - let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath - let allProductHeadersPath = buildArtifactDirectory - .appending(component: "all-product-headers.yaml") - - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: publicHeadersPath.pathString, - contents: - // Public headers - try Set(fileSystem.getDirectoryContents(publicHeadersPath) - .map(publicHeadersPath.appending(component:))) - .filter(publicHeadersPath.isAncestor(of:)) - .map { headerPath in - VFSOverlay.File( - name: headerPath.basename, - externalContents: headerPath.pathString - ) - } - ), - VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, - contents: [ - // Module map - VFSOverlay.File( - name: moduleMapFilename, - externalContents: - buildArtifactDirectory.appending(component: moduleMapFilename).pathString - ) - ] - ), - VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, - contents: [ - // Generated $(ModuleName)-Swift.h header - VFSOverlay.File( - name: generatedInteropHeaderPath.basename, - externalContents: generatedInteropHeaderPath.pathString - ) - ] - ), - ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + // TODO(ncooke3): Can the below conditional be refined? The Clang build + // description only builds a module map for `.library` targets. + if target.type != .test { + // Compiling the mixed target will require a Clang VFS overlay file + // with mappings to the target's module map and public headers. + let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir + let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath + let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath + let allProductHeadersPath = buildArtifactDirectory + .appending(component: "all-product-headers.yaml") + + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: publicHeadersPath.pathString, + contents: + // Public headers + try Set(fileSystem.getDirectoryContents(publicHeadersPath) + .map(publicHeadersPath.appending(component:))) + .filter(publicHeadersPath.isAncestor(of:)) + .map { headerPath in + VFSOverlay.File( + name: headerPath.basename, + externalContents: headerPath.pathString + ) + } + ), + VFSOverlay.Directory( + name: buildArtifactDirectory.pathString, + contents: [ + // Module map + VFSOverlay.File( + name: moduleMapFilename, + externalContents: + buildArtifactDirectory.appending(component: moduleMapFilename).pathString + ) + ] + ), + VFSOverlay.Directory( + name: buildArtifactDirectory.pathString, + contents: [ + // Generated $(ModuleName)-Swift.h header + VFSOverlay.File( + name: generatedInteropHeaderPath.basename, + externalContents: generatedInteropHeaderPath.pathString + ) + ] + ), + ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + + clangTargetBuildDescription.additionalFlags += [ + // For mixed targets, the Swift half of the target will generate + // an Objective-C compatibility header in the build folder. + // Compiling the Objective-C half of the target may require this + // generated header if the Objective-C half uses any APIs from + // the Swift half. For successful compilation, the directory + // with the generated header is added as a header search path. + "-I\(buildArtifactDirectory)" + ] + + swiftTargetBuildDescription.additionalFlags += [ + // Builds Objective-C portion of module. + "-import-underlying-module", + // Pass VFS overlay to the underlying Clang compiler. + "-Xcc", + "-ivfsoverlay", + "-Xcc", + allProductHeadersPath.pathString, + // Pass VFS overlay to the underlying Clang compiler. + "-Xcc", + "-ivfsoverlay", + "-Xcc", + "\(buildArtifactDirectory)/unextended-module-overlay.yaml", + ] + } } } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 2320e2d3b88..3b5dc53b5e8 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -896,16 +896,6 @@ extension LLBuildManifestBuilder { var args = try target.basicArguments(isCXX: isCXX, isC: isC) - if target.isWithinMixedTarget { - // For mixed targets, the Swift half of the target will generate - // an Objective-C compatibility header in the build folder. - // Compiling the Objective-C half of the target may require this - // generated header if the Objective-C half uses any APIs from - // the Swift half. For successful compilation, the directory - // with the generated header is added as a header search path. - args += ["-I\(target.tempsPath)"] - } - args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString] // Add language standard flag if needed. From 81b35377c19e512d7bd01fb94f99805de14c2bd8 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 11:29:33 -0500 Subject: [PATCH 019/178] Move more mixed target specific logic to MixedTargetBuildDescription --- Sources/Build/BuildPlan.swift | 54 +++++++++---------- .../PackageLoading/ModuleMapGenerator.swift | 3 ++ 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 4eca9aefc42..4ee4829f3f1 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -369,29 +369,11 @@ public final class ClangTargetBuildDescription { // Swift part without the generated header being considered // an input (because it won't exist yet and is an output of // that compilation command). - let unextendedModuleMapFilename = "unextended-module.modulemap" let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: unextendedModuleMapPath ) - - // The auxilliary module map is passed to the Clang compiler - // via a VFS overlay, represented by the below YAML file. - let unextendedModuleMapOverlayPath = tempsPath - .appending(component: "unextended-module-overlay.yaml") - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: tempsPath.pathString, - contents: [ - VFSOverlay.File( - name: moduleMapFilename, - externalContents: - tempsPath.appending(component: unextendedModuleMapFilename).pathString - ) - ] - ) - ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) } self.moduleMap = moduleMapPath @@ -1434,6 +1416,23 @@ public final class MixedTargetBuildDescription { let allProductHeadersPath = buildArtifactDirectory .appending(component: "all-product-headers.yaml") + // The auxilliary module map is passed to the Clang compiler + // via a VFS overlay, represented by the below YAML file. + let unextendedModuleMapOverlayPath = buildArtifactDirectory + .appending(component: "unextended-module-overlay.yaml") + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: buildArtifactDirectory.pathString, + contents: [ + VFSOverlay.File( + name: moduleMapFilename, + externalContents: + buildArtifactDirectory.appending(component: unextendedModuleMapFilename).pathString + ) + ] + ) + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + try VFSOverlay(roots: [ VFSOverlay.Directory( name: publicHeadersPath.pathString, @@ -1472,15 +1471,6 @@ public final class MixedTargetBuildDescription { ), ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - clangTargetBuildDescription.additionalFlags += [ - // For mixed targets, the Swift half of the target will generate - // an Objective-C compatibility header in the build folder. - // Compiling the Objective-C half of the target may require this - // generated header if the Objective-C half uses any APIs from - // the Swift half. For successful compilation, the directory - // with the generated header is added as a header search path. - "-I\(buildArtifactDirectory)" - ] swiftTargetBuildDescription.additionalFlags += [ // Builds Objective-C portion of module. @@ -1496,6 +1486,16 @@ public final class MixedTargetBuildDescription { "-Xcc", "\(buildArtifactDirectory)/unextended-module-overlay.yaml", ] + + clangTargetBuildDescription.additionalFlags += [ + // For mixed targets, the Swift half of the target will generate + // an Objective-C compatibility header in the build folder. + // Compiling the Objective-C half of the target may require this + // generated header if the Objective-C half uses any APIs from + // the Swift half. For successful compilation, the directory + // with the generated header is added as a header search path. + "-I\(buildArtifactDirectory)" + ] } } } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 65a3d15e558..0da3bd208b7 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -17,6 +17,9 @@ import PackageModel /// Name of the module map file recognized by the Clang and Swift compilers. public let moduleMapFilename = "module.modulemap" +/// Name of the auxilliary module map file used in the Clang VFS overlay sytem. +public let unextendedModuleMapFilename = "unextended-module.modulemap" + extension AbsolutePath { fileprivate var moduleEscapedPathString: String { return self.pathString.replacingOccurrences(of: "\\", with: "\\\\") From 89ea7615c2dd9621dd4cc53aa268d1845ef1735a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 12:56:10 -0500 Subject: [PATCH 020/178] Work around unsupported flags - I can the `emitSwiftModuleSeparately` control flow to work by adding the compatibility header node to the `objectNodes`. - The `useIntegratedSwiftDriver` does not seem as easy to get working. --- Sources/Build/LLBuildManifestBuilder.swift | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 3b5dc53b5e8..59405b8b2a6 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -260,13 +260,18 @@ extension LLBuildManifestBuilder { // Outputs. let objectNodes = try target.objects.map(Node.file) let moduleNode = Node.file(target.moduleOutputPath) - var cmdOutputs = objectNodes + [moduleNode] + let cmdOutputs = objectNodes + [moduleNode] + // TODO(ncooke3): Do we need to support building mixed targets with the + // `--use-integrated-swiftdriver` or `--emit-swift-module-separately` + // flags? if target.isWithinMixedTarget { - cmdOutputs += [Node.file(target.objCompatibilityHeaderPath)] - } - - if self.buildParameters.useIntegratedSwiftDriver { + try self.addCmdWithBuiltinSwiftTool( + target, + inputs: inputs, + cmdOutputs: cmdOutputs + [.file(target.objCompatibilityHeaderPath)] + ) + } else if buildParameters.useIntegratedSwiftDriver { try self.addSwiftCmdsViaIntegratedDriver( target, inputs: inputs, @@ -604,9 +609,8 @@ extension LLBuildManifestBuilder { let isLibrary = target.target.type == .library || target.target.type == .test let cmdName = target.target.getCommandName(config: self.buildConfig) -<<<<<<< HEAD self.manifest.addWriteSourcesFileListCommand(sources: target.sources, sourcesFileListPath: target.sourcesFileListPath) - + var otherArguments = try target.compileArguments() if mixedTarget { otherArguments += [ @@ -622,10 +626,7 @@ extension LLBuildManifestBuilder { ] } - self.manifest.addSwiftCmd( -======= manifest.addSwiftCmd( ->>>>>>> 136a03bc1 (Refactor approach) name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], outputs: mixedTarget ? cmdOutputs.dropLast() : cmdOutputs, From e4b14e8685f5f53eafebd15956311bbf8c414e3f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 17:05:25 -0500 Subject: [PATCH 021/178] Various improvements - Mixed target creates for .library target types only - Reuse unextended module map constant --- Sources/Build/BuildPlan.swift | 6 ++---- Sources/PackageLoading/PackageBuilder.swift | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 4ee4829f3f1..311bed53c2e 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1405,9 +1405,7 @@ public final class MixedTargetBuildDescription { isWithinMixedTarget: true ) - // TODO(ncooke3): Can the below conditional be refined? The Clang build - // description only builds a module map for `.library` targets. - if target.type != .test { + if target.type == .library { // Compiling the mixed target will require a Clang VFS overlay file // with mappings to the target's module map and public headers. let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir @@ -1484,7 +1482,7 @@ public final class MixedTargetBuildDescription { "-Xcc", "-ivfsoverlay", "-Xcc", - "\(buildArtifactDirectory)/unextended-module-overlay.yaml", + unextendedModuleMapOverlayPath.pathString ] clangTargetBuildDescription.additionalFlags += [ diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 063b9a5aefc..636b9167fe8 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -955,7 +955,6 @@ public final class PackageBuilder { } // Create and return the right kind of target depending on what kind of sources we found. - // TODO(ncooke3): Figure out if we should restrict Swift + Cxx. if sources.hasSwiftSources && sources.hasClangSources { let moduleMapType = try findModuleMapType( From 106d34cda0bfd35addab7ed0c1ed85399e9d0e21 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 18:06:44 -0500 Subject: [PATCH 022/178] Improve source filtering logic --- Sources/Build/BuildPlan.swift | 1 + Sources/PackageModel/Target.swift | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 311bed53c2e..202cd31d073 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1341,6 +1341,7 @@ public final class MixedTargetBuildDescription { target.underlyingTarget.resources + swiftTargetBuildDescription.pluginDerivedResources } + /// If this target is a test target. public var isTestTarget: Bool { clangTargetBuildDescription.isTestTarget && swiftTargetBuildDescription.isTestTarget diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 1a88d486730..670c0f9abc3 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -636,7 +636,10 @@ public final class MixedTarget: Target { ) throws { let swiftSources = Sources( - paths: sources.paths.filter { $0.extension == "swift" }, + paths: sources.paths.filter { path in + guard let ext = path.extension else { return false } + return SupportedLanguageExtension.swiftExtensions.contains(ext) + }, root: sources.root ) @@ -656,7 +659,10 @@ public final class MixedTarget: Target { ) let clangSources = Sources( - paths: sources.paths.filter { $0.extension != "swift" }, + paths: sources.paths.filter { path in + guard let ext = path.extension else { return false } + return SupportedLanguageExtension.clangTargetExtensions(toolsVersion: .current).contains(ext) + }, root: sources.root ) From 42d527718dbe56aa2591bfcd128af3a6f6f0cb48 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 18:54:25 -0500 Subject: [PATCH 023/178] Broaden API surface of MixedTargetBuildDescription --- Sources/Build/BuildPlan.swift | 64 +++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 202cd31d073..642878e0836 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -180,7 +180,7 @@ public enum TargetBuildDescription { case .clang(let target): return try target.objects case .mixed(let target): - return try target.swiftTargetBuildDescription.objects + target.clangTargetBuildDescription.objects + return try target.objects } } } @@ -206,7 +206,7 @@ public enum TargetBuildDescription { case .clang(let target): return target.bundlePath case .mixed(let target): - return target.swiftTargetBuildDescription.bundlePath + return target.bundlePath } } @@ -229,7 +229,7 @@ public enum TargetBuildDescription { case .clang(let target): return target.libraryBinaryPaths case .mixed(let target): - return target.swiftTargetBuildDescription.libraryBinaryPaths + return target.libraryBinaryPaths } } @@ -240,7 +240,7 @@ public enum TargetBuildDescription { case .clang(let target): return target.resourceBundleInfoPlistPath case .mixed(let target): - return target.swiftTargetBuildDescription.resourceBundleInfoPlistPath + return target.resourceBundleInfoPlistPath } } } @@ -643,7 +643,7 @@ public final class SwiftTargetBuildDescription { private var pluginDerivedSources: Sources /// These are the resource files derived from plugins. - internal var pluginDerivedResources: [Resource] + fileprivate var pluginDerivedResources: [Resource] /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { @@ -1334,24 +1334,47 @@ public final class SwiftTargetBuildDescription { public final class MixedTargetBuildDescription { /// The target described by this target. - public let target: ResolvedTarget + let target: ResolvedTarget - /// The list of all resource files in the target, including the derived ones. - public var resources: [Resource] { - target.underlyingTarget.resources + swiftTargetBuildDescription.pluginDerivedResources - } + /// The list of all resource files in the target. + var resources: [Resource] { target.underlyingTarget.resources } /// If this target is a test target. - public var isTestTarget: Bool { + var isTestTarget: Bool { clangTargetBuildDescription.isTestTarget && swiftTargetBuildDescription.isTestTarget } + /// The objects in this target. This includes both the Swift and Clang object files. + var objects: [AbsolutePath] { + get throws { + try swiftTargetBuildDescription.objects + + clangTargetBuildDescription.objects + } + } + + /// Path to the bundle generated for this module (if any). + var bundlePath: AbsolutePath? { swiftTargetBuildDescription.bundlePath } + + /// Path to the resource Info.plist file, if generated. + var resourceBundleInfoPlistPath: AbsolutePath? { + swiftTargetBuildDescription.resourceBundleInfoPlistPath + } + + /// The modulemap file for this target, if any + var moduleMap: AbsolutePath? { clangTargetBuildDescription.moduleMap } + + /// Paths to the binary libraries the target depends on. + var libraryBinaryPaths: Set { + swiftTargetBuildDescription.libraryBinaryPaths + .union(clangTargetBuildDescription.libraryBinaryPaths) + } + /// The build description for the Clang sources. - public let clangTargetBuildDescription: ClangTargetBuildDescription + let clangTargetBuildDescription: ClangTargetBuildDescription /// The build description for the Swift sources. - public let swiftTargetBuildDescription: SwiftTargetBuildDescription + let swiftTargetBuildDescription: SwiftTargetBuildDescription private let fileSystem: FileSystem @@ -2584,7 +2607,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { case let target as MixedTarget where target.type == .library: // Add the modulemap of the dependency if it has one. if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { - if let moduleMap = dependencyTargetDescription.clangTargetBuildDescription.moduleMap { + if let moduleMap = dependencyTargetDescription.moduleMap { clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] } } @@ -2642,11 +2665,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { guard case let .mixed(target)? = targetMap[dependency] else { throw InternalError("unexpected mixed target \(underlyingTarget)") } - // Add the path to modulemap of the dependency. Currently we require that all Clang targets have a - // modulemap but we may want to remove that requirement since it is valid for a target to exist without - // one. However, in that case it will not be importable in Swift targets. We may want to emit a warning - // in that case from here. - guard let moduleMap = target.clangTargetBuildDescription.moduleMap else { break } + // Add the path to modulemap of the dependency. Currently we + // require that all Mixed targets have a modulemap as it is + // required for interoperability. + guard let moduleMap = target.moduleMap else { break } swiftTarget.additionalFlags += [ "-Xcc", "-fmodule-map-file=\(moduleMap.pathString)", "-Xcc", "-I", "-Xcc", target.clangTargetBuildDescription.clangTarget.includeDir.pathString @@ -2693,7 +2715,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { arguments += ["-I", includeDir.pathString] } case .mixed(let targetDescription): - if let includeDir = targetDescription.clangTargetBuildDescription.moduleMap?.parentDirectory { + if let includeDir = targetDescription.moduleMap?.parentDirectory { arguments += ["-I", includeDir.pathString] } } @@ -2733,7 +2755,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { arguments += ["-I\(includeDir.pathString)"] } case .mixed(let targetDescription): - if let includeDir = targetDescription.clangTargetBuildDescription.moduleMap?.parentDirectory { + if let includeDir = targetDescription.moduleMap?.parentDirectory { arguments += ["-I\(includeDir.pathString)"] } } From 761e112d67ff606366b7e69350ecb7371ea209fe Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 19:07:22 -0500 Subject: [PATCH 024/178] Add errors for improper module map config --- Sources/Build/BuildPlan.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 642878e0836..86e7129ec7a 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -332,14 +332,14 @@ public final class ClangTargetBuildDescription { self.isWithinMixedTarget = isWithinMixedTarget // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. - // TODO(ncooke3): Will non-library mixed language targets be supported? - // If so, they may need a module map if the Objc implementation uses Swift. if target.type == .library { // If there's a custom module map, use it as given. if case .custom(let path) = clangTarget.moduleMapType { self.moduleMap = path - // TODO(ncooke3): We need to ensure that custom modulemaps - // expose the generated Swift header. + + if isWithinMixedTarget { + throw InternalError("Custom module maps do not work with mixed language target \(target.name)") + } } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { @@ -377,9 +377,12 @@ public final class ClangTargetBuildDescription { } self.moduleMap = moduleMapPath + } else { + // Otherwise there is no module map, and we leave `moduleMap` unset. + if isWithinMixedTarget { + throw InternalError("Mixed language library target \(target.name) requires a module map.") + } } - // Otherwise there is no module map, and we leave `moduleMap` unset. - // TODO(ncooke3): Mixed targets need a module map. } // Do nothing if we're not generating a bundle. From 64fe2804230abab706326e6ddf496bd66c5d31bd Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 19:17:47 -0500 Subject: [PATCH 025/178] Throw error for non-library and non-test targets --- Sources/PackageModel/Target.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 670c0f9abc3..443b7633e5e 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -634,6 +634,12 @@ public final class MixedTarget: Target { buildSettings: BuildSettings.AssignmentTable = .init(), pluginUsages: [PluginUsage] = [] ) throws { + guard type == .library || type == .test else { + throw StringError( + "Mixed target \(name) is a \(type) target, but mixed targets" + + "can only be either a library or test target. " + ) + } let swiftSources = Sources( paths: sources.paths.filter { path in From 70729dfeabd5083cea209a0ce396152f1a796ba9 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Nov 2022 19:18:33 -0500 Subject: [PATCH 026/178] Throw error when using Mixed target on non-Apple platforms --- Sources/Build/BuildPlan.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 86e7129ec7a..fd40c270061 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1396,6 +1396,10 @@ public final class MixedTargetBuildDescription { throw InternalError("underlying target type mismatch \(target)") } + guard buildParameters.triple.isDarwin() else { + throw StringError("Mixed language targets are only supported on Apple platforms.") + } + self.target = target self.fileSystem = fileSystem From fe7b530e375d2e85cd27fbb41ba155e060372201 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 14:54:28 -0500 Subject: [PATCH 027/178] Remove extra new line --- Sources/Build/BuildPlan.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index fd40c270061..0c5a048c507 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1500,7 +1500,6 @@ public final class MixedTargetBuildDescription { ), ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - swiftTargetBuildDescription.additionalFlags += [ // Builds Objective-C portion of module. "-import-underlying-module", From b068fe31ff1986337a4b2a09f7d8cc712e00267f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 15:37:23 -0500 Subject: [PATCH 028/178] Add ModuleMapGenerator test - Add unit test to confirm that Swift submodule is added to module map --- .../ModuleMapGenerationTests.swift | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift index 80834a55e78..32df2b72e7c 100644 --- a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift +++ b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift @@ -56,6 +56,30 @@ class ModuleMapGeneration: XCTestCase { } } + func testSwiftSubmoduleWhenGivenInteropHeaderPath() throws { + let root: AbsolutePath = AbsolutePath.root + + let fs = InMemoryFileSystem(emptyFiles: + root.appending(components: "include", "Foo.h").pathString + ) + + let interopHeaderPath = AbsolutePath(path: "/path/to/Foo-Swift.h") + + ModuleMapTester("Foo", interopHeaderPath: interopHeaderPath, in: fs) { result in + result.check(contents: """ + module Foo { + umbrella header "\(root.appending(components: "include", "Foo.h"))" + export * + } + module Foo.Swift { + header "/path/to/Foo-Swift.h" + requires objc + } + + """) + } + } + func testOtherCases() throws { let root: AbsolutePath = .root var fs: InMemoryFileSystem @@ -180,7 +204,7 @@ class ModuleMapGeneration: XCTestCase { } /// Helper function to test module map generation. Given a target name and optionally the name of a public-headers directory, this function determines the module map type of the public-headers directory by examining the contents of a file system and invokes a given block to check the module result (including any diagnostics). -func ModuleMapTester(_ targetName: String, includeDir: String = "include", in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { +func ModuleMapTester(_ targetName: String, includeDir: String = "include", interopHeaderPath: AbsolutePath? = nil, in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { let observability = ObservabilitySystem.makeForTesting() // Create a module map generator, and determine the type of module map to use for the header directory. This may emit diagnostics. let moduleMapGenerator = ModuleMapGenerator(targetName: targetName, moduleName: targetName.spm_mangledToC99ExtendedIdentifier(), publicHeadersDir: AbsolutePath.root.appending(component: includeDir), fileSystem: fileSystem) @@ -190,7 +214,7 @@ func ModuleMapTester(_ targetName: String, includeDir: String = "include", in fi let generatedModuleMapPath = AbsolutePath.root.appending(components: "module.modulemap") observability.topScope.trap { if let generatedModuleMapType = moduleMapType.generatedModuleMapType { - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath) + try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath, interopHeaderPath: interopHeaderPath) } } From 3bf03927dd907d8576cdad6782c994c288b78882 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 16:00:01 -0500 Subject: [PATCH 029/178] Remove unneeded error case --- Sources/PackageLoading/PackageBuilder.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 636b9167fe8..eac9d34ac01 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -194,8 +194,6 @@ extension Target { case emptyName } - /// The target contains an invalid mix of languages (e.g. both Swift and C). - case mixedSources(AbsolutePath) } } @@ -204,8 +202,6 @@ extension Target.Error: CustomStringConvertible { switch self { case .invalidName(let path, let problem): return "invalid target name at '\(path)'; \(problem)" - case .mixedSources(let path): - return "target at '\(path)' contains mixed language source files; feature not supported" } } } From b0cda54c12055c68ebe17f1d969eda8eb67f68c4 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 16:19:24 -0500 Subject: [PATCH 030/178] Make error message more precise --- Sources/PackageModel/Target.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 443b7633e5e..49f0416b019 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -636,8 +636,9 @@ public final class MixedTarget: Target { ) throws { guard type == .library || type == .test else { throw StringError( - "Mixed target \(name) is a \(type) target, but mixed targets" + - "can only be either a library or test target. " + "Target with mixed sources at \(path) is a \(type) target; " + + "targets with mixed language sources are only supported for " + + "library and test targets." ) } From 295e3858a52b1da7c0146afc2300bdddf3e3c1a3 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 16:51:11 -0500 Subject: [PATCH 031/178] Make error message more precise --- Sources/Build/BuildPlan.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 0c5a048c507..03e70f1e21d 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1397,7 +1397,8 @@ public final class MixedTargetBuildDescription { } guard buildParameters.triple.isDarwin() else { - throw StringError("Mixed language targets are only supported on Apple platforms.") + throw StringError("Targets with mixed language sources are only " + + "supported on Apple platforms.") } self.target = target From 64e5dcb408c3657aaff7c7e83d080d6f0dfdaf7c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 17:29:49 -0500 Subject: [PATCH 032/178] Move module map error earlier and add unit test --- Sources/Build/BuildPlan.swift | 10 +------- Sources/PackageModel/Target.swift | 8 ++++++ .../PackageBuilderTests.swift | 25 +++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 03e70f1e21d..c458f233333 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -336,10 +336,6 @@ public final class ClangTargetBuildDescription { // If there's a custom module map, use it as given. if case .custom(let path) = clangTarget.moduleMapType { self.moduleMap = path - - if isWithinMixedTarget { - throw InternalError("Custom module maps do not work with mixed language target \(target.name)") - } } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { @@ -377,12 +373,8 @@ public final class ClangTargetBuildDescription { } self.moduleMap = moduleMapPath - } else { - // Otherwise there is no module map, and we leave `moduleMap` unset. - if isWithinMixedTarget { - throw InternalError("Mixed language library target \(target.name) requires a module map.") - } } + // Otherwise there is no module map, and we leave `moduleMap` unset. } // Do nothing if we're not generating a bundle. diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 49f0416b019..0ebbb5e9213 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -642,6 +642,14 @@ public final class MixedTarget: Target { ) } + if case .custom = moduleMapType { + throw StringError( + "Target with mixed sources at \(path) contains a custom " + + "module map; targets with mixed language sources " + + "do not support custom module maps." + ) + } + let swiftSources = Sources( paths: sources.paths.filter { path in guard let ext = path.extension else { return false } diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index 9c37e7ea57e..c48f1eec71f 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -69,6 +69,31 @@ class PackageBuilderTests: XCTestCase { } } + func testMixedSourcesDoNotSupportCustomModuleMap() throws { + let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") + + let fs = InMemoryFileSystem(emptyFiles: + foo.appending(components: "Foo.swift").pathString, + foo.appending(components: "include", "Bar.h").pathString, + foo.appending(components: "Bar.m").pathString, + foo.appending(components: "include", "module.modulemap").pathString + ) + + let manifest = Manifest.createRootManifest( + name: "pkg", + path: .root, + targets: [ + try TargetDescription(name: "foo"), + ] + ) + PackageBuilderTester(manifest, in: fs) { _, diagnostics in + diagnostics.check( + diagnostic: "Target with mixed sources at /Sources/foo contains a custom module map; targets with mixed language sources do not support custom module maps.", + severity: .error + ) + } + } + func testBrokenSymlink() throws { try testWithTemporaryDirectory { path in let fs = localFileSystem From 38e1ba5af0acadc8fb6f46b3b1a1c1c6322d8b68 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 17:41:24 -0500 Subject: [PATCH 033/178] Remove temporary development logic --- Sources/PackageLoading/ManifestLoader.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index a69210eff16..623684a40e0 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -425,9 +425,6 @@ public final class ManifestLoader: ManifestLoaderProtocol { } do { - // Ran into an issue where my cache became invalidated. Adding this - // for development. - try? cache?.remove(key: key.sha256Checksum) // try to get it from the cache if let result = try cache?.get(key: key.sha256Checksum), let manifestJSON = result.manifestJSON, !manifestJSON.isEmpty { observabilityScope.emit(debug: "loading manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown") from cache") From 4b826e1481a26e5137e83334394e489de85c882b Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 21:33:08 -0500 Subject: [PATCH 034/178] Add BuildPlan unit test --- Tests/BuildTests/BuildPlanTests.swift | 117 +++++++++++++++++++++ Tests/BuildTests/MockBuildTestHelper.swift | 9 ++ 2 files changed, 126 insertions(+) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 447fcf98d96..f77f44a57be 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1455,6 +1455,123 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence]) } + func testBasicMixedLanguages() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Pkg/Sources/exe/main.swift", + "/Pkg/Sources/lib/Foo.swift", + "/Pkg/Sources/lib/include/Bar.h", + "/Pkg/Sources/lib/Bar.m" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + name: "Pkg", + path: .init(path: "/Pkg"), + targets: [ + TargetDescription(name: "exe", dependencies: ["lib"]), + TargetDescription(name: "lib") + ] + ) + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + #if !os(macOS) + XCTAssertThrowsError( + try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )), + "This should fail when run on non-Apple platforms." + ) { error in + XCTAssertEqual( + error.localizedDescription, + "Targets with mixed language sources are only supported on Apple platforms." + ) + } + #else + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + result.checkProductsCount(1) + result.checkTargetsCount(2) + + let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + + let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "4", + "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-fmodule-map-file=/path/to/build/debug/lib.build/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", + "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", .anySequence + ]) + + let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() + XCTAssertMatch(swiftPartOfLib, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "4", + "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-Xcc", + "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/all-product-headers.yaml", + "-Xcc", "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/unextended-module-overlay.yaml", + "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", .anySequence, + "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", + "/path/to/build/debug/lib.build/lib-Swift.h" + ]) + + let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) + XCTAssertMatch(clangPartOfLib, [ + "-fobjc-arc", "-target", "x86_64-apple-macosx10.13", "-g", "-O0", + "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", + "-I/path/to/build/debug/lib.build", + "-fmodules-cache-path=/path/to/build/debug/ModuleCache" + ]) + + XCTAssertEqual( + try result.target(for: "lib").mixedTarget().objects, + [ + buildPath.appending(components: "lib.build", "Foo.swift.o"), + buildPath.appending(components: "lib.build", "Bar.m.o"), + ] + ) + + XCTAssertEqual( + try result.target(for: "lib").mixedTarget().moduleMap?.pathString, + buildPath.appending(components: "lib.build", "module.modulemap").pathString + ) + + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", + "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", + "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, "-Xlinker", + "-add_ast_path", "-Xlinker", + buildPath.appending(components: "exe.build", "exe.swiftmodule").pathString + ]) + + testDiagnostics(observability.diagnostics) { result in + result.check(diagnostic: .contains("can be downloaded"), severity: .warning) + } + + #endif + } + func testSwiftCMixed() throws { let Pkg: AbsolutePath = "/Pkg" diff --git a/Tests/BuildTests/MockBuildTestHelper.swift b/Tests/BuildTests/MockBuildTestHelper.swift index e61f7da4dfc..39ceeac8479 100644 --- a/Tests/BuildTests/MockBuildTestHelper.swift +++ b/Tests/BuildTests/MockBuildTestHelper.swift @@ -171,4 +171,13 @@ extension TargetBuildDescription { throw BuildError.error("Unexpected \(self) type") } } + + func mixedTarget() throws -> MixedTargetBuildDescription { + switch self { + case .mixed(let target): + return target + default: + throw BuildError.error("Unexpected \(self) type") + } + } } From 9a2a7f207cb3d31d661d486a263d95901caf9c3a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Nov 2022 22:01:20 -0500 Subject: [PATCH 035/178] Revert change to var's access level --- Sources/Build/BuildPlan.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index c458f233333..5d2e640e135 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -638,7 +638,7 @@ public final class SwiftTargetBuildDescription { private var pluginDerivedSources: Sources /// These are the resource files derived from plugins. - fileprivate var pluginDerivedResources: [Resource] + private var pluginDerivedResources: [Resource] /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { From 35662f5a4429860712ea53ce4e9ac60d4e4b97a6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 18 Nov 2022 12:03:51 -0500 Subject: [PATCH 036/178] Add API for appending clang flags to SwiftTargetBuildDescription --- Sources/Build/BuildPlan.swift | 44 ++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 5d2e640e135..c42a7bc0b53 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1160,6 +1160,13 @@ public final class SwiftTargetBuildDescription { return result } + func appendClangFlags(_ flags: String...) { + flags.forEach { flag in + additionalFlags.append("-Xcc") + additionalFlags.append(flag) + } + } + /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { return buildParameters.triple.isDarwin() && target.type == .library @@ -1493,20 +1500,17 @@ public final class MixedTargetBuildDescription { ), ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - swiftTargetBuildDescription.additionalFlags += [ + swiftTargetBuildDescription.additionalFlags.append( // Builds Objective-C portion of module. - "-import-underlying-module", + "-import-underlying-module" + ) + + swiftTargetBuildDescription.appendClangFlags( // Pass VFS overlay to the underlying Clang compiler. - "-Xcc", - "-ivfsoverlay", - "-Xcc", - allProductHeadersPath.pathString, + "-ivfsoverlay", allProductHeadersPath.pathString, // Pass VFS overlay to the underlying Clang compiler. - "-Xcc", - "-ivfsoverlay", - "-Xcc", - unextendedModuleMapOverlayPath.pathString - ] + "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString + ) clangTargetBuildDescription.additionalFlags += [ // For mixed targets, the Swift half of the target will generate @@ -2643,10 +2647,11 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // one. However, in that case it will not be importable in Swift targets. We may want to emit a warning // in that case from here. guard let moduleMap = target.moduleMap else { break } - swiftTarget.additionalFlags += [ - "-Xcc", "-fmodule-map-file=\(moduleMap.pathString)", - "-Xcc", "-I", "-Xcc", target.clangTarget.includeDir.pathString, - ] + swiftTarget.appendClangFlags( + "-fmodule-map-file=\(moduleMap.pathString)", + "-I", + target.clangTarget.includeDir.pathString + ) case let target as SystemLibraryTarget: swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"] swiftTarget.additionalFlags += try pkgConfig(for: target).cFlags @@ -2668,10 +2673,11 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // require that all Mixed targets have a modulemap as it is // required for interoperability. guard let moduleMap = target.moduleMap else { break } - swiftTarget.additionalFlags += [ - "-Xcc", "-fmodule-map-file=\(moduleMap.pathString)", - "-Xcc", "-I", "-Xcc", target.clangTargetBuildDescription.clangTarget.includeDir.pathString - ] + swiftTarget.appendClangFlags( + "-fmodule-map-file=\(moduleMap.pathString)", + "-I", + target.clangTargetBuildDescription.clangTarget.includeDir.pathString + ) default: break } From accdcd8d0012e84ac49356fbe6a9212faa09452d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 18 Nov 2022 12:07:48 -0500 Subject: [PATCH 037/178] Use .append() for single flag --- Sources/Build/BuildPlan.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index c42a7bc0b53..dbe499edac0 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1512,15 +1512,13 @@ public final class MixedTargetBuildDescription { "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString ) - clangTargetBuildDescription.additionalFlags += [ - // For mixed targets, the Swift half of the target will generate - // an Objective-C compatibility header in the build folder. - // Compiling the Objective-C half of the target may require this - // generated header if the Objective-C half uses any APIs from - // the Swift half. For successful compilation, the directory - // with the generated header is added as a header search path. - "-I\(buildArtifactDirectory)" - ] + // For mixed targets, the Swift half of the target will generate + // an Objective-C compatibility header in the build folder. + // Compiling the Objective-C half of the target may require this + // generated header if the Objective-C half uses any APIs from + // the Swift half. For successful compilation, the directory + // with the generated header is added as a header search path. + clangTargetBuildDescription.additionalFlags.append("-I\(buildArtifactDirectory)") } } } From 2fdf1c3fe4794c05c66fd1c65269b1435538fcb9 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 18 Nov 2022 23:42:49 -0500 Subject: [PATCH 038/178] Generate resource bundle accessors for Clang sources --- Sources/Build/LLBuildManifestBuilder.swift | 24 ++++++++++++++-------- Sources/PackageModel/Target.swift | 7 ++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 59405b8b2a6..5c1f849c69d 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -822,7 +822,8 @@ extension LLBuildManifestBuilder { @discardableResult private func createClangCompileCommand( _ target: ClangTargetBuildDescription, - addTargetCmd: Bool = true + addTargetCmd: Bool = true, + createResourceBundle: Bool = true ) throws -> [Node] { let standards = [ (target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), @@ -831,12 +832,14 @@ extension LLBuildManifestBuilder { var inputs: [Node] = [] - // Add resources node as the input to the target. This isn't great because we - // don't need to block building of a module until its resources are assembled but - // we don't currently have a good way to express that resources should be built - // whenever a module is being built. - if let resourcesNode = try createResourcesBundle(for: .clang(target)) { - inputs.append(resourcesNode) + if createResourceBundle { + // Add resources node as the input to the target. This isn't great because we + // don't need to block building of a module until its resources are assembled but + // we don't currently have a good way to express that resources should be built + // whenever a module is being built. + if let resourcesNode = createResourcesBundle(for: .clang(target)) { + inputs.append(resourcesNode) + } } // If it's a mixed target, add the Objective-C compatibility header that @@ -946,7 +949,12 @@ extension LLBuildManifestBuilder { _ target: MixedTargetBuildDescription ) throws { let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false) - let clangOutputs = try createClangCompileCommand(target.clangTargetBuildDescription, addTargetCmd: false) + let clangOutputs = try createClangCompileCommand( + target.clangTargetBuildDescription, + addTargetCmd: false, + // The Swift compile command already created the resource bundle. + createResourceBundle: false + ) self.addTargetCmd(target: target.target, isTestTarget: target.isTestTarget, inputs: swiftOutputs + clangOutputs) } } diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 0ebbb5e9213..e7db58814eb 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -683,6 +683,7 @@ public final class MixedTarget: Target { self.clangTarget = try ClangTarget( name: name, + potentialBundleName: potentialBundleName, cLanguageStandard: cLanguageStandard, cxxLanguageStandard: cxxLanguageStandard, includeDir: includeDir, @@ -690,7 +691,11 @@ public final class MixedTarget: Target { headers: headers, type: type, path: path, - sources: clangSources + sources: clangSources, + resources: resources, + ignored: ignored, + others: others, + buildSettings: buildSettings ) super.init( From 0bac60b1f1e484e4c3008ded8a6954b512fcc35f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 30 Nov 2022 18:08:40 -0500 Subject: [PATCH 039/178] Fix mixed target builds when public headers path includes directories - When constructing the VFSOverlay for the all-product-headers.yaml, the code did not recursively search all paths in the public headers directory. --- Sources/Basics/FileSystem/VFSOverlay.swift | 45 +++++++++++++++++++++- Sources/Build/BuildPlan.swift | 13 ++----- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index d4b94c74e1a..7c56c13af6e 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -48,9 +48,9 @@ public struct VFSOverlay: Encodable { case contents } - private let contents: [File] + private let contents: [Resource] - public init(name: String, contents: [File]) { + public init(name: String, contents: [Resource]) { self.contents = contents super.init(name: name, type: "directory") } @@ -87,3 +87,44 @@ public struct VFSOverlay: Encodable { try JSONEncoder.makeWithDefaults(prettified: false).encode(path: path, fileSystem: fileSystem, self) } } + +public extension VFSOverlay { + /// Returns a tree of `VFSOverlay` resources for a given directory in the form of an array. Each item + /// in this array will be a resource (either file or directory) from the top most level of the given directory. + /// - Parameters: + /// - directoryPath: The directory to recursively search for resources in. + /// - fileSystem: The file system to search. + /// - Returns: An array of `VFSOverlay.Resource`s from the given directory. + /// - Throws: An error if the given path is a not a directory. + /// - Note: This API will recursively scan all subpaths of the given path. + static func overlayResources( + directoryPath: AbsolutePath, + fileSystem: FileSystem + ) throws -> [VFSOverlay.Resource] { + return + // Absolute path to each resource in the directory. + try fileSystem.getDirectoryContents(directoryPath).map(directoryPath.appending(component:)) + // Map each path to a corresponding VFSOverlay, recursing for directories. + .compactMap { resourcePath in + if fileSystem.isDirectory(resourcePath) { + return VFSOverlay.Directory( + name: resourcePath.basename, + contents: + try overlayResources( + directoryPath: resourcePath, + fileSystem: fileSystem + ) + ) + } else if fileSystem.isFile(resourcePath) { + return VFSOverlay.File( + name: resourcePath.basename, + externalContents: resourcePath.pathString + ) + } else { + // This case is not expected to be reached as a resource + // should be either a file or directory. + return nil + } + } + } +} diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index dbe499edac0..42c7f66d822 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1467,15 +1467,10 @@ public final class MixedTargetBuildDescription { name: publicHeadersPath.pathString, contents: // Public headers - try Set(fileSystem.getDirectoryContents(publicHeadersPath) - .map(publicHeadersPath.appending(component:))) - .filter(publicHeadersPath.isAncestor(of:)) - .map { headerPath in - VFSOverlay.File( - name: headerPath.basename, - externalContents: headerPath.pathString - ) - } + try VFSOverlay.overlayResources( + directoryPath: publicHeadersPath, + fileSystem: fileSystem + ) ), VFSOverlay.Directory( name: buildArtifactDirectory.pathString, From 2e5ac6f1b9a760473f885f3e59f372ab74296282 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 12 Dec 2022 15:09:30 -0500 Subject: [PATCH 040/178] Add support for custom module maps - This involved changing the all-product-headers.yaml overlay to only overlay the headers in the directory, and passing it to the Clang compile command. - Additionally, building a Mixed target dependency will pass the build directory module map instead of the custom one. This is because the custom one needs to be modified to include the generated Swift header. And, because the the rest of the module map's contents coms from the custom one, it may have relative header paths, so the all-product-headers.yaml overlay file needs to be passed along with the build directory module map when building a mixed target as a dependency. - Added TODO to address warning when using custom module map and resources. --- Sources/Basics/FileSystem/VFSOverlay.swift | 12 +- Sources/Build/BuildPlan.swift | 161 +++++++++++++++------ Sources/PackageModel/Target.swift | 8 - 3 files changed, 129 insertions(+), 52 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index 7c56c13af6e..b9203c84b81 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -94,25 +94,33 @@ public extension VFSOverlay { /// - Parameters: /// - directoryPath: The directory to recursively search for resources in. /// - fileSystem: The file system to search. + /// - shouldInclude: A closure used to determine if the tree should include a given path. + /// Defaults to including any path. /// - Returns: An array of `VFSOverlay.Resource`s from the given directory. /// - Throws: An error if the given path is a not a directory. /// - Note: This API will recursively scan all subpaths of the given path. static func overlayResources( directoryPath: AbsolutePath, - fileSystem: FileSystem + fileSystem: FileSystem, + shouldInclude: (AbsolutePath) -> Bool = { _ in true } ) throws -> [VFSOverlay.Resource] { return // Absolute path to each resource in the directory. try fileSystem.getDirectoryContents(directoryPath).map(directoryPath.appending(component:)) // Map each path to a corresponding VFSOverlay, recursing for directories. .compactMap { resourcePath in + guard shouldInclude(resourcePath) else { + return nil + } + if fileSystem.isDirectory(resourcePath) { return VFSOverlay.Directory( name: resourcePath.basename, contents: try overlayResources( directoryPath: resourcePath, - fileSystem: fileSystem + fileSystem: fileSystem, + shouldInclude: shouldInclude ) ) } else if fileSystem.isFile(resourcePath) { diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 42c7f66d822..92a59f6d89d 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -333,9 +333,62 @@ public final class ClangTargetBuildDescription { // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. if target.type == .library { - // If there's a custom module map, use it as given. - if case .custom(let path) = clangTarget.moduleMapType { - self.moduleMap = path + if case .custom(let customModuleMapPath) = clangTarget.moduleMapType { + if isWithinMixedTarget { + let customModuleMapContents: String = try fileSystem.readFileContents(customModuleMapPath) + + // Check that custom module map does not contain a Swift + // submodule. + if customModuleMapContents.contains("\(clangTarget.c99name).Swift") { + throw StringError("The target's module map may not " + + "contain a Swift submodule for " + + "the module \(target.c99name).") + } + + // Add a submodule to wrap the generated Swift header in + // the custom module map. + let modifiedModuleMapContents = """ + \(customModuleMapContents) + + module \(target.c99name).Swift { + header "\(target.c99name)-Swift.h" + requires objc + } + """ + + // Write the modified contents to a new module map in the + // build directory. + let writePath = tempsPath.appending(component: moduleMapFilename) + try fileSystem.createDirectory(writePath.parentDirectory, recursive: true) + + // If the file exists with the identical contents, we don't need to rewrite it. + // Otherwise, compiler will recompile even if nothing else has changed. + let contents = try? fileSystem.readFileContents(writePath) as String + if contents != modifiedModuleMapContents { + try fileSystem.writeFileContents(writePath, string: modifiedModuleMapContents) + } + + self.moduleMap = writePath + + // The unextended module map purposefully excludes the + // generated Swift header. This is so, when compiling the + // mixed target's Swift part, the generated Swift header is + // not considered an input (the header is an output of + // compiling the mixed target's Swift part). + // + // At this point, the custom module map has been checked + // that it does not include the Swift submodule. For that + // reason, it can be copied to the build directory to + // double as the unextended module map. + try? fileSystem.copy( + from: customModuleMapPath, + to: tempsPath.appending(component: unextendedModuleMapFilename) + ) + } else { + // When not building within a mixed target, use the custom + // module map as it does not need to be modified. + self.moduleMap = customModuleMapPath + } } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { @@ -465,7 +518,15 @@ public final class ClangTargetBuildDescription { args += ["-F", buildParameters.buildPath.pathString] } - args += ["-I", clangTarget.includeDir.pathString] + // For mixed targets, the `include` directory is instead overlayed over + // the build directory and that directory is passed as a header search + // path (See `MixedTargetBuildDescription`). This is done to avoid + // module re-declaration errors for cases when a mixed target contains + // a custom module map. + if !isWithinMixedTarget { + args += ["-I", clangTarget.includeDir.pathString] + } + args += additionalFlags if enableModules { args += try moduleCacheArgs @@ -1363,6 +1424,10 @@ public final class MixedTargetBuildDescription { swiftTargetBuildDescription.resourceBundleInfoPlistPath } + /// The path to the VFS overlay file that overlays the public headers of + /// the Clang half of the target over the target's build directory. + private(set) var allProductHeadersOverlay: AbsolutePath? = nil + /// The modulemap file for this target, if any var moduleMap: AbsolutePath? { clangTargetBuildDescription.moduleMap } @@ -1441,7 +1506,6 @@ public final class MixedTargetBuildDescription { // with mappings to the target's module map and public headers. let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath - let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath let allProductHeadersPath = buildArtifactDirectory .appending(component: "all-product-headers.yaml") @@ -1462,39 +1526,35 @@ public final class MixedTargetBuildDescription { ) ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + // TODO(ncooke3): What happens if a custom module map exists with a + // name other than `module.modulemap`? try VFSOverlay(roots: [ VFSOverlay.Directory( - name: publicHeadersPath.pathString, + name: buildArtifactDirectory.pathString, contents: // Public headers try VFSOverlay.overlayResources( directoryPath: publicHeadersPath, - fileSystem: fileSystem - ) - ), - VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, - contents: [ - // Module map - VFSOverlay.File( - name: moduleMapFilename, - externalContents: - buildArtifactDirectory.appending(component: moduleMapFilename).pathString - ) - ] - ), - VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, - contents: [ - // Generated $(ModuleName)-Swift.h header - VFSOverlay.File( - name: generatedInteropHeaderPath.basename, - externalContents: generatedInteropHeaderPath.pathString + fileSystem: fileSystem, + shouldInclude: { + // Filter out a potential custom module map as + // only the generated module map in the build + // directory is used. + !$0.pathString.hasSuffix("module.modulemap") + } ) - ] - ), + ) ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + // The Objective-C umbrella is virtually placed in the build + // directory via a VFS overlay. This is done to preserve + // relative paths in custom module maps. But when resources + // exist, a warning will appear because the generated resource + // header (also in the build directory) is not included in the + // umbrella directory. Passing `-Wno-incomplete-umbrella` seems + // to prevent it but is not an ideal workaround. + // TODO(ncooke3): Investigate a way around this. + swiftTargetBuildDescription.additionalFlags.append( // Builds Objective-C portion of module. "-import-underlying-module" @@ -1507,14 +1567,19 @@ public final class MixedTargetBuildDescription { "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString ) - // For mixed targets, the Swift half of the target will generate - // an Objective-C compatibility header in the build folder. - // Compiling the Objective-C half of the target may require this - // generated header if the Objective-C half uses any APIs from - // the Swift half. For successful compilation, the directory - // with the generated header is added as a header search path. - clangTargetBuildDescription.additionalFlags.append("-I\(buildArtifactDirectory)") - } + // Overlay the public headers over the build directory and add + // the directory to the header search paths. This will also help + // pickup generated headers in the build directory like the + // generated interop Swift header. + clangTargetBuildDescription.additionalFlags += [ + "-I", + buildArtifactDirectory.pathString, + "-ivfsoverlay", + allProductHeadersPath.pathString + ] + + self.allProductHeadersOverlay = allProductHeadersPath + } } } @@ -2603,8 +2668,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan { case let target as MixedTarget where target.type == .library: // Add the modulemap of the dependency if it has one. if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { - if let moduleMap = dependencyTargetDescription.moduleMap { - clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] + if let moduleMap = dependencyTargetDescription.moduleMap, + let allProductHeadersOverlay = dependencyTargetDescription.allProductHeadersOverlay { + clangTarget.additionalFlags += [ + "-fmodule-map-file=\(moduleMap.pathString)", + "-ivfsoverlay", + allProductHeadersOverlay.pathString + ] } } case let target as SystemLibraryTarget: @@ -2665,11 +2735,14 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Add the path to modulemap of the dependency. Currently we // require that all Mixed targets have a modulemap as it is // required for interoperability. - guard let moduleMap = target.moduleMap else { break } + guard + let moduleMap = target.moduleMap, + let allProductHeadersOverlay = target.allProductHeadersOverlay + else { break } swiftTarget.appendClangFlags( "-fmodule-map-file=\(moduleMap.pathString)", - "-I", - target.clangTargetBuildDescription.clangTarget.includeDir.pathString + "-ivfsoverlay", + allProductHeadersOverlay.pathString ) default: break @@ -2679,6 +2752,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Plan a Mixed target. private func plan(mixedTarget: MixedTargetBuildDescription) throws { + // TODO(ncooke3): Add tests for when mixed target depends on + // - MixedTarget + // - ClangTarget + // - SwiftTarget try plan(swiftTarget: mixedTarget.swiftTargetBuildDescription) try plan(clangTarget: mixedTarget.clangTargetBuildDescription) } diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index e7db58814eb..150f2eec782 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -642,14 +642,6 @@ public final class MixedTarget: Target { ) } - if case .custom = moduleMapType { - throw StringError( - "Target with mixed sources at \(path) contains a custom " + - "module map; targets with mixed language sources " + - "do not support custom module maps." - ) - } - let swiftSources = Sources( paths: sources.paths.filter { path in guard let ext = path.extension else { return false } From 4d54084dc131d0fb770ca2102dd1b34e816a3b3a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 12 Dec 2022 22:21:04 -0500 Subject: [PATCH 041/178] Resolve warning and update tests - This commit fixed the warning from the previous commit. This resolves a TODO. - Since custom module maps are now supported, the unit tests needed to be updated to reflect the updated implementation. --- Sources/Build/BuildPlan.swift | 60 +++++++++++-------- Sources/Build/LLBuildManifestBuilder.swift | 4 +- .../PackageLoading/ModuleMapGenerator.swift | 12 ++-- Tests/BuildTests/BuildPlanTests.swift | 38 ++++++------ .../ModuleMapGenerationTests.swift | 10 ++-- .../PackageBuilderTests.swift | 14 +++-- 6 files changed, 73 insertions(+), 65 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 92a59f6d89d..61a3c642dca 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -358,7 +358,9 @@ public final class ClangTargetBuildDescription { // Write the modified contents to a new module map in the // build directory. - let writePath = tempsPath.appending(component: moduleMapFilename) + let writePath = tempsPath + .appending(component: "Product") + .appending(component: moduleMapFilename) try fileSystem.createDirectory(writePath.parentDirectory, recursive: true) // If the file exists with the identical contents, we don't need to rewrite it. @@ -382,7 +384,9 @@ public final class ClangTargetBuildDescription { // double as the unextended module map. try? fileSystem.copy( from: customModuleMapPath, - to: tempsPath.appending(component: unextendedModuleMapFilename) + to: tempsPath + .appending(component: "Product") + .appending(component: unextendedModuleMapFilename) ) } else { // When not building within a mixed target, use the custom @@ -399,14 +403,13 @@ public final class ClangTargetBuildDescription { fileSystem: fileSystem ) - let generatedInteropHeaderPath = isWithinMixedTarget - ? tempsPath.appending(component: "\(target.c99name)-Swift.h") : nil - - let moduleMapPath = tempsPath.appending(component: moduleMapFilename) + let moduleMapPath = tempsPath + .appending(component: "Product") + .appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: moduleMapPath, - interopHeaderPath: generatedInteropHeaderPath + addSwiftSubmodule: true ) if isWithinMixedTarget { @@ -418,7 +421,9 @@ public final class ClangTargetBuildDescription { // Swift part without the generated header being considered // an input (because it won't exist yet and is an output of // that compilation command). - let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) + let unextendedModuleMapPath = tempsPath + .appending(component: "Product") + .appending(component: unextendedModuleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: unextendedModuleMapPath @@ -1506,6 +1511,8 @@ public final class MixedTargetBuildDescription { // with mappings to the target's module map and public headers. let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath + let buildArtifactProductDirectory = buildArtifactDirectory.appending(component: "Product") + let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath let allProductHeadersPath = buildArtifactDirectory .appending(component: "all-product-headers.yaml") @@ -1515,12 +1522,13 @@ public final class MixedTargetBuildDescription { .appending(component: "unextended-module-overlay.yaml") try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, + name: buildArtifactProductDirectory.pathString, contents: [ VFSOverlay.File( name: moduleMapFilename, - externalContents: - buildArtifactDirectory.appending(component: unextendedModuleMapFilename).pathString + externalContents: buildArtifactProductDirectory + .appending(component: unextendedModuleMapFilename) + .pathString ) ] ) @@ -1530,7 +1538,7 @@ public final class MixedTargetBuildDescription { // name other than `module.modulemap`? try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactDirectory.pathString, + name: buildArtifactProductDirectory.pathString, contents: // Public headers try VFSOverlay.overlayResources( @@ -1542,23 +1550,23 @@ public final class MixedTargetBuildDescription { // directory is used. !$0.pathString.hasSuffix("module.modulemap") } - ) + ) + [ + VFSOverlay.File( + name: generatedInteropHeaderPath.basename, + externalContents: generatedInteropHeaderPath.pathString + ) + ] ) ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - // The Objective-C umbrella is virtually placed in the build - // directory via a VFS overlay. This is done to preserve - // relative paths in custom module maps. But when resources - // exist, a warning will appear because the generated resource - // header (also in the build directory) is not included in the - // umbrella directory. Passing `-Wno-incomplete-umbrella` seems - // to prevent it but is not an ideal workaround. - // TODO(ncooke3): Investigate a way around this. - - swiftTargetBuildDescription.additionalFlags.append( + swiftTargetBuildDescription.additionalFlags += [ // Builds Objective-C portion of module. - "-import-underlying-module" - ) + "-import-underlying-module", + // Add the location of the module's Objective-C module map as + // a header search path. + "-I", + buildArtifactProductDirectory.pathString + ] swiftTargetBuildDescription.appendClangFlags( // Pass VFS overlay to the underlying Clang compiler. @@ -1573,7 +1581,7 @@ public final class MixedTargetBuildDescription { // generated interop Swift header. clangTargetBuildDescription.additionalFlags += [ "-I", - buildArtifactDirectory.pathString, + buildArtifactProductDirectory.pathString, "-ivfsoverlay", allProductHeadersPath.pathString ] diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 5c1f849c69d..28f49ae543a 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -847,7 +847,9 @@ extension LLBuildManifestBuilder { // input to the Clang compile command, which therefore forces the // Swift half of the mixed target to be built first. if target.isWithinMixedTarget { - inputs.append(Node.file(target.tempsPath.appending(component: "\(target.target.name)-Swift.h"))) + inputs += [ + .file(target.tempsPath.appending(component: "\(target.target.name)-Swift.h")) + ] } func addStaticTargetInputs(_ target: ResolvedTarget) { diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 0da3bd208b7..8f777d46f1a 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -180,12 +180,8 @@ public struct ModuleMapGenerator { } /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any - /// diagnostics are added to the receiver's diagnostics engine. - /// - /// The `interopHeaderPath` is the path to the generated interop header used to access a - /// module's Swift API in an Objective-C context (`$(ModuleName)-Swift.h`). If non-`nil`, the - /// created module map will include a submodule to access interop header's API. - public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath, interopHeaderPath: AbsolutePath? = nil) throws { + /// diagnostics are added to the receiver's diagnostics engine.. + public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath, addSwiftSubmodule: Bool = false) throws { let stream = BufferedOutputByteStream() stream <<< "module \(moduleName) {\n" switch type { @@ -196,9 +192,9 @@ public struct ModuleMapGenerator { } stream <<< " export *\n" stream <<< "}\n" - if let interopHeaderPath = interopHeaderPath { + if addSwiftSubmodule { stream <<< "module \(moduleName).Swift {\n" - stream <<< " header \"" <<< interopHeaderPath.pathString <<< "\"\n" + stream <<< " header \"\(moduleName)-Swift.h\"\n" stream <<< " requires objc\n" stream <<< "}\n" } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index f77f44a57be..f334998d100 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1187,7 +1187,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try ext.basicArguments(isCXX: false), args) XCTAssertEqual(try ext.objects, [buildPath.appending(components: "extlib.build", "extlib.c.o")]) - XCTAssertEqual(ext.moduleMap, buildPath.appending(components: "extlib.build", "module.modulemap")) + XCTAssertEqual(ext.moduleMap, buildPath.appending(components: "extlib.build", "Product", "module.modulemap")) let exe = try result.target(for: "exe").clangTarget() args = [] @@ -1206,9 +1206,9 @@ final class BuildPlanTests: XCTestCase { args += [ "-I", Pkg.appending(components: "Sources", "exe", "include").pathString, "-I", Pkg.appending(components: "Sources", "lib", "include").pathString, - "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", + "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", "-I", ExtPkg.appending(components: "Sources", "extlib", "include").pathString, - "-fmodule-map-file=\(buildPath.appending(components: "extlib.build", "module.modulemap"))", + "-fmodule-map-file=\(buildPath.appending(components: "extlib.build", "Product", "module.modulemap"))", ] #if !os(Windows) // FIXME(5473) - modules flags on Windows dropped args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] @@ -1513,8 +1513,9 @@ final class BuildPlanTests: XCTestCase { "-target", "\(defaultTargetTriple)", "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", - "-fmodule-map-file=/path/to/build/debug/lib.build/module.modulemap", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", + "-fmodule-map-file=/path/to/build/debug/lib.build/Product/module.modulemap", + "-Xcc", "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/all-product-headers.yaml", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence ]) @@ -1523,8 +1524,9 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(swiftPartOfLib, [ "-target", "\(defaultTargetTriple)", "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), - "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-Xcc", - "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/all-product-headers.yaml", + "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", + "/path/to/build/debug/lib.build/Product", "-Xcc", "-ivfsoverlay", + "-Xcc", "/path/to/build/debug/lib.build/all-product-headers.yaml", "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/unextended-module-overlay.yaml", "-module-cache-path", @@ -1537,8 +1539,8 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(clangPartOfLib, [ "-fobjc-arc", "-target", "x86_64-apple-macosx10.13", "-g", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", - "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", - "-I/path/to/build/debug/lib.build", + "-fmodule-name=lib", "-I", "/path/to/build/debug/lib.build/Product", + "-ivfsoverlay", "/path/to/build/debug/lib.build/all-product-headers.yaml", "-fmodules-cache-path=/path/to/build/debug/ModuleCache" ]) @@ -1552,7 +1554,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual( try result.target(for: "lib").mixedTarget().moduleMap?.pathString, - buildPath.appending(components: "lib.build", "module.modulemap").pathString + buildPath.appending(components: "lib.build", "Product", "module.modulemap").pathString ) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ @@ -1628,10 +1630,10 @@ final class BuildPlanTests: XCTestCase { args += [hostTriple.isWindows() ? "-gdwarf" : "-g"] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG","-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-g", .anySequence]) + XCTAssertMatch(exe, [.anySequence, "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG","-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-g", .anySequence]) #if os(macOS) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ @@ -1767,7 +1769,7 @@ final class BuildPlanTests: XCTestCase { let buildPath = plan.buildParameters.dataPath.appending(components: "debug") - XCTAssertEqual(try plan.createREPLArguments().sorted(), ["-I\(Dep.appending(components: "Sources", "CDep", "include"))", "-I\(buildPath)", "-I\(buildPath.appending(components: "lib.build"))", "-L\(buildPath)", "-lpkg__REPL", "repl"]) + XCTAssertEqual(try plan.createREPLArguments().sorted(), ["-I\(Dep.appending(components: "Sources", "CDep", "include"))", "-I\(buildPath)", "-I\(buildPath.appending(components: "lib.build", "Product"))", "-L\(buildPath)", "-lpkg__REPL", "repl"]) XCTAssertEqual(plan.graph.allProducts.map({ $0.name }).sorted(), [ "Dep", @@ -2639,7 +2641,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try lib.basicArguments(isCXX: true), expectedLibBasicArgs) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.cpp.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) #if os(macOS) XCTAssertEqual(try result.buildProduct(for: "lib").linkArguments(), [ @@ -3085,7 +3087,7 @@ final class BuildPlanTests: XCTestCase { ] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) let exe = try result.target(for: "exe").swiftTarget().compileArguments() XCTAssertMatch(exe, [ @@ -3095,7 +3097,7 @@ final class BuildPlanTests: XCTestCase { "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", - "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", + "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, @@ -3169,7 +3171,7 @@ final class BuildPlanTests: XCTestCase { ] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) let exe = try result.target(for: "app").swiftTarget().compileArguments() XCTAssertMatch( @@ -3177,7 +3179,7 @@ final class BuildPlanTests: XCTestCase { [ "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG","-Xcc", - "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", + "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-g", .anySequence, diff --git a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift index 32df2b72e7c..30516e899c7 100644 --- a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift +++ b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift @@ -63,16 +63,14 @@ class ModuleMapGeneration: XCTestCase { root.appending(components: "include", "Foo.h").pathString ) - let interopHeaderPath = AbsolutePath(path: "/path/to/Foo-Swift.h") - - ModuleMapTester("Foo", interopHeaderPath: interopHeaderPath, in: fs) { result in + ModuleMapTester("Foo", addSwiftSubmodule: true, in: fs) { result in result.check(contents: """ module Foo { umbrella header "\(root.appending(components: "include", "Foo.h"))" export * } module Foo.Swift { - header "/path/to/Foo-Swift.h" + header "Foo-Swift.h" requires objc } @@ -204,7 +202,7 @@ class ModuleMapGeneration: XCTestCase { } /// Helper function to test module map generation. Given a target name and optionally the name of a public-headers directory, this function determines the module map type of the public-headers directory by examining the contents of a file system and invokes a given block to check the module result (including any diagnostics). -func ModuleMapTester(_ targetName: String, includeDir: String = "include", interopHeaderPath: AbsolutePath? = nil, in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { +func ModuleMapTester(_ targetName: String, includeDir: String = "include", addSwiftSubmodule: Bool = false, in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { let observability = ObservabilitySystem.makeForTesting() // Create a module map generator, and determine the type of module map to use for the header directory. This may emit diagnostics. let moduleMapGenerator = ModuleMapGenerator(targetName: targetName, moduleName: targetName.spm_mangledToC99ExtendedIdentifier(), publicHeadersDir: AbsolutePath.root.appending(component: includeDir), fileSystem: fileSystem) @@ -214,7 +212,7 @@ func ModuleMapTester(_ targetName: String, includeDir: String = "include", inter let generatedModuleMapPath = AbsolutePath.root.appending(components: "module.modulemap") observability.topScope.trap { if let generatedModuleMapType = moduleMapType.generatedModuleMapType { - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath, interopHeaderPath: interopHeaderPath) + try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath, addSwiftSubmodule: addSwiftSubmodule) } } diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index c48f1eec71f..cffd52eb3a3 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -69,7 +69,7 @@ class PackageBuilderTests: XCTestCase { } } - func testMixedSourcesDoNotSupportCustomModuleMap() throws { + func testMixedSourcesWithCustomModuleMap() throws { let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") let fs = InMemoryFileSystem(emptyFiles: @@ -86,11 +86,13 @@ class PackageBuilderTests: XCTestCase { try TargetDescription(name: "foo"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in - diagnostics.check( - diagnostic: "Target with mixed sources at /Sources/foo contains a custom module map; targets with mixed language sources do not support custom module maps.", - severity: .error - ) + PackageBuilderTester(manifest, in: fs) { package, _ in + package.checkModule("foo") { module in + module.check(c99name: "foo", type: .library) + module.checkSources(root: foo.pathString, paths: "Foo.swift", "Bar.m") + module.check(includeDir: foo.appending(component: "include").pathString) + module.check(moduleMapType: .custom(foo.appending(components: "include", "module.modulemap"))) + } } } From 59e015eafd2759c635accf23a0704d81a4c6ffbc Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 12 Dec 2022 22:47:21 -0500 Subject: [PATCH 042/178] Throw error for mixed targets when tool version is insufficient --- Sources/PackageLoading/PackageBuilder.swift | 7 ++++++ .../PackageLoading/TargetSourcesBuilder.swift | 6 +++++ .../PackageBuilderTests.swift | 22 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index eac9d34ac01..c8e7964c0ec 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -194,6 +194,9 @@ extension Target { case emptyName } + /// The target contains an invalid mix of languages (e.g. both Swift and C). + case mixedSources(AbsolutePath) + } } @@ -202,6 +205,10 @@ extension Target.Error: CustomStringConvertible { switch self { case .invalidName(let path, let problem): return "invalid target name at '\(path)'; \(problem)" + case .mixedSources(let path): + // TODO(ncooke3): Update error message with support version. + return "target at '\(path)' contains mixed language source " + + "files; feature not supported until tools version XX" } } } diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index 1f8d763c254..0044cdaae08 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -179,6 +179,12 @@ public struct TargetSourcesBuilder { try diagnoseInfoPlistConflicts(in: resources) diagnoseInvalidResource(in: target.resources) + // It's an error to contain mixed language source files. + // TODO(ncooke3): Update `toolsVersion` when the PR merges. + if sources.containsMixedLanguage, toolsVersion < .v4 { + throw Target.Error.mixedSources(targetPath) + } + return (sources, resources, headers, ignored, others) } diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index cffd52eb3a3..0e8edb6a565 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -41,6 +41,28 @@ class PackageBuilderTests: XCTestCase { } } + func testMixedSourcesWhenUnsupportedToolsVersion() throws { + let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") + + let fs = InMemoryFileSystem(emptyFiles: + foo.appending(components: "Foo.swift").pathString, + foo.appending(components: "bar.c").pathString + ) + + let manifest = Manifest.createRootManifest( + name: "pkg", + path: .root, + toolsVersion: .v3, + targets: [ + try TargetDescription(name: "foo"), + ] + ) + PackageBuilderTester(manifest, in: fs) { _, diagnostics in + // TODO(ncooke3): Update error message with support version. + diagnostics.check(diagnostic: "target at '\(foo)' contains mixed language source files; feature not supported until tools version XX", severity: .error) + } + } + func testMixedSources() throws { let foo: AbsolutePath = "/Sources/foo" From 1b78a25fddb2422d81dbf91598b4b5183efd0c62 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 15 Dec 2022 15:06:06 -0500 Subject: [PATCH 043/178] Expose internal headers to Swift-half of target --- Sources/Build/BuildPlan.swift | 153 +++++++++++++++++++++++++--------- 1 file changed, 113 insertions(+), 40 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 61a3c642dca..6a1c728ebbe 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -331,8 +331,20 @@ public final class ClangTargetBuildDescription { self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) self.isWithinMixedTarget = isWithinMixedTarget - // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. - if target.type == .library { + // Try computing the modulemap path, creating a module map in the + // file system if necessary, for either a Clang source library or a + // Clang source test target being built within a mixed test target, + // The latter of which is allowed to support sharing test utilities + // between mixed language test files within a single test target. + if target.type == .library || (target.type == .test && isWithinMixedTarget) { + + let moduleMapGenerator = ModuleMapGenerator( + targetName: clangTarget.name, + moduleName: clangTarget.c99name, + publicHeadersDir: clangTarget.includeDir, + fileSystem: fileSystem + ) + if case .custom(let customModuleMapPath) = clangTarget.moduleMapType { if isWithinMixedTarget { let customModuleMapContents: String = try fileSystem.readFileContents(customModuleMapPath) @@ -372,21 +384,29 @@ public final class ClangTargetBuildDescription { self.moduleMap = writePath - // The unextended module map purposefully excludes the - // generated Swift header. This is so, when compiling the - // mixed target's Swift part, the generated Swift header is - // not considered an input (the header is an output of - // compiling the mixed target's Swift part). - // - // At this point, the custom module map has been checked - // that it does not include the Swift submodule. For that - // reason, it can be copied to the build directory to - // double as the unextended module map. - try? fileSystem.copy( - from: customModuleMapPath, - to: tempsPath - .appending(component: "Product") - .appending(component: unextendedModuleMapFilename) + // Intermediates module maps + let intermediateModuleMapPath = tempsPath + .appending(component: "Intermediates") + .appending(component: moduleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), + at: intermediateModuleMapPath, + addSwiftSubmodule: true + ) + // The underlying Clang target is building within a Mixed + // language target and needs an auxiliary module map that + // doesn't include the generated interop header from the + // Swift half of the mixed target. This will later allow the + // Clang half of the module to be built when compiling the + // Swift part without the generated header being considered + // an input (because it won't exist yet and is an output of + // that compilation command). + let unextendedModuleMapPath = tempsPath + .appending(component: "Intermediates") + .appending(component: unextendedModuleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), + at: unextendedModuleMapPath ) } else { // When not building within a mixed target, use the custom @@ -396,13 +416,6 @@ public final class ClangTargetBuildDescription { } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { - let moduleMapGenerator = ModuleMapGenerator( - targetName: clangTarget.name, - moduleName: clangTarget.c99name, - publicHeadersDir: clangTarget.includeDir, - fileSystem: fileSystem - ) - let moduleMapPath = tempsPath .appending(component: "Product") .appending(component: moduleMapFilename) @@ -413,6 +426,14 @@ public final class ClangTargetBuildDescription { ) if isWithinMixedTarget { + let intermediateModuleMapPath = tempsPath + .appending(component: "Intermediates") + .appending(component: moduleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), + at: intermediateModuleMapPath, + addSwiftSubmodule: true + ) // The underlying Clang target is building within a Mixed // language target and needs an auxiliary module map that // doesn't include the generated interop header from the @@ -422,10 +443,10 @@ public final class ClangTargetBuildDescription { // an input (because it won't exist yet and is an output of // that compilation command). let unextendedModuleMapPath = tempsPath - .appending(component: "Product") + .appending(component: "Intermediates") .appending(component: unextendedModuleMapFilename) try moduleMapGenerator.generateModuleMap( - type: generatedModuleMapType, + type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), at: unextendedModuleMapPath ) } @@ -1235,7 +1256,8 @@ public final class SwiftTargetBuildDescription { /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { - return buildParameters.triple.isDarwin() && target.type == .library + return buildParameters.triple.isDarwin() && + (target.type == .library || target.type == .test && isWithinMixedTarget) } private func writeOutputFileMap() throws -> AbsolutePath { @@ -1506,27 +1528,34 @@ public final class MixedTargetBuildDescription { isWithinMixedTarget: true ) - if target.type == .library { + if target.type == .library || target.type == .test { // Compiling the mixed target will require a Clang VFS overlay file // with mappings to the target's module map and public headers. let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir - let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath - let buildArtifactProductDirectory = buildArtifactDirectory.appending(component: "Product") let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath - let allProductHeadersPath = buildArtifactDirectory + + let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath + let allProductHeadersPathProduct = buildArtifactDirectory + .appending(component: "Product") + .appending(component: "all-product-headers.yaml") + + let buildArtifactIntermediatesDirectory = buildArtifactDirectory + .appending(component: "Intermediates") + + let allProductHeadersPath = buildArtifactIntermediatesDirectory .appending(component: "all-product-headers.yaml") // The auxilliary module map is passed to the Clang compiler // via a VFS overlay, represented by the below YAML file. - let unextendedModuleMapOverlayPath = buildArtifactDirectory + let unextendedModuleMapOverlayPath = buildArtifactIntermediatesDirectory .appending(component: "unextended-module-overlay.yaml") try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactProductDirectory.pathString, + name: buildArtifactIntermediatesDirectory.pathString, contents: [ VFSOverlay.File( name: moduleMapFilename, - externalContents: buildArtifactProductDirectory + externalContents: buildArtifactIntermediatesDirectory .appending(component: unextendedModuleMapFilename) .pathString ) @@ -1534,14 +1563,58 @@ public final class MixedTargetBuildDescription { ) ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + // TODO(ncooke3): Use @resultBuilder for this. + var roots = [ + VFSOverlay.Directory( + name: buildArtifactIntermediatesDirectory.pathString, + contents: + // All headers + try VFSOverlay.overlayResources( + // TODO(ncooke3): Figure out a way to get the root sources path. + directoryPath: publicHeadersPath.parentDirectory, + fileSystem: fileSystem, + shouldInclude: { + // Only include headers. + fileSystem.isDirectory($0) || $0.pathString.hasSuffix(".h") + } + ) + [ + VFSOverlay.File( + name: generatedInteropHeaderPath.basename, + externalContents: generatedInteropHeaderPath.pathString + ) + ] + ) + ] + + if case .custom(let customModuleMapPath) = clangTargetBuildDescription.clangTarget.moduleMapType { + roots.append( + VFSOverlay.Directory( + name: customModuleMapPath.parentDirectory.pathString, + contents: [ + VFSOverlay.File( + name: customModuleMapPath.basename, + externalContents: buildArtifactIntermediatesDirectory + .appending(component: "module.modulemap") + .pathString + ) + ] + ) + ) + } + // TODO(ncooke3): What happens if a custom module map exists with a // name other than `module.modulemap`? + try VFSOverlay(roots: roots) + .write(to: allProductHeadersPath, fileSystem: fileSystem) + + // For Product directory try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactProductDirectory.pathString, + name: buildArtifactDirectory + .appending(component: "Product").pathString, contents: // Public headers - try VFSOverlay.overlayResources( + try VFSOverlay.overlayResources( directoryPath: publicHeadersPath, fileSystem: fileSystem, shouldInclude: { @@ -1557,7 +1630,7 @@ public final class MixedTargetBuildDescription { ) ] ) - ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + ]).write(to: allProductHeadersPathProduct, fileSystem: fileSystem) swiftTargetBuildDescription.additionalFlags += [ // Builds Objective-C portion of module. @@ -1565,7 +1638,7 @@ public final class MixedTargetBuildDescription { // Add the location of the module's Objective-C module map as // a header search path. "-I", - buildArtifactProductDirectory.pathString + buildArtifactIntermediatesDirectory.pathString ] swiftTargetBuildDescription.appendClangFlags( @@ -1581,12 +1654,12 @@ public final class MixedTargetBuildDescription { // generated interop Swift header. clangTargetBuildDescription.additionalFlags += [ "-I", - buildArtifactProductDirectory.pathString, + buildArtifactIntermediatesDirectory.pathString, "-ivfsoverlay", allProductHeadersPath.pathString ] - self.allProductHeadersOverlay = allProductHeadersPath + self.allProductHeadersOverlay = allProductHeadersPathProduct } } } From 713d618f4f44486ae9eaf518b55f5de944872e4d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 19 Dec 2022 17:36:10 -0500 Subject: [PATCH 044/178] Add initial batch of Fixture tests - Adds a suite of tests for testing mixed target support. These tests also demonstrate the various ways a mixed target can be structured. - Commit some implementation fixes for issues surfaced when developing the tests. - Add TODOs for outstanding work items. --- .../Sources/Driver.m | 9 + .../Sources/Engine.swift | 4 + .../Sources/NewCar.swift | 14 + .../Sources/OldCar.m | 21 ++ .../Sources/Public/Driver.h | 6 + .../Sources/Public/OldCar.h | 14 + .../Sources/Transmission.h | 10 + .../Sources/Transmission.m | 8 + .../BasicMixedTargets/Package.swift | 159 ++++++++++ .../MixedTargets/BasicMixedTargets/README.md | 23 ++ .../Sources/BasicMixedTarget/Driver.m | 6 + .../Sources/BasicMixedTarget/Engine.swift | 4 + .../Sources/BasicMixedTarget/NewCar.swift | 14 + .../Sources/BasicMixedTarget/OldCar.m | 16 + .../Sources/BasicMixedTarget/Transmission.h | 10 + .../Sources/BasicMixedTarget/Transmission.m | 6 + .../Sources/BasicMixedTarget/include/Driver.h | 6 + .../Sources/BasicMixedTarget/include/OldCar.h | 14 + .../Sources/ClangTarget/Vessel.m | 6 + .../Sources/ClangTarget/include/Vessel.h | 6 + .../JunkYard.m | 15 + .../include/JunkYard.h | 4 + .../NewBoat.swift | 14 + .../MixedTargetDependsOnClangTarget/OldBoat.m | 16 + .../include/OldBoat.h | 6 + .../NewBoat.swift | 10 + .../MixedTargetDependsOnMixedTarget/OldBoat.m | 14 + .../include/OldBoat.h | 4 + .../NewBoat.swift | 7 + .../MixedTargetDependsOnSwiftTarget/OldBoat.m | 16 + .../include/OldBoat.h | 7 + .../Sources/MixedTargetWithC/factorial.swift | 5 + .../Sources/MixedTargetWithC/find_factorial.c | 6 + .../MixedTargetWithC/include/find_factorial.h | 1 + .../Sources/MixedTargetWithCXX/Calculator.mm | 15 + .../MixedTargetWithCXX/Factorial.swift | 5 + .../MixedTargetWithCXX/FactorialFinder.cpp | 6 + .../MixedTargetWithCXX/FactorialFinder.hpp | 5 + .../MixedTargetWithCXX/include/Calculator.h | 5 + .../MixedTargetWithCustomModuleMap/Driver.m | 6 + .../Engine.swift | 4 + .../NewCar.swift | 8 + .../MixedTargetWithCustomModuleMap/OldCar.m | 9 + .../include/Driver.h | 6 + .../include/MixedTarget.h | 2 + .../include/OldCar.h | 14 + .../include/module.modulemap | 4 + .../Driver.m | 6 + .../Engine.swift | 4 + .../NewCar.swift | 8 + .../OldCar.m | 9 + .../foo.txt | 1 + .../include/Driver.h | 6 + .../include/MixedTarget.h | 2 + .../include/OldCar.h | 14 + .../include/module.modulemap | 4 + .../Blah/Public/Driver/Driver.h | 6 + .../Blah/Public/OldCar.h | 18 ++ .../Driver.m | 10 + .../Engine.swift | 4 + .../NewCar.swift | 14 + .../OldCar.m | 19 ++ .../Transmission.h | 10 + .../Transmission.m | 6 + .../Driver.h | 6 + .../Driver.m | 6 + .../Engine.swift | 4 + .../NewCar.swift | 10 + .../OldCar.h | 14 + .../OldCar.m | 9 + .../MixedTargetWithNonPublicHeaders/Bar.h | 5 + .../MixedTargetWithNonPublicHeaders/Bar.m | 6 + .../MixedTargetWithNonPublicHeaders/Bat.swift | 7 + .../MixedTargetWithNonPublicHeaders/Baz.m | 6 + .../Foo/Foo/Foo.h | 5 + .../Foo/Foo/Foo.m | 6 + .../include/Baz.h | 4 + .../ObjcResourceReader.m | 18 ++ .../SwiftResourceReader.swift | 10 + .../Sources/MixedTargetWithResources/foo.txt | 1 + .../include/ObjcResourceReader.h | 6 + .../NewCar.swift | 4 + .../OldCar.m | 8 + .../include/OldCar.h | 4 + .../Sources/SwiftTarget/Vessel.swift | 3 + .../JunkYard.swift | 10 + .../BasicMixedTargetTests.swift | 25 ++ .../ObjcBasicMixedTargetTests.m | 22 ++ ...getDoesNotSupportModuleAliasingTests.swift | 1 + .../MixedTargetWithCTests.swift | 8 + .../MixedTargetWithCXXTests.swift | 8 + ...MixedTargetWithNonPublicHeadersTests.swift | 17 ++ .../MixedTargetWithResourcesTests.swift | 18 ++ .../MixedTargetTests.swift | 17 ++ .../ObjcMixedTargetTests.m | 23 ++ .../ObjcTestHelper.h | 4 + .../ObjcTestHelper.m | 7 + .../OtherObjcTestHelper.m | 7 + .../Subdirectory2/OtherObjcTestHelper.h | 7 + .../SwiftTestHelper.swift | 3 + ...dersCanBeTestedViaHeaderSearchPathsTests.m | 18 ++ Sources/Build/BuildPlan.swift | 7 +- Sources/PackageLoading/PackageBuilder.swift | 23 +- Sources/SPMTestSupport/XCTAssertHelpers.swift | 3 +- Tests/BuildTests/BuildPlanTests.swift | 7 + .../BuildTests/ModuleAliasingBuildTests.swift | 5 +- Tests/FunctionalTests/MixedTargetTests.swift | 274 ++++++++++++++++++ 107 files changed, 1358 insertions(+), 9 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Package.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/README.md create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/Vessel.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/OldBoat.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/factorial.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/find_factorial.c create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/include/find_factorial.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Calculator.mm create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Factorial.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/include/Calculator.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTarget.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Baz.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/ObjcResourceReader.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/SwiftResourceReader.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/foo.txt create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/include/ObjcResourceReader.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTarget/Vessel.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m create mode 100644 Tests/FunctionalTests/MixedTargetTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Driver.m new file mode 100644 index 00000000000..776934b7d1e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Driver.m @@ -0,0 +1,9 @@ +#import + +// All three import statements should be supported. +#import "Driver.h" +#import "Public/Driver.h" +#import "MixedTargetWithCustomPaths/Sources/Public/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift new file mode 100644 index 00000000000..fbf2e453b4f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift @@ -0,0 +1,14 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var driver: Driver? = nil + var transmission: Transmission? = nil + var hasStickShift: Bool { + return transmission != nil && transmission!.transmissionKind == .manual + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m new file mode 100644 index 00000000000..1948c11b3c5 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m @@ -0,0 +1,21 @@ +#import + +// All three import statements should be supported. +#import "OldCar.h" +#import "Public/OldCar.h" +#import "MixedTargetWithCustomPaths/Sources/Public/OldCar.h" + + +// Import the Swift half of the module. +#import "MixedTargetWithCustomPaths-Swift.h" + +// Both import statements should be supported. +#import "Transmission.h" +#import "MixedTargetWithCustomPaths/Sources/Transmission.h" + +@interface OldCar () +@property(nonatomic) Transmission *transmission; +@end + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h new file mode 100644 index 00000000000..b75ae54cce1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h @@ -0,0 +1,14 @@ +#import + +#import "Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h new file mode 100644 index 00000000000..4f0f6da629f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m new file mode 100644 index 00000000000..d649582b225 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m @@ -0,0 +1,8 @@ +#import + +// Both import statements should be supported. +#import "Transmission.h" +#import "MixedTargetWithCustomPaths/Sources/Transmission.h" + +@implementation Transmission +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift new file mode 100644 index 00000000000..c21bd83fe6a --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -0,0 +1,159 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "MixedTargets", + products: [ + .library( + name: "BasicMixedTarget", + targets: ["BasicMixedTarget"] + ), + .library( + name: "StaticallyLinkedBasicMixedTarget", + type: .static, + targets: ["BasicMixedTarget"] + ), + .library( + name: "DynamicallyLinkedBasicMixedTarget", + type: .dynamic, + targets: ["BasicMixedTarget"] + ) + ], + dependencies: [], + targets: [ + // MARK: - BasicMixedTarget + .target( + name: "BasicMixedTarget" + ), + .testTarget( + name: "BasicMixedTargetTests", + dependencies: ["BasicMixedTarget"] + ), + + // MARK: - MixedTargetWithResources + .target( + name: "MixedTargetWithResources", + resources: [ + .process("foo.txt") + ] + ), + .testTarget( + name: "MixedTargetWithResourcesTests", + dependencies: ["MixedTargetWithResources"] + ), + + // MARK: - MixedTargetWithCustomModuleMap + // TODO(ncooke3): Play around and try to break this target with a more + // complex module map. + .target( + name: "MixedTargetWithCustomModuleMap" + ), + + // MARK: - MixedTargetWithCustomModuleMapAndResources + .target( + name: "MixedTargetWithCustomModuleMapAndResources", + resources: [ + .process("foo.txt") + ] + ), + + // MARK: - MixedTargetWithC++ + .target( + name: "MixedTargetWithCXX" + ), + .testTarget( + name: "MixedTargetWithCXXTests", + dependencies: ["MixedTargetWithCXX"] + ), + + // MARK: - MixedTargetWithC + .target( + name: "MixedTargetWithC" + ), + .testTarget( + name: "MixedTargetWithCTests", + dependencies: ["MixedTargetWithC"] + ), + + // MARK: - MixedTargetWithNonPublicHeaders + .target( + name: "MixedTargetWithNonPublicHeaders" + ), + // This test target should fail to build. See + // `testNonPublicHeadersAreNotVisibleFromOutsideOfTarget`. + .testTarget( + name: "MixedTargetWithNonPublicHeadersTests", + dependencies: ["MixedTargetWithNonPublicHeaders"] + ), + + // MARK: - MixedTargetWithCustomPaths + .target( + name: "MixedTargetWithCustomPaths", + path: "MixedTargetWithCustomPaths/Sources", + publicHeadersPath: "Public", + cSettings: [ + .headerSearchPath("../../") + ] + ), + + // MARK: - MixedTargetWithNestedPublicHeaders + .target( + name: "MixedTargetWithNestedPublicHeaders", + publicHeadersPath: "Blah/Public" + ), + + // MARK: - MixedTargetWithNoPublicObjectiveCHeaders + // TODO(ncooke3): Re-enable when corresponding test is working. +// .target( +// name: "MixedTargetWithNoPublicObjectiveCHeaders" +// ), + + // MARK: - MixedTestTargetWithSharedTestUtilities + .testTarget( + name: "MixedTestTargetWithSharedUtilitiesTests" + ), + + // MARK: - PrivateHeadersCanBeTestedViaHeaderSearchPathsTests + .testTarget( + name: "PrivateHeadersCanBeTestedViaHeaderSearchPathsTests", + dependencies: ["MixedTargetWithNonPublicHeaders"], + cSettings: [ + // Adding a header search path at the root of the package will + // enable the Objective-C tests to import private headers. + .headerSearchPath("../../") + ] + ), + + // MARK: - MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource + .target( + name: "MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource" + ), + + // MARK: - Targets for testing the integration of a mixed target + .target( + name: "ClangTargetDependsOnMixedTarget", + dependencies: ["BasicMixedTarget"] + ), + .target( + name: "SwiftTargetDependsOnMixedTarget", + dependencies: ["BasicMixedTarget"] + ), + .target( + name: "MixedTargetDependsOnMixedTarget", + dependencies: ["BasicMixedTarget"] + ), + .target( + name: "MixedTargetDependsOnClangTarget", + dependencies: ["ClangTarget"] + ), + .target( + name: "MixedTargetDependsOnSwiftTarget", + dependencies: ["SwiftTarget"] + ), + // The below two targets are used for testing the above targets. + .target(name: "SwiftTarget"), + .target(name: "ClangTarget") + + ] +) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/README.md b/Fixtures/MixedTargets/BasicMixedTargets/README.md new file mode 100644 index 00000000000..cc59cbe9bb9 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/README.md @@ -0,0 +1,23 @@ +# BasicMixedTargets + +A collection of targets to test SPM's support of mixed language targets. + +## BasicMixedTarget +Represents a simple mixed package where: +- Swift half of the target used types from the Objective-C half of the module +- Objective-C half of the target used types from the Swift half of the module + +## MixedTargetWithResources +Represents a simple mixed package with a bundled resource where: +- resource can be accessed from an Swift context using `Bundle.module` +- resource can be accessed from an Objective-C context using + `SWIFTPM_MODULE_BUNDLE` macro + +## MixedTargetWithCustomModuleMap +- Represents a simple mixed package that contains a custom module map. + +## MixedTargetWithCustomModuleMapAndResources +- Represents a simple mixed package that contains a custom module map and + a bundled resource. + +TODO(ncooke3): Fill the rest of this out. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Driver.m new file mode 100644 index 00000000000..2fc73e43de1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Driver.m @@ -0,0 +1,6 @@ +#import + +#import "include/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift new file mode 100644 index 00000000000..fbf2e453b4f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift @@ -0,0 +1,14 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var driver: Driver? = nil + var transmission: Transmission? = nil + var hasStickShift: Bool { + return transmission != nil && transmission!.transmissionKind == .manual + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m new file mode 100644 index 00000000000..4b86a46f146 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m @@ -0,0 +1,16 @@ +#import + +#import "OldCar.h" +#import "include/OldCar.h" + +// Import the Swift half of the module. +#import "BasicMixedTarget-Swift.h" + +#import "Transmission.h" + +@interface OldCar () +@property(nonatomic) Transmission *transmission; +@end + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h new file mode 100644 index 00000000000..4f0f6da629f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m new file mode 100644 index 00000000000..be6350b11ed --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m @@ -0,0 +1,6 @@ +#import + +#import "Transmission.h" + +@implementation Transmission +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h new file mode 100644 index 00000000000..b75ae54cce1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h @@ -0,0 +1,14 @@ +#import + +#import "Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/Vessel.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/Vessel.m new file mode 100644 index 00000000000..d5a92f7251b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/Vessel.m @@ -0,0 +1,6 @@ +#import + +#import "include/Vessel.h" + +@implementation Vessel +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h new file mode 100644 index 00000000000..1e32755be5e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h @@ -0,0 +1,6 @@ +#import + +@interface Vessel : NSObject +@property (assign, getter=hasLifeJackets) BOOL lifeJackets; +@end + diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m new file mode 100644 index 00000000000..97eabe846ba --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m @@ -0,0 +1,15 @@ +#import + +#import "include/JunkYard.h" + +@import BasicMixedTarget; + +@interface JunkYard () +// The below types come from the `BasicMixedTarget` module. +@property(nullable) Engine *engine; +@property(nullable) Driver *driver; +@property(nullable) OldCar *oldCar; +@end + +@implementation JunkYard +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h new file mode 100644 index 00000000000..eb0ff084158 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h @@ -0,0 +1,4 @@ +#import + +@interface JunkYard : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift new file mode 100644 index 00000000000..de0f955a5fe --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift @@ -0,0 +1,14 @@ +import Foundation +import ClangTarget + +@objc public class NewBoat: Vessel { + + func checkForLifeJackets() { + if hasLifeJackets { + print("Life jackets on board!") + } else { + print("Life jackets missing!") + } + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/OldBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/OldBoat.m new file mode 100644 index 00000000000..5fdf97b6acb --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/OldBoat.m @@ -0,0 +1,16 @@ +#import + +#import "include/OldBoat.h" + +@implementation OldBoat + +- (void)checkForLifeJackets { + // Check that property from superclass is visible. + if (self.hasLifeJackets) { + NSLog(@"Life jackets on board!"); + } else { + NSLog(@"Life jackets missing!"); + } +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h new file mode 100644 index 00000000000..4facb42d276 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h @@ -0,0 +1,6 @@ +#import + +@import ClangTarget; + +@interface OldBoat : Vessel +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift new file mode 100644 index 00000000000..984eafd1d98 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift @@ -0,0 +1,10 @@ +import Foundation +import BasicMixedTarget + +public class NewBoat { + // The below types comes from the `BasicMixedTarget` module`. + var engine: Engine? = nil + var driver: Driver? = nil + + public init() {} +} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m new file mode 100644 index 00000000000..821df31b02b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m @@ -0,0 +1,14 @@ +#import + +#import "include/OldBoat.h" + +@import BasicMixedTarget; + +@interface OldBoat () +// The below types comes from the `BasicMixedTarget` module`. +@property(nonatomic, strong) Engine *engine; +@property(nonatomic, strong) Driver *driver; +@end + +@implementation OldBoat +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h new file mode 100644 index 00000000000..984adffd288 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h @@ -0,0 +1,4 @@ +#import + +@interface OldBoat : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift new file mode 100644 index 00000000000..8cb5992ed01 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift @@ -0,0 +1,7 @@ +import Foundation +import SwiftTarget + +@objc public class NewBoat: NSObject { + // The below types comes from the `SwiftTarget` module`. + var lifeJacket: [LifeJacket] = [] +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m new file mode 100644 index 00000000000..665f617f3a7 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m @@ -0,0 +1,16 @@ +#import + +#import "include/OldBoat.h" + +@implementation OldBoat + +- (void)checkForLifeJackets { + // Check that `LifeJacket` type from `SwiftTarget` is visible. + if (self.lifeJackets.count > 0) { + NSLog(@"Life jackets on board!"); + } else { + NSLog(@"Life jackets missing!"); + } +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h new file mode 100644 index 00000000000..b3fe04a7e44 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h @@ -0,0 +1,7 @@ +#import + +@import SwiftTarget; + +@interface OldBoat : NSObject +@property(nonatomic, readonly) NSArray *lifeJackets; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/factorial.swift new file mode 100644 index 00000000000..844a0f7a585 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/factorial.swift @@ -0,0 +1,5 @@ +import Foundation + +public func factorial(_ x: Int32) -> CLong { + return find_factorial(x) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/find_factorial.c b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/find_factorial.c new file mode 100644 index 00000000000..7c7895a0c3c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/find_factorial.c @@ -0,0 +1,6 @@ +#include "include/find_factorial.h" + +long find_factorial(int x) { + if (x == 0 || x == 1) return 1; + return x * find_factorial(x-1); +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/include/find_factorial.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/include/find_factorial.h new file mode 100644 index 00000000000..4f15e5d8adc --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/include/find_factorial.h @@ -0,0 +1 @@ +long find_factorial(int x); diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Calculator.mm b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Calculator.mm new file mode 100644 index 00000000000..c06770ecd5e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Calculator.mm @@ -0,0 +1,15 @@ +#import + +#import "include/Calculator.h" + +// Import C++ header. +#import "FactorialFinder.hpp" + +@implementation Calculator + ++ (long)factorialForInt:(int)integer { + FactorialFinder ff; + return ff.factorial(integer); +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Factorial.swift new file mode 100644 index 00000000000..51ba08e4d75 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Factorial.swift @@ -0,0 +1,5 @@ +import Foundation + +public func factorial(_ x: Int32) -> Int { + return Calculator.factorial(for: x) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.cpp new file mode 100644 index 00000000000..3e5e6bfe031 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.cpp @@ -0,0 +1,6 @@ +#include "FactorialFinder.hpp" + +long FactorialFinder::factorial(int n) { + if (n == 0 || n == 1) return 1; + return n * factorial(n-1); +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp new file mode 100644 index 00000000000..533730e7c97 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp @@ -0,0 +1,5 @@ +class FactorialFinder +{ +public: + long factorial(int n); +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/include/Calculator.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/include/Calculator.h new file mode 100644 index 00000000000..7de7e3bbe1d --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/include/Calculator.h @@ -0,0 +1,5 @@ +#import + +@interface Calculator : NSObject ++ (long)factorialForInt:(int)integer; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m new file mode 100644 index 00000000000..2fc73e43de1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m @@ -0,0 +1,6 @@ +#import + +#import "include/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift new file mode 100644 index 00000000000..3f5907ea423 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift @@ -0,0 +1,8 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // `Driver` is defined in Objective-C. + var driver: Driver? = nil +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m new file mode 100644 index 00000000000..1054933a04f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m @@ -0,0 +1,9 @@ +#import + +#import "include/OldCar.h" + +// Import the Swift half of the module. +#import "MixedTargetWithCustomModuleMap-Swift.h" + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h new file mode 100644 index 00000000000..cbe8b7ebef6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h @@ -0,0 +1,2 @@ +#import "OldCar.h" +#import "Driver.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h new file mode 100644 index 00000000000..b75ae54cce1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h @@ -0,0 +1,14 @@ +#import + +#import "Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap new file mode 100644 index 00000000000..0c7823261fc --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap @@ -0,0 +1,4 @@ +module MixedTargetWithCustomModuleMap { + umbrella header "MixedTarget.h" + export * +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m new file mode 100644 index 00000000000..2fc73e43de1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m @@ -0,0 +1,6 @@ +#import + +#import "include/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift new file mode 100644 index 00000000000..3f5907ea423 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift @@ -0,0 +1,8 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // `Driver` is defined in Objective-C. + var driver: Driver? = nil +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m new file mode 100644 index 00000000000..24899632a18 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m @@ -0,0 +1,9 @@ +#import + +#import "include/OldCar.h" + +// Import the Swift half of the module. +#import "MixedTargetWithCustomModuleMapAndResources-Swift.h" + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt new file mode 100644 index 00000000000..cd0875583aa --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt @@ -0,0 +1 @@ +Hello world! diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTarget.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTarget.h new file mode 100644 index 00000000000..cbe8b7ebef6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTarget.h @@ -0,0 +1,2 @@ +#import "OldCar.h" +#import "Driver.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h new file mode 100644 index 00000000000..b75ae54cce1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h @@ -0,0 +1,14 @@ +#import + +#import "Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap new file mode 100644 index 00000000000..0c7823261fc --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap @@ -0,0 +1,4 @@ +module MixedTargetWithCustomModuleMap { + umbrella header "MixedTarget.h" + export * +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h new file mode 100644 index 00000000000..46b7bcb25dc --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h @@ -0,0 +1,18 @@ +#import + +// Both import statements should be supported. +// - This one is from the root of the `publicHeadersPath`. +#import "Driver/Driver.h" +// - This one is from the root of the target's sources directory. +#import "Blah/Public/Driver/Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Driver.m new file mode 100644 index 00000000000..6fa02dafaf7 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Driver.m @@ -0,0 +1,10 @@ +#import + +// Both import statements should be supported. +// - This one is from the root of the `publicHeadersPath`. +#import "Driver/Driver.h" +// - This one is from the root of the target's sources directory. +#import "Blah/Public/Driver/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift new file mode 100644 index 00000000000..fbf2e453b4f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift @@ -0,0 +1,14 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var driver: Driver? = nil + var transmission: Transmission? = nil + var hasStickShift: Bool { + return transmission != nil && transmission!.transmissionKind == .manual + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m new file mode 100644 index 00000000000..9a0d468b3ff --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m @@ -0,0 +1,19 @@ +#import + +// Both import statements should be supported. +// - This one is from the root of the `publicHeadersPath`. +#import "OldCar.h" +// - This one is from the root of the target's sources directory. +#import "Blah/Public/OldCar.h" + +// Import the Swift half of the module. +#import "BasicMixedTarget-Swift.h" + +#import "Transmission.h" + +@interface OldCar () +@property(nonatomic) Transmission *transmission; +@end + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h new file mode 100644 index 00000000000..4f0f6da629f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m new file mode 100644 index 00000000000..be6350b11ed --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m @@ -0,0 +1,6 @@ +#import + +#import "Transmission.h" + +@implementation Transmission +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m new file mode 100644 index 00000000000..666a5c70cc6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m @@ -0,0 +1,6 @@ +#import + +#import "Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift new file mode 100644 index 00000000000..07ea404b85c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift @@ -0,0 +1,10 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // `Driver` is defined in Objective-C. + var driver: Driver? = nil + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h new file mode 100644 index 00000000000..b75ae54cce1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h @@ -0,0 +1,14 @@ +#import + +#import "Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m new file mode 100644 index 00000000000..203f52a7bb6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m @@ -0,0 +1,9 @@ +#import + +#import "OldCar.h" + +// Import the Swift half of the module. +#import "BasicMixedTarget-Swift.h" + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.h new file mode 100644 index 00000000000..fcafb67fdf6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.h @@ -0,0 +1,5 @@ +#import + +// This is a non-public header. +@interface Bar : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.m new file mode 100644 index 00000000000..b9947d8eed4 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.m @@ -0,0 +1,6 @@ +#import + +#import "Bar.h" + +@implementation Bar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift new file mode 100644 index 00000000000..d02a706f703 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift @@ -0,0 +1,7 @@ +import Foundation + +public class Bat { + // The following Objective-C types are defined in non-public headers. + let foo: Foo? = nil + let bar: Bar? = nil +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Baz.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Baz.m new file mode 100644 index 00000000000..6bdd81d4941 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Baz.m @@ -0,0 +1,6 @@ +#import + +#import "include/Baz.h" + +@implementation Baz +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h new file mode 100644 index 00000000000..4b35d352e75 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h @@ -0,0 +1,5 @@ +#import + +// This is a non-public header. +@interface Foo : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m new file mode 100644 index 00000000000..cf98126670e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m @@ -0,0 +1,6 @@ +#import + +#import "Foo/Foo/Foo.h" + +@implementation Foo +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h new file mode 100644 index 00000000000..efc750d007f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h @@ -0,0 +1,4 @@ +#import + +@interface Baz : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/ObjcResourceReader.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/ObjcResourceReader.m new file mode 100644 index 00000000000..92983302e7a --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/ObjcResourceReader.m @@ -0,0 +1,18 @@ +#import + +#import "include/ObjcResourceReader.h" + +@implementation ObjcResourceReader + ++ (NSString *)readResource:(NSString *)resource + ofType:(NSString *)type { + // The `SWIFTPM_MODULE_BUNDLE` macro is generated by SwiftPM to provide + // access to the target's bundled resources. + NSBundle *moduleBundle = SWIFTPM_MODULE_BUNDLE; + NSString *path = [moduleBundle pathForResource:resource ofType:type]; + return [[NSString alloc] initWithContentsOfFile:path + encoding:NSUTF8StringEncoding + error:nil]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/SwiftResourceReader.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/SwiftResourceReader.swift new file mode 100644 index 00000000000..054c5996a3c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/SwiftResourceReader.swift @@ -0,0 +1,10 @@ +import Foundation + +public class SwiftResourceReader { + public static func read(_ resource: String, type: String?) throws -> String { + // The `Bundle.module` is generated by SwiftPM to provide access to the + // target's bundled resources. + let path = Bundle.module.path(forResource: resource, ofType: type) + return try String(contentsOfFile: path!, encoding: .utf8) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/foo.txt b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/foo.txt new file mode 100644 index 00000000000..cd0875583aa --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/foo.txt @@ -0,0 +1 @@ +Hello world! diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/include/ObjcResourceReader.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/include/ObjcResourceReader.h new file mode 100644 index 00000000000..b0db1bb491b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/include/ObjcResourceReader.h @@ -0,0 +1,6 @@ +#import + +@interface ObjcResourceReader : NSObject ++ (NSString *)readResource:(NSString*)resource + ofType:(NSString*)type; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift new file mode 100644 index 00000000000..e8c724a2bce --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift @@ -0,0 +1,4 @@ +import Foundation + +@objc public class NewCar: NSObject { +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m new file mode 100644 index 00000000000..b0e6d0739c4 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m @@ -0,0 +1,8 @@ +#import + +// The following import statements should be supported. +#import "OldCar.h" +#import "include/OldCar.h" + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h new file mode 100644 index 00000000000..4f158fe5141 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h @@ -0,0 +1,4 @@ +#import + +@interface OldCar : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTarget/Vessel.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTarget/Vessel.swift new file mode 100644 index 00000000000..137f890fd55 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTarget/Vessel.swift @@ -0,0 +1,3 @@ +import Foundation + +@objc public class LifeJacket: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift new file mode 100644 index 00000000000..b4e2474fd87 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift @@ -0,0 +1,10 @@ +import Foundation +import BasicMixedTarget + +public struct JunkYard { + // The below types come from the `BasicMixedTarget` module. + var newCar: NewCar? + var engine: Engine? + var oldCar: OldCar? + var driver: Driver? +} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift new file mode 100644 index 00000000000..940ea537328 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift @@ -0,0 +1,25 @@ +import XCTest +import BasicMixedTarget + +final class BasicMixedTargetTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = NewCar() + let _ = Engine() + } + + func testPublicObjcAPI() throws { + // Check that Objective-C API surface is exposed... + let _ = OldCar() + let _ = Driver() + } + + func testModulePrefixingWorks() throws { + let _ = BasicMixedTarget.NewCar() + let _ = BasicMixedTarget.Engine() + let _ = BasicMixedTarget.OldCar() + let _ = BasicMixedTarget.Driver() + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m new file mode 100644 index 00000000000..7dc7de076ad --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m @@ -0,0 +1,22 @@ +#import + +@import BasicMixedTarget; + +@interface ObjcBasicMixedTargetTests : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetTests + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + OldCar *oldCar = [[OldCar alloc] init]; + Driver *driver = [[Driver alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift new file mode 100644 index 00000000000..6ac541db22e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift @@ -0,0 +1 @@ +// Intentially left blank. \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift new file mode 100644 index 00000000000..f8b9e16f7ed --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift @@ -0,0 +1,8 @@ +import XCTest +import MixedTargetWithC + +final class MixedTargetWithCTests: XCTestCase { + func testFactorial() throws { + XCTAssertEqual(factorial(5), 120) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift new file mode 100644 index 00000000000..eb541659e10 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift @@ -0,0 +1,8 @@ +import XCTest +import MixedTargetWithCXX + +final class MixedTargetWithCXXTests: XCTestCase { + func testFactorial() throws { + XCTAssertEqual(factorial(5), 120) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift new file mode 100644 index 00000000000..a74012e5d4a --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift @@ -0,0 +1,17 @@ +import XCTest +import MixedTargetWithNonPublicHeaders + +#if MIXED_TARGET_WITH_C_TESTS + +final class MixedTargetWithCTests: XCTestCase { + func testInternalObjcTypesAreNotExposed() throws { + // The following Objective-C types are defined in non-public headers + // within the `MixedTargetWithNonPublicHeaders` target. They should not be + // visible in this context and should cause a failure when building the + // test target associated with this file. + let _ = Foo() + let _ = Bar() + } +} + +#endif // MIXED_TARGET_WITH_C_TESTS diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift new file mode 100644 index 00000000000..2582cc2e335 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift @@ -0,0 +1,18 @@ +import XCTest +import MixedTargetWithResources + +final class MixedTargetWithResourcesTests: XCTestCase { + func testResourceCanBeAccessed() throws { + // From Swift context... + XCTAssertEqual( + try SwiftResourceReader.read("foo", type: "txt"), + "Hello world!\n" + ) + + // From Objective-C context... + XCTAssertEqual( + ObjcResourceReader.readResource("foo", ofType: "txt")!, + "Hello world!\n" + ) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift new file mode 100644 index 00000000000..83955c290ea --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift @@ -0,0 +1,17 @@ +import XCTest + +final class MixedTargetTests: XCTestCase { + + func testSwiftUtilityIsVisible() throws { + let _ = SwiftTestHelper() + } + + func testObjcUtilityIsVisibile() throws { + let _ = ObjcTestHelper() + } + + func testOtherObjcUtilityIsVisibile() throws { + let _ = OtherObjcTestHelper() + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m new file mode 100644 index 00000000000..a814976b9c5 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m @@ -0,0 +1,23 @@ +#import + +// Import test helper defined in Swift. +#import "MixedTestTargetWithSharedUtilitiesTests-Swift.h" +// Import test helpers defined in Objective-C. +#import "ObjcTestHelper.h" +#import "Subdirectory1/Subdirectory2/OtherObjcTestHelper.h" + +@interface ObjcMixedTargetTests : XCTestCase + +@end + +@implementation ObjcMixedTargetTests + +- (void)testSwiftUtilityIsVisible { + SwiftTestHelper *helper = [[SwiftTestHelper alloc] init]; +} + +- (void)testObjcUtilityIsVisibile { + ObjcTestHelper *helper = [[ObjcTestHelper alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h new file mode 100644 index 00000000000..7b98b400a14 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h @@ -0,0 +1,4 @@ +#import + +@interface ObjcTestHelper : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m new file mode 100644 index 00000000000..69c903b2097 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m @@ -0,0 +1,7 @@ +#import + +#import "ObjcTestHelper.h" + +@implementation ObjcTestHelper + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m new file mode 100644 index 00000000000..ef42693c32f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m @@ -0,0 +1,7 @@ +#import + +#import "Subdirectory1/Subdirectory2/OtherObjcTestHelper.h" + +@implementation OtherObjcTestHelper + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h new file mode 100644 index 00000000000..5dc87c0c787 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h @@ -0,0 +1,7 @@ +#import + +// The point of this header is that it is in a nested subdirectory of the root +// directory. It should be included in the test target's module. +@interface OtherObjcTestHelper : NSObject + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift new file mode 100644 index 00000000000..1a9eb90c9e2 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift @@ -0,0 +1,3 @@ +import Foundation + +@objc public class SwiftTestHelper: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m new file mode 100644 index 00000000000..a2522a1dc22 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m @@ -0,0 +1,18 @@ +#import + +@import MixedTargetWithNonPublicHeaders; + +#import "Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h" +#import "Sources/MixedTargetWithNonPublicHeaders/Bar.h" + +@interface PrivateHeadersCanBeTestedViaHeaderSearchPathsTests : XCTestCase +@end + +@implementation PrivateHeadersCanBeTestedViaHeaderSearchPathsTests + +- (void)testPrivateHeaders { + Foo *foo = [[Foo alloc] init]; + Bar*bar = [[Bar alloc] init]; +} + +@end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 6a1c728ebbe..d243b815f92 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -422,7 +422,7 @@ public final class ClangTargetBuildDescription { try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: moduleMapPath, - addSwiftSubmodule: true + addSwiftSubmodule: isWithinMixedTarget ) if isWithinMixedTarget { @@ -1570,7 +1570,7 @@ public final class MixedTargetBuildDescription { contents: // All headers try VFSOverlay.overlayResources( - // TODO(ncooke3): Figure out a way to get the root sources path. + // TODO(ncooke3): #456 Figure out a way to get the root sources path. directoryPath: publicHeadersPath.parentDirectory, fileSystem: fileSystem, shouldInclude: { @@ -1655,6 +1655,9 @@ public final class MixedTargetBuildDescription { clangTargetBuildDescription.additionalFlags += [ "-I", buildArtifactIntermediatesDirectory.pathString, + // TODO(ncooke3): #123 This is pretty hacky and doesn't scale well. +// "-I", +// buildArtifactIntermediatesDirectory.appending(component: "Public").pathString, "-ivfsoverlay", allProductHeadersPath.pathString ] diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index c8e7964c0ec..48c2f26483d 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -883,8 +883,14 @@ public final class PackageBuilder { let buildSettings = try self.buildSettings(for: manifestTarget, targetRoot: potentialModule.path, cxxLanguageStandard: self.manifest.cxxLanguageStandard) // Compute the path to public headers directory. - let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent - let publicHeadersPath = potentialModule.path.appending(try RelativePath(validating: publicHeaderComponent)) + let publicHeadersPath: AbsolutePath + switch potentialModule.type { + case .test: + publicHeadersPath = potentialModule.path + default: + let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent + publicHeadersPath = potentialModule.path.appending(try RelativePath(validating: publicHeaderComponent)) + } guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) } @@ -963,7 +969,8 @@ public final class PackageBuilder { let moduleMapType = try findModuleMapType( for: potentialModule, targetType: targetType, - publicHeadersPath: publicHeadersPath + publicHeadersPath: publicHeadersPath, + isMixedTarget: true ) return try MixedTarget( @@ -1228,7 +1235,12 @@ public final class PackageBuilder { } /// Determines the type of module map that will be appropriate for a potential target based on its header layout. - private func findModuleMapType(for potentialModule: PotentialModule, targetType: Target.Kind, publicHeadersPath: AbsolutePath) throws -> ModuleMapType { + private func findModuleMapType( + for potentialModule: PotentialModule, + targetType: Target.Kind, + publicHeadersPath: AbsolutePath, + isMixedTarget: Bool = false + ) throws -> ModuleMapType { if fileSystem.exists(publicHeadersPath) { let moduleMapGenerator = ModuleMapGenerator( targetName: potentialModule.name, @@ -1240,6 +1252,9 @@ public final class PackageBuilder { } else if targetType == .library, manifest.toolsVersion >= .v5_5 { // If this clang target is a library, it must contain "include" directory. throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) + } else if targetType == .test && isMixedTarget { + // TODO(ncooke3): Revisit and add comment. + return .umbrellaDirectory(potentialModule.path) } else { return .none } diff --git a/Sources/SPMTestSupport/XCTAssertHelpers.swift b/Sources/SPMTestSupport/XCTAssertHelpers.swift index 5459b1c912e..6d7198eee4f 100644 --- a/Sources/SPMTestSupport/XCTAssertHelpers.swift +++ b/Sources/SPMTestSupport/XCTAssertHelpers.swift @@ -106,6 +106,7 @@ public func XCTAssertSwiftTest( @discardableResult public func XCTAssertBuildFails( _ path: AbsolutePath, + extraArgs: [String] = [], Xcc: [String] = [], Xld: [String] = [], Xswiftc: [String] = [], @@ -114,7 +115,7 @@ public func XCTAssertBuildFails( line: UInt = #line ) -> CommandExecutionError? { var failure: CommandExecutionError? = nil - XCTAssertThrowsCommandExecutionError(try executeSwiftBuild(path, Xcc: Xcc, Xld: Xld, Xswiftc: Xswiftc), file: file, line: line) { error in + XCTAssertThrowsCommandExecutionError(try executeSwiftBuild(path, extraArgs: extraArgs, Xcc: Xcc, Xld: Xld, Xswiftc: Xswiftc), file: file, line: line) { error in failure = error } return failure diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index f334998d100..c82464d387c 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -27,6 +27,13 @@ import class TSCBasic.InMemoryFileSystem import enum TSCUtility.Diagnostics +// TODO(ncooke3): Add test for building statically linked mixed target. +// TODO(ncooke3): Add test for building dynamically linked mixed target. + +// TODO(ncooke3): Add test for expected failure cases: +// - Non-Library target +// - Non-Test target + final class BuildPlanTests: XCTestCase { let inputsDir = AbsolutePath(#file).parentDirectory.appending(components: "Inputs") private let driverSupport = DriverSupport() diff --git a/Tests/BuildTests/ModuleAliasingBuildTests.swift b/Tests/BuildTests/ModuleAliasingBuildTests.swift index 4f0b239ff17..ae65b959776 100644 --- a/Tests/BuildTests/ModuleAliasingBuildTests.swift +++ b/Tests/BuildTests/ModuleAliasingBuildTests.swift @@ -1002,8 +1002,9 @@ final class ModuleAliasingBuildTests: XCTestCase { "/thisPkg/Sources/Logging/file.swift", "/fooPkg/Sources/Utils/fileUtils.swift", "/fooPkg/Sources/Logging/fileLogging.m", - "/fooPkg/Sources/Logging/include/fileLogging.h" - ) + "/fooPkg/Sources/Logging/include/fileLogging.h", + "/fooPkg/Sources/Logging/FileLogging.swift" + ) let observability = ObservabilitySystem.makeForTesting() let _ = try loadPackageGraph( fileSystem: fs, diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift new file mode 100644 index 00000000000..f4725bfce49 --- /dev/null +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -0,0 +1,274 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2022 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 XCTest +import SPMTestSupport + +// TODO(ncooke3): Create a larger E2E test with a complex mixed target. +// TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. +// TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. + +// MARK: - MixedTargetTests + +final class MixedTargetTests: XCTestCase { + + // MARK: - Testing Mixed Targets + + func testMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "BasicMixedTarget"] + ) + } + } + + func testMixedTargetWithResources() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithResources"] + ) + } + } + + func testMixedTargetWithCustomModuleMap() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCustomModuleMap"] + ) + } + } + + func testMixedTargetWithCustomModuleMapAndResources() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCustomModuleMapAndResources"], + // Surface warning where custom umbrella header does not + // include `resource_bundle_accessor.h` in `build` directory. + Xswiftc: ["-warnings-as-errors"] + ) + } + } + + // TODO(ncooke3): Can you export a C++ type in a mixed Obj-C / Cxx project? + func testMixedTargetWithCXX() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCXX"] + ) + } + } + + func testMixedTargetWithC() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithC"] + ) + } + } + + // TODO(ncooke3): Update the implementation to support this. + func testMixedTargetWithNoPublicObjectiveCHeaders() throws { + XCTAssert(false) +// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// XCTAssertBuilds( +// fixturePath, +// extraArgs: ["--target", "MixedTargetWithNoPublicObjectiveCHeaders"] +// ) +// } + } + + func testNonPublicHeadersAreVisibleFromSwiftHalfOfMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithNonPublicHeaders"] + ) + } + } + + func testNonPublicHeadersAreNotVisibleFromOutsideOfTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + // The test target tries to access non-public headers so the build + // should fail. + XCTAssertBuildFails( + fixturePath, + extraArgs: ["--target", "MixedTargetWithNonPublicHeadersTests"], + // Without selectively enabling the tests with the below macro, + // the intentional build failure will break other unit tests + // since all targets in the package are build when running + // `swift test`. + Xswiftc: ["MIXED_TARGET_WITH_C_TESTS"] + ) + } + } + + // TODO(ncooke3): Blocked on search paths bug. + func testMixedTargetWithCustomPaths() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCustomPaths"] + ) + } + + } + + func testMixedTargetBuildsInReleaseMode() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: [ + "--target", "BasicMixedTarget", + "--configuration", "release" + ] + ) + } + } + + func testStaticallyLinkedMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--product", "StaticallyLinkedBasicMixedTarget"] + ) + } + } + + func testDynamicallyLinkedMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--product", "DynamicallyLinkedBasicMixedTarget"] + ) + } + } + + // TODO(ncooke3): Blocked on search paths bug #123. + func testMixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource() throws { + // Consider a mixed target with the following structure: + // + // MixedTarget + // ├── NewCar.swift + // ├── OldCar.m + // └── include + // └── OldCar.h + // + // Within the `OldCar.m` implementation, the `OldCar.h` header should + // be able to be imported via the following import statements: + // - #import "OldCar.h" + // - #import "include/OldCar.h" + // + // This aligns with the behavior of a Clang-only target. + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: [ + "--target", + "MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource" + ] + ) + } + } + + // TODO(ncooke3): Blocked on search paths bug #456 AND #123. + func testMixedTargetWithNestedPublicHeaders() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithNestedPublicHeaders"] + ) + } + } + + // MARK: - Testing Mixed *Test* Targets + + func testMixedTestTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "BasicMixedTargetTests"] + ) + } + } + + func testTestUtilitiesCanBeSharedAcrossSwiftAndObjcTestFiles() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTestTargetWithSharedUtilitiesTests"] + ) + } + } + + func testPrivateHeadersCanBeTestedViaHeaderSearchPaths() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "PrivateHeadersCanBeTestedViaHeaderSearchPathsTests"] + ) + } + } + + // MARK: - Integrating Mixed Target with other Targets + + func testClangTargetDependsOnMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "ClangTargetDependsOnMixedTarget"] + ) + } + } + + func testSwiftTargetDependsOnMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "SwiftTargetDependsOnMixedTarget"] + ) + } + } + + func testMixedTargetDependsOnOtherMixedTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetDependsOnMixedTarget"] + ) + } + } + + func testMixedTargetDependsOnClangTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetDependsOnClangTarget"] + ) + } + } + + func testMixedTargetDependsOnSwiftTarget() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetDependsOnSwiftTarget"] + ) + } + } + +} From ff610806184ff2f5db2bee327382fa2c7d1eb31d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 19 Dec 2022 21:52:49 -0500 Subject: [PATCH 045/178] Build overlay using root of the target's sources, resolving a TODO --- .../Sources/MixedTargetWithNestedPublicHeaders/OldCar.m | 2 +- Sources/Build/BuildPlan.swift | 3 +-- Tests/FunctionalTests/MixedTargetTests.swift | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m index 9a0d468b3ff..b04f5704b97 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m @@ -7,7 +7,7 @@ #import "Blah/Public/OldCar.h" // Import the Swift half of the module. -#import "BasicMixedTarget-Swift.h" +#import "MixedTargetWithNestedPublicHeaders-Swift.h" #import "Transmission.h" diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index d243b815f92..a8ef7f41d85 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1570,8 +1570,7 @@ public final class MixedTargetBuildDescription { contents: // All headers try VFSOverlay.overlayResources( - // TODO(ncooke3): #456 Figure out a way to get the root sources path. - directoryPath: publicHeadersPath.parentDirectory, + directoryPath: mixedTarget.path, fileSystem: fileSystem, shouldInclude: { // Only include headers. diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index f4725bfce49..b78c8e14e55 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -185,7 +185,7 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Blocked on search paths bug #456 AND #123. + // TODO(ncooke3): Blocked on search paths bug #123. func testMixedTargetWithNestedPublicHeaders() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( From f01700fe7d0f2dc7b97aaf4fc70706e39af2ae3a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 20 Dec 2022 19:41:58 -0500 Subject: [PATCH 046/178] Refactor overlay approach - Instead of manually building out the target's header tree in a VFS overlay, rely on the actual source directory. - This approach gixed several tests and resolved several TODOs --- .../BasicMixedTargets/Package.swift | 6 + .../MixedTargetWithCustomModuleMap/Driver.m | 2 + .../MixedTargetWithCustomModuleMap/OldCar.m | 2 + .../Driver.m | 2 + .../NewCar.swift | 2 + .../OldCar.m | 2 + .../Transmission.h | 10 ++ .../Transmission.m | 6 + .../Blah/Public/Driver/Driver.h | 6 + .../Blah/Public/OldCar.h | 18 ++ .../Blah/Public/module.modulemap | 5 + .../Driver.m | 10 ++ .../Engine.swift | 4 + .../NewCar.swift | 14 ++ .../OldCar.m | 19 ++ .../Transmission.h | 10 ++ .../Transmission.m | 6 + ...dersCanBeTestedViaHeaderSearchPathsTests.m | 4 +- Sources/Build/BuildPlan.swift | 168 ++++++++++-------- .../PackageLoading/ModuleMapGenerator.swift | 11 +- Sources/PackageLoading/PackageBuilder.swift | 4 +- Tests/FunctionalTests/MixedTargetTests.swift | 17 +- 22 files changed, 250 insertions(+), 78 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index c21bd83fe6a..eca124557f4 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -103,6 +103,12 @@ let package = Package( publicHeadersPath: "Blah/Public" ), + // MARK: - MixedTargetWithNestedPublicHeadersAndCustomModuleMap + .target( + name: "MixedTargetWithNestedPublicHeadersAndCustomModuleMap", + publicHeadersPath: "Blah/Public" + ), + // MARK: - MixedTargetWithNoPublicObjectiveCHeaders // TODO(ncooke3): Re-enable when corresponding test is working. // .target( diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m index 2fc73e43de1..5870c8e4c6d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m @@ -1,5 +1,7 @@ #import +// Both import statements should be supported. +#import "Driver.h" #import "include/Driver.h" @implementation Driver diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m index 1054933a04f..d27efbc36ac 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m @@ -1,5 +1,7 @@ #import +// Both import statements should be supported. +#import "OldCar.h" #import "include/OldCar.h" // Import the Swift half of the module. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m index 2fc73e43de1..5870c8e4c6d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m @@ -1,5 +1,7 @@ #import +// Both import statements should be supported. +#import "Driver.h" #import "include/Driver.h" @implementation Driver diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift index 3f5907ea423..2fd0108dc6f 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift @@ -5,4 +5,6 @@ public class NewCar { var engine: Engine? = nil // `Driver` is defined in Objective-C. var driver: Driver? = nil + // `Transmission` is defined ina private header. + var transmission: Transmission? = nil } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m index 24899632a18..c8ac23bdf45 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m @@ -1,5 +1,7 @@ #import +// Both import statements should be supported. +#import "OldCar.h" #import "include/OldCar.h" // Import the Swift half of the module. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h new file mode 100644 index 00000000000..4f0f6da629f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m new file mode 100644 index 00000000000..be6350b11ed --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m @@ -0,0 +1,6 @@ +#import + +#import "Transmission.h" + +@implementation Transmission +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h new file mode 100644 index 00000000000..46b7bcb25dc --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h @@ -0,0 +1,18 @@ +#import + +// Both import statements should be supported. +// - This one is from the root of the `publicHeadersPath`. +#import "Driver/Driver.h" +// - This one is from the root of the target's sources directory. +#import "Blah/Public/Driver/Driver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap new file mode 100644 index 00000000000..18291b50ad2 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap @@ -0,0 +1,5 @@ +module MixedTargetWithNestedPublicHeadersAndCustomModuleMap { + header "Driver/Driver.h" + header "OldCar.h" + export * +} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m new file mode 100644 index 00000000000..6fa02dafaf7 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m @@ -0,0 +1,10 @@ +#import + +// Both import statements should be supported. +// - This one is from the root of the `publicHeadersPath`. +#import "Driver/Driver.h" +// - This one is from the root of the target's sources directory. +#import "Blah/Public/Driver/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift new file mode 100644 index 00000000000..da1ee757331 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift new file mode 100644 index 00000000000..fbf2e453b4f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift @@ -0,0 +1,14 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var driver: Driver? = nil + var transmission: Transmission? = nil + var hasStickShift: Bool { + return transmission != nil && transmission!.transmissionKind == .manual + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m new file mode 100644 index 00000000000..8d33bd4069d --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m @@ -0,0 +1,19 @@ +#import + +// Both import statements should be supported. +// - This one is from the root of the `publicHeadersPath`. +#import "OldCar.h" +// - This one is from the root of the target's sources directory. +#import "Blah/Public/OldCar.h" + +// Import the Swift half of the module. +#import "MixedTargetWithNestedPublicHeadersAndCustomModuleMap-Swift.h" + +#import "Transmission.h" + +@interface OldCar () +@property(nonatomic) Transmission *transmission; +@end + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h new file mode 100644 index 00000000000..4f0f6da629f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m new file mode 100644 index 00000000000..be6350b11ed --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m @@ -0,0 +1,6 @@ +#import + +#import "Transmission.h" + +@implementation Transmission +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m index a2522a1dc22..c81fea234c5 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m @@ -11,8 +11,8 @@ @interface PrivateHeadersCanBeTestedViaHeaderSearchPathsTests : XCTestCase @implementation PrivateHeadersCanBeTestedViaHeaderSearchPathsTests - (void)testPrivateHeaders { - Foo *foo = [[Foo alloc] init]; - Bar*bar = [[Bar alloc] init]; + Foo *foo = [[Foo alloc] init]; + Bar *bar = [[Bar alloc] init]; } @end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index a8ef7f41d85..e0b0bdebcb9 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -389,7 +389,7 @@ public final class ClangTargetBuildDescription { .appending(component: "Intermediates") .appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), + type: .umbrellaDirectory(clangTarget.path), at: intermediateModuleMapPath, addSwiftSubmodule: true ) @@ -405,7 +405,7 @@ public final class ClangTargetBuildDescription { .appending(component: "Intermediates") .appending(component: unextendedModuleMapFilename) try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), + type: .umbrellaDirectory(clangTarget.path), at: unextendedModuleMapPath ) } else { @@ -430,10 +430,11 @@ public final class ClangTargetBuildDescription { .appending(component: "Intermediates") .appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), + type: .umbrellaDirectory(clangTarget.path), at: intermediateModuleMapPath, addSwiftSubmodule: true ) + // The underlying Clang target is building within a Mixed // language target and needs an auxiliary module map that // doesn't include the generated interop header from the @@ -445,9 +446,12 @@ public final class ClangTargetBuildDescription { let unextendedModuleMapPath = tempsPath .appending(component: "Intermediates") .appending(component: unextendedModuleMapFilename) + let nonObjcHeaders: [AbsolutePath] = clangTarget.headers + .filter { $0.extension != "h" } try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(tempsPath.appending(component: "Intermediates")), - at: unextendedModuleMapPath + type: .umbrellaDirectory(clangTarget.path), + at: unextendedModuleMapPath, + excludeHeaders: nonObjcHeaders ) } @@ -1531,7 +1535,6 @@ public final class MixedTargetBuildDescription { if target.type == .library || target.type == .test { // Compiling the mixed target will require a Clang VFS overlay file // with mappings to the target's module map and public headers. - let publicHeadersPath = clangTargetBuildDescription.clangTarget.includeDir let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath @@ -1549,64 +1552,89 @@ public final class MixedTargetBuildDescription { // via a VFS overlay, represented by the below YAML file. let unextendedModuleMapOverlayPath = buildArtifactIntermediatesDirectory .appending(component: "unextended-module-overlay.yaml") + + // Decide which directory to add the overlay resources in based on + // the presence of a custom module map. + let rootOverlayResourceDirectory: AbsolutePath + if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { + // Building a mixed target uses a modified module map to expose + // private headers to the Swift half of the module. To avoid + // the custom module map causing a module redeclaration error, + // a VFS overlay is used when building the target to redirect + // the custom module map to the modified module map in the + // build directory. This redirecting overlay is placed in the + // custom module map's parent directory, as to replace it. + rootOverlayResourceDirectory = customModuleMapPath.parentDirectory + // Importing the underlying module will build the Objective-C + // half of the module. In order to find the underlying module, + // a `module.modulemap` needs to be discoverable via a header + // search path. In the case of a custom module map, its parent + // directory is used. + swiftTargetBuildDescription.additionalFlags += [ + "-import-underlying-module", + "-I", customModuleMapPath.parentDirectory.pathString + ] + } else { + // TODO(ncooke3): I think this case may not even need an + // all-product-headers.yaml. + + // Since no custom module map exists, the build directory can + // be used as the root of the VFS overlay. In this case, the + // VFS overlay's sole purpose is to expose the generated Swift + // header. + rootOverlayResourceDirectory = buildArtifactIntermediatesDirectory + // Importing the underlying module will build the Objective-C + // half of the module. In order to find the underlying module, + // a `module.modulemap` needs to be discoverable via a header + // search path. In this case, the module map in the build + // directory is used. + swiftTargetBuildDescription.additionalFlags += [ + "-import-underlying-module", + "-I", buildArtifactIntermediatesDirectory.pathString + ] + } + + // For Intermediates directory try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactIntermediatesDirectory.pathString, + name: rootOverlayResourceDirectory.pathString, contents: [ + // Redirect the `module.modulemap` to the modified + // module map in the intermediates directory. VFSOverlay.File( name: moduleMapFilename, externalContents: buildArtifactIntermediatesDirectory - .appending(component: unextendedModuleMapFilename) + .appending(component: moduleMapFilename) .pathString + ), + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: generatedInteropHeaderPath.basename, + externalContents: generatedInteropHeaderPath.pathString ) ] ) - ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - // TODO(ncooke3): Use @resultBuilder for this. - var roots = [ + try VFSOverlay(roots: [ VFSOverlay.Directory( - name: buildArtifactIntermediatesDirectory.pathString, - contents: - // All headers - try VFSOverlay.overlayResources( - directoryPath: mixedTarget.path, - fileSystem: fileSystem, - shouldInclude: { - // Only include headers. - fileSystem.isDirectory($0) || $0.pathString.hasSuffix(".h") - } - ) + [ + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. VFSOverlay.File( - name: generatedInteropHeaderPath.basename, - externalContents: generatedInteropHeaderPath.pathString + name: moduleMapFilename, + externalContents: buildArtifactIntermediatesDirectory + .appending(component: unextendedModuleMapFilename) + .pathString ) ] ) - ] - - if case .custom(let customModuleMapPath) = clangTargetBuildDescription.clangTarget.moduleMapType { - roots.append( - VFSOverlay.Directory( - name: customModuleMapPath.parentDirectory.pathString, - contents: [ - VFSOverlay.File( - name: customModuleMapPath.basename, - externalContents: buildArtifactIntermediatesDirectory - .appending(component: "module.modulemap") - .pathString - ) - ] - ) - ) - } - - // TODO(ncooke3): What happens if a custom module map exists with a - // name other than `module.modulemap`? - try VFSOverlay(roots: roots) - .write(to: allProductHeadersPath, fileSystem: fileSystem) + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) // For Product directory + // TODO(ncooke3): Experiment with refactoring this next... try VFSOverlay(roots: [ VFSOverlay.Directory( name: buildArtifactDirectory @@ -1614,7 +1642,7 @@ public final class MixedTargetBuildDescription { contents: // Public headers try VFSOverlay.overlayResources( - directoryPath: publicHeadersPath, + directoryPath: mixedTarget.clangTarget.includeDir, fileSystem: fileSystem, shouldInclude: { // Filter out a potential custom module map as @@ -1631,34 +1659,34 @@ public final class MixedTargetBuildDescription { ) ]).write(to: allProductHeadersPathProduct, fileSystem: fileSystem) - swiftTargetBuildDescription.additionalFlags += [ - // Builds Objective-C portion of module. - "-import-underlying-module", - // Add the location of the module's Objective-C module map as - // a header search path. - "-I", - buildArtifactIntermediatesDirectory.pathString - ] - swiftTargetBuildDescription.appendClangFlags( - // Pass VFS overlay to the underlying Clang compiler. + // Pass both VFS overlays to the underlying Clang compiler. "-ivfsoverlay", allProductHeadersPath.pathString, - // Pass VFS overlay to the underlying Clang compiler. - "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString + "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, + // Adding the root of the target's source as a header search + // path allows for importing headers using paths relative to + // the root. + "-I", mixedTarget.path.pathString ) - // Overlay the public headers over the build directory and add - // the directory to the header search paths. This will also help - // pickup generated headers in the build directory like the - // generated interop Swift header. clangTargetBuildDescription.additionalFlags += [ - "-I", - buildArtifactIntermediatesDirectory.pathString, - // TODO(ncooke3): #123 This is pretty hacky and doesn't scale well. -// "-I", -// buildArtifactIntermediatesDirectory.appending(component: "Public").pathString, - "-ivfsoverlay", - allProductHeadersPath.pathString + // Adding the target's public headers directory as a header + // search path allows for importing headers using paths + // relative to the public headers directory. + "-I", mixedTarget.clangTarget.includeDir.pathString, + // Adding the root of the target's source as a header search + // path allows for importing headers using paths relative to + // the root. + "-I", mixedTarget.path.pathString, + // TODO(ncooke3): See above comment about removing + // all-product-headers.yaml for no-custom module map case... + + // Include overlay to add interop header to intermediates directory. + "-ivfsoverlay", allProductHeadersPath.pathString, + // The above overlay adds the interop header in the + // intermediates directory. Pass the intermediates directory as + // a search path so the generated header can be imported. + "-I", buildArtifactIntermediatesDirectory.pathString ] self.allProductHeadersOverlay = allProductHeadersPathProduct diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 8f777d46f1a..4ba55a9d35b 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -181,7 +181,12 @@ public struct ModuleMapGenerator { /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any /// diagnostics are added to the receiver's diagnostics engine.. - public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath, addSwiftSubmodule: Bool = false) throws { + public func generateModuleMap( + type: GeneratedModuleMapType, + at path: AbsolutePath, + excludeHeaders: [AbsolutePath] = [], + addSwiftSubmodule: Bool = false + ) throws { let stream = BufferedOutputByteStream() stream <<< "module \(moduleName) {\n" switch type { @@ -190,6 +195,10 @@ public struct ModuleMapGenerator { case .umbrellaDirectory(let dir): moduleMap.append(" umbrella \"\(dir.moduleEscapedPathString)\"\n") } + excludeHeaders.forEach { + stream <<< " exclude header \"\($0.moduleEscapedPathString)\"\n" + } + stream <<< " export *\n" stream <<< "}\n" if addSwiftSubmodule { diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 48c2f26483d..944f75041c3 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -1253,7 +1253,9 @@ public final class PackageBuilder { // If this clang target is a library, it must contain "include" directory. throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) } else if targetType == .test && isMixedTarget { - // TODO(ncooke3): Revisit and add comment. + // TODO(ncooke3): Consider if this should be handled in BuildPlan.swift? + // Mixed test targets use an umbrella directory to expose all + // headers to the Swift portion of the test target. return .umbrellaDirectory(potentialModule.path) } else { return .none diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index b78c8e14e55..0926cc437fb 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2023 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 @@ -117,7 +117,6 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Blocked on search paths bug. func testMixedTargetWithCustomPaths() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( @@ -158,7 +157,6 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Blocked on search paths bug #123. func testMixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource() throws { // Consider a mixed target with the following structure: // @@ -185,7 +183,6 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Blocked on search paths bug #123. func testMixedTargetWithNestedPublicHeaders() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( @@ -195,6 +192,18 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithNestedPublicHeadersAndCustomModuleMap() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: [ + "--target", + "MixedTargetWithNestedPublicHeadersAndCustomModuleMap" + ] + ) + } + } + // MARK: - Testing Mixed *Test* Targets func testMixedTestTarget() throws { From 06dc053a1c8200cf7c0db1ad6b1977cebf152ce9 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 20 Dec 2022 19:42:50 -0500 Subject: [PATCH 047/178] Add newlines to test files --- .../BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h | 2 +- .../BasicMixedTargets/Sources/ClangTarget/include/Vessel.h | 1 - .../Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift | 2 +- .../Sources/MixedTargetDependsOnMixedTarget/OldBoat.m | 2 +- .../MixedTargetWithCustomModuleMapAndResources/Transmission.h | 2 +- .../MixedTargetWithCustomModuleMapAndResources/Transmission.m | 2 +- .../Sources/MixedTargetWithNestedPublicHeaders/Transmission.h | 2 +- .../Sources/MixedTargetWithNestedPublicHeaders/Transmission.m | 2 +- .../Blah/Public/module.modulemap | 2 +- .../Transmission.h | 2 +- .../Transmission.m | 2 +- .../Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift | 2 +- .../MixedTargetDoesNotSupportModuleAliasingTests.swift | 2 +- 13 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h index 4f0f6da629f..57d0f1524b8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h @@ -7,4 +7,4 @@ typedef NS_ENUM(NSInteger, TransmissionKind) { @interface Transmission : NSObject @property (nonatomic, readonly, assign) TransmissionKind transmissionKind; -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h index 1e32755be5e..97af0487b92 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h @@ -3,4 +3,3 @@ @interface Vessel : NSObject @property (assign, getter=hasLifeJackets) BOOL lifeJackets; @end - diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift index 984eafd1d98..f32b60da772 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift @@ -7,4 +7,4 @@ public class NewBoat { var driver: Driver? = nil public init() {} -} \ No newline at end of file +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m index 821df31b02b..8aa647e0a7f 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m @@ -11,4 +11,4 @@ @interface OldBoat () @end @implementation OldBoat -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h index 4f0f6da629f..57d0f1524b8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h @@ -7,4 +7,4 @@ typedef NS_ENUM(NSInteger, TransmissionKind) { @interface Transmission : NSObject @property (nonatomic, readonly, assign) TransmissionKind transmissionKind; -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m index be6350b11ed..ced7e283e83 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m @@ -3,4 +3,4 @@ #import "Transmission.h" @implementation Transmission -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h index 4f0f6da629f..57d0f1524b8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h @@ -7,4 +7,4 @@ typedef NS_ENUM(NSInteger, TransmissionKind) { @interface Transmission : NSObject @property (nonatomic, readonly, assign) TransmissionKind transmissionKind; -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m index be6350b11ed..ced7e283e83 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m @@ -3,4 +3,4 @@ #import "Transmission.h" @implementation Transmission -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap index 18291b50ad2..6dde727eaea 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap @@ -2,4 +2,4 @@ module MixedTargetWithNestedPublicHeadersAndCustomModuleMap { header "Driver/Driver.h" header "OldCar.h" export * -} \ No newline at end of file +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h index 4f0f6da629f..57d0f1524b8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h @@ -7,4 +7,4 @@ typedef NS_ENUM(NSInteger, TransmissionKind) { @interface Transmission : NSObject @property (nonatomic, readonly, assign) TransmissionKind transmissionKind; -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m index be6350b11ed..ced7e283e83 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m @@ -3,4 +3,4 @@ #import "Transmission.h" @implementation Transmission -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift index b4e2474fd87..ae680cf2c06 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift @@ -7,4 +7,4 @@ public struct JunkYard { var engine: Engine? var oldCar: OldCar? var driver: Driver? -} \ No newline at end of file +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift index 6ac541db22e..38484f23622 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift @@ -1 +1 @@ -// Intentially left blank. \ No newline at end of file +// Intentially left blank. From c9cd563bcceae60572703fe3019e4a32cdaefe5d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 20 Dec 2022 19:46:09 -0500 Subject: [PATCH 048/178] Remove unneeded newline --- Sources/PackageLoading/PackageBuilder.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 944f75041c3..06d7c8fca37 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -196,7 +196,6 @@ extension Target { /// The target contains an invalid mix of languages (e.g. both Swift and C). case mixedSources(AbsolutePath) - } } From a8a51a06c92f7a0b0b700044ea956b95a40fdab2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 20 Dec 2022 19:48:42 -0500 Subject: [PATCH 049/178] Add more newlines to test files --- .../MixedTargetWithCustomPaths/Sources/Transmission.h | 2 +- .../MixedTargetWithCustomPaths/Sources/Transmission.m | 2 +- .../BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h index 4f0f6da629f..57d0f1524b8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h @@ -7,4 +7,4 @@ typedef NS_ENUM(NSInteger, TransmissionKind) { @interface Transmission : NSObject @property (nonatomic, readonly, assign) TransmissionKind transmissionKind; -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m index d649582b225..f456e749b76 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m @@ -5,4 +5,4 @@ #import "MixedTargetWithCustomPaths/Sources/Transmission.h" @implementation Transmission -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m index be6350b11ed..ced7e283e83 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m @@ -3,4 +3,4 @@ #import "Transmission.h" @implementation Transmission -@end \ No newline at end of file +@end From 3944276add33e8df129766b924cebfd3c70bac00 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 20 Dec 2022 22:47:43 -0500 Subject: [PATCH 050/178] Resolve PR comment, align module map logic --- .../MixedTargets/BasicMixedTargets/Package.swift | 5 +++++ .../Calculator.mm | 15 +++++++++++++++ .../Factorial.swift | 5 +++++ .../FactorialFinder.cpp | 6 ++++++ .../FactorialFinder.hpp | 5 +++++ .../include/Calculator.h | 5 +++++ .../include/module.modulemap | 4 ++++ Sources/Build/BuildPlan.swift | 5 ++++- Tests/FunctionalTests/MixedTargetTests.swift | 11 +++++++++++ 9 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index eca124557f4..94ff802ca3b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -67,6 +67,11 @@ let package = Package( dependencies: ["MixedTargetWithCXX"] ), + // MARK: MixedTargetWithCXXAndCustomModuleMap + .target( + name: "MixedTargetWithCXXAndCustomModuleMap" + ), + // MARK: - MixedTargetWithC .target( name: "MixedTargetWithC" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm new file mode 100644 index 00000000000..c06770ecd5e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm @@ -0,0 +1,15 @@ +#import + +#import "include/Calculator.h" + +// Import C++ header. +#import "FactorialFinder.hpp" + +@implementation Calculator + ++ (long)factorialForInt:(int)integer { + FactorialFinder ff; + return ff.factorial(integer); +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift new file mode 100644 index 00000000000..51ba08e4d75 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift @@ -0,0 +1,5 @@ +import Foundation + +public func factorial(_ x: Int32) -> Int { + return Calculator.factorial(for: x) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp new file mode 100644 index 00000000000..3e5e6bfe031 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp @@ -0,0 +1,6 @@ +#include "FactorialFinder.hpp" + +long FactorialFinder::factorial(int n) { + if (n == 0 || n == 1) return 1; + return n * factorial(n-1); +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp new file mode 100644 index 00000000000..533730e7c97 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp @@ -0,0 +1,5 @@ +class FactorialFinder +{ +public: + long factorial(int n); +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h new file mode 100644 index 00000000000..7de7e3bbe1d --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h @@ -0,0 +1,5 @@ +#import + +@interface Calculator : NSObject ++ (long)factorialForInt:(int)integer; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap new file mode 100644 index 00000000000..ee6a48eb15c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap @@ -0,0 +1,4 @@ +module MixedTargetWithCXXAndCustomModuleMap { + header "Calculator.h" + export * +} diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index e0b0bdebcb9..2ec1ebaa953 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -404,9 +404,12 @@ public final class ClangTargetBuildDescription { let unextendedModuleMapPath = tempsPath .appending(component: "Intermediates") .appending(component: unextendedModuleMapFilename) + let nonObjcHeaders: [AbsolutePath] = clangTarget.headers + .filter { $0.extension != "h" } try moduleMapGenerator.generateModuleMap( type: .umbrellaDirectory(clangTarget.path), - at: unextendedModuleMapPath + at: unextendedModuleMapPath, + excludeHeaders: nonObjcHeaders ) } else { // When not building within a mixed target, use the custom diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 0926cc437fb..2068535a525 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -17,6 +17,8 @@ import SPMTestSupport // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. // TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. +// TODO(ncooke3): Add test for mixed target with no ObjC-compatible Swift API. + // MARK: - MixedTargetTests final class MixedTargetTests: XCTestCase { @@ -72,6 +74,15 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithCXXAndCustomModuleMap() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCXXAndCustomModuleMap"] + ) + } + } + func testMixedTargetWithC() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertSwiftTest( From b0166ea5534657df06e28582881088d06c3c8dba Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 20 Dec 2022 22:58:37 -0500 Subject: [PATCH 051/178] Consolidate module map logic --- Sources/Build/BuildPlan.swift | 92 +++++++++++++---------------------- 1 file changed, 35 insertions(+), 57 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 2ec1ebaa953..8d44294afac 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -345,6 +345,8 @@ public final class ClangTargetBuildDescription { fileSystem: fileSystem ) + var generateIntermediateModuleMaps = false + if case .custom(let customModuleMapPath) = clangTarget.moduleMapType { if isWithinMixedTarget { let customModuleMapContents: String = try fileSystem.readFileContents(customModuleMapPath) @@ -384,33 +386,7 @@ public final class ClangTargetBuildDescription { self.moduleMap = writePath - // Intermediates module maps - let intermediateModuleMapPath = tempsPath - .appending(component: "Intermediates") - .appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(clangTarget.path), - at: intermediateModuleMapPath, - addSwiftSubmodule: true - ) - // The underlying Clang target is building within a Mixed - // language target and needs an auxiliary module map that - // doesn't include the generated interop header from the - // Swift half of the mixed target. This will later allow the - // Clang half of the module to be built when compiling the - // Swift part without the generated header being considered - // an input (because it won't exist yet and is an output of - // that compilation command). - let unextendedModuleMapPath = tempsPath - .appending(component: "Intermediates") - .appending(component: unextendedModuleMapFilename) - let nonObjcHeaders: [AbsolutePath] = clangTarget.headers - .filter { $0.extension != "h" } - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(clangTarget.path), - at: unextendedModuleMapPath, - excludeHeaders: nonObjcHeaders - ) + generateIntermediateModuleMaps = true } else { // When not building within a mixed target, use the custom // module map as it does not need to be modified. @@ -428,39 +404,41 @@ public final class ClangTargetBuildDescription { addSwiftSubmodule: isWithinMixedTarget ) - if isWithinMixedTarget { - let intermediateModuleMapPath = tempsPath - .appending(component: "Intermediates") - .appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(clangTarget.path), - at: intermediateModuleMapPath, - addSwiftSubmodule: true - ) - - // The underlying Clang target is building within a Mixed - // language target and needs an auxiliary module map that - // doesn't include the generated interop header from the - // Swift half of the mixed target. This will later allow the - // Clang half of the module to be built when compiling the - // Swift part without the generated header being considered - // an input (because it won't exist yet and is an output of - // that compilation command). - let unextendedModuleMapPath = tempsPath - .appending(component: "Intermediates") - .appending(component: unextendedModuleMapFilename) - let nonObjcHeaders: [AbsolutePath] = clangTarget.headers - .filter { $0.extension != "h" } - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(clangTarget.path), - at: unextendedModuleMapPath, - excludeHeaders: nonObjcHeaders - ) - } - self.moduleMap = moduleMapPath + + generateIntermediateModuleMaps = isWithinMixedTarget } // Otherwise there is no module map, and we leave `moduleMap` unset. + + // Generate the intermediate module maps if needed. + if generateIntermediateModuleMaps { + let intermediateModuleMapPath = tempsPath + .appending(component: "Intermediates") + .appending(component: moduleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(clangTarget.path), + at: intermediateModuleMapPath, + addSwiftSubmodule: true + ) + // The underlying Clang target is building within a Mixed + // language target and needs an auxiliary module map that + // doesn't include the generated interop header from the + // Swift half of the mixed target. This will later allow the + // Clang half of the module to be built when compiling the + // Swift part without the generated header being considered + // an input (because it won't exist yet and is an output of + // that compilation command). + let unextendedModuleMapPath = tempsPath + .appending(component: "Intermediates") + .appending(component: unextendedModuleMapFilename) + let nonObjcHeaders: [AbsolutePath] = clangTarget.headers + .filter { $0.extension != "h" } + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(clangTarget.path), + at: unextendedModuleMapPath, + excludeHeaders: nonObjcHeaders + ) + } } // Do nothing if we're not generating a bundle. From 3989c84c5eef60302f923d5e86c2779fd1a95434 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 21 Dec 2022 11:52:36 -0500 Subject: [PATCH 052/178] Add support for mixed targets with no public headers --- .../BasicMixedTargets/Package.swift | 13 +++-- .../Driver.h | 6 --- .../Driver.m | 6 --- .../OldCar.h | 14 ----- .../OldCar.m | 9 ---- .../XYZDriver.h | 12 +++++ .../XYZDriver.m | 6 +++ .../XYZOldCar.h | 18 +++++++ .../XYZOldCar.m | 9 ++++ ...etWithNoPublicObjectiveCHeadersTests.swift | 22 ++++++++ ...TargetWithNoPublicObjectiveCHeadersTests.m | 24 +++++++++ Sources/Build/BuildPlan.swift | 54 +++++++++++++------ Sources/Build/LLBuildManifestBuilder.swift | 1 - .../PackageLoading/ModuleMapGenerator.swift | 20 +++---- Sources/PackageLoading/PackageBuilder.swift | 2 +- Tests/FunctionalTests/MixedTargetTests.swift | 20 ++++--- 16 files changed, 160 insertions(+), 76 deletions(-) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 94ff802ca3b..2dc50b9f1ff 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -114,11 +114,14 @@ let package = Package( publicHeadersPath: "Blah/Public" ), - // MARK: - MixedTargetWithNoPublicObjectiveCHeaders - // TODO(ncooke3): Re-enable when corresponding test is working. -// .target( -// name: "MixedTargetWithNoPublicObjectiveCHeaders" -// ), + // MARK: - MixedTargetWithNoPublicObjectiveCHeaders. + .target( + name: "MixedTargetWithNoPublicObjectiveCHeaders" + ), + .testTarget( + name: "MixedTargetWithNoPublicObjectiveCHeadersTests", + dependencies: ["MixedTargetWithNoPublicObjectiveCHeaders"] + ), // MARK: - MixedTestTargetWithSharedTestUtilities .testTarget( diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h deleted file mode 100644 index 8326bf2f179..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.h +++ /dev/null @@ -1,6 +0,0 @@ -#import - -// This type is Swift compatible and used in `NewCar`. -@interface Driver : NSObject -@property(nonnull) NSString* name; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m deleted file mode 100644 index 666a5c70cc6..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Driver.m +++ /dev/null @@ -1,6 +0,0 @@ -#import - -#import "Driver.h" - -@implementation Driver -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h deleted file mode 100644 index b75ae54cce1..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.h +++ /dev/null @@ -1,14 +0,0 @@ -#import - -#import "Driver.h" - -// The `Engine` type is declared in the Swift half of the module. Such types -// must be forward declared in headers. -@class Engine; - -@interface OldCar : NSObject -// `Engine` is defined in Swift. -@property(nullable) Engine* engine; -// `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m deleted file mode 100644 index 203f52a7bb6..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OldCar.m +++ /dev/null @@ -1,9 +0,0 @@ -#import - -#import "OldCar.h" - -// Import the Swift half of the module. -#import "BasicMixedTarget-Swift.h" - -@implementation OldCar -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h new file mode 100644 index 00000000000..1921ac7046f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h @@ -0,0 +1,12 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +// +// Class prefix is needed to avoid conflict with identical type when building +// the test executable. This should not be needed in real packages as they +// likely would not have multiple types with the same name like in this +// test package. +NS_SWIFT_NAME(Driver) +@interface XYZDriver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m new file mode 100644 index 00000000000..97d3de51f0e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m @@ -0,0 +1,6 @@ +#import + +#import "XYZDriver.h" + +@implementation XYZDriver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h new file mode 100644 index 00000000000..9df7690f2e0 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h @@ -0,0 +1,18 @@ +#import + +#import "XYZDriver.h" + +// The `Engine` type is declared in the Swift half of the module. Such types +// must be forward declared in headers. +@class Engine; + +// Class prefix is needed to avoid conflict with identical type when building +// the test executable. This should not be needed in real packages as they +// likely would not have multiple types with the same name like in this +// test package. +@interface XYZOldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine* engine; +// `Driver` is defined in Objective-C. +@property(nullable) XYZDriver* driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m new file mode 100644 index 00000000000..9d73961eb0c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m @@ -0,0 +1,9 @@ +#import + +#import "XYZOldCar.h" + +// Import the Swift half of the module. +#import "MixedTargetWithNoPublicObjectiveCHeaders-Swift.h" + +@implementation XYZOldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift new file mode 100644 index 00000000000..872d700f911 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift @@ -0,0 +1,22 @@ +import XCTest +import MixedTargetWithNoPublicObjectiveCHeaders + +final class MixedTargetWithNoPublicObjectiveCHeadersTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = NewCar() + let _ = Engine() + let _ = MixedTargetWithNoPublicObjectiveCHeaders.NewCar() + let _ = MixedTargetWithNoPublicObjectiveCHeaders.Engine() + } + + #if EXPECT_FAILURE + func testObjcAPI() throws { + // No Objective-C API should be exposed... + let _ = OldCar() + let _ = Driver() + } + #endif + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m new file mode 100644 index 00000000000..a2dffe5b5e5 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m @@ -0,0 +1,24 @@ +#import + +@import MixedTargetWithNoPublicObjectiveCHeaders; + +@interface ObjcMixedTargetWithNoPublicObjectiveCHeadersTests : XCTestCase + +@end + +@implementation ObjcMixedTargetWithNoPublicObjectiveCHeadersTests + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +#if EXPECT_FAILURE +- (void)testObjcAPI { + // No Objective-C API surface should be exposed... + OldCar *oldCar = [[OldCar alloc] init]; + Driver *driver = [[Driver alloc] init]; +} +#endif + +@end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 8d44294afac..2356f7a77ac 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -407,6 +407,19 @@ public final class ClangTargetBuildDescription { self.moduleMap = moduleMapPath generateIntermediateModuleMaps = isWithinMixedTarget + } else if isWithinMixedTarget && clangTarget.moduleMapType == .none { + let moduleMapPath = tempsPath + .appending(component: "Product") + .appending(component: moduleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: nil, + at: moduleMapPath, + addSwiftSubmodule: isWithinMixedTarget + ) + + self.moduleMap = moduleMapPath + + generateIntermediateModuleMaps = true } // Otherwise there is no module map, and we leave `moduleMap` unset. @@ -1616,30 +1629,37 @@ public final class MixedTargetBuildDescription { // For Product directory // TODO(ncooke3): Experiment with refactoring this next... + var contents: [VFSOverlay.Resource] = [ + VFSOverlay.File( + name: generatedInteropHeaderPath.basename, + externalContents: generatedInteropHeaderPath.pathString + ) + ] + + if mixedTarget.clangTarget.moduleMapType != .none { + contents += + // Public headers + try VFSOverlay.overlayResources( + directoryPath: mixedTarget.clangTarget.includeDir, + fileSystem: fileSystem, + shouldInclude: { + // Filter out a potential custom module map as + // only the generated module map in the build + // directory is used. + !$0.pathString.hasSuffix("module.modulemap") + } + ) + } + try VFSOverlay(roots: [ VFSOverlay.Directory( name: buildArtifactDirectory .appending(component: "Product").pathString, - contents: - // Public headers - try VFSOverlay.overlayResources( - directoryPath: mixedTarget.clangTarget.includeDir, - fileSystem: fileSystem, - shouldInclude: { - // Filter out a potential custom module map as - // only the generated module map in the build - // directory is used. - !$0.pathString.hasSuffix("module.modulemap") - } - ) + [ - VFSOverlay.File( - name: generatedInteropHeaderPath.basename, - externalContents: generatedInteropHeaderPath.pathString - ) - ] + contents: contents ) ]).write(to: allProductHeadersPathProduct, fileSystem: fileSystem) + // Build flags swiftTargetBuildDescription.appendClangFlags( // Pass both VFS overlays to the underlying Clang compiler. "-ivfsoverlay", allProductHeadersPath.pathString, diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 28f49ae543a..88c5e150508 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -695,7 +695,6 @@ extension LLBuildManifestBuilder { for object in try target.clangTargetBuildDescription.objects { inputs.append(file: object) } - case nil: throw InternalError("unexpected: target \(target) not in target map \(self.plan.targetMap)") } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 4ba55a9d35b..6575470803b 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -182,21 +182,23 @@ public struct ModuleMapGenerator { /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any /// diagnostics are added to the receiver's diagnostics engine.. public func generateModuleMap( - type: GeneratedModuleMapType, + type: GeneratedModuleMapType?, at path: AbsolutePath, excludeHeaders: [AbsolutePath] = [], addSwiftSubmodule: Bool = false ) throws { let stream = BufferedOutputByteStream() stream <<< "module \(moduleName) {\n" - switch type { - case .umbrellaHeader(let hdr): - moduleMap.append(" umbrella header \"\(hdr.moduleEscapedPathString)\"\n") - case .umbrellaDirectory(let dir): - moduleMap.append(" umbrella \"\(dir.moduleEscapedPathString)\"\n") - } - excludeHeaders.forEach { - stream <<< " exclude header \"\($0.moduleEscapedPathString)\"\n" + if let type = type { + switch type { + case .umbrellaHeader(let hdr): + stream <<< " umbrella header \"\(hdr.moduleEscapedPathString)\"\n" + case .umbrellaDirectory(let dir): + stream <<< " umbrella \"\(dir.moduleEscapedPathString)\"\n" + } + excludeHeaders.forEach { + stream <<< " exclude header \"\($0.moduleEscapedPathString)\"\n" + } } stream <<< " export *\n" diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 06d7c8fca37..a68cc5ea23f 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -1248,7 +1248,7 @@ public final class PackageBuilder { fileSystem: self.fileSystem ) return moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) - } else if targetType == .library, manifest.toolsVersion >= .v5_5 { + } else if targetType == .library, manifest.toolsVersion >= .v5_5, !isMixedTarget { // If this clang target is a library, it must contain "include" directory. throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) } else if targetType == .test && isMixedTarget { diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 2068535a525..62fde633e7b 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -92,15 +92,19 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Update the implementation to support this. func testMixedTargetWithNoPublicObjectiveCHeaders() throws { - XCTAssert(false) -// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in -// XCTAssertBuilds( -// fixturePath, -// extraArgs: ["--target", "MixedTargetWithNoPublicObjectiveCHeaders"] -// ) -// } + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithNoPublicObjectiveCHeadersTests"] + ) + + XCTAssertBuildFails( + fixturePath, + extraArgs: ["--target", "MixedTargetWithNoPublicObjectiveCHeadersTests"], + Xcc: ["EXPECT_FAILURE"] + ) + } } func testNonPublicHeadersAreVisibleFromSwiftHalfOfMixedTarget() throws { From 6b735e1bc0d99ddf4f37bb0113150c8458236d1f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 21 Dec 2022 18:29:58 -0500 Subject: [PATCH 053/178] Fix older tests, add new test, refactor impl --- .../BasicMixedTargets/Package.swift | 10 ++- .../Driver.m | 6 ++ .../Engine.swift | 6 ++ .../NewCar.swift | 15 ++++ .../OldCar.m | 20 +++++ .../Transmission.h | 10 +++ .../Transmission.m | 6 ++ .../include/Driver.h | 6 ++ .../include/OldCar.h | 8 ++ Sources/Build/BuildPlan.swift | 4 - Sources/Build/LLBuildManifestBuilder.swift | 2 +- Sources/PackageLoading/PackageBuilder.swift | 75 ++++++++++--------- .../PackageLoading/TargetSourcesBuilder.swift | 4 +- Tests/BuildTests/BuildPlanTests.swift | 38 +++++----- .../BuildTests/ModuleAliasingBuildTests.swift | 4 +- Tests/FunctionalTests/MixedTargetTests.swift | 18 ++++- .../PackageBuilderTests.swift | 39 +++++++++- 17 files changed, 201 insertions(+), 70 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 2dc50b9f1ff..e02c7a92426 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -1,4 +1,5 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 999.0 +// FIXME(ncooke3): Update above version with the next version of SwiftPM. import PackageDescription @@ -114,7 +115,7 @@ let package = Package( publicHeadersPath: "Blah/Public" ), - // MARK: - MixedTargetWithNoPublicObjectiveCHeaders. + // MARK: - MixedTargetWithNoPublicObjectiveCHeaders .target( name: "MixedTargetWithNoPublicObjectiveCHeaders" ), @@ -123,6 +124,11 @@ let package = Package( dependencies: ["MixedTargetWithNoPublicObjectiveCHeaders"] ), + // MARK: - MixedTargetWithNoObjectiveCCompatibleSwiftAPI + .target( + name: "MixedTargetWithNoObjectiveCCompatibleSwiftAPI" + ), + // MARK: - MixedTestTargetWithSharedTestUtilities .testTarget( name: "MixedTestTargetWithSharedUtilitiesTests" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m new file mode 100644 index 00000000000..2fc73e43de1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m @@ -0,0 +1,6 @@ +#import + +#import "include/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift new file mode 100644 index 00000000000..d036e1fa3be --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift @@ -0,0 +1,6 @@ +import Foundation + +// This type is intentionally *NOT* Objective-C compatible. +public class Engine { + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift new file mode 100644 index 00000000000..8beaa7f21dd --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift @@ -0,0 +1,15 @@ +import Foundation + +// This type is intentionally *NOT* Objective-C compatible. +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var driver: Driver? = nil + var transmission: Transmission? = nil + var hasStickShift: Bool { + return transmission != nil && transmission!.transmissionKind == .manual + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m new file mode 100644 index 00000000000..a889ec00d56 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m @@ -0,0 +1,20 @@ +#import + +#import "OldCar.h" +#import "include/OldCar.h" + +// Import the Swift half of the module. Note that this header wouldn't vend any +// API as this target intentionally has no Objective-C compatible Swift API. +#import "MixedTargetWithNoObjectiveCCompatibleSwiftAPI-Swift.h" + +#import "Transmission.h" + +@interface OldCar () +@property(nonatomic) Transmission *transmission; +#if EXPECT_FAILURE +@property(nonatomic) Driver *driver; +#endif +@end + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h new file mode 100644 index 00000000000..57d0f1524b8 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m new file mode 100644 index 00000000000..ced7e283e83 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m @@ -0,0 +1,6 @@ +#import + +#import "Transmission.h" + +@implementation Transmission +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h new file mode 100644 index 00000000000..01290cc43e6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h @@ -0,0 +1,8 @@ +#import + +#import "Driver.h" + +@interface OldCar : NSObject +// `Driver` is defined in Objective-C. +@property(nullable) Driver* driver; +@end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 2356f7a77ac..b6e6bb18e2a 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -2864,10 +2864,6 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Plan a Mixed target. private func plan(mixedTarget: MixedTargetBuildDescription) throws { - // TODO(ncooke3): Add tests for when mixed target depends on - // - MixedTarget - // - ClangTarget - // - SwiftTarget try plan(swiftTarget: mixedTarget.swiftTargetBuildDescription) try plan(clangTarget: mixedTarget.clangTargetBuildDescription) } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 88c5e150508..c75a25643f1 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -264,7 +264,7 @@ extension LLBuildManifestBuilder { // TODO(ncooke3): Do we need to support building mixed targets with the // `--use-integrated-swiftdriver` or `--emit-swift-module-separately` - // flags? + // flags? Ideally no because doing so does not look straightforward. if target.isWithinMixedTarget { try self.addCmdWithBuiltinSwiftTool( target, diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index a68cc5ea23f..b752b1becf2 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -205,7 +205,7 @@ extension Target.Error: CustomStringConvertible { case .invalidName(let path, let problem): return "invalid target name at '\(path)'; \(problem)" case .mixedSources(let path): - // TODO(ncooke3): Update error message with support version. + // FIXME(ncooke3): Update error message with support version. return "target at '\(path)' contains mixed language source " + "files; feature not supported until tools version XX" } @@ -882,14 +882,9 @@ public final class PackageBuilder { let buildSettings = try self.buildSettings(for: manifestTarget, targetRoot: potentialModule.path, cxxLanguageStandard: self.manifest.cxxLanguageStandard) // Compute the path to public headers directory. - let publicHeadersPath: AbsolutePath - switch potentialModule.type { - case .test: - publicHeadersPath = potentialModule.path - default: - let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent - publicHeadersPath = potentialModule.path.appending(try RelativePath(validating: publicHeaderComponent)) - } + let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent + let publicHeadersPath = potentialModule.path.appending(try RelativePath(validating: publicHeaderComponent)) + guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) } @@ -965,19 +960,29 @@ public final class PackageBuilder { // Create and return the right kind of target depending on what kind of sources we found. if sources.hasSwiftSources && sources.hasClangSources { - let moduleMapType = try findModuleMapType( - for: potentialModule, - targetType: targetType, - publicHeadersPath: publicHeadersPath, - isMixedTarget: true - ) + let mixedTargetPublicHeadersPath: AbsolutePath + let moduleMapType: ModuleMapType + // Mixed test targets use the target's root as an umbrella + // directory to expose all headers to the Swift portion of the test + // target. This enables the sharing of test utility files. + if targetType == .test { + mixedTargetPublicHeadersPath = potentialModule.path + moduleMapType = .umbrellaDirectory(potentialModule.path) + } else { + mixedTargetPublicHeadersPath = publicHeadersPath + moduleMapType = findModuleMapType( + for: potentialModule, + targetType: targetType, + publicHeadersPath: publicHeadersPath + ) + } return try MixedTarget( name: potentialModule.name, potentialBundleName: potentialBundleName, cLanguageStandard: manifest.cLanguageStandard, cxxLanguageStandard: manifest.cxxLanguageStandard, - includeDir: publicHeadersPath, + includeDir: mixedTargetPublicHeadersPath, moduleMapType: moduleMapType, headers: headers, type: targetType, @@ -1009,7 +1014,7 @@ public final class PackageBuilder { } else { // It's not a Mixed or Swift target, so it's a Clang target. - let moduleMapType = try findModuleMapType( + let moduleMapType = findModuleMapType( for: potentialModule, targetType: targetType, publicHeadersPath: publicHeadersPath @@ -1019,6 +1024,11 @@ public final class PackageBuilder { throw ModuleError.embedInCodeNotSupported(target: potentialModule.name) } + if moduleMapType == .none, targetType == .library, manifest.toolsVersion >= .v5_5 { + // If this clang target is a library, it must contain "include" directory. + throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) + } + return try ClangTarget( name: potentialModule.name, potentialBundleName: potentialBundleName, @@ -1237,28 +1247,19 @@ public final class PackageBuilder { private func findModuleMapType( for potentialModule: PotentialModule, targetType: Target.Kind, - publicHeadersPath: AbsolutePath, - isMixedTarget: Bool = false - ) throws -> ModuleMapType { - if fileSystem.exists(publicHeadersPath) { - let moduleMapGenerator = ModuleMapGenerator( - targetName: potentialModule.name, - moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), - publicHeadersDir: publicHeadersPath, - fileSystem: self.fileSystem - ) - return moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) - } else if targetType == .library, manifest.toolsVersion >= .v5_5, !isMixedTarget { - // If this clang target is a library, it must contain "include" directory. - throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) - } else if targetType == .test && isMixedTarget { - // TODO(ncooke3): Consider if this should be handled in BuildPlan.swift? - // Mixed test targets use an umbrella directory to expose all - // headers to the Swift portion of the test target. - return .umbrellaDirectory(potentialModule.path) - } else { + publicHeadersPath: AbsolutePath + ) -> ModuleMapType { + guard fileSystem.exists(publicHeadersPath) else { return .none } + + let moduleMapGenerator = ModuleMapGenerator( + targetName: potentialModule.name, + moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), + publicHeadersDir: publicHeadersPath, + fileSystem: fileSystem + ) + return moduleMapGenerator.determineModuleMapType(observabilityScope: self.observabilityScope) } /// Find the test entry point file for the package. diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index 0044cdaae08..cff3bafe7fb 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -180,8 +180,8 @@ public struct TargetSourcesBuilder { diagnoseInvalidResource(in: target.resources) // It's an error to contain mixed language source files. - // TODO(ncooke3): Update `toolsVersion` when the PR merges. - if sources.containsMixedLanguage, toolsVersion < .v4 { + // FIXME(ncooke3): Update with next version of SPM. + if sources.containsMixedLanguage, toolsVersion < .vNext { throw Target.Error.mixedSources(targetPath) } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index c82464d387c..74b6ee86720 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -30,10 +30,6 @@ import enum TSCUtility.Diagnostics // TODO(ncooke3): Add test for building statically linked mixed target. // TODO(ncooke3): Add test for building dynamically linked mixed target. -// TODO(ncooke3): Add test for expected failure cases: -// - Non-Library target -// - Non-Test target - final class BuildPlanTests: XCTestCase { let inputsDir = AbsolutePath(#file).parentDirectory.appending(components: "Inputs") private let driverSupport = DriverSupport() @@ -1477,8 +1473,10 @@ final class BuildPlanTests: XCTestCase { Manifest.createRootManifest( name: "Pkg", path: .init(path: "/Pkg"), + // FIXME(ncooke3): Update error message with support version. + toolsVersion: .vNext, targets: [ - TargetDescription(name: "exe", dependencies: ["lib"]), + TargetDescription(name: "exe", dependencies: ["lib"], type: .executable), TargetDescription(name: "lib") ] ) @@ -1517,26 +1515,27 @@ final class BuildPlanTests: XCTestCase { let exe = try result.target(for: "exe").swiftTarget().compileArguments() XCTAssertMatch(exe, [ - "-target", "\(defaultTargetTriple)", "-swift-version", "4", + "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=/path/to/build/debug/lib.build/Product/module.modulemap", "-Xcc", "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/all-product-headers.yaml", + "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence ]) let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() XCTAssertMatch(swiftPartOfLib, [ - "-target", "\(defaultTargetTriple)", "-swift-version", "4", + "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", - "/path/to/build/debug/lib.build/Product", "-Xcc", "-ivfsoverlay", - "-Xcc", "/path/to/build/debug/lib.build/all-product-headers.yaml", + "/path/to/build/debug/lib.build/Intermediates", "-Xcc", + "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-Xcc", "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/unextended-module-overlay.yaml", - "-module-cache-path", + "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/lib.build/lib-Swift.h" @@ -1546,8 +1545,10 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(clangPartOfLib, [ "-fobjc-arc", "-target", "x86_64-apple-macosx10.13", "-g", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", - "-fmodule-name=lib", "-I", "/path/to/build/debug/lib.build/Product", - "-ivfsoverlay", "/path/to/build/debug/lib.build/all-product-headers.yaml", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", + "/Pkg/Sources/lib", "-ivfsoverlay", + "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", + "-I", "/path/to/build/debug/lib.build/Intermediates", "-fmodules-cache-path=/path/to/build/debug/ModuleCache" ]) @@ -1568,10 +1569,11 @@ final class BuildPlanTests: XCTestCase { result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", - "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", - "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, "-Xlinker", - "-add_ast_path", "-Xlinker", - buildPath.appending(components: "exe.build", "exe.swiftmodule").pathString + "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", + "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString ]) testDiagnostics(observability.diagnostics) { result in diff --git a/Tests/BuildTests/ModuleAliasingBuildTests.swift b/Tests/BuildTests/ModuleAliasingBuildTests.swift index ae65b959776..98c49f30b90 100644 --- a/Tests/BuildTests/ModuleAliasingBuildTests.swift +++ b/Tests/BuildTests/ModuleAliasingBuildTests.swift @@ -1004,7 +1004,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/fooPkg/Sources/Logging/fileLogging.m", "/fooPkg/Sources/Logging/include/fileLogging.h", "/fooPkg/Sources/Logging/FileLogging.swift" - ) + ) let observability = ObservabilitySystem.makeForTesting() let _ = try loadPackageGraph( fileSystem: fs, @@ -1012,6 +1012,8 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "fooPkg", path: "/fooPkg", + // FIXME(ncooke3): Update with next version of SPM. + toolsVersion: .vNext, products: [ ProductDescription(name: "Utils", type: .library(.automatic), targets: ["Utils"]), ], diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 62fde633e7b..3a97cb5c738 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -17,8 +17,6 @@ import SPMTestSupport // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. // TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. -// TODO(ncooke3): Add test for mixed target with no ObjC-compatible Swift API. - // MARK: - MixedTargetTests final class MixedTargetTests: XCTestCase { @@ -52,6 +50,7 @@ final class MixedTargetTests: XCTestCase { } } + // TODO(ncooke3): Figure out when this started failing. func testMixedTargetWithCustomModuleMapAndResources() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( @@ -107,6 +106,21 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithNoObjectiveCCompatibleSwiftAPI() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithNoObjectiveCCompatibleSwiftAPI"] + ) + + XCTAssertBuildFails( + fixturePath, + extraArgs: ["--target", "MixedTargetWithNoObjectiveCCompatibleSwiftAPI"], + Xcc: ["EXPECT_FAILURE"] + ) + } + } + func testNonPublicHeadersAreVisibleFromSwiftHalfOfMixedTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index 0e8edb6a565..ed4d86963b9 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -52,14 +52,18 @@ class PackageBuilderTests: XCTestCase { let manifest = Manifest.createRootManifest( name: "pkg", path: .root, - toolsVersion: .v3, + // Use older tools version where mixed targets are not supported. + toolsVersion: .v5, targets: [ try TargetDescription(name: "foo"), ] ) PackageBuilderTester(manifest, in: fs) { _, diagnostics in - // TODO(ncooke3): Update error message with support version. - diagnostics.check(diagnostic: "target at '\(foo)' contains mixed language source files; feature not supported until tools version XX", severity: .error) + // FIXME(ncooke3): Update error message with support version. + diagnostics.check( + diagnostic: "target at '\(foo)' contains mixed language source files; feature not supported until tools version XX", + severity: .error + ) } } @@ -77,6 +81,8 @@ class PackageBuilderTests: XCTestCase { let manifest = Manifest.createRootManifest( displayName: "pkg", path: .root, + // FIXME(ncooke3): Update with next version of SPM. + toolsVersion: .vNext, targets: [ try TargetDescription(name: "foo"), ] @@ -104,6 +110,8 @@ class PackageBuilderTests: XCTestCase { let manifest = Manifest.createRootManifest( name: "pkg", path: .root, + // FIXME(ncooke3): Update with next version of SPM. + toolsVersion: .vNext, targets: [ try TargetDescription(name: "foo"), ] @@ -118,6 +126,31 @@ class PackageBuilderTests: XCTestCase { } } + func testMixedTargetsDoNotSupportExecutables() throws { + let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") + + let fs = InMemoryFileSystem(emptyFiles: + foo.appending(components: "Foo.swift").pathString, + foo.appending(components: "main.c").pathString + ) + + let manifest = Manifest.createRootManifest( + name: "pkg", + path: .root, + // FIXME(ncooke3): Update with next version of SPM. + toolsVersion: .vNext, + targets: [ + try TargetDescription(name: "foo", type: .executable), + ] + ) + PackageBuilderTester(manifest, in: fs) { _, diagnostics in + diagnostics.check( + diagnostic: "Target with mixed sources at \(foo) is a \(Target.Kind.executable) target; targets with mixed language sources are only supported for library and test targets.", + severity: .error + ) + } + } + func testBrokenSymlink() throws { try testWithTemporaryDirectory { path in let fs = localFileSystem From c6aae7d06f9c09a926984b692aa4457ca81496d0 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 21 Dec 2022 21:21:35 -0500 Subject: [PATCH 054/178] Fix comment now that issue is understood --- Tests/FunctionalTests/MixedTargetTests.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 3a97cb5c738..bd05580d84e 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -50,15 +50,19 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Figure out when this started failing. func testMixedTargetWithCustomModuleMapAndResources() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, - extraArgs: ["--target", "MixedTargetWithCustomModuleMapAndResources"], - // Surface warning where custom umbrella header does not - // include `resource_bundle_accessor.h` in `build` directory. - Xswiftc: ["-warnings-as-errors"] + extraArgs: [ + "--target", "MixedTargetWithCustomModuleMapAndResources" + // FIXME(ncooke3): Blocked by fix for #5728. +// ], +// // Surface warning where custom umbrella header does not +// // include `resource_bundle_accessor.h` in `build` directory. +// Xswiftc: [ +// "-warnings-as-errors" + ] ) } } From 15eb0d192088bbc68cd405336af31031b7996f45 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 22 Dec 2022 14:17:10 -0500 Subject: [PATCH 055/178] Add linking tests (unit and integration) --- .../MixedTargets/DummyTargets/Package.swift | 50 +++++ Fixtures/MixedTargets/DummyTargets/README.md | 3 + .../main.m | 14 ++ .../main.m | 14 ++ .../main.swift | 7 + .../main.swift | 7 + Tests/BuildTests/BuildPlanTests.swift | 207 +++++++++++++++++- Tests/FunctionalTests/MixedTargetTests.swift | 49 +++++ 8 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 Fixtures/MixedTargets/DummyTargets/Package.swift create mode 100644 Fixtures/MixedTargets/DummyTargets/README.md create mode 100644 Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m create mode 100644 Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m create mode 100644 Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnDynamicallyLinkedMixedTarget/main.swift create mode 100644 Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnStaticallyLinkedMixedTarget/main.swift diff --git a/Fixtures/MixedTargets/DummyTargets/Package.swift b/Fixtures/MixedTargets/DummyTargets/Package.swift new file mode 100644 index 00000000000..18806ae1c02 --- /dev/null +++ b/Fixtures/MixedTargets/DummyTargets/Package.swift @@ -0,0 +1,50 @@ +// swift-tools-version: 999.0 +// FIXME(ncooke3): Update above version with the next version of SwiftPM. + +import PackageDescription + +let package = Package( + name: "DummyTargets", + dependencies: [ + .package(path: "../BasicMixedTargets") + ], + targets: [ + .executableTarget( + name: "ClangExecutableDependsOnDynamicallyLinkedMixedTarget", + dependencies: [ + .product( + name: "DynamicallyLinkedBasicMixedTarget", + package: "BasicMixedTargets" + ) + ] + ), + .executableTarget( + name: "ClangExecutableDependsOnStaticallyLinkedMixedTarget", + dependencies: [ + .product( + name: "StaticallyLinkedBasicMixedTarget", + package: "BasicMixedTargets" + ) + ] + ), + .executableTarget( + name: "SwiftExecutableDependsOnDynamicallyLinkedMixedTarget", + dependencies: [ + .product( + name: "DynamicallyLinkedBasicMixedTarget", + package: "BasicMixedTargets" + ) + ] + ), + .executableTarget( + name: "SwiftExecutableDependsOnStaticallyLinkedMixedTarget", + dependencies: [ + .product( + name: "StaticallyLinkedBasicMixedTarget", + package: "BasicMixedTargets" + ) + ] + ) + + ] +) diff --git a/Fixtures/MixedTargets/DummyTargets/README.md b/Fixtures/MixedTargets/DummyTargets/README.md new file mode 100644 index 00000000000..be78acc31bd --- /dev/null +++ b/Fixtures/MixedTargets/DummyTargets/README.md @@ -0,0 +1,3 @@ +# DummyTargets + +This package vends targets to aid in testing the BasicMixedTargets package. diff --git a/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m new file mode 100644 index 00000000000..6426a5ca13c --- /dev/null +++ b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m @@ -0,0 +1,14 @@ +#import + +@import BasicMixedTarget; + +int main(int argc, const char *argv[]) { + @autoreleasepool { + // Ensure that the module is actually loaded. + Engine *engine = [[Engine alloc] init]; + OldCar *oldCar = [[OldCar alloc] init]; + + NSLog(@"Hello, world!"); + } + return 0; +} diff --git a/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m new file mode 100644 index 00000000000..de47370c06e --- /dev/null +++ b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m @@ -0,0 +1,14 @@ +#import + +@import BasicMixedTarget; + +int main(int argc, const char * argv[]) { + @autoreleasepool { + // Ensure that the module is actually loaded. + Engine *engine = [[Engine alloc] init]; + OldCar *oldCar = [[OldCar alloc] init]; + + NSLog(@"Hello, world!"); + } + return 0; +} diff --git a/Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnDynamicallyLinkedMixedTarget/main.swift b/Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnDynamicallyLinkedMixedTarget/main.swift new file mode 100644 index 00000000000..39b867d7aa4 --- /dev/null +++ b/Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnDynamicallyLinkedMixedTarget/main.swift @@ -0,0 +1,7 @@ +import BasicMixedTarget + +// Ensure that the module is actually loaded. +let _ = NewCar() +let _ = OldCar() + +print("Hello, world!") diff --git a/Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnStaticallyLinkedMixedTarget/main.swift b/Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnStaticallyLinkedMixedTarget/main.swift new file mode 100644 index 00000000000..39b867d7aa4 --- /dev/null +++ b/Fixtures/MixedTargets/DummyTargets/Sources/SwiftExecutableDependsOnStaticallyLinkedMixedTarget/main.swift @@ -0,0 +1,7 @@ +import BasicMixedTarget + +// Ensure that the module is actually loaded. +let _ = NewCar() +let _ = OldCar() + +print("Hello, world!") diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 74b6ee86720..c9b7a6d1d70 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -27,9 +27,6 @@ import class TSCBasic.InMemoryFileSystem import enum TSCUtility.Diagnostics -// TODO(ncooke3): Add test for building statically linked mixed target. -// TODO(ncooke3): Add test for building dynamically linked mixed target. - final class BuildPlanTests: XCTestCase { let inputsDir = AbsolutePath(#file).parentDirectory.appending(components: "Inputs") private let driverSupport = DriverSupport() @@ -2485,6 +2482,210 @@ final class BuildPlanTests: XCTestCase { #endif } + func testDynamicProductsForMixedTarget() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Foo/Sources/Foo/main.swift", + "/Bar/Source/Bar/source.swift", + "/Bar/Source/Bar/include/source.h", + "/Bar/Source/Bar/source.c" + ) + + let observability = ObservabilitySystem.makeForTesting() + let g = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createFileSystemManifest( + name: "Bar", + path: .init(path: "/Bar"), + // FIXME(ncooke3): Update with next version of SPM. + toolsVersion: .vNext, + products: [ + ProductDescription( + name: "Bar-Baz", + type: .library(.dynamic), + targets: ["Bar"] + ), + ], + targets: [ + TargetDescription(name: "Bar"), + ]), + Manifest.createRootManifest( + name: "Foo", + path: .init(path: "/Foo"), + dependencies: [ + .localSourceControl( + path: .init(path: "/Bar"), + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + targets: [ + TargetDescription( + name: "Foo", + dependencies: ["Bar-Baz"], + type: .executable + ) + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + #if !os(macOS) + XCTAssertThrowsError( + try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )), + "This should fail when run on non-Apple platforms." + ) { error in + XCTAssertEqual( + error.localizedDescription, + "Targets with mixed language sources are only supported on Apple platforms." + ) + } + #else + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(), + graph: g, + fileSystem: fs, + observabilityScope: observability.topScope + )) + result.checkProductsCount(2) + result.checkTargetsCount(2) + + let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + + let fooLinkArgs = try result.buildProduct(for: "Foo").linkArguments() + let barLinkArgs = try result.buildProduct(for: "Bar-Baz").linkArguments() + + XCTAssertEqual(fooLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "Foo").pathString, + "-module-name", "Foo", + "-lBar-Baz", + "-emit-executable", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString + ]) + + XCTAssertEqual(barLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "libBar-Baz.dylib").pathString, + "-module-name", "Bar_Baz", + "-emit-library", + "-Xlinker", "-install_name", "-Xlinker", "@rpath/libBar-Baz.dylib", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "Bar-Baz.product", "Objects.LinkFileList"))", + "-runtime-compatibility-version", "none", "-target", defaultTargetTriple + ]) + #endif + } + + func testStaticProductsForMixedTarget() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Foo/Sources/Foo/main.swift", + "/Bar/Source/Bar/source.swift", + "/Bar/Source/Bar/include/source.h", + "/Bar/Source/Bar/source.c" + ) + + let observability = ObservabilitySystem.makeForTesting() + let g = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createFileSystemManifest( + name: "Bar", + path: .init(path: "/Bar"), + // FIXME(ncooke3): Update with next version of SPM. + toolsVersion: .vNext, + products: [ + ProductDescription( + name: "Bar-Baz", + type: .library(.static), + targets: ["Bar"] + ), + ], + targets: [ + TargetDescription(name: "Bar"), + ]), + Manifest.createRootManifest( + name: "Foo", + path: .init(path: "/Foo"), + dependencies: [ + .localSourceControl( + path: .init(path: "/Bar"), + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + targets: [ + TargetDescription( + name: "Foo", + dependencies: ["Bar-Baz"], + type: .executable + ) + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + #if !os(macOS) + XCTAssertThrowsError( + try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )), + "This should fail when run on non-Apple platforms." + ) { error in + XCTAssertEqual( + error.localizedDescription, + "Targets with mixed language sources are only supported on Apple platforms." + ) + } + #else + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(), + graph: g, + fileSystem: fs, + observabilityScope: observability.topScope + )) + result.checkProductsCount(2) + result.checkTargetsCount(2) + + let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + + let fooLinkArgs = try result.buildProduct(for: "Foo").linkArguments() + let barLinkArgs = try result.buildProduct(for: "Bar-Baz").linkArguments() + + XCTAssertEqual(fooLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "Foo").pathString, + "-module-name", "Foo", + "-emit-executable", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString + ]) + + // No arguments for linking static libraries. + XCTAssertEqual(barLinkArgs, []) + #endif + } + func testExecAsDependency() throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index bd05580d84e..16cc3658a06 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -17,6 +17,9 @@ import SPMTestSupport // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. // TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. +// Mixed language targets are only supported on macOS. +#if os(macOS) + // MARK: - MixedTargetTests final class MixedTargetTests: XCTestCase { @@ -179,6 +182,28 @@ final class MixedTargetTests: XCTestCase { extraArgs: ["--product", "StaticallyLinkedBasicMixedTarget"] ) } + + // Test that statically linked mixed library is successfully + // integrated into an Objective-C executable. + try fixture(name: "MixedTargets") { fixturePath in + let output = try executeSwiftRun( + fixturePath.appending(component: "DummyTargets"), + "ClangExecutableDependsOnStaticallyLinkedMixedTarget" + ) + // The program should print "Hello, world!" + XCTAssert(output.stderr.contains("Hello, world!")) + } + + // Test that statically linked mixed library is successfully + // integrated into a Swift executable. + try fixture(name: "MixedTargets") { fixturePath in + let output = try executeSwiftRun( + fixturePath.appending(component: "DummyTargets"), + "SwiftExecutableDependsOnStaticallyLinkedMixedTarget" + ) + // The program should print "Hello, world!" + XCTAssert(output.stdout.contains("Hello, world!")) + } } func testDynamicallyLinkedMixedTarget() throws { @@ -188,6 +213,28 @@ final class MixedTargetTests: XCTestCase { extraArgs: ["--product", "DynamicallyLinkedBasicMixedTarget"] ) } + + // Test that dynamically linked mixed library is successfully + // integrated into an Objective-C executable. + try fixture(name: "MixedTargets") { fixturePath in + let output = try executeSwiftRun( + fixturePath.appending(component: "DummyTargets"), + "ClangExecutableDependsOnDynamicallyLinkedMixedTarget" + ) + // The program should print "Hello, world!" + XCTAssert(output.stderr.contains("Hello, world!")) + } + + // Test that dynamically linked mixed library is successfully + // integrated into a Swift executable. + try fixture(name: "MixedTargets") { fixturePath in + let output = try executeSwiftRun( + fixturePath.appending(component: "DummyTargets"), + "SwiftExecutableDependsOnDynamicallyLinkedMixedTarget" + ) + // The program should print "Hello, world!" + XCTAssert(output.stdout.contains("Hello, world!")) + } } func testMixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource() throws { @@ -314,3 +361,5 @@ final class MixedTargetTests: XCTestCase { } } + +#endif From cd4ead53555e924e53162c0db59d783edf11ccc5 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 22 Dec 2022 21:35:38 -0500 Subject: [PATCH 056/178] Add CXX integration test and document use case --- .../BasicMixedTargets/Package.swift | 41 ++++++++++++++++- .../CXXFactorialFinder.cpp | 6 +++ .../CXXFactorialFinder.hpp | 5 +++ .../Factorial.swift | 5 +++ .../ObjcCalculator.mm | 21 +++++++++ .../MixedTargetWithPublicCXXAPI/Sum.swift | 5 +++ .../MixedTargetWithPublicCXXAPI/SumFinder.cpp | 5 +++ .../include/CXXSumFinder.hpp | 5 +++ .../include/ObjcCalculator.h | 6 +++ .../include/module.modulemap | 11 +++++ .../MixedTargetWithPublicCXXAPITests.swift | 12 +++++ .../ObjcMixedTargetWithPublicCXXAPITests.mm | 20 +++++++++ Tests/FunctionalTests/MixedTargetTests.swift | 44 ++++++++++++++++--- 13 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.hpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/ObjcCalculator.mm create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Sum.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/SumFinder.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/CXXSumFinder.hpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/ObjcCalculator.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/module.modulemap create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index e02c7a92426..33b59b0cd6b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -68,11 +68,50 @@ let package = Package( dependencies: ["MixedTargetWithCXX"] ), - // MARK: MixedTargetWithCXXAndCustomModuleMap + // MARK: - MixedTargetWithCXXAndCustomModuleMap .target( name: "MixedTargetWithCXXAndCustomModuleMap" ), + // MARK: - MixedTargetWithPublicCXXAPI + // In order to import this target into downstream targets, two + // additional things must be done (depending on whether the target is + // being imported into a Clang vs. Swift context): + // - Clang context: The downstream target must pass `-fcxx-modules` + // and `-fmodules` as unsafe flags in the target's `cSettings`. + // - Swift context: The mixed target needs to make a custom module + // map that only exposes public CXX headers in a non-Swift context. + // + // // module.modulemap + // module MixedTargetWithPublicCXXAPI { + // umbrella header "PublicNonCXXHeaders.h" + // + // module CXX { + // header "PublicCXXHeaders.h" + // export * + // requires !swift + // } + // + // export * + // } + // + .target( + name: "MixedTargetWithPublicCXXAPI" + ), + .testTarget( + name: "MixedTargetWithPublicCXXAPITests", + dependencies: ["MixedTargetWithPublicCXXAPI"], + cSettings: [ + // To get the `MixedTargetWithPublicCXXAPI` target to build for use in + // an Objective-C context (e.g. Objective-C++ test file), the following + // unsafe flags must be passed. + .unsafeFlags(["-fcxx-modules", "-fmodules"]) + ], + linkerSettings: [ + .linkedLibrary("c++"), + ] + ), + // MARK: - MixedTargetWithC .target( name: "MixedTargetWithC" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.cpp new file mode 100644 index 00000000000..a9a78c1feb9 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.cpp @@ -0,0 +1,6 @@ +#include "CXXFactorialFinder.hpp" + +long CXXFactorialFinder::factorial(int n) { + if (n == 0 || n == 1) return 1; + return n * factorial(n-1); +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.hpp new file mode 100644 index 00000000000..3308e76415b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.hpp @@ -0,0 +1,5 @@ +class CXXFactorialFinder +{ +public: + long factorial(int n); +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift new file mode 100644 index 00000000000..d8cdf5c3726 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift @@ -0,0 +1,5 @@ +import Foundation + +public func factorial(_ x: Int32) -> Int { + return ObjcCalculator.factorial(for: x) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/ObjcCalculator.mm b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/ObjcCalculator.mm new file mode 100644 index 00000000000..89b35f91fd8 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/ObjcCalculator.mm @@ -0,0 +1,21 @@ +#import + +#import "include/ObjcCalculator.h" + +// Import C++ headers. +#import "CXXFactorialFinder.hpp" +#import "CXXSumFinder.hpp" + +@implementation ObjcCalculator + ++ (long)factorialForInt:(int)integer { + CXXFactorialFinder ff; + return ff.factorial(integer); +} + ++ (long)sumX:(int)x andY:(int)y { + CXXSumFinder sf; + return sf.sum(x, y); +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Sum.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Sum.swift new file mode 100644 index 00000000000..ba47080c761 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Sum.swift @@ -0,0 +1,5 @@ +import Foundation + +public func sum(x: Int32, y: Int32) -> Int { + return ObjcCalculator.sum(x:x, y:y) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/SumFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/SumFinder.cpp new file mode 100644 index 00000000000..fdba9f04ec2 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/SumFinder.cpp @@ -0,0 +1,5 @@ +#include "CXXSumFinder.hpp" + +long CXXSumFinder::sum(int x, int y) { + return x + y; +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/CXXSumFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/CXXSumFinder.hpp new file mode 100644 index 00000000000..540134e63be --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/CXXSumFinder.hpp @@ -0,0 +1,5 @@ +class CXXSumFinder +{ +public: + long sum(int x, int y); +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/ObjcCalculator.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/ObjcCalculator.h new file mode 100644 index 00000000000..14378035ebe --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/ObjcCalculator.h @@ -0,0 +1,6 @@ +#import + +@interface ObjcCalculator : NSObject ++ (long)factorialForInt:(int)integer; ++ (long)sumX:(int)x andY:(int)y NS_SWIFT_NAME(sum(x:y:)); +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/module.modulemap new file mode 100644 index 00000000000..b9ac4b7d8dc --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/module.modulemap @@ -0,0 +1,11 @@ +module MixedTargetWithPublicCXXAPI { + header "ObjcCalculator.h" + + module PublicCXXAPI { + header "CXXSumFinder.hpp" + requires !swift + export * + } + + export * +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift new file mode 100644 index 00000000000..25da15aee36 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift @@ -0,0 +1,12 @@ +import XCTest +import MixedTargetWithPublicCXXAPI + +final class MixedTargetWithCXXTests: XCTestCase { + func testFactorial() throws { + XCTAssertEqual(factorial(5), 120) + } + + func testSum() throws { + XCTAssertEqual(sum(x: 60, y: 40), 100) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm new file mode 100644 index 00000000000..d1c36971534 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm @@ -0,0 +1,20 @@ +#import + +@import MixedTargetWithPublicCXXAPI; + +@interface ObjcMixedTargetWithPublicCXXAPITests : XCTestCase +@end + +@implementation ObjcMixedTargetWithPublicCXXAPITests + +- (void)testPublicObjcAPI { + XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); + XCTAssertEqual([ObjcCalculator sumX:1 andY:2], 3); +} + +- (void)testPublicCXXAPI { + CXXSumFinder sf; + XCTAssertEqual(sf.sum(1,2), 3); +} + +@end diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 16cc3658a06..ac365aec9ea 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -17,13 +17,13 @@ import SPMTestSupport // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. // TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. -// Mixed language targets are only supported on macOS. -#if os(macOS) - // MARK: - MixedTargetTests final class MixedTargetTests: XCTestCase { + // Mixed language targets are only supported on macOS. + #if os(macOS) + // MARK: - Testing Mixed Targets func testMixedTarget() throws { @@ -70,12 +70,11 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Can you export a C++ type in a mixed Obj-C / Cxx project? func testMixedTargetWithCXX() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertSwiftTest( fixturePath, - extraArgs: ["--filter", "MixedTargetWithCXX"] + extraArgs: ["--filter", "MixedTargetWithCXXTests"] ) } } @@ -89,6 +88,16 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithPublicCXXAPI() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithPublicCXXAPITests"] + ) + } + } + + func testMixedTargetWithC() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertSwiftTest( @@ -360,6 +369,27 @@ final class MixedTargetTests: XCTestCase { } } -} + #else -#endif + // MARK: - Test Mixed Targets unsupported on non-macOS + + func testMixedTargetOnlySupportedOnMacOS() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + let commandExecutionError = try XCTUnwrap( + XCTAssertBuildFails( + fixturePath, + extraArgs: ["--target", "BasicMixedTarget"] + ) + ) + + XCTAssert( + commandExecutionError.stderr.contains( + "error: Targets with mixed language sources are only supported on Apple platforms." + ) + ) + } + } + + #endif + +} From 84b0dde041254673a5df8d5bad73141f4a337dc2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 22 Dec 2022 21:38:37 -0500 Subject: [PATCH 057/178] s/half/part --- Sources/Build/BuildPlan.swift | 4 ++-- Sources/Build/LLBuildManifestBuilder.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index b6e6bb18e2a..f6c0c8ec9e7 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -436,7 +436,7 @@ public final class ClangTargetBuildDescription { // The underlying Clang target is building within a Mixed // language target and needs an auxiliary module map that // doesn't include the generated interop header from the - // Swift half of the mixed target. This will later allow the + // Swift part of the mixed target. This will later allow the // Clang half of the module to be built when compiling the // Swift part without the generated header being considered // an input (because it won't exist yet and is an output of @@ -1552,7 +1552,7 @@ public final class MixedTargetBuildDescription { let rootOverlayResourceDirectory: AbsolutePath if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { // Building a mixed target uses a modified module map to expose - // private headers to the Swift half of the module. To avoid + // private headers to the Swift part of the module. To avoid // the custom module map causing a module redeclaration error, // a VFS overlay is used when building the target to redirect // the custom module map to the modified module map in the diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index c75a25643f1..de35f71a02e 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -842,9 +842,9 @@ extension LLBuildManifestBuilder { } // If it's a mixed target, add the Objective-C compatibility header that - // the Swift half of the mixed target generates. This header acts as an + // the Swift part of the mixed target generates. This header acts as an // input to the Clang compile command, which therefore forces the - // Swift half of the mixed target to be built first. + // Swift part of the mixed target to be built first. if target.isWithinMixedTarget { inputs += [ .file(target.tempsPath.appending(component: "\(target.target.name)-Swift.h")) From 1f5c4473c90ab60c0f3cb18ab94197f6cfe58ca2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 22 Dec 2022 21:42:13 -0500 Subject: [PATCH 058/178] s/half/part (pt. 2) --- .../MixedTargetWithCustomPaths/Sources/OldCar.m | 7 +++---- .../MixedTargetWithCustomPaths/Sources/Public/OldCar.h | 6 +++--- Fixtures/MixedTargets/BasicMixedTargets/README.md | 4 ++-- .../BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m | 2 +- .../Sources/BasicMixedTarget/include/OldCar.h | 6 +++--- .../Sources/MixedTargetWithCustomModuleMap/OldCar.m | 2 +- .../MixedTargetWithCustomModuleMap/include/OldCar.h | 6 +++--- .../MixedTargetWithCustomModuleMapAndResources/OldCar.m | 2 +- .../include/OldCar.h | 6 +++--- .../Blah/Public/OldCar.h | 6 +++--- .../Sources/MixedTargetWithNestedPublicHeaders/OldCar.m | 2 +- .../Blah/Public/OldCar.h | 6 +++--- .../OldCar.m | 2 +- .../OldCar.m | 2 +- .../MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h | 6 +++--- .../MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m | 2 +- Sources/Build/BuildPlan.swift | 8 ++++---- Tests/FunctionalTests/MixedTargetTests.swift | 2 +- 18 files changed, 38 insertions(+), 39 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m index 1948c11b3c5..d5f94f038bd 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m @@ -1,17 +1,16 @@ #import // All three import statements should be supported. +#import "MixedTargetWithCustomPaths/Sources/Public/OldCar.h" #import "OldCar.h" #import "Public/OldCar.h" -#import "MixedTargetWithCustomPaths/Sources/Public/OldCar.h" - -// Import the Swift half of the module. +// Import the Swift part of the module. #import "MixedTargetWithCustomPaths-Swift.h" // Both import statements should be supported. -#import "Transmission.h" #import "MixedTargetWithCustomPaths/Sources/Transmission.h" +#import "Transmission.h" @interface OldCar () @property(nonatomic) Transmission *transmission; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h index b75ae54cce1..995e09f0ff9 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h @@ -2,13 +2,13 @@ #import "Driver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @interface OldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; +@property(nullable) Driver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/README.md b/Fixtures/MixedTargets/BasicMixedTargets/README.md index cc59cbe9bb9..89a506e2aeb 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/README.md +++ b/Fixtures/MixedTargets/BasicMixedTargets/README.md @@ -4,8 +4,8 @@ A collection of targets to test SPM's support of mixed language targets. ## BasicMixedTarget Represents a simple mixed package where: -- Swift half of the target used types from the Objective-C half of the module -- Objective-C half of the target used types from the Swift half of the module +- Swift part of the target used types from the Objective-C part of the module +- Objective-C p of the target used types from the Swift part of the module ## MixedTargetWithResources Represents a simple mixed package with a bundled resource where: diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m index 4b86a46f146..0e519c718fe 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m @@ -3,7 +3,7 @@ #import "OldCar.h" #import "include/OldCar.h" -// Import the Swift half of the module. +// Import the Swift part of the module. #import "BasicMixedTarget-Swift.h" #import "Transmission.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h index b75ae54cce1..995e09f0ff9 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h @@ -2,13 +2,13 @@ #import "Driver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @interface OldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; +@property(nullable) Driver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m index d27efbc36ac..f1496e613a6 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m @@ -4,7 +4,7 @@ #import "OldCar.h" #import "include/OldCar.h" -// Import the Swift half of the module. +// Import the Swift part of the module. #import "MixedTargetWithCustomModuleMap-Swift.h" @implementation OldCar diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h index b75ae54cce1..995e09f0ff9 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h @@ -2,13 +2,13 @@ #import "Driver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @interface OldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; +@property(nullable) Driver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m index c8ac23bdf45..3f69ff353b8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m @@ -4,7 +4,7 @@ #import "OldCar.h" #import "include/OldCar.h" -// Import the Swift half of the module. +// Import the Swift part of the module. #import "MixedTargetWithCustomModuleMapAndResources-Swift.h" @implementation OldCar diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h index b75ae54cce1..995e09f0ff9 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h @@ -2,13 +2,13 @@ #import "Driver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @interface OldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; +@property(nullable) Driver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h index 46b7bcb25dc..60a945def50 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h @@ -6,13 +6,13 @@ // - This one is from the root of the target's sources directory. #import "Blah/Public/Driver/Driver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @interface OldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; +@property(nullable) Driver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m index b04f5704b97..10522bd7820 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m @@ -6,7 +6,7 @@ // - This one is from the root of the target's sources directory. #import "Blah/Public/OldCar.h" -// Import the Swift half of the module. +// Import the Swift part of the module. #import "MixedTargetWithNestedPublicHeaders-Swift.h" #import "Transmission.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h index 46b7bcb25dc..60a945def50 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h @@ -6,13 +6,13 @@ // - This one is from the root of the target's sources directory. #import "Blah/Public/Driver/Driver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @interface OldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver* driver; +@property(nullable) Driver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m index 8d33bd4069d..6fe0be3ad91 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m @@ -6,7 +6,7 @@ // - This one is from the root of the target's sources directory. #import "Blah/Public/OldCar.h" -// Import the Swift half of the module. +// Import the Swift part of the module. #import "MixedTargetWithNestedPublicHeadersAndCustomModuleMap-Swift.h" #import "Transmission.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m index a889ec00d56..6c1ad2b75eb 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m @@ -3,7 +3,7 @@ #import "OldCar.h" #import "include/OldCar.h" -// Import the Swift half of the module. Note that this header wouldn't vend any +// Import the Swift part of the module. Note that this header wouldn't vend any // API as this target intentionally has no Objective-C compatible Swift API. #import "MixedTargetWithNoObjectiveCCompatibleSwiftAPI-Swift.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h index 9df7690f2e0..1324aa18b7d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h @@ -2,7 +2,7 @@ #import "XYZDriver.h" -// The `Engine` type is declared in the Swift half of the module. Such types +// The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; @@ -12,7 +12,7 @@ // test package. @interface XYZOldCar : NSObject // `Engine` is defined in Swift. -@property(nullable) Engine* engine; +@property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) XYZDriver* driver; +@property(nullable) XYZDriver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m index 9d73961eb0c..f8c80ac2afe 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m @@ -2,7 +2,7 @@ #import "XYZOldCar.h" -// Import the Swift half of the module. +// Import the Swift part of the module. #import "MixedTargetWithNoPublicObjectiveCHeaders-Swift.h" @implementation XYZOldCar diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index f6c0c8ec9e7..afdfd8c3d85 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -437,7 +437,7 @@ public final class ClangTargetBuildDescription { // language target and needs an auxiliary module map that // doesn't include the generated interop header from the // Swift part of the mixed target. This will later allow the - // Clang half of the module to be built when compiling the + // Clang part of the module to be built when compiling the // Swift part without the generated header being considered // an input (because it won't exist yet and is an output of // that compilation command). @@ -1450,7 +1450,7 @@ public final class MixedTargetBuildDescription { } /// The path to the VFS overlay file that overlays the public headers of - /// the Clang half of the target over the target's build directory. + /// the Clang part of the target over the target's build directory. private(set) var allProductHeadersOverlay: AbsolutePath? = nil /// The modulemap file for this target, if any @@ -1560,7 +1560,7 @@ public final class MixedTargetBuildDescription { // custom module map's parent directory, as to replace it. rootOverlayResourceDirectory = customModuleMapPath.parentDirectory // Importing the underlying module will build the Objective-C - // half of the module. In order to find the underlying module, + // part of the module. In order to find the underlying module, // a `module.modulemap` needs to be discoverable via a header // search path. In the case of a custom module map, its parent // directory is used. @@ -1578,7 +1578,7 @@ public final class MixedTargetBuildDescription { // header. rootOverlayResourceDirectory = buildArtifactIntermediatesDirectory // Importing the underlying module will build the Objective-C - // half of the module. In order to find the underlying module, + // part of the module. In order to find the underlying module, // a `module.modulemap` needs to be discoverable via a header // search path. In this case, the module map in the build // directory is used. diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index ac365aec9ea..740d5e84d7d 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -137,7 +137,7 @@ final class MixedTargetTests: XCTestCase { } } - func testNonPublicHeadersAreVisibleFromSwiftHalfOfMixedTarget() throws { + func testNonPublicHeadersAreVisibleFromSwiftPartfOfMixedTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, From da01fd7d06e6d59dd6002d40394683cedb53b1ed Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 23 Dec 2022 22:11:09 -0500 Subject: [PATCH 059/178] Refactor approach and add more tests --- .../BasicMixedTargets/Package.swift | 4 + .../Driver.m | 2 +- .../NewCar.swift | 2 + .../OldCar.m | 2 +- .../Transmission.h | 3 +- .../Transmission.m | 2 +- .../include/Driver.h | 3 +- .../include/OldCar.h | 5 +- .../include/module.modulemap | 2 +- ...WithCustomModuleMapAndResourcesTests.swift | 25 + ...rgetWithCustomModuleMapAndResourcesTests.m | 21 + Sources/Basics/FileSystem+Extensions.swift | 364 ++++++++++++ Sources/Basics/FileSystem/VFSOverlay.swift | 49 -- Sources/Build/BuildPlan.swift | 555 ++++++++---------- Sources/Build/LLBuildManifestBuilder.swift | 2 +- .../PackageLoading/ModuleMapGenerator.swift | 14 +- Tests/BuildTests/BuildPlanTests.swift | 27 +- Tests/FunctionalTests/MixedTargetTests.swift | 8 +- .../ModuleMapGenerationTests.swift | 16 +- 19 files changed, 713 insertions(+), 393 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m create mode 100644 Sources/Basics/FileSystem+Extensions.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 33b59b0cd6b..1ce2d4783ea 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -58,6 +58,10 @@ let package = Package( .process("foo.txt") ] ), + .testTarget( + name: "MixedTargetWithCustomModuleMapAndResourcesTests", + dependencies: ["MixedTargetWithCustomModuleMapAndResources"] + ), // MARK: - MixedTargetWithC++ .target( diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m index 5870c8e4c6d..fc76ea21a8e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m @@ -4,5 +4,5 @@ #import "Driver.h" #import "include/Driver.h" -@implementation Driver +@implementation ABCDriver @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift index 2fd0108dc6f..6f3fc717999 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift @@ -7,4 +7,6 @@ public class NewCar { var driver: Driver? = nil // `Transmission` is defined ina private header. var transmission: Transmission? = nil + + public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m index 3f69ff353b8..741d1bc3d46 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m @@ -7,5 +7,5 @@ // Import the Swift part of the module. #import "MixedTargetWithCustomModuleMapAndResources-Swift.h" -@implementation OldCar +@implementation ABCOldCar @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h index 57d0f1524b8..9dae75e314e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h @@ -5,6 +5,7 @@ typedef NS_ENUM(NSInteger, TransmissionKind) { TransmissionKindAutomatic }; -@interface Transmission : NSObject +NS_SWIFT_NAME(Transmission) +@interface ABCTransmission : NSObject @property (nonatomic, readonly, assign) TransmissionKind transmissionKind; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m index ced7e283e83..36f9d4edad3 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m @@ -2,5 +2,5 @@ #import "Transmission.h" -@implementation Transmission +@implementation ABCTransmission @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h index 8326bf2f179..8a70e1f9915 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h @@ -1,6 +1,7 @@ #import // This type is Swift compatible and used in `NewCar`. -@interface Driver : NSObject +NS_SWIFT_NAME(Driver) +@interface ABCDriver : NSObject @property(nonnull) NSString* name; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h index 995e09f0ff9..122c9846647 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h @@ -6,9 +6,10 @@ // must be forward declared in headers. @class Engine; -@interface OldCar : NSObject +NS_SWIFT_NAME(OldCar) +@interface ABCOldCar : NSObject // `Engine` is defined in Swift. @property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver *driver; +@property(nullable) ABCDriver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap index 0c7823261fc..5b8abc6c387 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap @@ -1,4 +1,4 @@ -module MixedTargetWithCustomModuleMap { +module MixedTargetWithCustomModuleMapAndResources { umbrella header "MixedTarget.h" export * } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift new file mode 100644 index 00000000000..70f1e106667 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift @@ -0,0 +1,25 @@ +import XCTest +import MixedTargetWithCustomModuleMapAndResources + +final class MixedTargetWithCustomModuleMapAndResourcesTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = NewCar() + let _ = Engine() + } + + func testPublicObjcAPI() throws { + // Check that Objective-C API surface is exposed... + let _ = OldCar() + let _ = Driver() + } + + func testModulePrefixingWorks() throws { + let _ = MixedTargetWithCustomModuleMapAndResources.NewCar() + let _ = MixedTargetWithCustomModuleMapAndResources.Engine() + let _ = MixedTargetWithCustomModuleMapAndResources.OldCar() + let _ = MixedTargetWithCustomModuleMapAndResources.Driver() + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m new file mode 100644 index 00000000000..00e11053c74 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m @@ -0,0 +1,21 @@ +#import + +@import MixedTargetWithCustomModuleMapAndResources; + +@interface ObjcMixedTargetWithCustomModuleMapAndResourcesTests : XCTestCase +@end + +@implementation ObjcMixedTargetWithCustomModuleMapAndResourcesTests + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + ABCOldCar *oldCar = [[ABCOldCar alloc] init]; + ABCDriver *driver = [[ABCDriver alloc] init]; +} + +@end diff --git a/Sources/Basics/FileSystem+Extensions.swift b/Sources/Basics/FileSystem+Extensions.swift new file mode 100644 index 00000000000..87f4a3191aa --- /dev/null +++ b/Sources/Basics/FileSystem+Extensions.swift @@ -0,0 +1,364 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2021 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 class Foundation.FileManager +import struct Foundation.Data +import struct Foundation.UUID +import SystemPackage +import TSCBasic + +// MARK: - user level + +extension FileSystem { + /// SwiftPM directory under user's home directory (~/.swiftpm) + public var dotSwiftPM: AbsolutePath { + get throws { + return try self.homeDirectory.appending(component: ".swiftpm") + } + } + + fileprivate var idiomaticSwiftPMDirectory: AbsolutePath? { + get throws { + return try FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first.flatMap { try AbsolutePath(validating: $0.path) }?.appending(component: "org.swift.swiftpm") + } + } +} + +// MARK: - cache + +extension FileSystem { + private var idiomaticUserCacheDirectory: AbsolutePath? { + // in TSC: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) + self.cachesDirectory + } + + /// SwiftPM cache directory under user's caches directory (if exists) + public var swiftPMCacheDirectory: AbsolutePath { + get throws { + if let path = self.idiomaticUserCacheDirectory { + return path.appending(component: "org.swift.swiftpm") + } else { + return try self.dotSwiftPMCachesDirectory + } + } + } + + fileprivate var dotSwiftPMCachesDirectory: AbsolutePath { + get throws { + return try self.dotSwiftPM.appending(component: "cache") + } + } +} + +extension FileSystem { + public func getOrCreateSwiftPMCacheDirectory() throws -> AbsolutePath { + let idiomaticCacheDirectory = try self.swiftPMCacheDirectory + // Create idiomatic if necessary + if !self.exists(idiomaticCacheDirectory) { + try self.createDirectory(idiomaticCacheDirectory, recursive: true) + } + // Create ~/.swiftpm if necessary + if !self.exists(try self.dotSwiftPM) { + try self.createDirectory(self.dotSwiftPM, recursive: true) + } + // Create ~/.swiftpm/cache symlink if necessary + // locking ~/.swiftpm to protect from concurrent access + try self.withLock(on: self.dotSwiftPM, type: .exclusive) { + if !self.exists(try self.dotSwiftPMCachesDirectory, followSymlink: false) { + try self.createSymbolicLink(dotSwiftPMCachesDirectory, pointingAt: idiomaticCacheDirectory, relative: false) + } + } + return idiomaticCacheDirectory + } +} + +// MARK: - configuration + +extension FileSystem { + /// SwiftPM config directory under user's config directory (if exists) + public var swiftPMConfigurationDirectory: AbsolutePath { + get throws { + if let path = try self.idiomaticSwiftPMDirectory { + return path.appending(component: "configuration") + } else { + return try self.dotSwiftPMConfigurationDirectory + } + } + } + + fileprivate var dotSwiftPMConfigurationDirectory: AbsolutePath { + get throws { + return try self.dotSwiftPM.appending(component: "configuration") + } + } +} + +extension FileSystem { + public func getOrCreateSwiftPMConfigurationDirectory(warningHandler: @escaping (String) -> Void) throws -> AbsolutePath { + let idiomaticConfigurationDirectory = try self.swiftPMConfigurationDirectory + + // temporary 5.6, remove on next version: transition from previous configuration location + if !self.exists(idiomaticConfigurationDirectory) { + try self.createDirectory(idiomaticConfigurationDirectory, recursive: true) + } + + let handleExistingFiles = { (configurationFiles: [AbsolutePath]) in + for file in configurationFiles { + let destination = idiomaticConfigurationDirectory.appending(component: file.basename) + if !self.exists(destination) { + try self.copy(from: file, to: destination) + } else { + // Only emit a warning if source and destination file differ in their contents. + let srcContents = try? self.readFileContents(file) + let dstContents = try? self.readFileContents(destination) + if srcContents != dstContents { + warningHandler("Usage of \(file) has been deprecated. Please delete it and use the new \(destination) instead.") + } + } + } + } + + // in the case where ~/.swiftpm/configuration is not the idiomatic location (eg on macOS where its /Users//Library/org.swift.swiftpm/configuration) + if try idiomaticConfigurationDirectory != self.dotSwiftPMConfigurationDirectory { + // copy the configuration files from old location (eg /Users//Library/org.swift.swiftpm) to new one (eg /Users//Library/org.swift.swiftpm/configuration) + // but leave them there for backwards compatibility (eg older xcode) + let oldConfigDirectory = idiomaticConfigurationDirectory.parentDirectory + if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) { + let configurationFiles = try self.getDirectoryContents(oldConfigDirectory) + .map{ oldConfigDirectory.appending(component: $0) } + .filter{ self.isFile($0) && !self.isSymlink($0) && $0.extension != "lock" && ((try? self.readFileContents($0)) ?? []).count > 0 } + try handleExistingFiles(configurationFiles) + } + // in the case where ~/.swiftpm/configuration is the idiomatic location (eg on Linux) + } else { + // copy the configuration files from old location (~/.swiftpm/config) to new one (~/.swiftpm/configuration) + // but leave them there for backwards compatibility (eg older toolchain) + let oldConfigDirectory = try self.dotSwiftPM.appending(component: "config") + if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) { + let configurationFiles = try self.getDirectoryContents(oldConfigDirectory) + .map{ oldConfigDirectory.appending(component: $0) } + .filter{ self.isFile($0) && !self.isSymlink($0) && $0.extension != "lock" && ((try? self.readFileContents($0)) ?? []).count > 0 } + try handleExistingFiles(configurationFiles) + } + } + // ~temporary 5.6 migration + + // Create idiomatic if necessary + if !self.exists(idiomaticConfigurationDirectory) { + try self.createDirectory(idiomaticConfigurationDirectory, recursive: true) + } + // Create ~/.swiftpm if necessary + if !self.exists(try self.dotSwiftPM) { + try self.createDirectory(self.dotSwiftPM, recursive: true) + } + // Create ~/.swiftpm/configuration symlink if necessary + // locking ~/.swiftpm to protect from concurrent access + try self.withLock(on: self.dotSwiftPM, type: .exclusive) { + if !self.exists(try self.dotSwiftPMConfigurationDirectory, followSymlink: false) { + try self.createSymbolicLink(dotSwiftPMConfigurationDirectory, pointingAt: idiomaticConfigurationDirectory, relative: false) + } + } + + return idiomaticConfigurationDirectory + } +} + +// MARK: - security + +extension FileSystem { + /// SwiftPM security directory under user's security directory (if exists) + public var swiftPMSecurityDirectory: AbsolutePath { + get throws { + if let path = try self.idiomaticSwiftPMDirectory { + return path.appending(component: "security") + } else { + return try self.dotSwiftPMSecurityDirectory + } + } + } + + fileprivate var dotSwiftPMSecurityDirectory: AbsolutePath { + get throws { + return try self.dotSwiftPM.appending(component: "security") + } + } +} + +extension FileSystem { + public func getOrCreateSwiftPMSecurityDirectory() throws -> AbsolutePath { + let idiomaticSecurityDirectory = try self.swiftPMSecurityDirectory + + // temporary 5.6, remove on next version: transition from ~/.swiftpm/security to idiomatic location + symbolic link + if try idiomaticSecurityDirectory != self.dotSwiftPMSecurityDirectory && + self.exists(try self.dotSwiftPMSecurityDirectory) && + self.isDirectory(try self.dotSwiftPMSecurityDirectory) { + try self.removeFileTree(self.dotSwiftPMSecurityDirectory) + } + // ~temporary 5.6 migration + + // Create idiomatic if necessary + if !self.exists(idiomaticSecurityDirectory) { + try self.createDirectory(idiomaticSecurityDirectory, recursive: true) + } + // Create ~/.swiftpm if necessary + if !self.exists(try self.dotSwiftPM) { + try self.createDirectory(self.dotSwiftPM, recursive: true) + } + // Create ~/.swiftpm/security symlink if necessary + // locking ~/.swiftpm to protect from concurrent access + try self.withLock(on: self.dotSwiftPM, type: .exclusive) { + if !self.exists(try self.dotSwiftPMSecurityDirectory, followSymlink: false) { + try self.createSymbolicLink(dotSwiftPMSecurityDirectory, pointingAt: idiomaticSecurityDirectory, relative: false) + } + } + return idiomaticSecurityDirectory + } +} + +// MARK: - cross-compilation destinations + +private let crossCompilationDestinationsDirectoryName = "destinations" + +extension FileSystem { + /// SwiftPM cross-compilation destinations directory (if exists) + public var swiftPMCrossCompilationDestinationsDirectory: AbsolutePath { + get throws { + if let path = try idiomaticSwiftPMDirectory { + return path.appending(component: crossCompilationDestinationsDirectoryName) + } else { + return try dotSwiftPMCrossCompilationDestinationsDirectory + } + } + } + + fileprivate var dotSwiftPMCrossCompilationDestinationsDirectory: AbsolutePath { + get throws { + return try dotSwiftPM.appending(component: crossCompilationDestinationsDirectoryName) + } + } + + public func getSharedCrossCompilationDestinationsDirectory( + explicitDirectory: AbsolutePath? + ) throws -> AbsolutePath? { + if let explicitDestinationsDirectory = explicitDirectory { + // Create the explicit SDKs path if necessary + if !exists(explicitDestinationsDirectory) { + try createDirectory(explicitDestinationsDirectory, recursive: true) + } + return explicitDestinationsDirectory + } else { + return try swiftPMCrossCompilationDestinationsDirectory + } + } + + public func getOrCreateSwiftPMCrossCompilationDestinationsDirectory() throws -> AbsolutePath { + let idiomaticDestinationsDirectory = try swiftPMCrossCompilationDestinationsDirectory + + // Create idiomatic if necessary + if !exists(idiomaticDestinationsDirectory) { + try createDirectory(idiomaticDestinationsDirectory, recursive: true) + } + // Create ~/.swiftpm if necessary + if !exists(try dotSwiftPM) { + try createDirectory(dotSwiftPM, recursive: true) + } + // Create ~/.swiftpm/destinations symlink if necessary + // locking ~/.swiftpm to protect from concurrent access + try withLock(on: dotSwiftPM, type: .exclusive) { + if !exists(try dotSwiftPMCrossCompilationDestinationsDirectory, followSymlink: false) { + try createSymbolicLink( + dotSwiftPMCrossCompilationDestinationsDirectory, + pointingAt: idiomaticDestinationsDirectory, + relative: false + ) + } + } + return idiomaticDestinationsDirectory + } +} + +// MARK: - Utilities + +extension FileSystem { + public func readFileContents(_ path: AbsolutePath) throws -> Data { + return try Data(self.readFileContents(path).contents) + } + + public func readFileContents(_ path: AbsolutePath) throws -> String { + return try String(decoding: self.readFileContents(path), as: UTF8.self) + } + + public func writeFileContents(_ path: AbsolutePath, data: Data) throws { + return try self.writeFileContents(path, bytes: .init(data)) + } + + public func writeFileContents(_ path: AbsolutePath, string: String) throws { + return try self.writeFileContents(path, bytes: .init(encodingAsUTF8: string)) + } + + public func writeFileContents(_ path: AbsolutePath, provider: () -> String) throws { + return try self.writeFileContents(path, string: provider()) + } +} + +extension FileSystem { + public func forceCreateDirectory(at path: AbsolutePath) throws { + try self.createDirectory(path.parentDirectory, recursive: true) + if self.exists(path) { + try self.removeFileTree(path) + } + try self.createDirectory(path, recursive: true) + } +} + +extension FileSystem { + public func stripFirstLevel(of path: AbsolutePath) throws { + let topLevelDirectories = try self.getDirectoryContents(path) + .map{ path.appending(component: $0) } + .filter{ self.isDirectory($0) } + + guard topLevelDirectories.count == 1, let rootDirectory = topLevelDirectories.first else { + throw StringError("stripFirstLevel requires single top level directory") + } + + let tempDirectory = path.parentDirectory.appending(component: UUID().uuidString) + try self.move(from: rootDirectory, to: tempDirectory) + + let rootContents = try self.getDirectoryContents(tempDirectory) + for entry in rootContents { + try self.move(from: tempDirectory.appending(component: entry), to: path.appending(component: entry)) + } + + try self.removeFileTree(tempDirectory) + } +} + +extension FileSystem { + /// Writes the given bytes to the given path. If the file at the given path already exists with the given + /// bytes, nothing is done. + /// - Parameters: + /// - path: The given path to write to. + /// - bytes: The given byte string to write. + /// - Note: If the given path's parent directory does not exist, the file system will recursively create + /// intermediate directories so the given path can be resolved. + public func writeFileContentsIfNeeded(_ path: AbsolutePath, bytes: ByteString) throws { + try createDirectory(path.parentDirectory, recursive: true) + + // If the file exists with the identical contents, we don't need to + // rewrite it. Otherwise, compiler will recompile even if nothing else + // has changed. + if let contents = try? readFileContents(path), contents == bytes { + return + } + try writeFileContents(path, bytes: bytes) + } +} diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index b9203c84b81..a16758ac45e 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -87,52 +87,3 @@ public struct VFSOverlay: Encodable { try JSONEncoder.makeWithDefaults(prettified: false).encode(path: path, fileSystem: fileSystem, self) } } - -public extension VFSOverlay { - /// Returns a tree of `VFSOverlay` resources for a given directory in the form of an array. Each item - /// in this array will be a resource (either file or directory) from the top most level of the given directory. - /// - Parameters: - /// - directoryPath: The directory to recursively search for resources in. - /// - fileSystem: The file system to search. - /// - shouldInclude: A closure used to determine if the tree should include a given path. - /// Defaults to including any path. - /// - Returns: An array of `VFSOverlay.Resource`s from the given directory. - /// - Throws: An error if the given path is a not a directory. - /// - Note: This API will recursively scan all subpaths of the given path. - static func overlayResources( - directoryPath: AbsolutePath, - fileSystem: FileSystem, - shouldInclude: (AbsolutePath) -> Bool = { _ in true } - ) throws -> [VFSOverlay.Resource] { - return - // Absolute path to each resource in the directory. - try fileSystem.getDirectoryContents(directoryPath).map(directoryPath.appending(component:)) - // Map each path to a corresponding VFSOverlay, recursing for directories. - .compactMap { resourcePath in - guard shouldInclude(resourcePath) else { - return nil - } - - if fileSystem.isDirectory(resourcePath) { - return VFSOverlay.Directory( - name: resourcePath.basename, - contents: - try overlayResources( - directoryPath: resourcePath, - fileSystem: fileSystem, - shouldInclude: shouldInclude - ) - ) - } else if fileSystem.isFile(resourcePath) { - return VFSOverlay.File( - name: resourcePath.basename, - externalContents: resourcePath.pathString - ) - } else { - // This case is not expected to be reached as a resource - // should be either a file or directory. - return nil - } - } - } -} diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index afdfd8c3d85..4e0d43bea0c 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -332,126 +332,20 @@ public final class ClangTargetBuildDescription { self.isWithinMixedTarget = isWithinMixedTarget // Try computing the modulemap path, creating a module map in the - // file system if necessary, for either a Clang source library or a - // Clang source test target being built within a mixed test target, - // The latter of which is allowed to support sharing test utilities - // between mixed language test files within a single test target. - if target.type == .library || (target.type == .test && isWithinMixedTarget) { - - let moduleMapGenerator = ModuleMapGenerator( - targetName: clangTarget.name, - moduleName: clangTarget.c99name, - publicHeadersDir: clangTarget.includeDir, - fileSystem: fileSystem - ) - - var generateIntermediateModuleMaps = false - - if case .custom(let customModuleMapPath) = clangTarget.moduleMapType { - if isWithinMixedTarget { - let customModuleMapContents: String = try fileSystem.readFileContents(customModuleMapPath) - - // Check that custom module map does not contain a Swift - // submodule. - if customModuleMapContents.contains("\(clangTarget.c99name).Swift") { - throw StringError("The target's module map may not " + - "contain a Swift submodule for " + - "the module \(target.c99name).") - } - - // Add a submodule to wrap the generated Swift header in - // the custom module map. - let modifiedModuleMapContents = """ - \(customModuleMapContents) - - module \(target.c99name).Swift { - header "\(target.c99name)-Swift.h" - requires objc - } - """ - - // Write the modified contents to a new module map in the - // build directory. - let writePath = tempsPath - .appending(component: "Product") - .appending(component: moduleMapFilename) - try fileSystem.createDirectory(writePath.parentDirectory, recursive: true) - - // If the file exists with the identical contents, we don't need to rewrite it. - // Otherwise, compiler will recompile even if nothing else has changed. - let contents = try? fileSystem.readFileContents(writePath) as String - if contents != modifiedModuleMapContents { - try fileSystem.writeFileContents(writePath, string: modifiedModuleMapContents) - } - - self.moduleMap = writePath - - generateIntermediateModuleMaps = true - } else { - // When not building within a mixed target, use the custom - // module map as it does not need to be modified. - self.moduleMap = customModuleMapPath - } + // file system if necessary. + if target.type == .library && !isWithinMixedTarget { + // If there's a custom module map, use it as given. + if case .custom(let path) = clangTarget.moduleMapType { + self.moduleMap = path } // If a generated module map is needed, generate one now in our temporary directory. else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { - let moduleMapPath = tempsPath - .appending(component: "Product") - .appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: generatedModuleMapType, - at: moduleMapPath, - addSwiftSubmodule: isWithinMixedTarget - ) - - self.moduleMap = moduleMapPath - - generateIntermediateModuleMaps = isWithinMixedTarget - } else if isWithinMixedTarget && clangTarget.moduleMapType == .none { - let moduleMapPath = tempsPath - .appending(component: "Product") - .appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: nil, - at: moduleMapPath, - addSwiftSubmodule: isWithinMixedTarget - ) - - self.moduleMap = moduleMapPath - - generateIntermediateModuleMaps = true + let path = tempsPath.appending(component: moduleMapFilename) + let moduleMapGenerator = ModuleMapGenerator(targetName: clangTarget.name, moduleName: clangTarget.c99name, publicHeadersDir: clangTarget.includeDir, fileSystem: fileSystem) + try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: path) + self.moduleMap = path } // Otherwise there is no module map, and we leave `moduleMap` unset. - - // Generate the intermediate module maps if needed. - if generateIntermediateModuleMaps { - let intermediateModuleMapPath = tempsPath - .appending(component: "Intermediates") - .appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(clangTarget.path), - at: intermediateModuleMapPath, - addSwiftSubmodule: true - ) - // The underlying Clang target is building within a Mixed - // language target and needs an auxiliary module map that - // doesn't include the generated interop header from the - // Swift part of the mixed target. This will later allow the - // Clang part of the module to be built when compiling the - // Swift part without the generated header being considered - // an input (because it won't exist yet and is an output of - // that compilation command). - let unextendedModuleMapPath = tempsPath - .appending(component: "Intermediates") - .appending(component: unextendedModuleMapFilename) - let nonObjcHeaders: [AbsolutePath] = clangTarget.headers - .filter { $0.extension != "h" } - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(clangTarget.path), - at: unextendedModuleMapPath, - excludeHeaders: nonObjcHeaders - ) - } } // Do nothing if we're not generating a bundle. @@ -542,14 +436,7 @@ public final class ClangTargetBuildDescription { args += ["-F", buildParameters.buildPath.pathString] } - // For mixed targets, the `include` directory is instead overlayed over - // the build directory and that directory is passed as a header search - // path (See `MixedTargetBuildDescription`). This is done to avoid - // module re-declaration errors for cases when a mixed target contains - // a custom module map. - if !isWithinMixedTarget { - args += ["-I", clangTarget.includeDir.pathString] - } + args += ["-I", clangTarget.includeDir.pathString] args += additionalFlags if enableModules { @@ -944,7 +831,7 @@ public final class SwiftTargetBuildDescription { self.pluginDerivedSources.relativePaths.append(relPath) } - // If building for a mixed target, the Objective-C portion of the build + // If building for a mixed target, the mixed target build // description will create the module map and include the Swift // interoptability header. if shouldEmitObjCCompatibilityHeader && !isWithinMixedTarget { @@ -1453,8 +1340,11 @@ public final class MixedTargetBuildDescription { /// the Clang part of the target over the target's build directory. private(set) var allProductHeadersOverlay: AbsolutePath? = nil - /// The modulemap file for this target, if any - var moduleMap: AbsolutePath? { clangTargetBuildDescription.moduleMap } + /// The modulemap file for this target. + let moduleMap: AbsolutePath + + /// The path to the Objective-C compatibility header for the underlying Swift target. + private let interopHeaderPath: AbsolutePath /// Paths to the binary libraries the target depends on. var libraryBinaryPaths: Set { @@ -1526,172 +1416,237 @@ public final class MixedTargetBuildDescription { isWithinMixedTarget: true ) - if target.type == .library || target.type == .test { - // Compiling the mixed target will require a Clang VFS overlay file - // with mappings to the target's module map and public headers. - let generatedInteropHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath + self.interopHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath + + let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath + let buildArtifactIntermediatesDirectory = buildArtifactDirectory + .appending(component: "Intermediates") + let buildArtifactProductDirectory = buildArtifactDirectory + .appending(component: "Product") + + // Used to generate both product and intermediate artifacts for the + // target. + let moduleMapGenerator = ModuleMapGenerator( + targetName: mixedTarget.clangTarget.name, + moduleName: mixedTarget.clangTarget.c99name, + publicHeadersDir: mixedTarget.clangTarget.includeDir, + fileSystem: fileSystem + ) - let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath - let allProductHeadersPathProduct = buildArtifactDirectory - .appending(component: "Product") - .appending(component: "all-product-headers.yaml") + // MARK: Generate products to be used by client of the target. + + // Path to the module map used by clients to access the mixed target's + // public API. + let productModuleMapPath = buildArtifactProductDirectory + .appending(component: moduleMapFilename) + + switch mixedTarget.clangTarget.moduleMapType { + // When the mixed target has a custom module map, clients of the target + // will be passed a module map *and* VFS overlay at buildtime to access + // the mixed target's public API. The following is therefore needed: + // - Create a copy of the custom module map, adding a submodule to + // expose the target's generated interop header. This allows clients + // of the target to consume the mixed target's public Objective-C + // compatible Swift API in a Clang context. + // - Create a VFS overlay to swap in the modified module map for the + // original custom module map. This is done so relative paths in the + // modified module map can be resolved as they would have been in the + // original module map. + case .custom(let customModuleMapPath): + let customModuleMapContents: String = + try fileSystem.readFileContents(customModuleMapPath) + + // Check that custom module map does not contain a Swift submodule. + if customModuleMapContents.contains("\(target.c99name).Swift") { + throw StringError( + "The target's module map may not contain a Swift " + + "submodule for the module \(target.c99name)." + ) + } - let buildArtifactIntermediatesDirectory = buildArtifactDirectory - .appending(component: "Intermediates") + // Extend the contents and write it to disk, if needed. + let stream = BufferedOutputByteStream() + stream <<< customModuleMapContents + stream <<< """ + module \(target.c99name).Swift { + header "\(interopHeaderPath)" + requires objc + } + """ + try fileSystem.writeFileContentsIfNeeded( + productModuleMapPath, + bytes: stream.bytes + ) - let allProductHeadersPath = buildArtifactIntermediatesDirectory + // Set the original custom module map path as the module map path + // for the target. The below VFS overlay will redirect to the + // contents of the modified module map. + self.moduleMap = customModuleMapPath + self.allProductHeadersOverlay = buildArtifactProductDirectory .appending(component: "all-product-headers.yaml") - // The auxilliary module map is passed to the Clang compiler - // via a VFS overlay, represented by the below YAML file. - let unextendedModuleMapOverlayPath = buildArtifactIntermediatesDirectory - .appending(component: "unextended-module-overlay.yaml") - - // Decide which directory to add the overlay resources in based on - // the presence of a custom module map. - let rootOverlayResourceDirectory: AbsolutePath - if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { - // Building a mixed target uses a modified module map to expose - // private headers to the Swift part of the module. To avoid - // the custom module map causing a module redeclaration error, - // a VFS overlay is used when building the target to redirect - // the custom module map to the modified module map in the - // build directory. This redirecting overlay is placed in the - // custom module map's parent directory, as to replace it. - rootOverlayResourceDirectory = customModuleMapPath.parentDirectory - // Importing the underlying module will build the Objective-C - // part of the module. In order to find the underlying module, - // a `module.modulemap` needs to be discoverable via a header - // search path. In the case of a custom module map, its parent - // directory is used. - swiftTargetBuildDescription.additionalFlags += [ - "-import-underlying-module", - "-I", customModuleMapPath.parentDirectory.pathString - ] - } else { - // TODO(ncooke3): I think this case may not even need an - // all-product-headers.yaml. - - // Since no custom module map exists, the build directory can - // be used as the root of the VFS overlay. In this case, the - // VFS overlay's sole purpose is to expose the generated Swift - // header. - rootOverlayResourceDirectory = buildArtifactIntermediatesDirectory - // Importing the underlying module will build the Objective-C - // part of the module. In order to find the underlying module, - // a `module.modulemap` needs to be discoverable via a header - // search path. In this case, the module map in the build - // directory is used. - swiftTargetBuildDescription.additionalFlags += [ - "-import-underlying-module", - "-I", buildArtifactIntermediatesDirectory.pathString + try VFSOverlay( + roots: [ + VFSOverlay.Directory( + name: customModuleMapPath.parentDirectory.pathString, + contents: [ + VFSOverlay.File( + name: moduleMapFilename, + externalContents: productModuleMapPath.pathString + ) + ] + ) ] - } + ).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) + + // When the mixed target does not have a custom module map, one will be + // generated as a product for use by clients. + // - Note: When `.none`, the mixed target has no public headers. Even + // then, a module map is created to expose the generated interop + // header so clients can access the public Objective-C compatible + // Swift API in a Clang context. + case .umbrellaHeader, .umbrellaDirectory, .none: + let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType + try moduleMapGenerator.generateModuleMap( + type: generatedModuleMapType, + at: productModuleMapPath, + interopHeaderPath: interopHeaderPath + ) - // For Intermediates directory - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the modified - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: buildArtifactIntermediatesDirectory - .appending(component: moduleMapFilename) - .pathString - ), - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: generatedInteropHeaderPath.basename, - externalContents: generatedInteropHeaderPath.pathString - ) - ] - ) - ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the *unextended* - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: buildArtifactIntermediatesDirectory - .appending(component: unextendedModuleMapFilename) - .pathString - ) - ] - ) - ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) - - // For Product directory - // TODO(ncooke3): Experiment with refactoring this next... - var contents: [VFSOverlay.Resource] = [ - VFSOverlay.File( - name: generatedInteropHeaderPath.basename, - externalContents: generatedInteropHeaderPath.pathString - ) - ] + // Set the generated module map as the module map for the target. + self.moduleMap = productModuleMapPath + } - if mixedTarget.clangTarget.moduleMapType != .none { - contents += - // Public headers - try VFSOverlay.overlayResources( - directoryPath: mixedTarget.clangTarget.includeDir, - fileSystem: fileSystem, - shouldInclude: { - // Filter out a potential custom module map as - // only the generated module map in the build - // directory is used. - !$0.pathString.hasSuffix("module.modulemap") - } - ) - } + // ------- FINAL END ------- // - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: buildArtifactDirectory - .appending(component: "Product").pathString, - contents: contents - ) - ]).write(to: allProductHeadersPathProduct, fileSystem: fileSystem) - - // Build flags - swiftTargetBuildDescription.appendClangFlags( - // Pass both VFS overlays to the underlying Clang compiler. - "-ivfsoverlay", allProductHeadersPath.pathString, - "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, - // Adding the root of the target's source as a header search - // path allows for importing headers using paths relative to - // the root. - "-I", mixedTarget.path.pathString + // MARK: Generate intermediate artifacts used to build the target. + // Building a mixed target uses intermediate module maps to expose + // private headers to the Swift part of the module. + + // TODO(ncooke3): I wonder if this is really needed? + // 1. Generate an intermediate module map that exposes all headers, + // including the submodule with the generated Swift header. + let intermediateModuleMapPath = buildArtifactIntermediatesDirectory + .appending(component: moduleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(mixedTarget.clangTarget.path), + at: intermediateModuleMapPath, + interopHeaderPath: interopHeaderPath + ) + + // 2. Generate an intermediate module map that exposes all headers. + // When building the Swift part of the mixed target, a module map will + // be needed to access types from the Objective-C part of the target. + // However, this module map should not expose the generated Swift + // header since it will not exist yet. + let unextendedModuleMapPath = buildArtifactIntermediatesDirectory + .appending(component: unextendedModuleMapFilename) + // Generating module maps that include non-Objective-C headers is not + // supported. + // FIXME(ncooke3): Link to evolution post. + let nonObjcHeaders: [AbsolutePath] = mixedTarget.clangTarget.headers + .filter { $0.extension != "h" } + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(mixedTarget.clangTarget.path), + at: unextendedModuleMapPath, + excludeHeaders: nonObjcHeaders + ) + + // 3. Use VFS overlays to purposefully expose specific resources (e.g. + // module map) during the build. The directory to add a VFS overlay in + // depends on the presence of a custom module map. + let rootOverlayResourceDirectory: AbsolutePath + switch mixedTarget.clangTarget.moduleMapType { + case .custom(let customModuleMapPath): + // To avoid the custom module map causing a module redeclaration + // error, a VFS overlay is used when building the target to + // redirect the custom module map to the modified module map in the + // build directory. This redirecting overlay is placed in the + // custom module map's parent directory, as to replace it. + rootOverlayResourceDirectory = customModuleMapPath.parentDirectory + case .umbrellaHeader, .umbrellaDirectory, .none: + // Since no custom module map exists, the build directory can + // be used as the root of the VFS overlay. In this case, the + // VFS overlay's sole purpose is to expose the generated Swift + // header. + rootOverlayResourceDirectory = buildArtifactIntermediatesDirectory + } + + let allProductHeadersPath = buildArtifactIntermediatesDirectory + .appending(component: "all-product-headers.yaml") + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the modified + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: buildArtifactIntermediatesDirectory + .appending(component: moduleMapFilename) + .pathString + ), + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + ] ) + ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + + let unextendedModuleMapOverlayPath = buildArtifactIntermediatesDirectory + .appending(component: "unextended-module-overlay.yaml") + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: buildArtifactIntermediatesDirectory + .appending(component: unextendedModuleMapFilename) + .pathString + ) + ] + ) + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + + // 4. Tie everything together by passing build flags. - clangTargetBuildDescription.additionalFlags += [ - // Adding the target's public headers directory as a header - // search path allows for importing headers using paths - // relative to the public headers directory. - "-I", mixedTarget.clangTarget.includeDir.pathString, - // Adding the root of the target's source as a header search - // path allows for importing headers using paths relative to - // the root. - "-I", mixedTarget.path.pathString, - // TODO(ncooke3): See above comment about removing - // all-product-headers.yaml for no-custom module map case... - - // Include overlay to add interop header to intermediates directory. - "-ivfsoverlay", allProductHeadersPath.pathString, - // The above overlay adds the interop header in the - // intermediates directory. Pass the intermediates directory as - // a search path so the generated header can be imported. - "-I", buildArtifactIntermediatesDirectory.pathString - ] - - self.allProductHeadersOverlay = allProductHeadersPathProduct - } + // Importing the underlying module will build the Objective-C + // part of the module. In order to find the underlying module, + // a `module.modulemap` needs to be discoverable via a header + // search path. + swiftTargetBuildDescription.additionalFlags += [ + "-import-underlying-module", + "-I", rootOverlayResourceDirectory.pathString + ] + + swiftTargetBuildDescription.appendClangFlags( + // Pass both VFS overlays to the underlying Clang compiler. + "-ivfsoverlay", allProductHeadersPath.pathString, + "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, + // Adding the root of the target's source as a header search + // path allows for importing headers using paths relative to + // the root. + "-I", mixedTarget.path.pathString + ) + + clangTargetBuildDescription.additionalFlags += [ + // Adding the root of the target's source as a header search + // path allows for importing headers using paths relative to + // the root. + "-I", mixedTarget.path.pathString, + // Include overlay to add interop header to intermediates directory. + "-ivfsoverlay", allProductHeadersPath.pathString, + // The above overlay adds the interop header in the + // intermediates directory. Pass the intermediates directory as + // a search path so the generated header can be imported. + "-I", buildArtifactIntermediatesDirectory.pathString + ] } } @@ -2778,14 +2733,15 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } } case let target as MixedTarget where target.type == .library: - // Add the modulemap of the dependency if it has one. + // Add the modulemap of the dependency. if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { - if let moduleMap = dependencyTargetDescription.moduleMap, - let allProductHeadersOverlay = dependencyTargetDescription.allProductHeadersOverlay { + clangTarget.additionalFlags.append( + "-fmodule-map-file=\(dependencyTargetDescription.moduleMap.pathString)" + ) + + if let allProductHeadersOverlay = dependencyTargetDescription.allProductHeadersOverlay { clangTarget.additionalFlags += [ - "-fmodule-map-file=\(moduleMap.pathString)", - "-ivfsoverlay", - allProductHeadersOverlay.pathString + "-ivfsoverlay", allProductHeadersOverlay.pathString ] } } @@ -2847,15 +2803,16 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Add the path to modulemap of the dependency. Currently we // require that all Mixed targets have a modulemap as it is // required for interoperability. - guard - let moduleMap = target.moduleMap, - let allProductHeadersOverlay = target.allProductHeadersOverlay - else { break } swiftTarget.appendClangFlags( - "-fmodule-map-file=\(moduleMap.pathString)", - "-ivfsoverlay", - allProductHeadersOverlay.pathString + "-fmodule-map-file=\(target.moduleMap.pathString)" ) + + if let allProductHeadersOverlay = target.allProductHeadersOverlay { + swiftTarget.appendClangFlags( + "-ivfsoverlay", allProductHeadersOverlay.pathString + ) + } + default: break } @@ -2898,9 +2855,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { arguments += ["-I", includeDir.pathString] } case .mixed(let targetDescription): - if let includeDir = targetDescription.moduleMap?.parentDirectory { - arguments += ["-I", includeDir.pathString] - } + let includeDir = targetDescription.moduleMap.parentDirectory + arguments += ["-I", includeDir.pathString] } } @@ -2938,9 +2894,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { arguments += ["-I\(includeDir.pathString)"] } case .mixed(let targetDescription): - if let includeDir = targetDescription.moduleMap?.parentDirectory { - arguments += ["-I\(includeDir.pathString)"] - } + let includeDir = targetDescription.moduleMap.parentDirectory + arguments += ["-I\(includeDir.pathString)"] } } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index de35f71a02e..5284513b18a 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -847,7 +847,7 @@ extension LLBuildManifestBuilder { // Swift part of the mixed target to be built first. if target.isWithinMixedTarget { inputs += [ - .file(target.tempsPath.appending(component: "\(target.target.name)-Swift.h")) + .file(target.tempsPath.appending(component: "\(target.target.c99name)-Swift.h")) ] } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 6575470803b..622ef70768c 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -185,7 +185,7 @@ public struct ModuleMapGenerator { type: GeneratedModuleMapType?, at path: AbsolutePath, excludeHeaders: [AbsolutePath] = [], - addSwiftSubmodule: Bool = false + interopHeaderPath: AbsolutePath? = nil ) throws { let stream = BufferedOutputByteStream() stream <<< "module \(moduleName) {\n" @@ -203,22 +203,16 @@ public struct ModuleMapGenerator { stream <<< " export *\n" stream <<< "}\n" - if addSwiftSubmodule { + if let interopHeaderPath = interopHeaderPath { stream <<< "module \(moduleName).Swift {\n" - stream <<< " header \"\(moduleName)-Swift.h\"\n" + stream <<< " header \"\(interopHeaderPath.moduleEscapedPathString)\"\n" stream <<< " requires objc\n" stream <<< "}\n" } - // FIXME: This doesn't belong here. - try fileSystem.createDirectory(path.parentDirectory, recursive: true) - // If the file exists with the identical contents, we don't need to rewrite it. // Otherwise, compiler will recompile even if nothing else has changed. - if let contents = try? fileSystem.readFileContents(path).validDescription, contents == moduleMap { - return - } - try fileSystem.writeFileContents(path, string: moduleMap) + try fileSystem.writeFileContentsIfNeeded(path, bytes: stream.bytes) } } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index c9b7a6d1d70..5b2014dbc65 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1187,7 +1187,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try ext.basicArguments(isCXX: false), args) XCTAssertEqual(try ext.objects, [buildPath.appending(components: "extlib.build", "extlib.c.o")]) - XCTAssertEqual(ext.moduleMap, buildPath.appending(components: "extlib.build", "Product", "module.modulemap")) + XCTAssertEqual(ext.moduleMap, buildPath.appending(components: "extlib.build", "module.modulemap")) let exe = try result.target(for: "exe").clangTarget() args = [] @@ -1206,9 +1206,9 @@ final class BuildPlanTests: XCTestCase { args += [ "-I", Pkg.appending(components: "Sources", "exe", "include").pathString, "-I", Pkg.appending(components: "Sources", "lib", "include").pathString, - "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", + "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-I", ExtPkg.appending(components: "Sources", "extlib", "include").pathString, - "-fmodule-map-file=\(buildPath.appending(components: "extlib.build", "Product", "module.modulemap"))", + "-fmodule-map-file=\(buildPath.appending(components: "extlib.build", "module.modulemap"))", ] #if !os(Windows) // FIXME(5473) - modules flags on Windows dropped args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] @@ -1455,6 +1455,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence]) } + // TODO(ncooke3): Add unit test for mixed languages with custom module map. func testBasicMixedLanguages() throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", @@ -1516,8 +1517,6 @@ final class BuildPlanTests: XCTestCase { "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=/path/to/build/debug/lib.build/Product/module.modulemap", - "-Xcc", "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence ]) @@ -1558,7 +1557,7 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertEqual( - try result.target(for: "lib").mixedTarget().moduleMap?.pathString, + try result.target(for: "lib").mixedTarget().moduleMap.pathString, buildPath.appending(components: "lib.build", "Product", "module.modulemap").pathString ) @@ -1636,10 +1635,10 @@ final class BuildPlanTests: XCTestCase { args += [hostTriple.isWindows() ? "-gdwarf" : "-g"] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG","-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-g", .anySequence]) + XCTAssertMatch(exe, [.anySequence, "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG","-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-g", .anySequence]) #if os(macOS) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ @@ -1775,7 +1774,7 @@ final class BuildPlanTests: XCTestCase { let buildPath = plan.buildParameters.dataPath.appending(components: "debug") - XCTAssertEqual(try plan.createREPLArguments().sorted(), ["-I\(Dep.appending(components: "Sources", "CDep", "include"))", "-I\(buildPath)", "-I\(buildPath.appending(components: "lib.build", "Product"))", "-L\(buildPath)", "-lpkg__REPL", "repl"]) + XCTAssertEqual(try plan.createREPLArguments().sorted(), ["-I\(Dep.appending(components: "Sources", "CDep", "include"))", "-I\(buildPath)", "-I\(buildPath.appending(component: "lib.build"))", "-L\(buildPath)", "-lpkg__REPL", "repl"]) XCTAssertEqual(plan.graph.allProducts.map({ $0.name }).sorted(), [ "Dep", @@ -2851,7 +2850,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try lib.basicArguments(isCXX: true), expectedLibBasicArgs) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.cpp.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) #if os(macOS) XCTAssertEqual(try result.buildProduct(for: "lib").linkArguments(), [ @@ -3297,7 +3296,7 @@ final class BuildPlanTests: XCTestCase { ] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) let exe = try result.target(for: "exe").swiftTarget().compileArguments() XCTAssertMatch(exe, [ @@ -3307,7 +3306,7 @@ final class BuildPlanTests: XCTestCase { "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", - "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", + "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, @@ -3381,7 +3380,7 @@ final class BuildPlanTests: XCTestCase { ] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) - XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "Product", "module.modulemap")) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) let exe = try result.target(for: "app").swiftTarget().compileArguments() XCTAssertMatch( @@ -3389,7 +3388,7 @@ final class BuildPlanTests: XCTestCase { [ "-swift-version", "4", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG","-Xcc", - "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "Product", "module.modulemap"))", + "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-g", .anySequence, diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 740d5e84d7d..106a9901ea5 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -55,11 +55,11 @@ final class MixedTargetTests: XCTestCase { func testMixedTargetWithCustomModuleMapAndResources() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( + XCTAssertSwiftTest( fixturePath, extraArgs: [ - "--target", "MixedTargetWithCustomModuleMapAndResources" - // FIXME(ncooke3): Blocked by fix for #5728. + "--filter", "MixedTargetWithCustomModuleMapAndResourcesTests" +// FIXME(ncooke3): Blocked by fix for #5728. // ], // // Surface warning where custom umbrella header does not // // include `resource_bundle_accessor.h` in `build` directory. @@ -67,7 +67,7 @@ final class MixedTargetTests: XCTestCase { // "-warnings-as-errors" ] ) - } + } } func testMixedTargetWithCXX() throws { diff --git a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift index 30516e899c7..b8707da745e 100644 --- a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift +++ b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift @@ -63,14 +63,16 @@ class ModuleMapGeneration: XCTestCase { root.appending(components: "include", "Foo.h").pathString ) - ModuleMapTester("Foo", addSwiftSubmodule: true, in: fs) { result in + let interopHeaderPath = AbsolutePath(path: "/path/to/Foo-Swift.h") + + ModuleMapTester("Foo", interopHeaderPath: interopHeaderPath, in: fs) { result in result.check(contents: """ module Foo { umbrella header "\(root.appending(components: "include", "Foo.h"))" export * } module Foo.Swift { - header "Foo-Swift.h" + header "/path/to/Foo-Swift.h" requires objc } @@ -202,24 +204,24 @@ class ModuleMapGeneration: XCTestCase { } /// Helper function to test module map generation. Given a target name and optionally the name of a public-headers directory, this function determines the module map type of the public-headers directory by examining the contents of a file system and invokes a given block to check the module result (including any diagnostics). -func ModuleMapTester(_ targetName: String, includeDir: String = "include", addSwiftSubmodule: Bool = false, in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { +func ModuleMapTester(_ targetName: String, includeDir: String = "include", interopHeaderPath: AbsolutePath? = nil, in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { let observability = ObservabilitySystem.makeForTesting() // Create a module map generator, and determine the type of module map to use for the header directory. This may emit diagnostics. let moduleMapGenerator = ModuleMapGenerator(targetName: targetName, moduleName: targetName.spm_mangledToC99ExtendedIdentifier(), publicHeadersDir: AbsolutePath.root.appending(component: includeDir), fileSystem: fileSystem) let moduleMapType = moduleMapGenerator.determineModuleMapType(observabilityScope: observability.topScope) - + // Generate a module map and capture any emitted diagnostics. let generatedModuleMapPath = AbsolutePath.root.appending(components: "module.modulemap") observability.topScope.trap { if let generatedModuleMapType = moduleMapType.generatedModuleMapType { - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath, addSwiftSubmodule: addSwiftSubmodule) + try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath, interopHeaderPath: interopHeaderPath) } } - + // Invoke the closure to check the results. let result = ModuleMapResult(diagnostics: observability.diagnostics, path: generatedModuleMapPath, fs: fileSystem) body(result) - + // Check for any unexpected diagnostics (the ones the closure didn't check for). result.validateDiagnostics() } From 773bd474a34c51fb0994bde5604cc67b230e022c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 24 Dec 2022 18:11:28 -0500 Subject: [PATCH 060/178] Remove outdated comment --- Sources/Build/BuildPlan.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 4e0d43bea0c..5e81d0664e6 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1517,8 +1517,6 @@ public final class MixedTargetBuildDescription { self.moduleMap = productModuleMapPath } - // ------- FINAL END ------- // - // MARK: Generate intermediate artifacts used to build the target. // Building a mixed target uses intermediate module maps to expose // private headers to the Swift part of the module. From 61d2e5504dce3bbaf0bd4e7d26d4644fbf94fe37 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 24 Dec 2022 18:36:55 -0500 Subject: [PATCH 061/178] Cleanup constants --- Sources/Build/BuildPlan.swift | 49 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 5e81d0664e6..367655af9a5 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1338,11 +1338,14 @@ public final class MixedTargetBuildDescription { /// The path to the VFS overlay file that overlays the public headers of /// the Clang part of the target over the target's build directory. - private(set) var allProductHeadersOverlay: AbsolutePath? = nil + let allProductHeadersOverlay: AbsolutePath? /// The modulemap file for this target. let moduleMap: AbsolutePath + /// Path to the temporary directory for this target. + private let tempsPath: AbsolutePath + /// The path to the Objective-C compatibility header for the underlying Swift target. private let interopHeaderPath: AbsolutePath @@ -1382,6 +1385,7 @@ public final class MixedTargetBuildDescription { self.target = target self.fileSystem = fileSystem + self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") let clangResolvedTarget = ResolvedTarget( target: mixedTarget.clangTarget, @@ -1418,11 +1422,16 @@ public final class MixedTargetBuildDescription { self.interopHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath - let buildArtifactDirectory = swiftTargetBuildDescription.tempsPath - let buildArtifactIntermediatesDirectory = buildArtifactDirectory - .appending(component: "Intermediates") - let buildArtifactProductDirectory = buildArtifactDirectory - .appending(component: "Product") + // A mixed target's build directory uses two subdirectories to + // distinguish between build artifacts: + // - Intermediates: Stores artifacts used during the target's build. + // - Product: Stores artifacts used by clients of the target. + let intermediatesDirectory = tempsPath.appending(component: "Intermediates") + let productDirectory = tempsPath.appending(component: "Product") + + // Filenames for VFS overlay files. + let allProductHeadersFilename = "all-product-headers.yaml" + let unextendedModuleOverlayFilename = "unextended-module-overlay.yaml" // Used to generate both product and intermediate artifacts for the // target. @@ -1437,8 +1446,7 @@ public final class MixedTargetBuildDescription { // Path to the module map used by clients to access the mixed target's // public API. - let productModuleMapPath = buildArtifactProductDirectory - .appending(component: moduleMapFilename) + let productModuleMapPath = productDirectory.appending(component: moduleMapFilename) switch mixedTarget.clangTarget.moduleMapType { // When the mixed target has a custom module map, clients of the target @@ -1482,8 +1490,7 @@ public final class MixedTargetBuildDescription { // for the target. The below VFS overlay will redirect to the // contents of the modified module map. self.moduleMap = customModuleMapPath - self.allProductHeadersOverlay = buildArtifactProductDirectory - .appending(component: "all-product-headers.yaml") + self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) try VFSOverlay( roots: [ @@ -1515,6 +1522,9 @@ public final class MixedTargetBuildDescription { // Set the generated module map as the module map for the target. self.moduleMap = productModuleMapPath + // An overlay is not neccesssary as there was no original custom + // module map to replace. + self.allProductHeadersOverlay = nil } // MARK: Generate intermediate artifacts used to build the target. @@ -1524,7 +1534,7 @@ public final class MixedTargetBuildDescription { // TODO(ncooke3): I wonder if this is really needed? // 1. Generate an intermediate module map that exposes all headers, // including the submodule with the generated Swift header. - let intermediateModuleMapPath = buildArtifactIntermediatesDirectory + let intermediateModuleMapPath = intermediatesDirectory .appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( type: .umbrellaDirectory(mixedTarget.clangTarget.path), @@ -1537,8 +1547,7 @@ public final class MixedTargetBuildDescription { // be needed to access types from the Objective-C part of the target. // However, this module map should not expose the generated Swift // header since it will not exist yet. - let unextendedModuleMapPath = buildArtifactIntermediatesDirectory - .appending(component: unextendedModuleMapFilename) + let unextendedModuleMapPath = intermediatesDirectory.appending(component: unextendedModuleMapFilename) // Generating module maps that include non-Objective-C headers is not // supported. // FIXME(ncooke3): Link to evolution post. @@ -1567,11 +1576,10 @@ public final class MixedTargetBuildDescription { // be used as the root of the VFS overlay. In this case, the // VFS overlay's sole purpose is to expose the generated Swift // header. - rootOverlayResourceDirectory = buildArtifactIntermediatesDirectory + rootOverlayResourceDirectory = intermediatesDirectory } - let allProductHeadersPath = buildArtifactIntermediatesDirectory - .appending(component: "all-product-headers.yaml") + let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) try VFSOverlay(roots: [ VFSOverlay.Directory( name: rootOverlayResourceDirectory.pathString, @@ -1580,7 +1588,7 @@ public final class MixedTargetBuildDescription { // module map in the intermediates directory. VFSOverlay.File( name: moduleMapFilename, - externalContents: buildArtifactIntermediatesDirectory + externalContents: intermediatesDirectory .appending(component: moduleMapFilename) .pathString ), @@ -1594,8 +1602,7 @@ public final class MixedTargetBuildDescription { ) ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - let unextendedModuleMapOverlayPath = buildArtifactIntermediatesDirectory - .appending(component: "unextended-module-overlay.yaml") + let unextendedModuleMapOverlayPath = intermediatesDirectory.appending(component: unextendedModuleOverlayFilename) try VFSOverlay(roots: [ VFSOverlay.Directory( name: rootOverlayResourceDirectory.pathString, @@ -1604,7 +1611,7 @@ public final class MixedTargetBuildDescription { // module map in the intermediates directory. VFSOverlay.File( name: moduleMapFilename, - externalContents: buildArtifactIntermediatesDirectory + externalContents: intermediatesDirectory .appending(component: unextendedModuleMapFilename) .pathString ) @@ -1643,7 +1650,7 @@ public final class MixedTargetBuildDescription { // The above overlay adds the interop header in the // intermediates directory. Pass the intermediates directory as // a search path so the generated header can be imported. - "-I", buildArtifactIntermediatesDirectory.pathString + "-I", intermediatesDirectory.pathString ] } } From adcc335f333acdc9e95576faae163b9606762d1d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 24 Dec 2022 18:41:23 -0500 Subject: [PATCH 062/178] Migrate private properties to local constants --- Sources/Build/BuildPlan.swift | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 367655af9a5..d2301f21922 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1343,12 +1343,6 @@ public final class MixedTargetBuildDescription { /// The modulemap file for this target. let moduleMap: AbsolutePath - /// Path to the temporary directory for this target. - private let tempsPath: AbsolutePath - - /// The path to the Objective-C compatibility header for the underlying Swift target. - private let interopHeaderPath: AbsolutePath - /// Paths to the binary libraries the target depends on. var libraryBinaryPaths: Set { swiftTargetBuildDescription.libraryBinaryPaths @@ -1361,8 +1355,6 @@ public final class MixedTargetBuildDescription { /// The build description for the Swift sources. let swiftTargetBuildDescription: SwiftTargetBuildDescription - private let fileSystem: FileSystem - init( package: ResolvedPackage, target: ResolvedTarget, @@ -1384,8 +1376,6 @@ public final class MixedTargetBuildDescription { } self.target = target - self.fileSystem = fileSystem - self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") let clangResolvedTarget = ResolvedTarget( target: mixedTarget.clangTarget, @@ -1420,12 +1410,13 @@ public final class MixedTargetBuildDescription { isWithinMixedTarget: true ) - self.interopHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath + let interopHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath // A mixed target's build directory uses two subdirectories to // distinguish between build artifacts: // - Intermediates: Stores artifacts used during the target's build. // - Product: Stores artifacts used by clients of the target. + let tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") let intermediatesDirectory = tempsPath.appending(component: "Intermediates") let productDirectory = tempsPath.appending(component: "Product") From c63c9b000a81aed45f29f19d2c7b53c82741a0ce Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 24 Dec 2022 19:24:12 -0500 Subject: [PATCH 063/178] Consolidate local constants --- Sources/Build/BuildPlan.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index d2301f21922..96c9f09978e 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1522,11 +1522,9 @@ public final class MixedTargetBuildDescription { // Building a mixed target uses intermediate module maps to expose // private headers to the Swift part of the module. - // TODO(ncooke3): I wonder if this is really needed? // 1. Generate an intermediate module map that exposes all headers, // including the submodule with the generated Swift header. - let intermediateModuleMapPath = intermediatesDirectory - .appending(component: moduleMapFilename) + let intermediateModuleMapPath = intermediatesDirectory.appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( type: .umbrellaDirectory(mixedTarget.clangTarget.path), at: intermediateModuleMapPath, @@ -1579,9 +1577,7 @@ public final class MixedTargetBuildDescription { // module map in the intermediates directory. VFSOverlay.File( name: moduleMapFilename, - externalContents: intermediatesDirectory - .appending(component: moduleMapFilename) - .pathString + externalContents: intermediateModuleMapPath.pathString ), // Add a generated Swift header that redirects to the // generated header in the build directory's root. @@ -1602,9 +1598,7 @@ public final class MixedTargetBuildDescription { // module map in the intermediates directory. VFSOverlay.File( name: moduleMapFilename, - externalContents: intermediatesDirectory - .appending(component: unextendedModuleMapFilename) - .pathString + externalContents: unextendedModuleMapPath.pathString ) ] ) From 4a5feff5d2292a6a2da58f5f5b92601fef3ae018 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 24 Dec 2022 19:31:03 -0500 Subject: [PATCH 064/178] Add unit test for custom moudle map case --- Tests/BuildTests/BuildPlanTests.swift | 128 +++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 5b2014dbc65..f2e1ed5da3f 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1455,7 +1455,6 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence]) } - // TODO(ncooke3): Add unit test for mixed languages with custom module map. func testBasicMixedLanguages() throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", @@ -1579,6 +1578,133 @@ final class BuildPlanTests: XCTestCase { #endif } + func testBasicMixedLanguagesWithCustomModuleMap() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Pkg/Sources/exe/main.swift", + "/Pkg/Sources/lib/Foo.swift", + "/Pkg/Sources/lib/include/Bar.h", + "/Pkg/Sources/lib/include/module.modulemap", + "/Pkg/Sources/lib/Bar.m" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + name: "Pkg", + path: .init(path: "/Pkg"), + // FIXME(ncooke3): Update error message with support version. + toolsVersion: .vNext, + targets: [ + TargetDescription(name: "exe", dependencies: ["lib"], type: .executable), + TargetDescription(name: "lib") + ] + ) + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + #if !os(macOS) + XCTAssertThrowsError( + try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )), + "This should fail when run on non-Apple platforms." + ) { error in + XCTAssertEqual( + error.localizedDescription, + "Targets with mixed language sources are only supported on Apple platforms." + ) + } + #else + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + result.checkProductsCount(1) + result.checkTargetsCount(2) + + let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + + let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "5", + "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-fmodule-map-file=/Pkg/Sources/lib/include/module.modulemap", "-Xcc", + "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", + "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", .anySequence + ]) + + let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() + XCTAssertMatch(swiftPartOfLib, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "5", + "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", + "/Pkg/Sources/lib/include", "-Xcc", "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", + "-Xcc", "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", .anySequence, + "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", + "/path/to/build/debug/lib.build/lib-Swift.h" + ]) + + let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) + XCTAssertMatch(clangPartOfLib, [ + "-fobjc-arc", "-target", "x86_64-apple-macosx10.13", "-g", "-O0", + "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", + "/Pkg/Sources/lib", "-ivfsoverlay", + "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", + "-I", "/path/to/build/debug/lib.build/Intermediates", + "-fmodules-cache-path=/path/to/build/debug/ModuleCache" + ]) + + XCTAssertEqual( + try result.target(for: "lib").mixedTarget().objects, + [ + buildPath.appending(components: "lib.build", "Foo.swift.o"), + buildPath.appending(components: "lib.build", "Bar.m.o"), + ] + ) + + XCTAssertEqual( + try result.target(for: "lib").mixedTarget().moduleMap.pathString, + "/Pkg/Sources/lib/include/module.modulemap" + ) + + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", + "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", + "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString + ]) + + testDiagnostics(observability.diagnostics) { result in + result.check(diagnostic: .contains("can be downloaded"), severity: .warning) + } + + #endif + } + + + func testSwiftCMixed() throws { let Pkg: AbsolutePath = "/Pkg" From be66b099975edc351453c27403f6407484712c7c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 24 Dec 2022 23:56:46 -0500 Subject: [PATCH 065/178] Add unit test to test invalid module map --- .../BasicMixedTargets/Package.swift | 5 +++++ .../Foo.m | 0 .../Foo.swift | 0 .../include/Foo.h | 0 .../include/module.modulemap | 5 +++++ Tests/FunctionalTests/MixedTargetTests.swift | 19 +++++++++++++++++++ 6 files changed, 29 insertions(+) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 1ce2d4783ea..519a13a740a 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -51,6 +51,11 @@ let package = Package( name: "MixedTargetWithCustomModuleMap" ), + // MARK: - MixedTargetWithInvalidCustomModuleMap + .target( + name: "MixedTargetWithInvalidCustomModuleMap" + ), + // MARK: - MixedTargetWithCustomModuleMapAndResources .target( name: "MixedTargetWithCustomModuleMapAndResources", diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap new file mode 100644 index 00000000000..ed8f7926173 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap @@ -0,0 +1,5 @@ +module MixedTargetWithInvalidCustomModuleMap { + header "Foo.h" +} + +module MixedTargetWithInvalidCustomModuleMap.Swift {} \ No newline at end of file diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 106a9901ea5..3948fb9507a 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -16,6 +16,7 @@ import SPMTestSupport // TODO(ncooke3): Create a larger E2E test with a complex mixed target. // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. // TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. +// TODO(ncooke3): Audit all test imports to test all supported routes. // MARK: - MixedTargetTests @@ -53,6 +54,24 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithInvalidCustomModuleMap() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + let commandExecutionError = try XCTUnwrap( + XCTAssertBuildFails( + fixturePath, + extraArgs: ["--target", "MixedTargetWithInvalidCustomModuleMap"] + ) + ) + + XCTAssert( + commandExecutionError.stderr.contains( + "error: The target's module map may not contain a Swift " + + "submodule for the module MixedTargetWithInvalidCustomModuleMap." + ) + ) + } + } + func testMixedTargetWithCustomModuleMapAndResources() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertSwiftTest( From ecc929199b96425ffd61dcc4cc652ea30fff039b Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 26 Dec 2022 15:02:00 -0500 Subject: [PATCH 066/178] Remove newline --- Sources/Build/BuildPlan.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 96c9f09978e..a2afdd42b75 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -437,7 +437,6 @@ public final class ClangTargetBuildDescription { } args += ["-I", clangTarget.includeDir.pathString] - args += additionalFlags if enableModules { args += try moduleCacheArgs From 8028c08944b479a15898fc67d869444c78dad77e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Dec 2022 13:53:59 -0500 Subject: [PATCH 067/178] Fix tests --- .../include/module.modulemap | 2 -- Tests/FunctionalTests/MixedTargetTests.swift | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap index ed8f7926173..747426e4769 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap @@ -1,5 +1,3 @@ module MixedTargetWithInvalidCustomModuleMap { header "Foo.h" } - -module MixedTargetWithInvalidCustomModuleMap.Swift {} \ No newline at end of file diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 3948fb9507a..d63649e098b 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -56,6 +56,24 @@ final class MixedTargetTests: XCTestCase { func testMixedTargetWithInvalidCustomModuleMap() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + // An invalid module map will cause the whole package to fail to + // build. To work around this, the module map is made invalid + // during the actual test. + let moduleMapPath = fixturePath.appending( + .init("Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap") + ) + + // In this case, an invalid module map is one that include a + // submodule of the form `$(ModuleName).Swift`. This is invalid + // because it collides with the submodule that SwiftPM will generate. + try """ + module MixedTargetWithInvalidCustomModuleMap { + header "Foo.h" + } + + module MixedTargetWithInvalidCustomModuleMap.Swift {} + """.write(to: moduleMapPath.asURL, atomically: true, encoding: .utf8) + let commandExecutionError = try XCTUnwrap( XCTAssertBuildFails( fixturePath, From 321c46738618459692bdf70b65f3de0a8137ae2d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Dec 2022 21:55:06 -0500 Subject: [PATCH 068/178] Rename testing copts --- .../MixedTargetWithNonPublicHeadersTests.swift | 4 ++-- Tests/FunctionalTests/MixedTargetTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift index a74012e5d4a..051bcad91ff 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift @@ -1,7 +1,7 @@ import XCTest import MixedTargetWithNonPublicHeaders -#if MIXED_TARGET_WITH_C_TESTS +#if EXPECT_FAILURE final class MixedTargetWithCTests: XCTestCase { func testInternalObjcTypesAreNotExposed() throws { @@ -14,4 +14,4 @@ final class MixedTargetWithCTests: XCTestCase { } } -#endif // MIXED_TARGET_WITH_C_TESTS +#endif // EXPECT_FAILURE diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index d63649e098b..aa8fa974b39 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -194,7 +194,7 @@ final class MixedTargetTests: XCTestCase { // the intentional build failure will break other unit tests // since all targets in the package are build when running // `swift test`. - Xswiftc: ["MIXED_TARGET_WITH_C_TESTS"] + Xswiftc: ["EXPECT_FAILURE"] ) } } From 5a00f6a87cff29e9cd848126d4a4a80e84e61584 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 12:29:32 -0500 Subject: [PATCH 069/178] Tweak error message --- Sources/PackageModel/Target.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 150f2eec782..5ce2a3f4d05 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -636,9 +636,9 @@ public final class MixedTarget: Target { ) throws { guard type == .library || type == .test else { throw StringError( - "Target with mixed sources at \(path) is a \(type) target; " + - "targets with mixed language sources are only supported for " + - "library and test targets." + "Target with mixed sources at '\(path)' is a \(type) " + + "target; targets with mixed language sources are only " + + "supported for library and test targets." ) } From 6c3a7804569e1629256dbbd7a1abad5f018703ea Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 12:29:58 -0500 Subject: [PATCH 070/178] Use if rather than switch --- Sources/Build/BuildPlan.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index a2afdd42b75..37d88e2d56a 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1551,15 +1551,14 @@ public final class MixedTargetBuildDescription { // module map) during the build. The directory to add a VFS overlay in // depends on the presence of a custom module map. let rootOverlayResourceDirectory: AbsolutePath - switch mixedTarget.clangTarget.moduleMapType { - case .custom(let customModuleMapPath): + if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { // To avoid the custom module map causing a module redeclaration // error, a VFS overlay is used when building the target to // redirect the custom module map to the modified module map in the // build directory. This redirecting overlay is placed in the // custom module map's parent directory, as to replace it. rootOverlayResourceDirectory = customModuleMapPath.parentDirectory - case .umbrellaHeader, .umbrellaDirectory, .none: + } else { // Since no custom module map exists, the build directory can // be used as the root of the VFS overlay. In this case, the // VFS overlay's sole purpose is to expose the generated Swift From 31b48352608433e7703f86a144081473910edbf2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 16:46:59 -0500 Subject: [PATCH 071/178] Update comment --- Sources/Basics/FileSystem+Extensions.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Basics/FileSystem+Extensions.swift b/Sources/Basics/FileSystem+Extensions.swift index 87f4a3191aa..d1139fd1f73 100644 --- a/Sources/Basics/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem+Extensions.swift @@ -353,9 +353,8 @@ extension FileSystem { public func writeFileContentsIfNeeded(_ path: AbsolutePath, bytes: ByteString) throws { try createDirectory(path.parentDirectory, recursive: true) - // If the file exists with the identical contents, we don't need to - // rewrite it. Otherwise, compiler will recompile even if nothing else - // has changed. + // If the file exists with the identical contents, there is no need to + // rewrite it. if let contents = try? readFileContents(path), contents == bytes { return } From 8fe94a917a7f98c8a6811da6bff3864e63cc458c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 16:47:23 -0500 Subject: [PATCH 072/178] Resolve outdated TODOs --- Tests/FunctionalTests/MixedTargetTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index aa8fa974b39..f2e9a699b05 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -13,10 +13,8 @@ import XCTest import SPMTestSupport -// TODO(ncooke3): Create a larger E2E test with a complex mixed target. // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. // TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. -// TODO(ncooke3): Audit all test imports to test all supported routes. // MARK: - MixedTargetTests From 0d73056e962de7fd4bdc92c7c7db741d9148603a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 17:29:14 -0500 Subject: [PATCH 073/178] Remove unneeded README --- .../MixedTargets/BasicMixedTargets/README.md | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/README.md diff --git a/Fixtures/MixedTargets/BasicMixedTargets/README.md b/Fixtures/MixedTargets/BasicMixedTargets/README.md deleted file mode 100644 index 89a506e2aeb..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# BasicMixedTargets - -A collection of targets to test SPM's support of mixed language targets. - -## BasicMixedTarget -Represents a simple mixed package where: -- Swift part of the target used types from the Objective-C part of the module -- Objective-C p of the target used types from the Swift part of the module - -## MixedTargetWithResources -Represents a simple mixed package with a bundled resource where: -- resource can be accessed from an Swift context using `Bundle.module` -- resource can be accessed from an Objective-C context using - `SWIFTPM_MODULE_BUNDLE` macro - -## MixedTargetWithCustomModuleMap -- Represents a simple mixed package that contains a custom module map. - -## MixedTargetWithCustomModuleMapAndResources -- Represents a simple mixed package that contains a custom module map and - a bundled resource. - -TODO(ncooke3): Fill the rest of this out. From bcb5868c2462ff44d33349579de2aa34ff6cce4f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 17:30:22 -0500 Subject: [PATCH 074/178] Remove unneeded README (2) --- Fixtures/MixedTargets/DummyTargets/Package.swift | 1 + Fixtures/MixedTargets/DummyTargets/README.md | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 Fixtures/MixedTargets/DummyTargets/README.md diff --git a/Fixtures/MixedTargets/DummyTargets/Package.swift b/Fixtures/MixedTargets/DummyTargets/Package.swift index 18806ae1c02..99cee89d0e7 100644 --- a/Fixtures/MixedTargets/DummyTargets/Package.swift +++ b/Fixtures/MixedTargets/DummyTargets/Package.swift @@ -3,6 +3,7 @@ import PackageDescription +// This package vends targets to aid in testing the BasicMixedTargets package. let package = Package( name: "DummyTargets", dependencies: [ diff --git a/Fixtures/MixedTargets/DummyTargets/README.md b/Fixtures/MixedTargets/DummyTargets/README.md deleted file mode 100644 index be78acc31bd..00000000000 --- a/Fixtures/MixedTargets/DummyTargets/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# DummyTargets - -This package vends targets to aid in testing the BasicMixedTargets package. From 6224a9561117bdf124fb8051b0122969712fcb3e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 17:31:14 -0500 Subject: [PATCH 075/178] Make comments more precise --- Sources/Build/BuildPlan.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 37d88e2d56a..cc5e93f9340 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -332,7 +332,8 @@ public final class ClangTargetBuildDescription { self.isWithinMixedTarget = isWithinMixedTarget // Try computing the modulemap path, creating a module map in the - // file system if necessary. + // file system if necessary. If building for a mixed target, the mixed + // target build description handle the module map. if target.type == .library && !isWithinMixedTarget { // If there's a custom module map, use it as given. if case .custom(let path) = clangTarget.moduleMapType { @@ -1141,6 +1142,9 @@ public final class SwiftTargetBuildDescription { /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { return buildParameters.triple.isDarwin() && + // Emitting the interop header for mixed test targets enables the + // sharing of Objective-C compatible Swift test helpers between + // Swift and Objective-C test files. (target.type == .library || target.type == .test && isWithinMixedTarget) } @@ -1628,11 +1632,11 @@ public final class MixedTargetBuildDescription { // path allows for importing headers using paths relative to // the root. "-I", mixedTarget.path.pathString, - // Include overlay to add interop header to intermediates directory. + // Include overlay file to add interop header to overlay directory. "-ivfsoverlay", allProductHeadersPath.pathString, - // The above overlay adds the interop header in the - // intermediates directory. Pass the intermediates directory as - // a search path so the generated header can be imported. + // The above two args add the interop header in the overlayed + // directory. Pass the overlay directory as a search path so the + // generated header can be imported. "-I", intermediatesDirectory.pathString ] } From 0cd0d06a19521bf0439f2e537b5053337778535e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 17:31:42 -0500 Subject: [PATCH 076/178] Simplify property's computation --- Sources/Build/BuildPlan.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index cc5e93f9340..d6912de44aa 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1318,10 +1318,7 @@ public final class MixedTargetBuildDescription { var resources: [Resource] { target.underlyingTarget.resources } /// If this target is a test target. - var isTestTarget: Bool { - clangTargetBuildDescription.isTestTarget && - swiftTargetBuildDescription.isTestTarget - } + var isTestTarget: Bool { target.underlyingTarget.type == .test } /// The objects in this target. This includes both the Swift and Clang object files. var objects: [AbsolutePath] { From 3e1bc936de04c7fb092f752d46cf96b73f42677f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 17:32:27 -0500 Subject: [PATCH 077/178] Fix grammar in comment --- Sources/Build/LLBuildManifestBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 5284513b18a..e86e588669e 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -248,7 +248,7 @@ extension LLBuildManifestBuilder { // MARK: - Compile Swift extension LLBuildManifestBuilder { - /// Create a llbuild target for a Swift target description and returns the Swift targets outputs. + /// Create a llbuild target for a Swift target description and returns the Swift target's outputs. @discardableResult private func createSwiftCompileCommand( _ target: SwiftTargetBuildDescription, From 31de8c5168b7e1237448eddcffd8d31fa4fcac9c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 28 Dec 2022 17:35:38 -0500 Subject: [PATCH 078/178] Fix test --- Tests/PackageLoadingTests/PackageBuilderTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index ed4d86963b9..ceab9d8e161 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -145,7 +145,7 @@ class PackageBuilderTests: XCTestCase { ) PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check( - diagnostic: "Target with mixed sources at \(foo) is a \(Target.Kind.executable) target; targets with mixed language sources are only supported for library and test targets.", + diagnostic: "Target with mixed sources at '\(foo)' is a \(Target.Kind.executable) target; targets with mixed language sources are only supported for library and test targets.", severity: .error ) } From 075206f9eeff0131d7107a6714ba8f6353ad75d8 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 3 Jan 2023 14:27:41 -0500 Subject: [PATCH 079/178] Explicitly expose public headers to clients via -I (pt. 1) - The issue: failure to build Clang sources that depend on a mixed target when the mixed target's generated Swift header imports an umbrella header for the module*. - *: The generated Swift header will import an umbrella header when the mixed target contains an Objective-C compatible Swift type that references an ObjC type (from the Clang part of the mixed target) in a way that cannot be forwarded declared when generating the ObjC interface for the Swift type. - Adds a unit test to test assert the correct behavior. --- .../BasicMixedTargets/Package.swift | 11 ++++++++++ .../Sources/BasicMixedTargetBeta/CarPart.m | 12 ++++++++++ .../Sources/BasicMixedTargetBeta/Driver.m | 12 ++++++++++ .../Sources/BasicMixedTargetBeta/Engine.swift | 4 ++++ .../Sources/BasicMixedTargetBeta/NewCar.swift | 14 ++++++++++++ .../Sources/BasicMixedTargetBeta/OldCar.m | 21 ++++++++++++++++++ .../BasicMixedTargetBeta/Transmission.h | 10 +++++++++ .../BasicMixedTargetBeta/Transmission.m | 6 +++++ .../BasicMixedTargetBeta.h | 5 +++++ .../include/BasicMixedTargetBeta/CarPart.h | 4 ++++ .../include/BasicMixedTargetBeta/Driver.h | 6 +++++ .../include/BasicMixedTargetBeta/OldCar.h | 14 ++++++++++++ .../ObjcBasicMixedTargetBetaTests.m | 22 +++++++++++++++++++ Sources/Build/BuildPlan.swift | 3 +++ 14 files changed, 144 insertions(+) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 519a13a740a..0cf432465fe 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -32,6 +32,17 @@ let package = Package( dependencies: ["BasicMixedTarget"] ), + // MARK: - BasicMixedTargetBeta + // TODO(ncooke3): Add note about compares this target compares with + // `BasicMixedTarget`. + .target( + name: "BasicMixedTargetBeta" + ), + .testTarget( + name: "BasicMixedTargetBetaTests", + dependencies: ["BasicMixedTargetBeta"] + ), + // MARK: - MixedTargetWithResources .target( name: "MixedTargetWithResources", diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m new file mode 100644 index 00000000000..90f8dc8d16f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m @@ -0,0 +1,12 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetBeta/CarPart.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `CarPart` can be imported via: +#import "include/BasicMixedTargetBeta/CarPart.h" +#import "BasicMixedTargetBeta/CarPart.h" + +@implementation CarPart +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m new file mode 100644 index 00000000000..10e2a7dfa61 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m @@ -0,0 +1,12 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetBeta/Driver.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `Driver` can be imported via: +#import "include/BasicMixedTargetBeta/Driver.h" +#import "BasicMixedTargetBeta/Driver.h" + +@implementation Driver +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift new file mode 100644 index 00000000000..bd269a81124 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Engine: CarPart {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift new file mode 100644 index 00000000000..fbf2e453b4f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift @@ -0,0 +1,14 @@ +import Foundation + +public class NewCar { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var driver: Driver? = nil + var transmission: Transmission? = nil + var hasStickShift: Bool { + return transmission != nil && transmission!.transmissionKind == .manual + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m new file mode 100644 index 00000000000..ce85ffa6ef0 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m @@ -0,0 +1,21 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetBeta/OldCar.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `OldCar` can be imported via: +#import "include/BasicMixedTargetBeta/OldCar.h" +#import "BasicMixedTargetBeta/OldCar.h" + +// Import the Swift part of the module. +#import "BasicMixedTargetBeta-Swift.h" + +#import "Transmission.h" + +@interface OldCar () +@property(nonatomic) Transmission *transmission; +@end + +@implementation OldCar +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h new file mode 100644 index 00000000000..57d0f1524b8 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h @@ -0,0 +1,10 @@ +#import + +typedef NS_ENUM(NSInteger, TransmissionKind) { + TransmissionKindManual, + TransmissionKindAutomatic +}; + +@interface Transmission : NSObject +@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m new file mode 100644 index 00000000000..ced7e283e83 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m @@ -0,0 +1,6 @@ +#import + +#import "Transmission.h" + +@implementation Transmission +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h new file mode 100644 index 00000000000..1da2e41c7b7 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h @@ -0,0 +1,5 @@ +// Note: Importing `#import ` is not supported +// within this directory. +#import "CarPart.h" +#import "Driver.h" +#import "OldCar.h" \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h new file mode 100644 index 00000000000..73e3ef360f0 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h @@ -0,0 +1,4 @@ +#import + +@interface CarPart : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h new file mode 100644 index 00000000000..8326bf2f179 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h @@ -0,0 +1,6 @@ +#import + +// This type is Swift compatible and used in `NewCar`. +@interface Driver : NSObject +@property(nonnull) NSString* name; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h new file mode 100644 index 00000000000..995e09f0ff9 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h @@ -0,0 +1,14 @@ +#import + +#import "Driver.h" + +// The `Engine` type is declared in the Swift part of the module. Such types +// must be forward declared in headers. +@class Engine; + +@interface OldCar : NSObject +// `Engine` is defined in Swift. +@property(nullable) Engine *engine; +// `Driver` is defined in Objective-C. +@property(nullable) Driver *driver; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m new file mode 100644 index 00000000000..660b8445399 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m @@ -0,0 +1,22 @@ +#import + +@import BasicMixedTargetBeta; + +@interface ObjcBasicMixedTargetBetaTests : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetBetaTests + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + OldCar *oldCar = [[OldCar alloc] init]; + Driver *driver = [[Driver alloc] init]; +} + +@end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index d6912de44aa..ce5335bb2e8 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -2722,6 +2722,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } } case let target as MixedTarget where target.type == .library: + // Add the public headers of the dependency. + clangTarget.additionalFlags += ["-I", target.clangTarget.includeDir.pathString] + // Add the modulemap of the dependency. if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { clangTarget.additionalFlags.append( From 072b2b030aa2c9fe062e843d9b1f22204965545a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 3 Jan 2023 15:38:51 -0500 Subject: [PATCH 080/178] Rename test files to avoid collisions in test bundle --- .../Sources/BasicMixedTargetBeta/CabinClass.h | 7 +++++++ .../Sources/BasicMixedTargetBeta/CarPart.m | 12 ----------- .../Sources/BasicMixedTargetBeta/Driver.m | 12 ----------- .../Sources/BasicMixedTargetBeta/Engine.swift | 2 +- .../Sources/BasicMixedTargetBeta/NewCar.swift | 14 ------------- .../BasicMixedTargetBeta/NewPlane.swift | 14 +++++++++++++ .../Sources/BasicMixedTargetBeta/OldCar.m | 21 ------------------- .../Sources/BasicMixedTargetBeta/OldPlane.m | 21 +++++++++++++++++++ .../Sources/BasicMixedTargetBeta/Pilot.m | 12 +++++++++++ .../Sources/BasicMixedTargetBeta/PlanePart.m | 12 +++++++++++ .../BasicMixedTargetBeta/Transmission.h | 10 --------- .../BasicMixedTargetBeta/Transmission.m | 6 ------ .../BasicMixedTargetBeta.h | 8 +++---- .../{OldCar.h => OldPlane.h} | 8 +++---- .../{Driver.h => Pilot.h} | 2 +- .../{CarPart.h => PlanePart.h} | 2 +- .../ObjcBasicMixedTargetBetaTests.m | 4 ++-- 17 files changed, 79 insertions(+), 88 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CabinClass.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewPlane.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m rename Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/{OldCar.h => OldPlane.h} (66%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/{Driver.h => Pilot.h} (81%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/{CarPart.h => PlanePart.h} (55%) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CabinClass.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CabinClass.h new file mode 100644 index 00000000000..a2dd7552178 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CabinClass.h @@ -0,0 +1,7 @@ +#import + +typedef NS_ENUM(NSInteger, CabinClass) { + CabinClassFirstClass, + CabinClassBusinessClass, + CabinClassEconomyClass +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m deleted file mode 100644 index 90f8dc8d16f..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CarPart.m +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetBeta/CarPart.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `CarPart` can be imported via: -#import "include/BasicMixedTargetBeta/CarPart.h" -#import "BasicMixedTargetBeta/CarPart.h" - -@implementation CarPart -@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m deleted file mode 100644 index 10e2a7dfa61..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Driver.m +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetBeta/Driver.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `Driver` can be imported via: -#import "include/BasicMixedTargetBeta/Driver.h" -#import "BasicMixedTargetBeta/Driver.h" - -@implementation Driver -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift index bd269a81124..4e751867798 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift @@ -1,4 +1,4 @@ import Foundation // This type is Objective-C compatible and used in `OldCar`. -@objc public class Engine: CarPart {} +@objc public class Engine: PlanePart {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift deleted file mode 100644 index fbf2e453b4f..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewCar.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -public class NewCar { - // `Engine` is defined in Swift. - var engine: Engine? = nil - // The following types are defined in Objective-C. - var driver: Driver? = nil - var transmission: Transmission? = nil - var hasStickShift: Bool { - return transmission != nil && transmission!.transmissionKind == .manual - } - - public init() {} -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewPlane.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewPlane.swift new file mode 100644 index 00000000000..8cc1eff3a0b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewPlane.swift @@ -0,0 +1,14 @@ +import Foundation + +public class NewPlane { + // `Engine` is defined in Swift. + var engine: Engine? = nil + // The following types are defined in Objective-C. + var pilot: Pilot? = nil + var cabinClass: CabinClass? = nil + var hasTrolleyService: Bool { + return cabinClass != nil && cabinClass != .economyClass + } + + public init() {} +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m deleted file mode 100644 index ce85ffa6ef0..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldCar.m +++ /dev/null @@ -1,21 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetBeta/OldCar.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `OldCar` can be imported via: -#import "include/BasicMixedTargetBeta/OldCar.h" -#import "BasicMixedTargetBeta/OldCar.h" - -// Import the Swift part of the module. -#import "BasicMixedTargetBeta-Swift.h" - -#import "Transmission.h" - -@interface OldCar () -@property(nonatomic) Transmission *transmission; -@end - -@implementation OldCar -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m new file mode 100644 index 00000000000..3765bec3812 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m @@ -0,0 +1,21 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetBeta/OldPlane.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `OldPlane` can be imported via: +#import "include/BasicMixedTargetBeta/OldPlane.h" +#import "BasicMixedTargetBeta/OldPlane.h" + +// Import the Swift part of the module. +#import "BasicMixedTargetBeta-Swift.h" + +#import "CabinClass.h" + +@interface OldPlane () +@property(nonatomic, assign) CabinClass cabinClass; +@end + +@implementation OldPlane +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m new file mode 100644 index 00000000000..e861344dce0 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m @@ -0,0 +1,12 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetBeta/Pilot.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `Pilot` can be imported via: +#import "include/BasicMixedTargetBeta/Pilot.h" +#import "BasicMixedTargetBeta/Pilot.h" + +@implementation Pilot +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m new file mode 100644 index 00000000000..bbc944d455c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m @@ -0,0 +1,12 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetBeta/PlanePart.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `PlanePart` can be imported via: +#import "include/BasicMixedTargetBeta/PlanePart.h" +#import "BasicMixedTargetBeta/PlanePart.h" + +@implementation PlanePart +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h deleted file mode 100644 index 57d0f1524b8..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.h +++ /dev/null @@ -1,10 +0,0 @@ -#import - -typedef NS_ENUM(NSInteger, TransmissionKind) { - TransmissionKindManual, - TransmissionKindAutomatic -}; - -@interface Transmission : NSObject -@property (nonatomic, readonly, assign) TransmissionKind transmissionKind; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m deleted file mode 100644 index ced7e283e83..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Transmission.m +++ /dev/null @@ -1,6 +0,0 @@ -#import - -#import "Transmission.h" - -@implementation Transmission -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h index 1da2e41c7b7..5dc7027b6d2 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h @@ -1,5 +1,5 @@ -// Note: Importing `#import ` is not supported +// Note: Importing `#import ` is not supported // within this directory. -#import "CarPart.h" -#import "Driver.h" -#import "OldCar.h" \ No newline at end of file +#import "PlanePart.h" +#import "Pilot.h" +#import "OldPlane.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldPlane.h similarity index 66% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldPlane.h index 995e09f0ff9..30be4cb8f00 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldPlane.h @@ -1,14 +1,14 @@ #import -#import "Driver.h" +#import "Pilot.h" // The `Engine` type is declared in the Swift part of the module. Such types // must be forward declared in headers. @class Engine; -@interface OldCar : NSObject +@interface OldPlane : NSObject // `Engine` is defined in Swift. @property(nullable) Engine *engine; -// `Driver` is defined in Objective-C. -@property(nullable) Driver *driver; +// `Pilot` is defined in Objective-C. +@property(nullable) Pilot *pilot; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Pilot.h similarity index 81% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Pilot.h index 8326bf2f179..a34a5a3626e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Driver.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Pilot.h @@ -1,6 +1,6 @@ #import // This type is Swift compatible and used in `NewCar`. -@interface Driver : NSObject +@interface Pilot : NSObject @property(nonnull) NSString* name; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h similarity index 55% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h index 73e3ef360f0..27594bd413e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/CarPart.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h @@ -1,4 +1,4 @@ #import -@interface CarPart : NSObject +@interface PlanePart : NSObject @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m index 660b8445399..4f0e8143e55 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m @@ -15,8 +15,8 @@ - (void)testPublicSwiftAPI { - (void)testPublicObjcAPI { // Check that Objective-C API surface is exposed... - OldCar *oldCar = [[OldCar alloc] init]; - Driver *driver = [[Driver alloc] init]; + OldPlane *oldPlane = [[OldPlane alloc] init]; + Pilot *pilot = [[Pilot alloc] init]; } @end From feb7e1822a2d3dad843cff5dfa9383e4a118b8db Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 3 Jan 2023 15:39:26 -0500 Subject: [PATCH 081/178] Explicitly expose public headers to clients via -I (pt. 2) - This commit is similar to the corresponding pt. 1 commit except that is fixes the build failure for SWIFT clients of the mixed target. - The issue: failure to build Swift sources that depend on a mixed target when the mixed target's generated Swift header imports an umbrella header for the module*. - *: The generated Swift header will import an umbrella header when the mixed target contains an Objective-C compatible Swift type that references an ObjC type (from the Clang part of the mixed target) in a way that cannot be forwarded declared when generating the ObjC interface for the Swift type. --- .../BasicMixedTargetBetaTests.swift | 25 +++++++++++++++++++ Sources/Build/BuildPlan.swift | 10 +++----- 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift new file mode 100644 index 00000000000..33144f9d944 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift @@ -0,0 +1,25 @@ +import XCTest +import BasicMixedTargetBeta + +final class BasicMixedTargetBetaTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = NewPlane() + let _ = Engine() + } + + func testPublicObjcAPI() throws { + // Check that Objective-C API surface is exposed... + let _ = OldPlane() + let _ = Pilot() + } + + func testModulePrefixingWorks() throws { + let _ = BasicMixedTargetBeta.NewPlane() + let _ = BasicMixedTargetBeta.Engine() + let _ = BasicMixedTargetBeta.OldPlane() + let _ = BasicMixedTargetBeta.Pilot() + } + +} diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index ce5335bb2e8..1a4126b8755 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -2772,8 +2772,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { guard let moduleMap = target.moduleMap else { break } swiftTarget.appendClangFlags( "-fmodule-map-file=\(moduleMap.pathString)", - "-I", - target.clangTarget.includeDir.pathString + "-I", target.clangTarget.includeDir.pathString ) case let target as SystemLibraryTarget: swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"] @@ -2792,11 +2791,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { guard case let .mixed(target)? = targetMap[dependency] else { throw InternalError("unexpected mixed target \(underlyingTarget)") } - // Add the path to modulemap of the dependency. Currently we - // require that all Mixed targets have a modulemap as it is - // required for interoperability. + // Add the dependency's modulemap and public headers. swiftTarget.appendClangFlags( - "-fmodule-map-file=\(target.moduleMap.pathString)" + "-fmodule-map-file=\(target.moduleMap.pathString)", + "-I", target.clangTargetBuildDescription.clangTarget.includeDir.pathString ) if let allProductHeadersOverlay = target.allProductHeadersOverlay { From 971233f35ec477cb3c4d1e1a1fc28a87e7ed791d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 3 Jan 2023 15:49:14 -0500 Subject: [PATCH 082/178] Add unit test for issue fixed in previous 3 commits --- Tests/FunctionalTests/MixedTargetTests.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index f2e9a699b05..827c0e7d9e2 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -34,6 +34,21 @@ final class MixedTargetTests: XCTestCase { } } + // See `Fixtures/MixedTargets/BasicMixedTargets/Package.swift` for + // explanation of `BasicMixedTargetBeta` target. + func testMixedTargetBeta() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "BasicMixedTargetBeta"] + ) + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "BasicMixedTargetBetaTests"] + ) + } + } + func testMixedTargetWithResources() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertSwiftTest( From b12688b201affd949e291548c7c3f7fc9802e829 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 3 Jan 2023 16:48:14 -0500 Subject: [PATCH 083/178] Resolve TODO --- Fixtures/MixedTargets/BasicMixedTargets/Package.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 0cf432465fe..4dfa630a8b7 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -56,8 +56,6 @@ let package = Package( ), // MARK: - MixedTargetWithCustomModuleMap - // TODO(ncooke3): Play around and try to break this target with a more - // complex module map. .target( name: "MixedTargetWithCustomModuleMap" ), From 891a05be38dc914d64beaca90bf60ec96b67cf9a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 6 Jan 2023 11:48:56 -0500 Subject: [PATCH 084/178] Add unit tests to highlight current failures --- .../BasicMixedTargets/Package.swift | 19 +++++++++----- .../Sources/BasicMixedTarget/CarPart.m | 7 ++++++ .../Sources/BasicMixedTarget/Engine.swift | 2 +- .../BasicMixedTarget/include/CarPart.h | 5 ++++ .../Sources/BasicMixedTargetBeta/Engine.swift | 4 --- .../Sources/BasicMixedTargetBeta/Pilot.m | 12 --------- .../Sources/BasicMixedTargetBeta/PlanePart.m | 12 --------- .../BasicMixedTargetBeta.h | 5 ---- .../include/BasicMixedTargetBeta/PlanePart.h | 4 --- .../CabinClass.h | 0 .../Engine.swift | 4 +++ .../NewPlane.swift | 0 .../OldPlane.m | 10 ++++---- .../Pilot.m | 12 +++++++++ .../PlanePart.m | 12 +++++++++ ...BasicMixedTargetWithNestedUmbrellaHeader.h | 5 ++++ .../OldPlane.h | 0 .../Pilot.h | 2 +- .../PlanePart.h | 5 ++++ .../Bakery.m | 14 +++++++++++ .../Coffee.swift | 5 ++++ .../Cookie.swift | 3 +++ .../Dessert.m | 7 ++++++ .../include/Bakery.h | 7 ++++++ .../BasicMixedTargetWithUmbrellaHeader.h | 2 ++ .../include/Dessert.h | 4 +++ .../BasicMixedTargetBetaTests.swift | 25 ------------------- .../BasicMixedTargetTests.swift | 2 ++ .../ObjcBasicMixedTargetTests.m | 1 + ...dTargetWithNestedUmbrellaHeaderTests.swift | 25 +++++++++++++++++++ ...ixedTargetWithNestedUmbrellaHeaderTests.m} | 6 ++--- ...icMixedTargetWithUmbrellaHeaderTests.swift | 25 +++++++++++++++++++ ...cBasicMixedTargetWithUmbrellaHeaderTests.m | 22 ++++++++++++++++ Tests/FunctionalTests/MixedTargetTests.swift | 22 +++++++++++++--- 34 files changed, 208 insertions(+), 82 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/CarPart.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetBeta => BasicMixedTargetWithNestedUmbrellaHeader}/CabinClass.h (100%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Engine.swift rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetBeta => BasicMixedTargetWithNestedUmbrellaHeader}/NewPlane.swift (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetBeta => BasicMixedTargetWithNestedUmbrellaHeader}/OldPlane.m (51%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetBeta/include/BasicMixedTargetBeta => BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader}/OldPlane.h (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetBeta/include/BasicMixedTargetBeta => BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader}/Pilot.h (64%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift rename Fixtures/MixedTargets/BasicMixedTargets/Tests/{BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m => BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m} (65%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 4dfa630a8b7..76d5b834e84 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -32,15 +32,22 @@ let package = Package( dependencies: ["BasicMixedTarget"] ), - // MARK: - BasicMixedTargetBeta - // TODO(ncooke3): Add note about compares this target compares with - // `BasicMixedTarget`. + // MARK: - BasicMixedTargetWithNestedUmbrellaHeader .target( - name: "BasicMixedTargetBeta" + name: "BasicMixedTargetWithNestedUmbrellaHeader" ), .testTarget( - name: "BasicMixedTargetBetaTests", - dependencies: ["BasicMixedTargetBeta"] + name: "BasicMixedTargetWithNestedUmbrellaHeaderTests", + dependencies: ["BasicMixedTargetWithNestedUmbrellaHeader"] + ), + + // MARK: - BasicMixedTargetWithUmbrellaHeader + .target( + name: "BasicMixedTargetWithUmbrellaHeader" + ), + .testTarget( + name: "BasicMixedTargetWithUmbrellaHeaderTests", + dependencies: ["BasicMixedTargetWithUmbrellaHeader"] ), // MARK: - MixedTargetWithResources diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m new file mode 100644 index 00000000000..87ebd2a4eeb --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m @@ -0,0 +1,7 @@ +#import + +#import "CarPart.h" +#import "include/CarPart.h" + +@implementation CarPart +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift index da1ee757331..bd269a81124 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift @@ -1,4 +1,4 @@ import Foundation // This type is Objective-C compatible and used in `OldCar`. -@objc public class Engine: NSObject {} +@objc public class Engine: CarPart {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/CarPart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/CarPart.h new file mode 100644 index 00000000000..68c343e6201 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/CarPart.h @@ -0,0 +1,5 @@ +#import + +// This type is subclassed by the Objective-C compatible Swift `Engine` type. +@interface CarPart : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift deleted file mode 100644 index 4e751867798..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Engine.swift +++ /dev/null @@ -1,4 +0,0 @@ -import Foundation - -// This type is Objective-C compatible and used in `OldCar`. -@objc public class Engine: PlanePart {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m deleted file mode 100644 index e861344dce0..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/Pilot.m +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetBeta/Pilot.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `Pilot` can be imported via: -#import "include/BasicMixedTargetBeta/Pilot.h" -#import "BasicMixedTargetBeta/Pilot.h" - -@implementation Pilot -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m deleted file mode 100644 index bbc944d455c..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/PlanePart.m +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetBeta/PlanePart.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `PlanePart` can be imported via: -#import "include/BasicMixedTargetBeta/PlanePart.h" -#import "BasicMixedTargetBeta/PlanePart.h" - -@implementation PlanePart -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h deleted file mode 100644 index 5dc7027b6d2..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/BasicMixedTargetBeta.h +++ /dev/null @@ -1,5 +0,0 @@ -// Note: Importing `#import ` is not supported -// within this directory. -#import "PlanePart.h" -#import "Pilot.h" -#import "OldPlane.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h deleted file mode 100644 index 27594bd413e..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/PlanePart.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface PlanePart : NSObject -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CabinClass.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/CabinClass.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/CabinClass.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/CabinClass.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Engine.swift new file mode 100644 index 00000000000..222c4b45157 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Engine.swift @@ -0,0 +1,4 @@ +import Foundation + +// This type is Objective-C compatible and used in `OldPlane`. +@objc public class Engine: PlanePart {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewPlane.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/NewPlane.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/NewPlane.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/NewPlane.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.m similarity index 51% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.m index 3765bec3812..37486ce3cfd 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/OldPlane.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.m @@ -1,15 +1,15 @@ #import -// The below import syntax resolves to the `"BasicMixedTargetBeta/OldPlane.h"` +// The below import syntax resolves to the `"BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h"` // path within the public headers directory. It is not related to a // framework style import. -#import +#import // Alternatively, the above `OldPlane` can be imported via: -#import "include/BasicMixedTargetBeta/OldPlane.h" -#import "BasicMixedTargetBeta/OldPlane.h" +#import "include/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h" +#import "BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h" // Import the Swift part of the module. -#import "BasicMixedTargetBeta-Swift.h" +#import "BasicMixedTargetWithNestedUmbrellaHeader-Swift.h" #import "CabinClass.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m new file mode 100644 index 00000000000..847d06e565f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m @@ -0,0 +1,12 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `Pilot` can be imported via: +#import "include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h" +#import "BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h" + +@implementation Pilot +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m new file mode 100644 index 00000000000..0da54000a84 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m @@ -0,0 +1,12 @@ +#import + +// The below import syntax resolves to the `"BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h"` +// path within the public headers directory. It is not related to a +// framework style import. +#import +// Alternatively, the above `PlanePart` can be imported via: +#import "include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h" +#import "BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h" + +@implementation PlanePart +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h new file mode 100644 index 00000000000..72aabf5e960 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h @@ -0,0 +1,5 @@ +// Note: Importing `#import ` is not supported +// within this directory. +#import "PlanePart.h" +#import "Pilot.h" +#import "OldPlane.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldPlane.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/OldPlane.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Pilot.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h similarity index 64% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Pilot.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h index a34a5a3626e..2bd5b9b60ea 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetBeta/include/BasicMixedTargetBeta/Pilot.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h @@ -1,6 +1,6 @@ #import -// This type is Swift compatible and used in `NewCar`. +// This type is Swift compatible and used in `NewPlane`. @interface Pilot : NSObject @property(nonnull) NSString* name; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h new file mode 100644 index 00000000000..6ee9ef215ed --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h @@ -0,0 +1,5 @@ +#import + +// This type is subclassed by the Objective-C compatible Swift `Engine` type. +@interface PlanePart : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m new file mode 100644 index 00000000000..73590b4c125 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m @@ -0,0 +1,14 @@ +#import + +#import "include/Bakery.h" +#import "Bakery.h" + +#import "BasicMixedTargetWithUmbrellaHeader-Swift.h" + +@implementation Bakery + +- (Cookie*)cookieOfTheDay { + return [[Cookie alloc] init]; +} + +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift new file mode 100644 index 00000000000..d2376f2db5e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift @@ -0,0 +1,5 @@ +import Foundation + +public struct Coffee { + public init() {} +} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift new file mode 100644 index 00000000000..0647bce18af --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift @@ -0,0 +1,3 @@ +import Foundation + +@objc open class Cookie: Dessert {} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m new file mode 100644 index 00000000000..29f88198853 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m @@ -0,0 +1,7 @@ +#import + +#import "include/Dessert.h" +#import "Dessert.h" + +@implementation Dessert +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h new file mode 100644 index 00000000000..2a6646b29e9 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h @@ -0,0 +1,7 @@ +#import + +@class Cookie; + +@interface Bakery : NSObject +- (Cookie*)cookieOfTheDay; +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h new file mode 100644 index 00000000000..c4caf8f890d --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h @@ -0,0 +1,2 @@ +#import "Bakery.h" +#import "Dessert.h" \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h new file mode 100644 index 00000000000..f5099159eea --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h @@ -0,0 +1,4 @@ +#import + +@interface Dessert : NSObject +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift deleted file mode 100644 index 33144f9d944..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/BasicMixedTargetBetaTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -import XCTest -import BasicMixedTargetBeta - -final class BasicMixedTargetBetaTests: XCTestCase { - - func testPublicSwiftAPI() throws { - // Check that Swift API surface is exposed... - let _ = NewPlane() - let _ = Engine() - } - - func testPublicObjcAPI() throws { - // Check that Objective-C API surface is exposed... - let _ = OldPlane() - let _ = Pilot() - } - - func testModulePrefixingWorks() throws { - let _ = BasicMixedTargetBeta.NewPlane() - let _ = BasicMixedTargetBeta.Engine() - let _ = BasicMixedTargetBeta.OldPlane() - let _ = BasicMixedTargetBeta.Pilot() - } - -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift index 940ea537328..d4626948315 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift @@ -13,6 +13,7 @@ final class BasicMixedTargetTests: XCTestCase { // Check that Objective-C API surface is exposed... let _ = OldCar() let _ = Driver() + let _ = CarPart() } func testModulePrefixingWorks() throws { @@ -20,6 +21,7 @@ final class BasicMixedTargetTests: XCTestCase { let _ = BasicMixedTarget.Engine() let _ = BasicMixedTarget.OldCar() let _ = BasicMixedTarget.Driver() + let _ = BasicMixedTarget.CarPart() } } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m index 7dc7de076ad..c29f9730240 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m @@ -17,6 +17,7 @@ - (void)testPublicObjcAPI { // Check that Objective-C API surface is exposed... OldCar *oldCar = [[OldCar alloc] init]; Driver *driver = [[Driver alloc] init]; + CarPart *carPart = [[CarPart alloc] init]; } @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift new file mode 100644 index 00000000000..c3d2d8d3786 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift @@ -0,0 +1,25 @@ +import XCTest +import BasicMixedTargetWithNestedUmbrellaHeader + +final class BasicMixedTargetWithNestedUmbrellaHeaderTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = NewPlane() + let _ = Engine() + } + + func testPublicObjcAPI() throws { + // Check that Objective-C API surface is exposed... + let _ = OldPlane() + let _ = Pilot() + } + + func testModulePrefixingWorks() throws { + let _ = BasicMixedTargetWithNestedUmbrellaHeader.NewPlane() + let _ = BasicMixedTargetWithNestedUmbrellaHeader.Engine() + let _ = BasicMixedTargetWithNestedUmbrellaHeader.OldPlane() + let _ = BasicMixedTargetWithNestedUmbrellaHeader.Pilot() + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m similarity index 65% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m index 4f0e8143e55..e56728fa62d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetBetaTests/ObjcBasicMixedTargetBetaTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m @@ -1,12 +1,12 @@ #import -@import BasicMixedTargetBeta; +@import BasicMixedTargetWithNestedUmbrellaHeader; -@interface ObjcBasicMixedTargetBetaTests : XCTestCase +@interface ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests : XCTestCase @end -@implementation ObjcBasicMixedTargetBetaTests +@implementation ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift new file mode 100644 index 00000000000..315766a4beb --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift @@ -0,0 +1,25 @@ +import XCTest +import BasicMixedTargetWithUmbrellaHeader + +final class BasicMixedTargetWithNestellaHeaderTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = Cookie() + let _ = Coffee() + } + + func testPublicObjcAPI() throws { + // Check that Objective-C API surface is exposed... + let _ = Bakery() + let _ = Dessert() + } + + func testModulePrefixingWorks() throws { + let _ = BasicMixedTargetWithUmbrellaHeader.Cookie() + let _ = BasicMixedTargetWithUmbrellaHeader.Coffee() + let _ = BasicMixedTargetWithUmbrellaHeader.Bakery() + let _ = BasicMixedTargetWithUmbrellaHeader.Dessert() + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m new file mode 100644 index 00000000000..a7de6369b5f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m @@ -0,0 +1,22 @@ +#import + +@import BasicMixedTargetWithUmbrellaHeader; + +@interface ObjcBasicMixedTargetWithUmbrellaHeaderTests : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetWithUmbrellaHeaderTests + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Cookie *cookie = [[Cookie alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + Bakery *bakery = [[Bakery alloc] init]; + Dessert *dessert = [[Dessert alloc] init]; +} + +@end diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 827c0e7d9e2..ba6c20d6903 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -34,17 +34,31 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithUmbrellaHeader() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "BasicMixedTargetWithUmbrellaHeader"] + ) + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "BasicMixedTargetWithUmbrellaHeaderTests"] + ) + } + } + + // TODO(ncooke3): Remove below comment if `-import-objc-header` works. // See `Fixtures/MixedTargets/BasicMixedTargets/Package.swift` for - // explanation of `BasicMixedTargetBeta` target. - func testMixedTargetBeta() throws { + // explanation of `BasicMixedTargetWithNestedUmbrellaHeader` target. + func testMixedTargetWithNestedUmbrellaHeader() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, - extraArgs: ["--target", "BasicMixedTargetBeta"] + extraArgs: ["--target", "BasicMixedTargetWithNestedUmbrellaHeader"] ) XCTAssertSwiftTest( fixturePath, - extraArgs: ["--filter", "BasicMixedTargetBetaTests"] + extraArgs: ["--filter", "BasicMixedTargetWithNestedUmbrellaHeaderTests"] ) } } From 5049814c442c4a97b5e6e7c81ded8496da1ce87d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 6 Jan 2023 11:49:46 -0500 Subject: [PATCH 085/178] Address current failures (approach #1) --- Sources/Basics/FileSystem/VFSOverlay.swift | 27 ++++ Sources/Build/BuildPlan.swift | 155 +++++++++++++++------ 2 files changed, 140 insertions(+), 42 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index a16758ac45e..1c9e1c11a27 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -55,6 +55,13 @@ public struct VFSOverlay: Encodable { super.init(name: name, type: "directory") } + public convenience init( + name: String, + @VFSOverlayBuilder contents: () -> [VFSOverlay.Resource] + ) { + self.init(name: name, contents: contents()) + } + public override func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(contents, forKey: .contents) @@ -87,3 +94,23 @@ public struct VFSOverlay: Encodable { try JSONEncoder.makeWithDefaults(prettified: false).encode(path: path, fileSystem: fileSystem, self) } } + +// TOOD(ncooke3): Gate this? +@resultBuilder +public struct VFSOverlayBuilder { + public static func buildBlock(_ components: [VFSOverlay.Resource]...) -> [VFSOverlay.Resource] { + return components.flatMap { $0 } + } + + public static func buildExpression(_ expression: VFSOverlay.Resource) -> [VFSOverlay.Resource] { + return [expression] + } + + public static func buildExpression(_ expression: [VFSOverlay.Resource]) -> [VFSOverlay.Resource] { + return expression + } + + public static func buildOptional(_ components: [VFSOverlay.Resource]?) -> [VFSOverlay.Resource] { + return components ?? [] + } +} diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 1a4126b8755..6413ce2d890 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1433,6 +1433,54 @@ public final class MixedTargetBuildDescription { fileSystem: fileSystem ) + // MARK: Conditionally generate an umbrella header + + // When the Swift compiler creates the generated interop header for + // Objective-C compatible Swift API (via `-emit-objc-header`), any + // Objective-C symbol that cannot be forward declared (e.g. superclass, + // protocol, etc.) will attempt to be imported via a module import of + // the module's umbrella header (e.g. `#import `). The + // Swift compiler assumes (1) the module has the structure of a + // framework and (2) that an umbrella header exists. However, SwiftPM + // does not build frameworks (breaking assumption #1) and an umbrella + // header may not exist (breaking assumption #2). Instead, to guarantee + // the generated interop header's import can be resolved, the package + // manager generates an umbrella header if needed and will later use a + // VFS overlay to make the generated header appear in the proper + // location so the problematic import in the generated interop header + // can be resolved during the build. + var generatedUmbrellaHeaderPath: AbsolutePath? = nil + let relativeUmbrellaHeaderPath = + RelativePath("\(mixedTarget.c99name)/\(mixedTarget.c99name).h") + let potentialUmbrellaHeaderPath = + mixedTarget.clangTarget.includeDir.appending(relativeUmbrellaHeaderPath) + if !fileSystem.isFile(potentialUmbrellaHeaderPath) { + generatedUmbrellaHeaderPath = tempsPath.appending(component: "\(mixedTarget.c99name).h") + // Populate a stream that will become the generated umbrella header. + let stream = BufferedOutputByteStream() + mixedTarget.clangTarget.headers + // One of the requirements for a Swift API to be Objective-C + // compatible and therefore included in the generated interop + // header is that it has `public` or `open` visbility. This + // means that such Swift API can only reference (e.g. subclass) + // Objective-C types defined in the target's public headers. + // Because of this, the generated umbrella header will only + // include public headers so all other can be filtered out. + .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } + // Filter out non-Objective-C/C headers. + .filter { $0.basename.hasSuffix(".h") } + // Add each remaining header to the generated umbrella header. + .forEach { + // Import the header, followed by a newline. + stream <<< "#import \"\($0)\"\n" + } + + try fileSystem.writeFileContentsIfNeeded( + generatedUmbrellaHeaderPath!, + bytes: stream.bytes + ) + } + // MARK: Generate products to be used by client of the target. // Path to the module map used by clients to access the mixed target's @@ -1483,19 +1531,23 @@ public final class MixedTargetBuildDescription { self.moduleMap = customModuleMapPath self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) - try VFSOverlay( - roots: [ - VFSOverlay.Directory( - name: customModuleMapPath.parentDirectory.pathString, - contents: [ + try VFSOverlay(roots: [ + VFSOverlay.Directory(name: customModuleMapPath.parentDirectory.pathString) { + VFSOverlay.File( + name: moduleMapFilename, + externalContents: productModuleMapPath.pathString + ) + + if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { + VFSOverlay.Directory(name: mixedTarget.c99name) { VFSOverlay.File( - name: moduleMapFilename, - externalContents: productModuleMapPath.pathString + name: generatedUmbrellaHeaderPath.basename, + externalContents: generatedUmbrellaHeaderPath.pathString ) - ] - ) - ] - ).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) + } + } + } + ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) // When the mixed target does not have a custom module map, one will be // generated as a product for use by clients. @@ -1513,9 +1565,25 @@ public final class MixedTargetBuildDescription { // Set the generated module map as the module map for the target. self.moduleMap = productModuleMapPath - // An overlay is not neccesssary as there was no original custom - // module map to replace. - self.allProductHeadersOverlay = nil + + if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { + // If an umbrella header was generated, it needs to be + // overlayed within the public headers directory. + self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) + try VFSOverlay(roots: [ + VFSOverlay.Directory(name: mixedTarget.clangTarget.includeDir.pathString) { + VFSOverlay.Directory(name: mixedTarget.c99name) { + VFSOverlay.File( + name: generatedUmbrellaHeaderPath.basename, + externalContents: generatedUmbrellaHeaderPath.pathString + ) + } + } + ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) + } else { + // Else, no product overlay is needed. + self.allProductHeadersOverlay = nil + } } // MARK: Generate intermediate artifacts used to build the target. @@ -1569,38 +1637,41 @@ public final class MixedTargetBuildDescription { let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the modified - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: intermediateModuleMapPath.pathString - ), - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - ] - ) + VFSOverlay.Directory(name: rootOverlayResourceDirectory.pathString) { + // Redirect the `module.modulemap` to the modified + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: intermediateModuleMapPath.pathString + ) + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + + if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { + VFSOverlay.Directory(name: mixedTarget.c99name) { + VFSOverlay.File( + name: generatedUmbrellaHeaderPath.basename, + externalContents: generatedUmbrellaHeaderPath.pathString + ) + } + } + } ]).write(to: allProductHeadersPath, fileSystem: fileSystem) let unextendedModuleMapOverlayPath = intermediatesDirectory.appending(component: unextendedModuleOverlayFilename) try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the *unextended* - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: unextendedModuleMapPath.pathString - ) - ] - ) + VFSOverlay.Directory(name: rootOverlayResourceDirectory.pathString) { + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: unextendedModuleMapPath.pathString + ) + } ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) // 4. Tie everything together by passing build flags. From 3179871a1592fb380d74a1a0342c7b9f398dd0bc Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 6 Jan 2023 18:26:09 -0500 Subject: [PATCH 086/178] Improve comments --- Sources/Basics/FileSystem/VFSOverlay.swift | 3 ++- Sources/Build/BuildPlan.swift | 26 +++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index 1c9e1c11a27..dc6132920bd 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -55,6 +55,7 @@ public struct VFSOverlay: Encodable { super.init(name: name, type: "directory") } + // TODO(ncooke3): Do we need to wrap this in `#if swift(>=5.4)`? public convenience init( name: String, @VFSOverlayBuilder contents: () -> [VFSOverlay.Resource] @@ -95,7 +96,7 @@ public struct VFSOverlay: Encodable { } } -// TOOD(ncooke3): Gate this? +// TODO(ncooke3): Do we need to wrap this in `#if swift(>=5.4)`? @resultBuilder public struct VFSOverlayBuilder { public static func buildBlock(_ components: [VFSOverlay.Resource]...) -> [VFSOverlay.Resource] { diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 6413ce2d890..bd4cb828b55 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1438,17 +1438,21 @@ public final class MixedTargetBuildDescription { // When the Swift compiler creates the generated interop header for // Objective-C compatible Swift API (via `-emit-objc-header`), any // Objective-C symbol that cannot be forward declared (e.g. superclass, - // protocol, etc.) will attempt to be imported via a module import of - // the module's umbrella header (e.g. `#import `). The - // Swift compiler assumes (1) the module has the structure of a - // framework and (2) that an umbrella header exists. However, SwiftPM - // does not build frameworks (breaking assumption #1) and an umbrella - // header may not exist (breaking assumption #2). Instead, to guarantee - // the generated interop header's import can be resolved, the package - // manager generates an umbrella header if needed and will later use a - // VFS overlay to make the generated header appear in the proper - // location so the problematic import in the generated interop header - // can be resolved during the build. + // protocol, etc.) will attempt to be imported via a bridging header. + // Unfortunately, a custom bridging header can not be specified because + // the target is evaluated as a framework target (as opposed to an app + // target), and framework target do not support bridging headers. So, + // the compiler defaults to importing the following in the generated + // interop header ($(ModuleName)-Swift.h): + // + // #import <$(ModuleName)/$(ModuleName).h> + // + // The compiler assumes that the above path can be resolved. Instead of + // forcing package authors to structure their packages around that + // constraint, the package manager generates an umbrella header if + // needed and will later use a VFS overlay to make the generated header + // appear in the proper location so the bridging header import in the + // generated interop header can be resolved during the build. var generatedUmbrellaHeaderPath: AbsolutePath? = nil let relativeUmbrellaHeaderPath = RelativePath("\(mixedTarget.c99name)/\(mixedTarget.c99name).h") From 8838f58a76ecfc2d3660f256de27c2b868de9ddb Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 11:37:06 -0500 Subject: [PATCH 087/178] Resolve TODO and rename test resource --- .../MixedTargets/BasicMixedTargets/Package.swift | 8 ++++---- .../CabinClass.h | 0 .../Engine.swift | 0 .../NewPlane.swift | 0 .../OldPlane.m | 10 +++++----- .../Pilot.m | 8 ++++---- .../PlanePart.m | 8 ++++---- .../BasicMixedTargetWithManualBridgingHeader.h} | 2 +- .../OldPlane.h | 0 .../Pilot.h | 0 .../PlanePart.h | 0 ...sicMixedTargetWithNestedUmbrellaHeaderTests.swift | 12 ++++++------ ...jcBasicMixedTargetWithNestedUmbrellaHeaderTests.m | 6 +++--- Tests/FunctionalTests/MixedTargetTests.swift | 9 +++------ 14 files changed, 30 insertions(+), 33 deletions(-) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader}/CabinClass.h (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader}/Engine.swift (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader}/NewPlane.swift (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader}/OldPlane.m (60%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader}/Pilot.m (56%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader}/PlanePart.m (56%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h => BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h} (64%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader}/OldPlane.h (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader}/Pilot.h (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader => BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader}/PlanePart.h (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Tests/{BasicMixedTargetWithNestedUmbrellaHeaderTests => BasicMixedTargetWithManualBridgingHeaderTests}/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift (51%) rename Fixtures/MixedTargets/BasicMixedTargets/Tests/{BasicMixedTargetWithNestedUmbrellaHeaderTests => BasicMixedTargetWithManualBridgingHeaderTests}/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m (68%) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 76d5b834e84..ff6e986b81e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -32,13 +32,13 @@ let package = Package( dependencies: ["BasicMixedTarget"] ), - // MARK: - BasicMixedTargetWithNestedUmbrellaHeader + // MARK: - BasicMixedTargetWithManualBridgingHeader .target( - name: "BasicMixedTargetWithNestedUmbrellaHeader" + name: "BasicMixedTargetWithManualBridgingHeader" ), .testTarget( - name: "BasicMixedTargetWithNestedUmbrellaHeaderTests", - dependencies: ["BasicMixedTargetWithNestedUmbrellaHeader"] + name: "BasicMixedTargetWithManualBridgingHeaderTests", + dependencies: ["BasicMixedTargetWithManualBridgingHeader"] ), // MARK: - BasicMixedTargetWithUmbrellaHeader diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/CabinClass.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/CabinClass.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/CabinClass.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/CabinClass.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Engine.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/NewPlane.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/NewPlane.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/NewPlane.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/NewPlane.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m similarity index 60% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.m rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m index 37486ce3cfd..eb61e9e523f 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m @@ -1,15 +1,15 @@ #import -// The below import syntax resolves to the `"BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h"` +// The below import syntax resolves to the `"BasicMixedTargetWithManualBridgingHeader/OldPlane.h"` // path within the public headers directory. It is not related to a // framework style import. -#import +#import // Alternatively, the above `OldPlane` can be imported via: -#import "include/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h" -#import "BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h" +#import "include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h" +#import "BasicMixedTargetWithManualBridgingHeader/OldPlane.h" // Import the Swift part of the module. -#import "BasicMixedTargetWithNestedUmbrellaHeader-Swift.h" +#import "BasicMixedTargetWithManualBridgingHeader-Swift.h" #import "CabinClass.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m similarity index 56% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m index 847d06e565f..71f34a9031e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m @@ -1,12 +1,12 @@ #import -// The below import syntax resolves to the `"BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h"` +// The below import syntax resolves to the `"BasicMixedTargetWithManualBridgingHeader/Pilot.h"` // path within the public headers directory. It is not related to a // framework style import. -#import +#import // Alternatively, the above `Pilot` can be imported via: -#import "include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h" -#import "BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h" +#import "include/BasicMixedTargetWithManualBridgingHeader/Pilot.h" +#import "BasicMixedTargetWithManualBridgingHeader/Pilot.h" @implementation Pilot @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m similarity index 56% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m index 0da54000a84..dac124120ff 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m @@ -1,12 +1,12 @@ #import -// The below import syntax resolves to the `"BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h"` +// The below import syntax resolves to the `"BasicMixedTargetWithManualBridgingHeader/PlanePart.h"` // path within the public headers directory. It is not related to a // framework style import. -#import +#import // Alternatively, the above `PlanePart` can be imported via: -#import "include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h" -#import "BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h" +#import "include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h" +#import "BasicMixedTargetWithManualBridgingHeader/PlanePart.h" @implementation PlanePart @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h similarity index 64% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h index 72aabf5e960..30eb5f50868 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/BasicMixedTargetWithNestedUmbrellaHeader.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h @@ -1,4 +1,4 @@ -// Note: Importing `#import ` is not supported +// Note: Importing `#import ` is not supported // within this directory. #import "PlanePart.h" #import "Pilot.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/OldPlane.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/Pilot.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/Pilot.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/Pilot.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithNestedUmbrellaHeader/include/BasicMixedTargetWithNestedUmbrellaHeader/PlanePart.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift similarity index 51% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift index c3d2d8d3786..2a1f04ee384 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift @@ -1,7 +1,7 @@ import XCTest -import BasicMixedTargetWithNestedUmbrellaHeader +import BasicMixedTargetWithManualBridgingHeader -final class BasicMixedTargetWithNestedUmbrellaHeaderTests: XCTestCase { +final class BasicMixedTargetWithManualBridgingHeaderTests: XCTestCase { func testPublicSwiftAPI() throws { // Check that Swift API surface is exposed... @@ -16,10 +16,10 @@ final class BasicMixedTargetWithNestedUmbrellaHeaderTests: XCTestCase { } func testModulePrefixingWorks() throws { - let _ = BasicMixedTargetWithNestedUmbrellaHeader.NewPlane() - let _ = BasicMixedTargetWithNestedUmbrellaHeader.Engine() - let _ = BasicMixedTargetWithNestedUmbrellaHeader.OldPlane() - let _ = BasicMixedTargetWithNestedUmbrellaHeader.Pilot() + let _ = BasicMixedTargetWithManualBridgingHeader.NewPlane() + let _ = BasicMixedTargetWithManualBridgingHeader.Engine() + let _ = BasicMixedTargetWithManualBridgingHeader.OldPlane() + let _ = BasicMixedTargetWithManualBridgingHeader.Pilot() } } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m similarity index 68% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m index e56728fa62d..4d21a07ad54 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithNestedUmbrellaHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m @@ -1,12 +1,12 @@ #import -@import BasicMixedTargetWithNestedUmbrellaHeader; +@import BasicMixedTargetWithManualBridgingHeader; -@interface ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests : XCTestCase +@interface ObjcBasicMixedTargetWithManualBridgingHeaderTests : XCTestCase @end -@implementation ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests +@implementation ObjcBasicMixedTargetWithManualBridgingHeaderTests - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index ba6c20d6903..db9cab26478 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -47,18 +47,15 @@ final class MixedTargetTests: XCTestCase { } } - // TODO(ncooke3): Remove below comment if `-import-objc-header` works. - // See `Fixtures/MixedTargets/BasicMixedTargets/Package.swift` for - // explanation of `BasicMixedTargetWithNestedUmbrellaHeader` target. - func testMixedTargetWithNestedUmbrellaHeader() throws { + func testMixedTargetWithManualBridgingHeader() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, - extraArgs: ["--target", "BasicMixedTargetWithNestedUmbrellaHeader"] + extraArgs: ["--target", "BasicMixedTargetWithManualBridgingHeader"] ) XCTAssertSwiftTest( fixturePath, - extraArgs: ["--filter", "BasicMixedTargetWithNestedUmbrellaHeaderTests"] + extraArgs: ["--filter", "BasicMixedTargetWithManualBridgingHeaderTests"] ) } } From 0eb90cbcd600e6f473dd326a918ca2079b71784b Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 15:50:13 -0500 Subject: [PATCH 088/178] Resolve TODO after investigation --- Tests/FunctionalTests/MixedTargetTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index db9cab26478..3203b0588df 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -14,7 +14,6 @@ import XCTest import SPMTestSupport // TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. -// TODO(ncooke3): Explore using different ways to import $(ModuleName)-Swift header. // MARK: - MixedTargetTests From 0ddd9376b77f27862272a522f990b93b62d837d7 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 16:10:06 -0500 Subject: [PATCH 089/178] Add extra imports to test --- .../MixedTargetWithCustomPaths/Sources/OldCar.m | 4 +++- .../BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m index d5f94f038bd..e5be86f7ffa 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m @@ -1,9 +1,11 @@ #import -// All three import statements should be supported. +// The below import statements should be supported. #import "MixedTargetWithCustomPaths/Sources/Public/OldCar.h" #import "OldCar.h" #import "Public/OldCar.h" +#import +#import // Import the Swift part of the module. #import "MixedTargetWithCustomPaths-Swift.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m index 87ebd2a4eeb..ecd82eb5ea7 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m @@ -1,7 +1,10 @@ #import +// The below import statements should be supported. #import "CarPart.h" #import "include/CarPart.h" +#import +#import @implementation CarPart -@end \ No newline at end of file +@end From de78c7a8f4c6c1f7e50a284667d5a26f84e8f887 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 16:13:57 -0500 Subject: [PATCH 090/178] Add newlines where needed --- .../Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m | 2 +- .../Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift | 2 +- .../Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift | 2 +- .../Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m | 2 +- .../Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h | 2 +- .../include/BasicMixedTargetWithUmbrellaHeader.h | 2 +- .../BasicMixedTargetWithUmbrellaHeader/include/Dessert.h | 2 +- .../Sources/ClangTargetDependsOnMixedTarget/JunkYard.m | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m index 73590b4c125..9bc34a981d2 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m @@ -11,4 +11,4 @@ - (Cookie*)cookieOfTheDay { return [[Cookie alloc] init]; } -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift index d2376f2db5e..3f763e40c63 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift @@ -2,4 +2,4 @@ import Foundation public struct Coffee { public init() {} -} \ No newline at end of file +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift index 0647bce18af..ac522961d17 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift @@ -1,3 +1,3 @@ import Foundation -@objc open class Cookie: Dessert {} \ No newline at end of file +@objc open class Cookie: Dessert {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m index 29f88198853..334f70338ef 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m @@ -4,4 +4,4 @@ #import "Dessert.h" @implementation Dessert -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h index 2a6646b29e9..90ebc14227e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h @@ -4,4 +4,4 @@ @interface Bakery : NSObject - (Cookie*)cookieOfTheDay; -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h index c4caf8f890d..1e831674929 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h @@ -1,2 +1,2 @@ #import "Bakery.h" -#import "Dessert.h" \ No newline at end of file +#import "Dessert.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h index f5099159eea..85bdc173623 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h @@ -1,4 +1,4 @@ #import @interface Dessert : NSObject -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m index 97eabe846ba..ba8771b6316 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m @@ -12,4 +12,4 @@ @interface JunkYard () @end @implementation JunkYard -@end \ No newline at end of file +@end From 6cb8601f1697c90770d161459790f87221adffea Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 16:53:36 -0500 Subject: [PATCH 091/178] Resolve TODO about VFSOverlay API --- Sources/Basics/FileSystem/VFSOverlay.swift | 6 ++++-- Sources/Build/BuildPlan.swift | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index dc6132920bd..1ea3d6cbb9e 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -55,13 +55,14 @@ public struct VFSOverlay: Encodable { super.init(name: name, type: "directory") } - // TODO(ncooke3): Do we need to wrap this in `#if swift(>=5.4)`? +#if swift(>=5.4) public convenience init( name: String, @VFSOverlayBuilder contents: () -> [VFSOverlay.Resource] ) { self.init(name: name, contents: contents()) } +#endif public override func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -96,7 +97,7 @@ public struct VFSOverlay: Encodable { } } -// TODO(ncooke3): Do we need to wrap this in `#if swift(>=5.4)`? +#if swift(>=5.4) @resultBuilder public struct VFSOverlayBuilder { public static func buildBlock(_ components: [VFSOverlay.Resource]...) -> [VFSOverlay.Resource] { @@ -115,3 +116,4 @@ public struct VFSOverlayBuilder { return components ?? [] } } +#endif diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index bd4cb828b55..b42c6cd7934 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1535,6 +1535,7 @@ public final class MixedTargetBuildDescription { self.moduleMap = customModuleMapPath self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) +#if swift(>=5.4) try VFSOverlay(roots: [ VFSOverlay.Directory(name: customModuleMapPath.parentDirectory.pathString) { VFSOverlay.File( @@ -1552,6 +1553,7 @@ public final class MixedTargetBuildDescription { } } ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) +#endif // When the mixed target does not have a custom module map, one will be // generated as a product for use by clients. @@ -1574,6 +1576,7 @@ public final class MixedTargetBuildDescription { // If an umbrella header was generated, it needs to be // overlayed within the public headers directory. self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) +#if swift(>=5.4) try VFSOverlay(roots: [ VFSOverlay.Directory(name: mixedTarget.clangTarget.includeDir.pathString) { VFSOverlay.Directory(name: mixedTarget.c99name) { @@ -1584,6 +1587,7 @@ public final class MixedTargetBuildDescription { } } ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) +#endif } else { // Else, no product overlay is needed. self.allProductHeadersOverlay = nil @@ -1640,6 +1644,7 @@ public final class MixedTargetBuildDescription { } let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) +#if swift(>=5.4) try VFSOverlay(roots: [ VFSOverlay.Directory(name: rootOverlayResourceDirectory.pathString) { // Redirect the `module.modulemap` to the modified @@ -1665,8 +1670,10 @@ public final class MixedTargetBuildDescription { } } ]).write(to: allProductHeadersPath, fileSystem: fileSystem) +#endif let unextendedModuleMapOverlayPath = intermediatesDirectory.appending(component: unextendedModuleOverlayFilename) +#if swift(>=5.4) try VFSOverlay(roots: [ VFSOverlay.Directory(name: rootOverlayResourceDirectory.pathString) { // Redirect the `module.modulemap` to the *unextended* @@ -1677,6 +1684,7 @@ public final class MixedTargetBuildDescription { ) } ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) +#endif // 4. Tie everything together by passing build flags. From b06aee5080fa3d5c79d280eb097b34af94e52ecd Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 17:00:09 -0500 Subject: [PATCH 092/178] Add TODO to discuss during review --- Sources/Basics/FileSystem/VFSOverlay.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index 1ea3d6cbb9e..4f90edae6c4 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -97,6 +97,7 @@ public struct VFSOverlay: Encodable { } } +// TODO(ncooke3): Is gating this API necessary? #if swift(>=5.4) @resultBuilder public struct VFSOverlayBuilder { From eb17874323f1759be8af45011d14f5bc5c157329 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 9 Jan 2023 17:48:00 -0500 Subject: [PATCH 093/178] Resolve TODO after experimenting --- Tests/FunctionalTests/MixedTargetTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 3203b0588df..6bac65d8eff 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -13,8 +13,6 @@ import XCTest import SPMTestSupport -// TODO(ncooke3): Explore using non-module import of mixed package in Objc Context. - // MARK: - MixedTargetTests final class MixedTargetTests: XCTestCase { From 15f35fb92298e1e73b840f6244b741eddfce9e2f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 10 Jan 2023 10:34:18 -0500 Subject: [PATCH 094/178] Convert variable to constant --- Sources/Build/BuildPlan.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index b42c6cd7934..191405347ed 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1453,7 +1453,7 @@ public final class MixedTargetBuildDescription { // needed and will later use a VFS overlay to make the generated header // appear in the proper location so the bridging header import in the // generated interop header can be resolved during the build. - var generatedUmbrellaHeaderPath: AbsolutePath? = nil + let generatedUmbrellaHeaderPath: AbsolutePath? let relativeUmbrellaHeaderPath = RelativePath("\(mixedTarget.c99name)/\(mixedTarget.c99name).h") let potentialUmbrellaHeaderPath = @@ -1483,6 +1483,8 @@ public final class MixedTargetBuildDescription { generatedUmbrellaHeaderPath!, bytes: stream.bytes ) + } else { + generatedUmbrellaHeaderPath = nil } // MARK: Generate products to be used by client of the target. From f09eb58da33e943f6105871a8a8e34b4a84a5ca4 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 10 Jan 2023 11:13:06 -0500 Subject: [PATCH 095/178] Add additional CXX tests --- .../BasicMixedTargets/Package.swift | 6 +++-- .../Factorial.swift | 6 +++++ ...TargetWithPublicCXXAPIViaHeaderImportTests | 26 +++++++++++++++++++ ...etWithPublicCXXAPIViaModuleImportTests.mm} | 8 ++++-- Tests/FunctionalTests/MixedTargetTests.swift | 6 ++++- 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests rename Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/{ObjcMixedTargetWithPublicCXXAPITests.mm => ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm} (56%) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index ff6e986b81e..fd29eb11c39 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -102,8 +102,10 @@ let package = Package( // In order to import this target into downstream targets, two // additional things must be done (depending on whether the target is // being imported into a Clang vs. Swift context): - // - Clang context: The downstream target must pass `-fcxx-modules` - // and `-fmodules` as unsafe flags in the target's `cSettings`. + // - Clang context: If the client wants to import the module, client + // must pass `-fcxx-modules` and `-fmodules` as unsafe flags in + // the target's `cSettings`. Else, the client can just import + // individual public headers without further configuring the target. // - Swift context: The mixed target needs to make a custom module // map that only exposes public CXX headers in a non-Swift context. // diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift index d8cdf5c3726..e23f0a7f93b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift @@ -1,5 +1,11 @@ import Foundation +@objc public class Factorial: NSObject { + @objc public static func text() -> String { + return "Hello, World!" + } +} + public func factorial(_ x: Int32) -> Int { return ObjcCalculator.factorial(for: x) } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests new file mode 100644 index 00000000000..3a5ff49fcc2 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests @@ -0,0 +1,26 @@ +#import + +#import "CXXSumFinder.hpp" +#import "ObjcCalculator.h" +#import "MixedTargetWithPublicCXXAPI-Swift.h" + +@interface ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests : XCTestCase +@end + +@implementation ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests + +- (void)testPublicObjcAPI { + XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); + XCTAssertEqual([ObjcCalculator sumX:1 andY:2], 3); +} + +- (void)testPublicSwiftAPI { + XCTAssertEqualObjects([Factorial text], @"Hello, World!"); +} + +- (void)testPublicCXXAPI { + CXXSumFinder sf; + XCTAssertEqual(sf.sum(1,2), 3); +} + +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm similarity index 56% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm index d1c36971534..b9dcfdd49fb 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITests.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm @@ -2,16 +2,20 @@ @import MixedTargetWithPublicCXXAPI; -@interface ObjcMixedTargetWithPublicCXXAPITests : XCTestCase +@interface ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests : XCTestCase @end -@implementation ObjcMixedTargetWithPublicCXXAPITests +@implementation ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests - (void)testPublicObjcAPI { XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); XCTAssertEqual([ObjcCalculator sumX:1 andY:2], 3); } +- (void)testPublicSwiftAPI { + XCTAssertEqualObjects([Factorial text], @"Hello, World!"); +} + - (void)testPublicCXXAPI { CXXSumFinder sf; XCTAssertEqual(sf.sum(1,2), 3); diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 6bac65d8eff..d9c25cdc0ff 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -150,7 +150,11 @@ final class MixedTargetTests: XCTestCase { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, - extraArgs: ["--target", "MixedTargetWithPublicCXXAPITests"] + extraArgs: ["--target", "MixedTargetWithPublicCXXAPI"] + ) + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithPublicCXXAPITests"] ) } } From b7a66de5728e7d0e9cf2caa9cbefac108b4726fb Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 13 Jan 2023 16:47:12 -0500 Subject: [PATCH 096/178] Fix bug, add test resources, add TODOs - A few commits ago, the public headers were added to the header search paths (HSP) of clients. This meant Clang clients could import public headers. One bug fixed in this commit was that the generated interop header needed to be added to the HSP. This was done by adding the generated interop header to the Product all-product-headers.yaml overlay file. - A few tests were added/modified to appropriately test that Clang clients could import all public headers (including generated ones) of a mixed test target. - A few TODOs related to detecting C++ files were added to be discussed during PR review. - One TODO was added to track a remaining bug: This bug is a warning that comes from the generated bridging header being overlayed within the public headers directory. Clang will sometimes complain that the target's umbrella header (if one exists) does not include the generated bridging header. --- .../BasicMixedTargets/Package.swift | 4 ++ .../MixedTargetWithCustomModuleMap/Driver.m | 2 +- .../Engine.swift | 2 +- .../MixedTargetWithCustomModuleMap/Machine.m | 8 ++++ .../NewCar.swift | 6 ++- .../MixedTargetWithCustomModuleMap/OldCar.m | 2 +- .../include/Driver.h | 3 +- .../include/Machine.h | 6 +++ .../include/MixedTarget.h | 1 + .../include/OldCar.h | 5 ++- ...edTargetWithCustomModuleMapAndResources.h} | 0 .../include/module.modulemap | 2 +- ...jcBasicMixedTargetTestsViaBridgingHeader.m | 24 ++++++++++++ ...bjcBasicMixedTargetTestsViaHeaderImport.m} | 4 +- ...ObjcBasicMixedTargetTestsViaModuleImport.m | 26 +++++++++++++ ...TargetWithManualBridgingHeaderTests.swift} | 0 ...ManualBridgingHeaderTestsViaHeaderImport.m | 27 +++++++++++++ ...anualBridgingHeaderTestsViaModuleImport.m} | 5 ++- .../MixedTargetWithCustomModuleMapTests.swift | 27 +++++++++++++ ...ModuleMapTestsViaGeneratedBridgingHeader.m | 23 +++++++++++ ...tWithCustomModuleMapTestsViaHeaderImport.m | 26 +++++++++++++ ...tWithCustomModuleMapTestsViaModuleImport.m | 22 +++++++++++ ...etWithPublicCXXAPITestsViaHeaderImport.mm} | 4 +- ...etWithPublicCXXAPITestsViaModuleImport.mm} | 4 +- Sources/Build/BuildPlan.swift | 38 +++++++++++++------ Tests/FunctionalTests/MixedTargetTests.swift | 5 +++ 26 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Machine.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Machine.h rename Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/{MixedTarget.h => MixedTargetWithCustomModuleMapAndResources.h} (100%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m rename Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/{ObjcBasicMixedTargetTests.m => ObjcBasicMixedTargetTestsViaHeaderImport.m} (78%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m rename Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/{BasicMixedTargetWithNestedUmbrellaHeaderTests.swift => BasicMixedTargetWithManualBridgingHeaderTests.swift} (100%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m rename Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/{ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m => ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m} (76%) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m rename Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/{ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests => ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm} (78%) rename Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/{ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm => ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm} (76%) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index fd29eb11c39..c5146e3ebee 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -66,6 +66,10 @@ let package = Package( .target( name: "MixedTargetWithCustomModuleMap" ), + .testTarget( + name: "MixedTargetWithCustomModuleMapTests", + dependencies: ["MixedTargetWithCustomModuleMap"] + ), // MARK: - MixedTargetWithInvalidCustomModuleMap .target( diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m index 5870c8e4c6d..3de8905ffa2 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m @@ -4,5 +4,5 @@ #import "Driver.h" #import "include/Driver.h" -@implementation Driver +@implementation MyDriver @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift index da1ee757331..93bc3a224da 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift @@ -1,4 +1,4 @@ import Foundation // This type is Objective-C compatible and used in `OldCar`. -@objc public class Engine: NSObject {} +@objc public class Engine: MyMachine {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Machine.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Machine.m new file mode 100644 index 00000000000..1bbd6928ffd --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Machine.m @@ -0,0 +1,8 @@ +#import + +// Both import statements should be supported. +#import "Machine.h" +#import "include/Machine.h" + +@implementation MyMachine +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift index 3f5907ea423..2bccb0068fc 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift @@ -3,6 +3,8 @@ import Foundation public class NewCar { // `Engine` is defined in Swift. var engine: Engine? = nil - // `Driver` is defined in Objective-C. - var driver: Driver? = nil + // `MyDriver` is defined in Objective-C. + var driver: MyDriver? = nil + + public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m index f1496e613a6..9eb7a363945 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m @@ -7,5 +7,5 @@ // Import the Swift part of the module. #import "MixedTargetWithCustomModuleMap-Swift.h" -@implementation OldCar +@implementation MyOldCar @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h index 8326bf2f179..2fbaa124098 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h @@ -1,6 +1,7 @@ #import // This type is Swift compatible and used in `NewCar`. -@interface Driver : NSObject +// `My` prefix is to avoid naming collision in test bundle. +@interface MyDriver : NSObject @property(nonnull) NSString* name; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Machine.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Machine.h new file mode 100644 index 00000000000..48a724bff9e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Machine.h @@ -0,0 +1,6 @@ +#import + +// This type is subclassed by the Swift type `Engine`. +// `My` prefix is to avoid naming collision in test bundle. +@interface MyMachine : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h index cbe8b7ebef6..7909c0ae8e3 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h @@ -1,2 +1,3 @@ +#import "Machine.h" #import "OldCar.h" #import "Driver.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h index 995e09f0ff9..4ccd96616fc 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h @@ -6,9 +6,10 @@ // must be forward declared in headers. @class Engine; -@interface OldCar : NSObject +// `My` prefix is to avoid naming collision in test bundle. +@interface MyOldCar : NSObject // `Engine` is defined in Swift. @property(nullable) Engine *engine; // `Driver` is defined in Objective-C. -@property(nullable) Driver *driver; +@property(nullable) MyDriver *driver; @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTarget.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTarget.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap index 5b8abc6c387..3b68fe493c8 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap @@ -1,4 +1,4 @@ module MixedTargetWithCustomModuleMapAndResources { - umbrella header "MixedTarget.h" + umbrella header "MixedTargetWithCustomModuleMapAndResources.h" export * } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m new file mode 100644 index 00000000000..bdd9da28f8b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m @@ -0,0 +1,24 @@ +#import + +#import "BasicMixedTarget-Swift.h" +#import "BasicMixedTarget/BasicMixedTarget.h" + +@interface ObjcBasicMixedTargetTestsViaBridgingHeader : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetTestsViaBridgingHeader + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + OldCar *oldCar = [[OldCar alloc] init]; + Driver *driver = [[Driver alloc] init]; + CarPart *carPart = [[CarPart alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m similarity index 78% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m index c29f9730240..36738c8bb5a 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m @@ -2,11 +2,11 @@ @import BasicMixedTarget; -@interface ObjcBasicMixedTargetTests : XCTestCase +@interface ObjcBasicMixedTargetTestsViaHeaderImport : XCTestCase @end -@implementation ObjcBasicMixedTargetTests +@implementation ObjcBasicMixedTargetTestsViaHeaderImport - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m new file mode 100644 index 00000000000..ad484324347 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m @@ -0,0 +1,26 @@ +#import + +#import "CarPart.h" +#import "Driver.h" +#import "OldCar.h" +#import "BasicMixedTarget-Swift.h" + +@interface ObjcBasicMixedTargetTestsViaModuleImport : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetTestsViaModuleImport + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + OldCar *oldCar = [[OldCar alloc] init]; + Driver *driver = [[Driver alloc] init]; + CarPart *carPart = [[CarPart alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithManualBridgingHeaderTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithNestedUmbrellaHeaderTests.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithManualBridgingHeaderTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m new file mode 100644 index 00000000000..14ce94e63b6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m @@ -0,0 +1,27 @@ +#import + +// Import the target via header imports. +#import +#import +#import +#import +#import + +@interface ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + OldPlane *oldPlane = [[OldPlane alloc] init]; + Pilot *pilot = [[Pilot alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m similarity index 76% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m index 4d21a07ad54..61f8bd7d34b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithNestedUmbrellaHeaderTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m @@ -1,12 +1,13 @@ #import +// Import the target via a module import. @import BasicMixedTargetWithManualBridgingHeader; -@interface ObjcBasicMixedTargetWithManualBridgingHeaderTests : XCTestCase +@interface ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport : XCTestCase @end -@implementation ObjcBasicMixedTargetWithManualBridgingHeaderTests +@implementation ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift new file mode 100644 index 00000000000..e9dcb12be3c --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift @@ -0,0 +1,27 @@ +import XCTest +import MixedTargetWithCustomModuleMap + +final class MixedTargetWithCustomModuleMapTests: XCTestCase { + + func testPublicSwiftAPI() throws { + // Check that Swift API surface is exposed... + let _ = NewCar() + let _ = Engine() + } + + func testPublicObjcAPI() throws { + // Check that Objective-C API surface is exposed... + let _ = MyOldCar() + let _ = MyDriver() + let _ = MyMachine() + } + + func testModulePrefixingWorks() throws { + let _ = MixedTargetWithCustomModuleMap.MyMachine() + let _ = MixedTargetWithCustomModuleMap.NewCar() + let _ = MixedTargetWithCustomModuleMap.Engine() + let _ = MixedTargetWithCustomModuleMap.MyOldCar() + let _ = MixedTargetWithCustomModuleMap.MyDriver() + } + +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m new file mode 100644 index 00000000000..01cd2786d51 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m @@ -0,0 +1,23 @@ +#import + +#import "MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h" +#import "MixedTargetWithCustomModuleMap-Swift.h" + +@interface ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader : XCTestCase +@end + +@implementation ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + MyMachine *machine = [[MyMachine alloc] init]; + MyOldCar *oldCar = [[MyOldCar alloc] init]; + MyDriver *driver = [[MyDriver alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m new file mode 100644 index 00000000000..b77ee46f2e4 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m @@ -0,0 +1,26 @@ +#import + +#import "Machine.h" +#import "MixedTarget.h" +#import "Driver.h" +#import "OldCar.h" +#import "MixedTargetWithCustomModuleMap-Swift.h" + +@interface ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport : XCTestCase +@end + +@implementation ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + MyMachine *machine = [[MyMachine alloc] init]; + MyOldCar *oldCar = [[MyOldCar alloc] init]; + MyDriver *driver = [[MyDriver alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m new file mode 100644 index 00000000000..4666c1b8efe --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m @@ -0,0 +1,22 @@ +#import + +@import MixedTargetWithCustomModuleMap; + +@interface ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport : XCTestCase +@end + +@implementation ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + MyMachine *machine = [[MyMachine alloc] init]; + MyOldCar *oldCar = [[MyOldCar alloc] init]; + MyDriver *driver = [[MyDriver alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm similarity index 78% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm index 3a5ff49fcc2..96e42520cce 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaHeaderImportTests +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm @@ -4,10 +4,10 @@ #import "ObjcCalculator.h" #import "MixedTargetWithPublicCXXAPI-Swift.h" -@interface ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests : XCTestCase +@interface ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport : XCTestCase @end -@implementation ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests +@implementation ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport - (void)testPublicObjcAPI { XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm similarity index 76% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm index b9dcfdd49fb..4a34d4cc752 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm @@ -2,10 +2,10 @@ @import MixedTargetWithPublicCXXAPI; -@interface ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests : XCTestCase +@interface ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport : XCTestCase @end -@implementation ObjcMixedTargetWithPublicCXXAPIViaModuleImportTests +@implementation ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport - (void)testPublicObjcAPI { XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 191405347ed..de38f7a308e 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1338,6 +1338,7 @@ public final class MixedTargetBuildDescription { /// The path to the VFS overlay file that overlays the public headers of /// the Clang part of the target over the target's build directory. + // TODO(ncooke3): Review to see if it should be non-optional. let allProductHeadersOverlay: AbsolutePath? /// The modulemap file for this target. @@ -1472,6 +1473,7 @@ public final class MixedTargetBuildDescription { // include public headers so all other can be filtered out. .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } // Filter out non-Objective-C/C headers. + // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? .filter { $0.basename.hasSuffix(".h") } // Add each remaining header to the generated umbrella header. .forEach { @@ -1545,6 +1547,17 @@ public final class MixedTargetBuildDescription { externalContents: productModuleMapPath.pathString ) + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + + // TODO(ncooke3): Unfortunately, this can trigger an + // umbrella header warning that the umbrella header does + // not include the header in this directory. So this + // overlay needs to be pulled out of the public header + // path and placed elsewhere. This also means there will be + // two public header search paths. if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { VFSOverlay.Directory(name: mixedTarget.c99name) { VFSOverlay.File( @@ -1573,14 +1586,19 @@ public final class MixedTargetBuildDescription { // Set the generated module map as the module map for the target. self.moduleMap = productModuleMapPath + self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) - if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { - // If an umbrella header was generated, it needs to be - // overlayed within the public headers directory. - self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) #if swift(>=5.4) - try VFSOverlay(roots: [ - VFSOverlay.Directory(name: mixedTarget.clangTarget.includeDir.pathString) { + try VFSOverlay(roots: [ + VFSOverlay.Directory(name: mixedTarget.clangTarget.includeDir.pathString) { + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + + // If an umbrella header was generated, it needs to be + // overlayed within the public headers directory. + if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { VFSOverlay.Directory(name: mixedTarget.c99name) { VFSOverlay.File( name: generatedUmbrellaHeaderPath.basename, @@ -1588,12 +1606,9 @@ public final class MixedTargetBuildDescription { ) } } - ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) + } + ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) #endif - } else { - // Else, no product overlay is needed. - self.allProductHeadersOverlay = nil - } } // MARK: Generate intermediate artifacts used to build the target. @@ -1618,6 +1633,7 @@ public final class MixedTargetBuildDescription { // Generating module maps that include non-Objective-C headers is not // supported. // FIXME(ncooke3): Link to evolution post. + // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? let nonObjcHeaders: [AbsolutePath] = mixedTarget.clangTarget.headers .filter { $0.extension != "h" } try moduleMapGenerator.generateModuleMap( diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index d9c25cdc0ff..7ca8758ba3b 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -72,6 +72,11 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: ["--target", "MixedTargetWithCustomModuleMap"] ) + + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCustomModuleMapTests"] + ) } } From 95b24506a05007914a7293927d4619740fa20cdc Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 13 Jan 2023 23:28:46 -0500 Subject: [PATCH 097/178] Fix warning bug from previous commit description - This commit fixes the warning described in the previous commit. - Basically, instead of overlaying the generated umbrella header in the public headers directory, it is placed in a new directory within the build directory. The benefit here is that _a mixed target with a custom module map that includes an umbrella header_ will not cause a warning that the umbrella header does not include the generated umbrella header. - Updated the tests the test this behavior. - One weird issue that arose is the umbrella header does not work as expected when imported into a client. Despite being in the HSP and containing absolute paths to each public header, it will throw a weird error like such: ``` error: declaration of 'Driver' must be imported from module 'BasicMixedTarget' before it is required @property(nullable) Driver *driver; ^ /Users/nickcooke/Developer/swift-package-manager/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h:4:12: note: declaration here is not visible @interface Driver : NSObject ``` - The next step is to figure out if I can exclude the generated umbrella's HSP when compiling individual clang files... - ALTERNATIVE APPROACH: An alternative approach that might be better is to parse the custom module map and add `header $(ModuleName)/$(ModuleName).h` --- .../JunkYard.m | 2 + .../RecyclingCenter.h | 4 + .../RecyclingCenter.m | 25 +++ .../MixedTargetDependsOnMixedTarget/OldBoat.m | 1 + .../SpeedBoat.h | 4 + .../SpeedBoat.m | 25 +++ ...jcBasicMixedTargetTestsViaBridgingHeader.m | 24 --- Sources/Build/BuildPlan.swift | 163 +++++++++--------- 8 files changed, 144 insertions(+), 104 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m index ba8771b6316..86fdb76bef1 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m @@ -2,6 +2,7 @@ #import "include/JunkYard.h" +// Import the mixed target's module. @import BasicMixedTarget; @interface JunkYard () @@ -9,6 +10,7 @@ @interface JunkYard () @property(nullable) Engine *engine; @property(nullable) Driver *driver; @property(nullable) OldCar *oldCar; +@property(nullable) CarPart *carPart; @end @implementation JunkYard diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h new file mode 100644 index 00000000000..f616cef1144 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h @@ -0,0 +1,4 @@ +#import + +@interface RecyclingCenter : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m new file mode 100644 index 00000000000..9a0ff86e15f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m @@ -0,0 +1,25 @@ +#import + +#import "RecyclingCenter.h" + +// Import the mixed target's public headers. Both "..." and <...> style imports +// should resolve. +#import "CarPart.h" +#import +#import "Driver.h" +#import +#import "OldCar.h" +#import +#import "BasicMixedTarget-Swift.h" +#import + +@interface RecyclingCenter () +// The below types come from the `BasicMixedTarget` module. +@property(nullable) Engine *engine; +@property(nullable) Driver *driver; +@property(nullable) OldCar *oldCar; +@property(nullable) CarPart *carPart; +@end + +@implementation RecyclingCenter +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m index 8aa647e0a7f..b6aefd10139 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m @@ -2,6 +2,7 @@ #import "include/OldBoat.h" +// Import the mixed target's module. @import BasicMixedTarget; @interface OldBoat () diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h new file mode 100644 index 00000000000..25feaedf3e7 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h @@ -0,0 +1,4 @@ +#import + +@interface SpeedBoat : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m new file mode 100644 index 00000000000..fe9775a6fb1 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m @@ -0,0 +1,25 @@ +#import + +#import "SpeedBoat.h" +#import + +// Import the mixed target's public headers. Both "..." and <...> style imports +// should resolve. +#import "CarPart.h" +#import +#import "Driver.h" +#import +#import "OldCar.h" +#import +#import "BasicMixedTarget-Swift.h" +#import + +@interface SpeedBoat () + +// The below types comes from the `BasicMixedTarget` module`. +@property(nonatomic, strong) Engine *engine; +@property(nonatomic, strong) Driver *driver; +@end + +@implementation SpeedBoat +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m deleted file mode 100644 index bdd9da28f8b..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaBridgingHeader.m +++ /dev/null @@ -1,24 +0,0 @@ -#import - -#import "BasicMixedTarget-Swift.h" -#import "BasicMixedTarget/BasicMixedTarget.h" - -@interface ObjcBasicMixedTargetTestsViaBridgingHeader : XCTestCase - -@end - -@implementation ObjcBasicMixedTargetTestsViaBridgingHeader - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - OldCar *oldCar = [[OldCar alloc] init]; - Driver *driver = [[Driver alloc] init]; - CarPart *carPart = [[CarPart alloc] init]; -} - -@end diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index de38f7a308e..9db1d15a129 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1338,8 +1338,10 @@ public final class MixedTargetBuildDescription { /// The path to the VFS overlay file that overlays the public headers of /// the Clang part of the target over the target's build directory. - // TODO(ncooke3): Review to see if it should be non-optional. - let allProductHeadersOverlay: AbsolutePath? + let allProductHeadersOverlay: AbsolutePath + + /// The paths to the targets's public headers. + let publicHeaderPaths: [AbsolutePath] /// The modulemap file for this target. let moduleMap: AbsolutePath @@ -1413,13 +1415,16 @@ public final class MixedTargetBuildDescription { let interopHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath - // A mixed target's build directory uses two subdirectories to + // A mixed target's build directory uses three subdirectories to // distinguish between build artifacts: // - Intermediates: Stores artifacts used during the target's build. // - Product: Stores artifacts used by clients of the target. + // - InteropSupport: If needed, stores a generated umbrella header + // for use during the target's build and by clients of the target. let tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") let intermediatesDirectory = tempsPath.appending(component: "Intermediates") let productDirectory = tempsPath.appending(component: "Product") + let interopSupportDirectory: AbsolutePath? // Filenames for VFS overlay files. let allProductHeadersFilename = "all-product-headers.yaml" @@ -1434,33 +1439,35 @@ public final class MixedTargetBuildDescription { fileSystem: fileSystem ) - // MARK: Conditionally generate an umbrella header - - // When the Swift compiler creates the generated interop header for - // Objective-C compatible Swift API (via `-emit-objc-header`), any - // Objective-C symbol that cannot be forward declared (e.g. superclass, - // protocol, etc.) will attempt to be imported via a bridging header. - // Unfortunately, a custom bridging header can not be specified because - // the target is evaluated as a framework target (as opposed to an app - // target), and framework target do not support bridging headers. So, - // the compiler defaults to importing the following in the generated - // interop header ($(ModuleName)-Swift.h): + // MARK: Conditionally generate an umbrella header for interoptability + + // When the Swift compiler creates the generated interop header + // (`$(ModuleName)-Swift.h`) for Objective-C compatible Swift API + // (via `-emit-objc-header`), any Objective-C symbol that cannot be + // forward declared (e.g. superclass, protocol, etc.) will attempt to + // be imported via a bridging or umbrella header. Since the compiler + // evaluates the target as a framework (as opposed to an app), the + // compiler assumes* an umbrella header exists in a subdirectory (named + // after the module) within the public headers directory: // // #import <$(ModuleName)/$(ModuleName).h> // - // The compiler assumes that the above path can be resolved. Instead of - // forcing package authors to structure their packages around that - // constraint, the package manager generates an umbrella header if - // needed and will later use a VFS overlay to make the generated header - // appear in the proper location so the bridging header import in the - // generated interop header can be resolved during the build. - let generatedUmbrellaHeaderPath: AbsolutePath? - let relativeUmbrellaHeaderPath = - RelativePath("\(mixedTarget.c99name)/\(mixedTarget.c99name).h") - let potentialUmbrellaHeaderPath = - mixedTarget.clangTarget.includeDir.appending(relativeUmbrellaHeaderPath) - if !fileSystem.isFile(potentialUmbrellaHeaderPath) { - generatedUmbrellaHeaderPath = tempsPath.appending(component: "\(mixedTarget.c99name).h") + // The compiler assumes that the above path can be resolved relative to + // the public header directory. Instead of forcing package authors to + // structure their packages around that constraint, the package manager + // generates an umbrella header if needed and will pass it along as a + // header search path when building the target. + // + // *: https://developer.apple.com/documentation/swift/importing-objective-c-into-swift + let umbrellaHeaderPathComponents = [mixedTarget.c99name, "\(mixedTarget.c99name).h"] + let potentialUmbrellaHeadersPath = mixedTarget.clangTarget.includeDir + .appending(components: umbrellaHeaderPathComponents) + // Check if an umbrella header at + // `PUBLIC_HDRS_DIR/$(ModuleName)/$(ModuleName).h` already exists. + if !fileSystem.isFile(potentialUmbrellaHeadersPath) { + interopSupportDirectory = tempsPath.appending(component: "InteropSupport") + let generatedUmbrellaHeaderPath = interopSupportDirectory! + .appending(components: umbrellaHeaderPathComponents) // Populate a stream that will become the generated umbrella header. let stream = BufferedOutputByteStream() mixedTarget.clangTarget.headers @@ -1482,13 +1489,25 @@ public final class MixedTargetBuildDescription { } try fileSystem.writeFileContentsIfNeeded( - generatedUmbrellaHeaderPath!, + generatedUmbrellaHeaderPath, bytes: stream.bytes ) } else { - generatedUmbrellaHeaderPath = nil + // An umbrella header in the desired format already exists so the + // interop support directory is not needed. + interopSupportDirectory = nil } + // TODO(ncooke3): Is there a way to remove the generated umbrella + // header when compiling individual clang files? This will reduce the + // misuse of importing the generated umbrella header. + + // Clients will later depend on the public header directory, and, if an + // umbrella header was created, the header's root directory. + self.publicHeaderPaths = interopSupportDirectory != nil ? + [mixedTarget.clangTarget.includeDir, interopSupportDirectory!] : + [mixedTarget.clangTarget.includeDir] + // MARK: Generate products to be used by client of the target. // Path to the module map used by clients to access the mixed target's @@ -1539,6 +1558,7 @@ public final class MixedTargetBuildDescription { self.moduleMap = customModuleMapPath self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) +// TODO(ncooke3): Probably can remove the builder pattern now. #if swift(>=5.4) try VFSOverlay(roots: [ VFSOverlay.Directory(name: customModuleMapPath.parentDirectory.pathString) { @@ -1551,23 +1571,8 @@ public final class MixedTargetBuildDescription { name: interopHeaderPath.basename, externalContents: interopHeaderPath.pathString ) - - // TODO(ncooke3): Unfortunately, this can trigger an - // umbrella header warning that the umbrella header does - // not include the header in this directory. So this - // overlay needs to be pulled out of the public header - // path and placed elsewhere. This also means there will be - // two public header search paths. - if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { - VFSOverlay.Directory(name: mixedTarget.c99name) { - VFSOverlay.File( - name: generatedUmbrellaHeaderPath.basename, - externalContents: generatedUmbrellaHeaderPath.pathString - ) - } - } } - ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) + ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) #endif // When the mixed target does not have a custom module map, one will be @@ -1595,19 +1600,8 @@ public final class MixedTargetBuildDescription { name: interopHeaderPath.basename, externalContents: interopHeaderPath.pathString ) - - // If an umbrella header was generated, it needs to be - // overlayed within the public headers directory. - if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { - VFSOverlay.Directory(name: mixedTarget.c99name) { - VFSOverlay.File( - name: generatedUmbrellaHeaderPath.basename, - externalContents: generatedUmbrellaHeaderPath.pathString - ) - } - } } - ]).write(to: self.allProductHeadersOverlay!, fileSystem: fileSystem) + ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) #endif } @@ -1677,15 +1671,6 @@ public final class MixedTargetBuildDescription { name: interopHeaderPath.basename, externalContents: interopHeaderPath.pathString ) - - if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath { - VFSOverlay.Directory(name: mixedTarget.c99name) { - VFSOverlay.File( - name: generatedUmbrellaHeaderPath.basename, - externalContents: generatedUmbrellaHeaderPath.pathString - ) - } - } } ]).write(to: allProductHeadersPath, fileSystem: fileSystem) #endif @@ -1737,6 +1722,18 @@ public final class MixedTargetBuildDescription { // generated header can be imported. "-I", intermediatesDirectory.pathString ] + + // If a generated umbrella header was created, add it's root directory + // as a header search path. This will resolve its import within the + // generated interop header. + if let interopSupportDirectory = interopSupportDirectory { + swiftTargetBuildDescription.appendClangFlags( + "-I", interopSupportDirectory.pathString + ) + clangTargetBuildDescription.additionalFlags += [ + "-I", interopSupportDirectory.pathString + ] + } } } @@ -2823,20 +2820,22 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } } case let target as MixedTarget where target.type == .library: - // Add the public headers of the dependency. - clangTarget.additionalFlags += ["-I", target.clangTarget.includeDir.pathString] - // Add the modulemap of the dependency. if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { + // Add the dependency's modulemap. clangTarget.additionalFlags.append( "-fmodule-map-file=\(dependencyTargetDescription.moduleMap.pathString)" ) - if let allProductHeadersOverlay = dependencyTargetDescription.allProductHeadersOverlay { - clangTarget.additionalFlags += [ - "-ivfsoverlay", allProductHeadersOverlay.pathString - ] + // Add the dependency's public headers. + dependencyTargetDescription.publicHeaderPaths.forEach { + clangTarget.additionalFlags += [ "-I", $0.pathString ] } + + // Add the dependency's public VFS overlay. + clangTarget.additionalFlags += [ + "-ivfsoverlay", dependencyTargetDescription.allProductHeadersOverlay.pathString + ] } case let target as SystemLibraryTarget: clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"] @@ -2892,18 +2891,22 @@ public class BuildPlan: SPMBuildCore.BuildPlan { guard case let .mixed(target)? = targetMap[dependency] else { throw InternalError("unexpected mixed target \(underlyingTarget)") } - // Add the dependency's modulemap and public headers. + + // Add the dependency's modulemap. swiftTarget.appendClangFlags( - "-fmodule-map-file=\(target.moduleMap.pathString)", - "-I", target.clangTargetBuildDescription.clangTarget.includeDir.pathString + "-fmodule-map-file=\(target.moduleMap.pathString)" ) - if let allProductHeadersOverlay = target.allProductHeadersOverlay { - swiftTarget.appendClangFlags( - "-ivfsoverlay", allProductHeadersOverlay.pathString - ) + // Add the dependency's public headers. + target.publicHeaderPaths.forEach { + swiftTarget.appendClangFlags("-I", $0.pathString) } + // Add the dependency's public VFS overlay. + swiftTarget.appendClangFlags( + "-ivfsoverlay", target.allProductHeadersOverlay.pathString + ) + default: break } From 268f1a9ade6688e77edc061883b016e5e837275d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 14 Jan 2023 12:24:05 -0500 Subject: [PATCH 098/178] Discovered workaround to previous commit's issue - See the last two commit messages for more context. - It seems that one can import the generated bridging header and if it is imported, it must be imported before the generated Swift header. --- ...xedTargetTestsViaGeneratedBridgingHeader.m | 26 +++++++++++++++++++ ...ModuleMapTestsViaGeneratedBridgingHeader.m | 2 ++ Sources/Build/BuildPlan.swift | 4 --- 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m new file mode 100644 index 00000000000..93a8db46a91 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m @@ -0,0 +1,26 @@ +#import + +// IMPORTANT: The generated "bridging" header must be imported before the +// generated interop header. +#import "BasicMixedTarget/BasicMixedTarget.h" +#import "BasicMixedTarget-Swift.h" + +@interface ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader : XCTestCase + +@end + +@implementation ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader + +- (void)testPublicSwiftAPI { + // Check that Objective-C compatible Swift API surface is exposed... + Engine *engine = [[Engine alloc] init]; +} + +- (void)testPublicObjcAPI { + // Check that Objective-C API surface is exposed... + OldCar *oldCar = [[OldCar alloc] init]; + Driver *driver = [[Driver alloc] init]; + CarPart *carPart = [[CarPart alloc] init]; +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m index 01cd2786d51..f49d5f3f0ec 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m @@ -1,5 +1,7 @@ #import +// IMPORTANT: The generated "bridging" header must be imported before the +// generated interop header. #import "MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h" #import "MixedTargetWithCustomModuleMap-Swift.h" diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 9db1d15a129..535ca0d4524 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1498,10 +1498,6 @@ public final class MixedTargetBuildDescription { interopSupportDirectory = nil } - // TODO(ncooke3): Is there a way to remove the generated umbrella - // header when compiling individual clang files? This will reduce the - // misuse of importing the generated umbrella header. - // Clients will later depend on the public header directory, and, if an // umbrella header was created, the header's root directory. self.publicHeaderPaths = interopSupportDirectory != nil ? From 358bffc77f3c1cd3031eb9556f60a0192db4e32e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 14 Jan 2023 12:48:19 -0500 Subject: [PATCH 099/178] Remove VFSOverlayBuilder type as it is no longer needed --- Sources/Basics/FileSystem/VFSOverlay.swift | 26 +---- Sources/Build/BuildPlan.swift | 106 +++++++++++---------- 2 files changed, 58 insertions(+), 74 deletions(-) diff --git a/Sources/Basics/FileSystem/VFSOverlay.swift b/Sources/Basics/FileSystem/VFSOverlay.swift index 4f90edae6c4..83541da0715 100644 --- a/Sources/Basics/FileSystem/VFSOverlay.swift +++ b/Sources/Basics/FileSystem/VFSOverlay.swift @@ -55,14 +55,12 @@ public struct VFSOverlay: Encodable { super.init(name: name, type: "directory") } -#if swift(>=5.4) public convenience init( name: String, - @VFSOverlayBuilder contents: () -> [VFSOverlay.Resource] + contents: () -> [VFSOverlay.Resource] ) { self.init(name: name, contents: contents()) } -#endif public override func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -96,25 +94,3 @@ public struct VFSOverlay: Encodable { try JSONEncoder.makeWithDefaults(prettified: false).encode(path: path, fileSystem: fileSystem, self) } } - -// TODO(ncooke3): Is gating this API necessary? -#if swift(>=5.4) -@resultBuilder -public struct VFSOverlayBuilder { - public static func buildBlock(_ components: [VFSOverlay.Resource]...) -> [VFSOverlay.Resource] { - return components.flatMap { $0 } - } - - public static func buildExpression(_ expression: VFSOverlay.Resource) -> [VFSOverlay.Resource] { - return [expression] - } - - public static func buildExpression(_ expression: [VFSOverlay.Resource]) -> [VFSOverlay.Resource] { - return expression - } - - public static func buildOptional(_ components: [VFSOverlay.Resource]?) -> [VFSOverlay.Resource] { - return components ?? [] - } -} -#endif diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 535ca0d4524..27431e3cc60 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -1554,22 +1554,25 @@ public final class MixedTargetBuildDescription { self.moduleMap = customModuleMapPath self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) -// TODO(ncooke3): Probably can remove the builder pattern now. -#if swift(>=5.4) try VFSOverlay(roots: [ - VFSOverlay.Directory(name: customModuleMapPath.parentDirectory.pathString) { - VFSOverlay.File( - name: moduleMapFilename, - externalContents: productModuleMapPath.pathString - ) - - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - } + VFSOverlay.Directory( + name: customModuleMapPath.parentDirectory.pathString, + contents: [ + // Redirect the custom `module.modulemap` to the + // modified module map in the product directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: productModuleMapPath.pathString + ), + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + ] + ) ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) -#endif // When the mixed target does not have a custom module map, one will be // generated as a product for use by clients. @@ -1589,16 +1592,19 @@ public final class MixedTargetBuildDescription { self.moduleMap = productModuleMapPath self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) -#if swift(>=5.4) try VFSOverlay(roots: [ - VFSOverlay.Directory(name: mixedTarget.clangTarget.includeDir.pathString) { - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - } + VFSOverlay.Directory( + name: mixedTarget.clangTarget.includeDir.pathString, + contents: [ + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + ] + ) ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) -#endif } // MARK: Generate intermediate artifacts used to build the target. @@ -1652,38 +1658,40 @@ public final class MixedTargetBuildDescription { } let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) -#if swift(>=5.4) try VFSOverlay(roots: [ - VFSOverlay.Directory(name: rootOverlayResourceDirectory.pathString) { - // Redirect the `module.modulemap` to the modified - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: intermediateModuleMapPath.pathString - ) - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - } + VFSOverlay.Directory( + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the modified + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: intermediateModuleMapPath.pathString + ), + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ) + ] + ) ]).write(to: allProductHeadersPath, fileSystem: fileSystem) -#endif let unextendedModuleMapOverlayPath = intermediatesDirectory.appending(component: unextendedModuleOverlayFilename) -#if swift(>=5.4) try VFSOverlay(roots: [ - VFSOverlay.Directory(name: rootOverlayResourceDirectory.pathString) { - // Redirect the `module.modulemap` to the *unextended* - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: unextendedModuleMapPath.pathString - ) - } + VFSOverlay.Directory( + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: unextendedModuleMapPath.pathString + ) + ] + ) ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) -#endif // 4. Tie everything together by passing build flags. @@ -1719,7 +1727,7 @@ public final class MixedTargetBuildDescription { "-I", intermediatesDirectory.pathString ] - // If a generated umbrella header was created, add it's root directory + // If a generated umbrella header was created, add its root directory // as a header search path. This will resolve its import within the // generated interop header. if let interopSupportDirectory = interopSupportDirectory { From 8ed1652a61a26953a334dd8cc2dd9edc92c9cb01 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 14 Jan 2023 17:08:25 -0500 Subject: [PATCH 100/178] post rebase fixes --- .../ClangTargetBuildDescription.swift | 13 +- .../MixedTargetBuildDescription.swift | 452 ++++ .../SwiftTargetBuildDescription.swift | 22 +- .../TargetBuildDescription.swift | 15 + Sources/Build/BuildPlan.swift | 1956 +---------------- 5 files changed, 497 insertions(+), 1961 deletions(-) create mode 100644 Sources/Build/BuildDescription/MixedTargetBuildDescription.swift diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index c169e48d26c..2a6c2f782d0 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -100,6 +100,11 @@ public final class ClangTargetBuildDescription { /// The filesystem to operate on. private let fileSystem: FileSystem + /// Whether or not the target belongs to a mixed language target. + /// + /// Mixed language targets consist of an underlying Swift and Clang target. + let isWithinMixedTarget: Bool + /// If this target is a test target. public var isTestTarget: Bool { target.type == .test @@ -117,6 +122,7 @@ public final class ClangTargetBuildDescription { buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], fileSystem: FileSystem, + isWithinMixedTarget: Bool = false, observabilityScope: ObservabilityScope ) throws { guard target.underlyingTarget is ClangTarget else { @@ -129,6 +135,7 @@ public final class ClangTargetBuildDescription { self.buildParameters = buildParameters self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") self.derivedSources = Sources(paths: [], root: tempsPath.appending("DerivedSources")) + self.isWithinMixedTarget = isWithinMixedTarget // We did not use to apply package plugins to C-family targets in prior tools-versions, this preserves the behavior. if toolsVersion >= .v5_9 { @@ -149,8 +156,10 @@ public final class ClangTargetBuildDescription { self.pluginDerivedResources = [] } - // Try computing modulemap path for a C library. This also creates the file in the file system, if needed. - if target.type == .library { + // Try computing the modulemap path, creating a module map in the + // file system if necessary. If building for a mixed target, the mixed + // target build description handle the module map. + if target.type == .library, !isWithinMixedTarget { // If there's a custom module map, use it as given. if case .custom(let path) = clangTarget.moduleMapType { self.moduleMap = path diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift new file mode 100644 index 00000000000..abc7eb541c4 --- /dev/null +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -0,0 +1,452 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 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 Basics +import Foundation +import PackageGraph +import PackageLoading +import PackageModel +import SPMBuildCore +import TSCBasic + +public final class MixedTargetBuildDescription { + /// The target described by this target. + let target: ResolvedTarget + + /// The list of all resource files in the target. + var resources: [Resource] { self.target.underlyingTarget.resources } + + /// If this target is a test target. + var isTestTarget: Bool { self.target.underlyingTarget.type == .test } + + /// The objects in this target. This includes both the Swift and Clang object files. + var objects: [AbsolutePath] { + get throws { + try self.swiftTargetBuildDescription.objects + + self.clangTargetBuildDescription.objects + } + } + + /// Path to the bundle generated for this module (if any). + var bundlePath: AbsolutePath? { self.swiftTargetBuildDescription.bundlePath } + + /// Path to the resource Info.plist file, if generated. + var resourceBundleInfoPlistPath: AbsolutePath? { + self.swiftTargetBuildDescription.resourceBundleInfoPlistPath + } + + /// The path to the VFS overlay file that overlays the public headers of + /// the Clang part of the target over the target's build directory. + let allProductHeadersOverlay: AbsolutePath + + /// The paths to the targets's public headers. + let publicHeaderPaths: [AbsolutePath] + + /// The modulemap file for this target. + let moduleMap: AbsolutePath + + /// Paths to the binary libraries the target depends on. + var libraryBinaryPaths: Set { + self.swiftTargetBuildDescription.libraryBinaryPaths + .union(self.clangTargetBuildDescription.libraryBinaryPaths) + } + + /// The build description for the Clang sources. + let clangTargetBuildDescription: ClangTargetBuildDescription + + /// The build description for the Swift sources. + let swiftTargetBuildDescription: SwiftTargetBuildDescription + + init( + package: ResolvedPackage, + target: ResolvedTarget, + toolsVersion: ToolsVersion, + additionalFileRules: [FileRuleDescription] = [], + buildParameters: BuildParameters, + buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], + prebuildCommandResults: [PrebuildCommandResult] = [], + fileSystem: FileSystem, + observabilityScope: ObservabilityScope + ) throws { + guard let mixedTarget = target.underlyingTarget as? MixedTarget else { + throw InternalError("underlying target type mismatch \(target)") + } + + guard buildParameters.triple.isDarwin() else { + throw StringError("Targets with mixed language sources are only " + + "supported on Apple platforms.") + } + + self.target = target + + let clangResolvedTarget = ResolvedTarget( + target: mixedTarget.clangTarget, + dependencies: target.dependencies, + defaultLocalization: target.defaultLocalization, + platforms: target.platforms + ) + self.clangTargetBuildDescription = try ClangTargetBuildDescription( + target: clangResolvedTarget, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + fileSystem: fileSystem, + isWithinMixedTarget: true + ) + + let swiftResolvedTarget = ResolvedTarget( + target: mixedTarget.swiftTarget, + dependencies: target.dependencies, + defaultLocalization: target.defaultLocalization, + platforms: target.platforms + ) + self.swiftTargetBuildDescription = try SwiftTargetBuildDescription( + package: package, + target: swiftResolvedTarget, + toolsVersion: toolsVersion, + additionalFileRules: additionalFileRules, + buildParameters: buildParameters, + buildToolPluginInvocationResults: buildToolPluginInvocationResults, + prebuildCommandResults: prebuildCommandResults, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + isWithinMixedTarget: true + ) + + let interopHeaderPath = self.swiftTargetBuildDescription.objCompatibilityHeaderPath + + // A mixed target's build directory uses three subdirectories to + // distinguish between build artifacts: + // - Intermediates: Stores artifacts used during the target's build. + // - Product: Stores artifacts used by clients of the target. + // - InteropSupport: If needed, stores a generated umbrella header + // for use during the target's build and by clients of the target. + let tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") + let intermediatesDirectory = tempsPath.appending(component: "Intermediates") + let productDirectory = tempsPath.appending(component: "Product") + let interopSupportDirectory: AbsolutePath? + + // Filenames for VFS overlay files. + let allProductHeadersFilename = "all-product-headers.yaml" + let unextendedModuleOverlayFilename = "unextended-module-overlay.yaml" + + // Used to generate both product and intermediate artifacts for the + // target. + let moduleMapGenerator = ModuleMapGenerator( + targetName: mixedTarget.clangTarget.name, + moduleName: mixedTarget.clangTarget.c99name, + publicHeadersDir: mixedTarget.clangTarget.includeDir, + fileSystem: fileSystem + ) + + // MARK: Conditionally generate an umbrella header for interoptability + + // When the Swift compiler creates the generated interop header + // (`$(ModuleName)-Swift.h`) for Objective-C compatible Swift API + // (via `-emit-objc-header`), any Objective-C symbol that cannot be + // forward declared (e.g. superclass, protocol, etc.) will attempt to + // be imported via a bridging or umbrella header. Since the compiler + // evaluates the target as a framework (as opposed to an app), the + // compiler assumes* an umbrella header exists in a subdirectory (named + // after the module) within the public headers directory: + // + // #import <$(ModuleName)/$(ModuleName).h> + // + // The compiler assumes that the above path can be resolved relative to + // the public header directory. Instead of forcing package authors to + // structure their packages around that constraint, the package manager + // generates an umbrella header if needed and will pass it along as a + // header search path when building the target. + // + // *: https://developer.apple.com/documentation/swift/importing-objective-c-into-swift + let umbrellaHeaderPathComponents = [mixedTarget.c99name, "\(mixedTarget.c99name).h"] + let potentialUmbrellaHeadersPath = mixedTarget.clangTarget.includeDir + .appending(components: umbrellaHeaderPathComponents) + // Check if an umbrella header at + // `PUBLIC_HDRS_DIR/$(ModuleName)/$(ModuleName).h` already exists. + if !fileSystem.isFile(potentialUmbrellaHeadersPath) { + interopSupportDirectory = tempsPath.appending(component: "InteropSupport") + let generatedUmbrellaHeaderPath = interopSupportDirectory! + .appending(components: umbrellaHeaderPathComponents) + // Populate a stream that will become the generated umbrella header. + let stream = BufferedOutputByteStream() + mixedTarget.clangTarget.headers + // One of the requirements for a Swift API to be Objective-C + // compatible and therefore included in the generated interop + // header is that it has `public` or `open` visbility. This + // means that such Swift API can only reference (e.g. subclass) + // Objective-C types defined in the target's public headers. + // Because of this, the generated umbrella header will only + // include public headers so all other can be filtered out. + .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } + // Filter out non-Objective-C/C headers. + // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? + .filter { $0.basename.hasSuffix(".h") } + // Add each remaining header to the generated umbrella header. + .forEach { + // Import the header, followed by a newline. + stream <<< "#import \"\($0)\"\n" + } + + try fileSystem.writeFileContentsIfNeeded( + generatedUmbrellaHeaderPath, + bytes: stream.bytes + ) + } else { + // An umbrella header in the desired format already exists so the + // interop support directory is not needed. + interopSupportDirectory = nil + } + + // Clients will later depend on the public header directory, and, if an + // umbrella header was created, the header's root directory. + self.publicHeaderPaths = interopSupportDirectory != nil ? + [mixedTarget.clangTarget.includeDir, interopSupportDirectory!] : + [mixedTarget.clangTarget.includeDir] + + // MARK: Generate products to be used by client of the target. + + // Path to the module map used by clients to access the mixed target's + // public API. + let productModuleMapPath = productDirectory.appending(component: moduleMapFilename) + + switch mixedTarget.clangTarget.moduleMapType { + // When the mixed target has a custom module map, clients of the target + // will be passed a module map *and* VFS overlay at buildtime to access + // the mixed target's public API. The following is therefore needed: + // - Create a copy of the custom module map, adding a submodule to + // expose the target's generated interop header. This allows clients + // of the target to consume the mixed target's public Objective-C + // compatible Swift API in a Clang context. + // - Create a VFS overlay to swap in the modified module map for the + // original custom module map. This is done so relative paths in the + // modified module map can be resolved as they would have been in the + // original module map. + case .custom(let customModuleMapPath): + let customModuleMapContents: String = + try fileSystem.readFileContents(customModuleMapPath) + + // Check that custom module map does not contain a Swift submodule. + if customModuleMapContents.contains("\(target.c99name).Swift") { + throw StringError( + "The target's module map may not contain a Swift " + + "submodule for the module \(target.c99name)." + ) + } + + // Extend the contents and write it to disk, if needed. + let stream = BufferedOutputByteStream() + stream <<< customModuleMapContents + stream <<< """ + module \(target.c99name).Swift { + header "\(interopHeaderPath)" + requires objc + } + """ + try fileSystem.writeFileContentsIfNeeded( + productModuleMapPath, + bytes: stream.bytes + ) + + // Set the original custom module map path as the module map path + // for the target. The below VFS overlay will redirect to the + // contents of the modified module map. + self.moduleMap = customModuleMapPath + self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) + + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: customModuleMapPath.parentDirectory.pathString, + contents: [ + // Redirect the custom `module.modulemap` to the + // modified module map in the product directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: productModuleMapPath.pathString + ), + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ), + ] + ), + ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) + + // When the mixed target does not have a custom module map, one will be + // generated as a product for use by clients. + // - Note: When `.none`, the mixed target has no public headers. Even + // then, a module map is created to expose the generated interop + // header so clients can access the public Objective-C compatible + // Swift API in a Clang context. + case .umbrellaHeader, .umbrellaDirectory, .none: + let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType + try moduleMapGenerator.generateModuleMap( + type: generatedModuleMapType, + at: productModuleMapPath, + interopHeaderPath: interopHeaderPath + ) + + // Set the generated module map as the module map for the target. + self.moduleMap = productModuleMapPath + self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) + + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: mixedTarget.clangTarget.includeDir.pathString, + contents: [ + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ), + ] + ), + ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) + } + + // MARK: Generate intermediate artifacts used to build the target. + + // Building a mixed target uses intermediate module maps to expose + // private headers to the Swift part of the module. + + // 1. Generate an intermediate module map that exposes all headers, + // including the submodule with the generated Swift header. + let intermediateModuleMapPath = intermediatesDirectory.appending(component: moduleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(mixedTarget.clangTarget.path), + at: intermediateModuleMapPath, + interopHeaderPath: interopHeaderPath + ) + + // 2. Generate an intermediate module map that exposes all headers. + // When building the Swift part of the mixed target, a module map will + // be needed to access types from the Objective-C part of the target. + // However, this module map should not expose the generated Swift + // header since it will not exist yet. + let unextendedModuleMapPath = intermediatesDirectory.appending(component: unextendedModuleMapFilename) + // Generating module maps that include non-Objective-C headers is not + // supported. + // FIXME(ncooke3): Link to evolution post. + // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? + let nonObjcHeaders: [AbsolutePath] = mixedTarget.clangTarget.headers + .filter { $0.extension != "h" } + try moduleMapGenerator.generateModuleMap( + type: .umbrellaDirectory(mixedTarget.clangTarget.path), + at: unextendedModuleMapPath, + excludeHeaders: nonObjcHeaders + ) + + // 3. Use VFS overlays to purposefully expose specific resources (e.g. + // module map) during the build. The directory to add a VFS overlay in + // depends on the presence of a custom module map. + let rootOverlayResourceDirectory: AbsolutePath + if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { + // To avoid the custom module map causing a module redeclaration + // error, a VFS overlay is used when building the target to + // redirect the custom module map to the modified module map in the + // build directory. This redirecting overlay is placed in the + // custom module map's parent directory, as to replace it. + rootOverlayResourceDirectory = customModuleMapPath.parentDirectory + } else { + // Since no custom module map exists, the build directory can + // be used as the root of the VFS overlay. In this case, the + // VFS overlay's sole purpose is to expose the generated Swift + // header. + rootOverlayResourceDirectory = intermediatesDirectory + } + + let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the modified + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: intermediateModuleMapPath.pathString + ), + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. + VFSOverlay.File( + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString + ), + ] + ), + ]).write(to: allProductHeadersPath, fileSystem: fileSystem) + + let unextendedModuleMapOverlayPath = intermediatesDirectory + .appending(component: unextendedModuleOverlayFilename) + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: rootOverlayResourceDirectory.pathString, + contents: [ + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: unextendedModuleMapPath.pathString + ), + ] + ), + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + + // 4. Tie everything together by passing build flags. + + // Importing the underlying module will build the Objective-C + // part of the module. In order to find the underlying module, + // a `module.modulemap` needs to be discoverable via a header + // search path. + self.swiftTargetBuildDescription.additionalFlags += [ + "-import-underlying-module", + "-I", rootOverlayResourceDirectory.pathString, + ] + + self.swiftTargetBuildDescription.appendClangFlags( + // Pass both VFS overlays to the underlying Clang compiler. + "-ivfsoverlay", allProductHeadersPath.pathString, + "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, + // Adding the root of the target's source as a header search + // path allows for importing headers using paths relative to + // the root. + "-I", mixedTarget.path.pathString + ) + + self.clangTargetBuildDescription.additionalFlags += [ + // Adding the root of the target's source as a header search + // path allows for importing headers using paths relative to + // the root. + "-I", mixedTarget.path.pathString, + // Include overlay file to add interop header to overlay directory. + "-ivfsoverlay", allProductHeadersPath.pathString, + // The above two args add the interop header in the overlayed + // directory. Pass the overlay directory as a search path so the + // generated header can be imported. + "-I", intermediatesDirectory.pathString, + ] + + // If a generated umbrella header was created, add its root directory + // as a header search path. This will resolve its import within the + // generated interop header. + if let interopSupportDirectory = interopSupportDirectory { + self.swiftTargetBuildDescription.appendClangFlags( + "-I", interopSupportDirectory.pathString + ) + self.clangTargetBuildDescription.additionalFlags += [ + "-I", interopSupportDirectory.pathString, + ] + } + } +} diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index 329f55e7a9e..bf74b61517f 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -131,6 +131,11 @@ public final class SwiftTargetBuildDescription { /// Any addition flags to be added. These flags are expected to be computed during build planning. var additionalFlags: [String] = [] + /// Whether or not the target belongs to a mixed language target. + /// + /// Mixed language targets consist of an underlying Swift and Clang target. + let isWithinMixedTarget: Bool + /// The swift version for this target. var swiftVersion: SwiftLanguageVersion { (self.target.underlyingTarget as! SwiftTarget).swiftVersion @@ -241,7 +246,8 @@ public final class SwiftTargetBuildDescription { requiredMacroProducts: [ResolvedProduct] = [], testTargetRole: TestTargetRole? = nil, fileSystem: FileSystem, - observabilityScope: ObservabilityScope + observabilityScope: ObservabilityScope, + isWithinMixedTarget: Bool = false ) throws { guard target.underlyingTarget is SwiftTarget else { throw InternalError("underlying target type mismatch \(target)") @@ -265,6 +271,7 @@ public final class SwiftTargetBuildDescription { self.prebuildCommandResults = prebuildCommandResults self.requiredMacroProducts = requiredMacroProducts self.observabilityScope = observabilityScope + self.isWithinMixedTarget = isWithinMixedTarget (self.pluginDerivedSources, self.pluginDerivedResources) = SharedTargetBuildDescription.computePluginGeneratedFiles( target: target, @@ -276,7 +283,10 @@ public final class SwiftTargetBuildDescription { observabilityScope: observabilityScope ) - if self.shouldEmitObjCCompatibilityHeader { + // If building for a mixed target, the mixed target build + // description will create the module map and include the Swift + // interoptability header. + if self.shouldEmitObjCCompatibilityHeader, !isWithinMixedTarget { self.moduleMap = try self.generateModuleMap() } @@ -379,7 +389,7 @@ public final class SwiftTargetBuildDescription { if packageAccess { let pkgID = pkg.identity.description.spm_mangledToC99ExtendedIdentifier() return [flag, pkgID] - } + } } return [] } @@ -599,7 +609,11 @@ public final class SwiftTargetBuildDescription { /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { - self.buildParameters.targetTriple.isDarwin() && self.target.type == .library + self.buildParameters.targetTriple.isDarwin() && + // Emitting the interop header for mixed test targets enables the + // sharing of Objective-C compatible Swift test helpers between + // Swift and Objective-C test files. + (self.target.type == .library || self.target.type == .test && self.isWithinMixedTarget) } func writeOutputFileMap() throws -> AbsolutePath { diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index c177a0b7dcd..8d90b60dc32 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -23,6 +23,9 @@ public enum TargetBuildDescription { /// Clang target description. case clang(ClangTargetBuildDescription) + /// Mixed (Swift + Clang) target description. + case mixed(MixedTargetBuildDescription) + /// The objects in this target. var objects: [AbsolutePath] { get throws { @@ -31,6 +34,8 @@ public enum TargetBuildDescription { return try target.objects case .clang(let target): return try target.objects + case .mixed(let target): + return try target.objects } } } @@ -42,6 +47,8 @@ public enum TargetBuildDescription { return target.resources case .clang(let target): return target.resources + case .mixed(let target): + return target.resources } } @@ -52,6 +59,8 @@ public enum TargetBuildDescription { return target.bundlePath case .clang(let target): return target.bundlePath + case .mixed(let target): + return target.bundlePath } } @@ -61,6 +70,8 @@ public enum TargetBuildDescription { return target.target case .clang(let target): return target.target + case .mixed(let target): + return target.target } } @@ -71,6 +82,8 @@ public enum TargetBuildDescription { return target.libraryBinaryPaths case .clang(let target): return target.libraryBinaryPaths + case .mixed(let target): + return target.libraryBinaryPaths } } @@ -80,6 +93,8 @@ public enum TargetBuildDescription { return target.resourceBundleInfoPlistPath case .clang(let target): return target.resourceBundleInfoPlistPath + case .mixed(let target): + return target.resourceBundleInfoPlistPath } } diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 27431e3cc60..e4d9ecc6659 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -159,1960 +159,6 @@ extension BuildParameters { } } -/// A target description which can either be for a Swift or Clang target. -public enum TargetBuildDescription { - - /// Swift target description. - case swift(SwiftTargetBuildDescription) - - /// Clang target description. - case clang(ClangTargetBuildDescription) - - /// Mixed (Swift + Clang) target description. - case mixed(MixedTargetBuildDescription) - - /// The objects in this target. - var objects: [AbsolutePath] { - get throws { - switch self { - case .swift(let target): - return try target.objects - case .clang(let target): - return try target.objects - case .mixed(let target): - return try target.objects - } - } - } - - /// The resources in this target. - var resources: [Resource] { - switch self { - case .swift(let target): - return target.resources - case .clang(let target): - // TODO: Clang targets should support generated resources in the future. - return target.target.underlyingTarget.resources - case .mixed(let target): - return target.resources - } - } - - /// Path to the bundle generated for this module (if any). - var bundlePath: AbsolutePath? { - switch self { - case .swift(let target): - return target.bundlePath - case .clang(let target): - return target.bundlePath - case .mixed(let target): - return target.bundlePath - } - } - - var target: ResolvedTarget { - switch self { - case .swift(let target): - return target.target - case .clang(let target): - return target.target - case .mixed(let target): - return target.target - } - } - - /// Paths to the binary libraries the target depends on. - var libraryBinaryPaths: Set { - switch self { - case .swift(let target): - return target.libraryBinaryPaths - case .clang(let target): - return target.libraryBinaryPaths - case .mixed(let target): - return target.libraryBinaryPaths - } - } - - var resourceBundleInfoPlistPath: AbsolutePath? { - switch self { - case .swift(let target): - return target.resourceBundleInfoPlistPath - case .clang(let target): - return target.resourceBundleInfoPlistPath - case .mixed(let target): - return target.resourceBundleInfoPlistPath - } - } -} - -/// Target description for a Clang target i.e. C language family target. -public final class ClangTargetBuildDescription { - - /// The target described by this target. - public let target: ResolvedTarget - - /// The underlying clang target. - public var clangTarget: ClangTarget { - return target.underlyingTarget as! ClangTarget - } - - /// The tools version of the package that declared the target. This can - /// can be used to conditionalize semantically significant changes in how - /// a target is built. - public let toolsVersion: ToolsVersion - - /// The build parameters. - let buildParameters: BuildParameters - - /// The build environment. - var buildEnvironment: BuildEnvironment { - buildParameters.buildEnvironment - } - - /// Path to the bundle generated for this module (if any). - var bundlePath: AbsolutePath? { - target.underlyingTarget.bundleName.map(buildParameters.bundlePath(named:)) - } - - /// The modulemap file for this target, if any. - public private(set) var moduleMap: AbsolutePath? - - /// Path to the temporary directory for this target. - var tempsPath: AbsolutePath - - /// The directory containing derived sources of this target. - /// - /// These are the source files generated during the build. - private var derivedSources: Sources - - /// Path to the resource accessor header file, if generated. - public private(set) var resourceAccessorHeaderFile: AbsolutePath? - - /// Path to the resource Info.plist file, if generated. - public private(set) var resourceBundleInfoPlistPath: AbsolutePath? - - /// The objects in this target. - public var objects: [AbsolutePath] { - get throws { - return try compilePaths().map({ $0.object }) - } - } - - /// Paths to the binary libraries the target depends on. - fileprivate(set) var libraryBinaryPaths: Set = [] - - /// Any addition flags to be added. These flags are expected to be computed during build planning. - fileprivate var additionalFlags: [String] = [] - - /// The filesystem to operate on. - private let fileSystem: FileSystem - - /// Whether or not the target belongs to a mixed language target. - /// - /// Mixed language targets consist of an underlying Swift and Clang target. - let isWithinMixedTarget: Bool - - /// If this target is a test target. - public var isTestTarget: Bool { - return target.type == .test - } - - /// Create a new target description with target and build parameters. - init(target: ResolvedTarget, toolsVersion: ToolsVersion, buildParameters: BuildParameters, fileSystem: FileSystem, isWithinMixedTarget: Bool = false) throws { - guard target.underlyingTarget is ClangTarget else { - throw InternalError("underlying target type mismatch \(target)") - } - - self.fileSystem = fileSystem - self.target = target - self.toolsVersion = toolsVersion - self.buildParameters = buildParameters - self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") - self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) - self.isWithinMixedTarget = isWithinMixedTarget - - // Try computing the modulemap path, creating a module map in the - // file system if necessary. If building for a mixed target, the mixed - // target build description handle the module map. - if target.type == .library && !isWithinMixedTarget { - // If there's a custom module map, use it as given. - if case .custom(let path) = clangTarget.moduleMapType { - self.moduleMap = path - } - // If a generated module map is needed, generate one now in our temporary directory. - else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { - let path = tempsPath.appending(component: moduleMapFilename) - let moduleMapGenerator = ModuleMapGenerator(targetName: clangTarget.name, moduleName: clangTarget.c99name, publicHeadersDir: clangTarget.includeDir, fileSystem: fileSystem) - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: path) - self.moduleMap = path - } - // Otherwise there is no module map, and we leave `moduleMap` unset. - } - - // Do nothing if we're not generating a bundle. - if bundlePath != nil { - try self.generateResourceAccessor() - - let infoPlistPath = tempsPath.appending(component: "Info.plist") - if try generateResourceInfoPlist(fileSystem: fileSystem, target: target, path: infoPlistPath) { - resourceBundleInfoPlistPath = infoPlistPath - } - } - } - - /// An array of tuple containing filename, source, object and dependency path for each of the source in this target. - public func compilePaths() - throws -> [(filename: RelativePath, source: AbsolutePath, object: AbsolutePath, deps: AbsolutePath)] - { - let sources = [ - target.sources.root: target.sources.relativePaths, - derivedSources.root: derivedSources.relativePaths, - ] - - return try sources.flatMap { (root, relativePaths) in - try relativePaths.map { source in - let path = root.appending(source) - let object = try AbsolutePath(validating: "\(source.pathString).o", relativeTo: tempsPath) - let deps = try AbsolutePath(validating: "\(source.pathString).d", relativeTo: tempsPath) - return (source, path, object, deps) - } - } - } - - /// Builds up basic compilation arguments for a source file in this target; these arguments may be different for C++ vs non-C++. - /// NOTE: The parameter to specify whether to get C++ semantics is currently optional, but this is only for revlock avoidance with clients. Callers should always specify what they want based either the user's indication or on a default value (possibly based on the filename suffix). - public func basicArguments( - isCXX isCXXOverride: Bool? = .none, - isC: Bool = false - ) throws -> [String] { - // For now fall back on the hold semantics if the C++ nature isn't specified. This is temporary until clients have been updated. - let isCXX = isCXXOverride ?? clangTarget.isCXX - - var args = [String]() - // Only enable ARC on macOS. - if buildParameters.triple.isDarwin() { - args += ["-fobjc-arc"] - } - args += try buildParameters.targetTripleArgs(for: target) - args += ["-g"] - if buildParameters.triple.isWindows() { - args += ["-gcodeview"] - } - args += optimizationArguments - args += activeCompilationConditions - args += ["-fblocks"] - - // Enable index store, if appropriate. - // - // This feature is not widely available in OSS clang. So, we only enable - // index store for Apple's clang or if explicitly asked to. - if ProcessEnv.vars.keys.contains("SWIFTPM_ENABLE_CLANG_INDEX_STORE") { - args += buildParameters.indexStoreArguments(for: target) - } else if buildParameters.triple.isDarwin(), (try? buildParameters.toolchain._isClangCompilerVendorApple()) == true { - args += buildParameters.indexStoreArguments(for: target) - } - - // Enable Clang module flags, if appropriate. - let enableModules: Bool - if toolsVersion < .v5_8 { - // For version < 5.8, we enable them except in these cases: - // 1. on Darwin when compiling for C++, because C++ modules are disabled on Apple-built Clang releases - // 2. on Windows when compiling for any language, because of issues with the Windows SDK - // 3. on Android when compiling for any language, because of issues with the Android SDK - enableModules = !(buildParameters.triple.isDarwin() && isCXX) && !buildParameters.triple.isWindows() && !buildParameters.triple.isAndroid() - } else { - // For version >= 5.8, we disable them when compiling for C++ regardless of platforms, see: - // https://github.com/llvm/llvm-project/issues/55980 for clang frontend crash when module - // enabled for C++ on c++17 standard and above. - enableModules = !isCXX && !buildParameters.triple.isWindows() && !buildParameters.triple.isAndroid() - } - - if enableModules { - // Using modules currently conflicts with the Windows and Android SDKs. - args += ["-fmodules", "-fmodule-name=" + target.c99name] - } - - // Only add the build path to the framework search path if there are binary frameworks to link against. - if !libraryBinaryPaths.isEmpty { - args += ["-F", buildParameters.buildPath.pathString] - } - - args += ["-I", clangTarget.includeDir.pathString] - args += additionalFlags - if enableModules { - args += try moduleCacheArgs - } - args += buildParameters.sanitizers.compileCFlags() - - // Add arguments from declared build settings. - args += try self.buildSettingsFlags() - - // Include the path to the resource header unless the arguments are - // being evaluated for a C file. A C file cannot depend on the resource - // accessor header due to it exporting a Foundation type (`NSBundle`). - if let resourceAccessorHeaderFile = self.resourceAccessorHeaderFile, !isC { - args += ["-include", resourceAccessorHeaderFile.pathString] - } - - args += buildParameters.toolchain.extraFlags.cCompilerFlags - // User arguments (from -Xcc and -Xcxx below) should follow generated arguments to allow user overrides - args += buildParameters.flags.cCompilerFlags - - // Add extra C++ flags if this target contains C++ files. - if clangTarget.isCXX { - args += self.buildParameters.flags.cxxCompilerFlags - } - return args - } - - /// Returns the build flags from the declared build settings. - private func buildSettingsFlags() throws -> [String] { - let scope = buildParameters.createScope(for: target) - var flags: [String] = [] - - // C defines. - let cDefines = scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) - flags += cDefines.map({ "-D" + $0 }) - - // Header search paths. - let headerSearchPaths = scope.evaluate(.HEADER_SEARCH_PATHS) - flags += try headerSearchPaths.map({ - "-I\(try AbsolutePath(validating: $0, relativeTo: target.sources.root).pathString)" - }) - - // Other C flags. - flags += scope.evaluate(.OTHER_CFLAGS) - - // Other CXX flags. - flags += scope.evaluate(.OTHER_CPLUSPLUSFLAGS) - - return flags - } - - /// Optimization arguments according to the build configuration. - private var optimizationArguments: [String] { - switch buildParameters.configuration { - case .debug: - return ["-O0"] - case .release: - return ["-O2"] - } - } - - /// A list of compilation conditions to enable for conditional compilation expressions. - private var activeCompilationConditions: [String] { - var compilationConditions = ["-DSWIFT_PACKAGE=1"] - - switch buildParameters.configuration { - case .debug: - compilationConditions += ["-DDEBUG=1"] - case .release: - break - } - - return compilationConditions - } - - /// Module cache arguments. - private var moduleCacheArgs: [String] { - get throws { - return try ["-fmodules-cache-path=\(buildParameters.moduleCache.pathString)"] - } - } - - /// Generate the resource bundle accessor, if appropriate. - private func generateResourceAccessor() throws { - // Only generate access when we have a bundle and ObjC files. - guard let bundlePath = self.bundlePath, clangTarget.sources.containsObjcFiles else { return } - - // Compute the basename of the bundle. - let bundleBasename = bundlePath.basename - - let implFileStream = BufferedOutputByteStream() - implFileStream <<< """ - #import - - NSBundle* \(target.c99name)_SWIFTPM_MODULE_BUNDLE() { - NSURL *bundleURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"\(bundleBasename)"]; - - NSBundle *preferredBundle = [NSBundle bundleWithURL:bundleURL]; - if (preferredBundle == nil) { - return [NSBundle bundleWithPath:@"\(bundlePath.pathString)"]; - } - - return preferredBundle; - } - """ - - let implFileSubpath = RelativePath("resource_bundle_accessor.m") - - // Add the file to the derived sources. - derivedSources.relativePaths.append(implFileSubpath) - - // Write this file out. - // FIXME: We should generate this file during the actual build. - try fileSystem.writeIfChanged( - path: derivedSources.root.appending(implFileSubpath), - bytes: implFileStream.bytes - ) - - let headerFileStream = BufferedOutputByteStream() - headerFileStream <<< """ - #import - - #if __cplusplus - extern "C" { - #endif - - NSBundle* \(target.c99name)_SWIFTPM_MODULE_BUNDLE(void); - - #define SWIFTPM_MODULE_BUNDLE \(target.c99name)_SWIFTPM_MODULE_BUNDLE() - - #if __cplusplus - } - #endif - """ - let headerFile = derivedSources.root.appending(component: "resource_bundle_accessor.h") - self.resourceAccessorHeaderFile = headerFile - - try fileSystem.writeIfChanged( - path: headerFile, - bytes: headerFileStream.bytes - ) - } -} - -/// Target description for a Swift target. -public final class SwiftTargetBuildDescription { - /// The package this target belongs to. - public let package: ResolvedPackage - - /// The target described by this target. - public let target: ResolvedTarget - - /// The tools version of the package that declared the target. This can - /// can be used to conditionalize semantically significant changes in how - /// a target is built. - public let toolsVersion: ToolsVersion - - /// The build parameters. - let buildParameters: BuildParameters - - /// Path to the temporary directory for this target. - let tempsPath: AbsolutePath - - /// The directory containing derived sources of this target. - /// - /// These are the source files generated during the build. - private var derivedSources: Sources - - /// These are the source files derived from plugins. - private var pluginDerivedSources: Sources - - /// These are the resource files derived from plugins. - private var pluginDerivedResources: [Resource] - - /// Path to the bundle generated for this module (if any). - var bundlePath: AbsolutePath? { - if let bundleName = target.underlyingTarget.potentialBundleName, !resources.isEmpty { - return buildParameters.bundlePath(named: bundleName) - } else { - return .none - } - } - - /// The list of all source files in the target, including the derived ones. - public var sources: [AbsolutePath] { - target.sources.paths + derivedSources.paths + pluginDerivedSources.paths - } - - /// The list of all resource files in the target, including the derived ones. - public var resources: [Resource] { - target.underlyingTarget.resources + pluginDerivedResources - } - - /// The objects in this target. - public var objects: [AbsolutePath] { - get throws { - let relativePaths = target.sources.relativePaths + derivedSources.relativePaths + pluginDerivedSources.relativePaths - return try relativePaths.map { - try AbsolutePath(validating: "\($0.pathString).o", relativeTo: tempsPath) - } - } - } - - /// The path to the swiftmodule file after compilation. - var moduleOutputPath: AbsolutePath { - // If we're an executable and we're not allowing test targets to link against us, we hide the module. - let allowLinkingAgainstExecutables = (buildParameters.triple.isDarwin() || buildParameters.triple.isLinux() || buildParameters.triple.isWindows()) && toolsVersion >= .v5_5 - let dirPath = (target.type == .executable && !allowLinkingAgainstExecutables) ? tempsPath : buildParameters.buildPath - return dirPath.appending(component: target.c99name + ".swiftmodule") - } - - /// The path to the wrapped swift module which is created using the modulewrap tool. This is required - /// for supporting debugging on non-Darwin platforms (On Darwin, we just pass the swiftmodule to the linker - /// using the `-add_ast_path` flag). - var wrappedModuleOutputPath: AbsolutePath { - return tempsPath.appending(component: target.c99name + ".swiftmodule.o") - } - - /// The path to the swifinterface file after compilation. - var parseableModuleInterfaceOutputPath: AbsolutePath { - return buildParameters.buildPath.appending(component: target.c99name + ".swiftinterface") - } - - /// Path to the resource Info.plist file, if generated. - public private(set) var resourceBundleInfoPlistPath: AbsolutePath? - - /// Paths to the binary libraries the target depends on. - fileprivate(set) var libraryBinaryPaths: Set = [] - - /// Any addition flags to be added. These flags are expected to be computed during build planning. - fileprivate var additionalFlags: [String] = [] - - /// Whether or not the target belongs to a mixed language target. - /// - /// Mixed language targets consist of an underlying Swift and Clang target. - let isWithinMixedTarget: Bool - - /// The swift version for this target. - var swiftVersion: SwiftLanguageVersion { - return (target.underlyingTarget as! SwiftTarget).swiftVersion - } - - /// Describes the purpose of a test target, including any special roles such as containing a list of discovered tests or - /// serving as the manifest target which contains the main entry point. - public enum TestTargetRole { - /// An ordinary test target, defined explicitly in a package, containing test code. - case `default` - - /// A test target which was synthesized automatically, containing a list of discovered tests - /// from `plain` test targets. - case discovery - - /// A test target which was either synthesized automatically and contains an entry point file configured to run all discovered - /// tests, or contains a custom entry point file. In the latter case, the custom entry point file may have been discovered in - /// the package automatically (e.g. `XCTMain.swift`) or may have been provided explicitly via a CLI flag. - case entryPoint(isSynthesized: Bool) - } - - public let testTargetRole: TestTargetRole? - - /// If this target is a test target. - public var isTestTarget: Bool { - testTargetRole != nil - } - - /// True if this module needs to be parsed as a library based on the target type and the configuration - /// of the source code - var needsToBeParsedAsLibrary: Bool { - switch self.target.type { - case .library, .test: - return true - case .executable, .snippet: - // This deactivates heuristics in the Swift compiler that treats single-file modules and source files - // named "main.swift" specially w.r.t. whether they can have an entry point. - // - // See https://bugs.swift.org/browse/SR-14488 for discussion about improvements so that SwiftPM can - // convey the intent to build an executable module to the compiler regardless of the number of files - // in the module or their names. - if self.toolsVersion < .v5_5 || self.sources.count != 1 { - return false - } - // looking into the file content to see if it is using the @main annotation which requires parse-as-library - return (try? self.containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false - default: - return false - } - } - - // looking into the file content to see if it is using the @main annotation - // this is not bullet-proof since theoretically the file can contain the @main string for other reasons - // but it is the closest to accurate we can do at this point - func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool { - let content: String = try self.fileSystem.readFileContents(path) - let lines = content.split(separator: "\n").compactMap { String($0).spm_chuzzle() } - - var multilineComment = false - for line in lines { - if line.hasPrefix("//") { - continue - } - if line.hasPrefix("/*") { - multilineComment = true - } - if line.hasSuffix("*/") { - multilineComment = false - } - if multilineComment { - continue - } - if line.hasPrefix("@main") { - return true - } - } - return false - } - - - /// The filesystem to operate on. - let fileSystem: FileSystem - - /// The modulemap file for this target, if any. - private(set) var moduleMap: AbsolutePath? - - /// The results of applying any build tool plugins to this target. - public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] - - /// The results of running any prebuild commands for this target. - public let prebuildCommandResults: [PrebuildCommandResult] - - /// ObservabilityScope with which to emit diagnostics - private let observabilityScope: ObservabilityScope - - /// Create a new target description with target and build parameters. - init( - package: ResolvedPackage, - target: ResolvedTarget, - toolsVersion: ToolsVersion, - additionalFileRules: [FileRuleDescription] = [], - buildParameters: BuildParameters, - buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], - prebuildCommandResults: [PrebuildCommandResult] = [], - testTargetRole: TestTargetRole? = nil, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope, - isWithinMixedTarget: Bool = false - ) throws { - guard target.underlyingTarget is SwiftTarget else { - throw InternalError("underlying target type mismatch \(target)") - } - self.package = package - self.target = target - self.toolsVersion = toolsVersion - self.buildParameters = buildParameters - // Unless mentioned explicitly, use the target type to determine if this is a test target. - if let testTargetRole = testTargetRole { - self.testTargetRole = testTargetRole - } else if target.type == .test { - self.testTargetRole = .default - } else { - self.testTargetRole = nil - } - self.fileSystem = fileSystem - self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") - self.derivedSources = Sources(paths: [], root: tempsPath.appending(component: "DerivedSources")) - self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath) - self.buildToolPluginInvocationResults = buildToolPluginInvocationResults - self.prebuildCommandResults = prebuildCommandResults - self.observabilityScope = observabilityScope - self.isWithinMixedTarget = isWithinMixedTarget - - // Add any derived files that were declared for any commands from plugin invocations. - var pluginDerivedFiles = [AbsolutePath]() - for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) { - for absPath in command.outputFiles { - pluginDerivedFiles.append(absPath) - } - } - - // Add any derived files that were discovered from output directories of prebuild commands. - for result in self.prebuildCommandResults { - for path in result.derivedFiles { - pluginDerivedFiles.append(path) - } - } - - // Let `TargetSourcesBuilder` compute the treatment of plugin generated files. - let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(for: pluginDerivedFiles, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, defaultLocalization: target.defaultLocalization, targetName: target.name, targetPath: target.underlyingTarget.path, observabilityScope: observabilityScope) - self.pluginDerivedResources = derivedResources - derivedSources.forEach { absPath in - let relPath = absPath.relative(to: self.pluginDerivedSources.root) - self.pluginDerivedSources.relativePaths.append(relPath) - } - - // If building for a mixed target, the mixed target build - // description will create the module map and include the Swift - // interoptability header. - if shouldEmitObjCCompatibilityHeader && !isWithinMixedTarget { - self.moduleMap = try self.generateModuleMap() - } - - // Do nothing if we're not generating a bundle. - if bundlePath != nil { - try self.generateResourceAccessor() - - let infoPlistPath = tempsPath.appending(component: "Info.plist") - if try generateResourceInfoPlist(fileSystem: self.fileSystem, target: target, path: infoPlistPath) { - resourceBundleInfoPlistPath = infoPlistPath - } - } - } - - /// Generate the resource bundle accessor, if appropriate. - private func generateResourceAccessor() throws { - // Do nothing if we're not generating a bundle. - guard let bundlePath = self.bundlePath else { return } - - let mainPathSubstitution: String - if buildParameters.triple.isWASI() { - // We prefer compile-time evaluation of the bundle path here for WASI. There's no benefit in evaluating this at runtime, - // especially as Bundle support in WASI Foundation is partial. We expect all resource paths to evaluate to - // `/\(resourceBundleName)/\(resourcePath)`, which allows us to pass this path to JS APIs like `fetch` directly, or to - // ` [String] { - var args = [String]() - args += try buildParameters.targetTripleArgs(for: target) - args += ["-swift-version", swiftVersion.rawValue] - - // Enable batch mode in debug mode. - // - // Technically, it should be enabled whenever WMO is off but we - // don't currently make that distinction in SwiftPM - switch buildParameters.configuration { - case .debug: - args += ["-enable-batch-mode"] - case .release: break - } - - args += buildParameters.indexStoreArguments(for: target) - args += optimizationArguments - args += testingArguments - args += ["-g"] - args += ["-j\(buildParameters.jobs)"] - args += activeCompilationConditions - args += additionalFlags - args += try moduleCacheArgs - args += stdlibArguments - args += buildParameters.sanitizers.compileSwiftFlags() - args += ["-parseable-output"] - - // If we're compiling the main module of an executable other than the one that - // implements a test suite, and if the package tools version indicates that we - // should, we rename the `_main` entry point to `__main`. - // - // This will allow tests to link against the module without any conflicts. And - // when we link the executable, we will ask the linker to rename the entry point - // symbol to just `_main` again (or if the linker doesn't support it, we'll - // generate a source containing a redirect). - if (target.type == .executable || target.type == .snippet) - && !isTestTarget && toolsVersion >= .v5_5 { - // We only do this if the linker supports it, as indicated by whether we - // can construct the linker flags. In the future we will use a generated - // code stub for the cases in which the linker doesn't support it, so that - // we can rename the symbol unconditionally. - // No `-` for these flags because the set of Strings in driver.supportedFrontendFlags do - // not have a leading `-` - if buildParameters.canRenameEntrypointFunctionName, - buildParameters.linkerFlagsForRenamingMainFunction(of: target) != nil { - args += ["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "\(target.c99name)_main"] - } - } - - // If the target needs to be parsed without any special semantics involving "main.swift", do so now. - if self.needsToBeParsedAsLibrary { - args += ["-parse-as-library"] - } - - // Only add the build path to the framework search path if there are binary frameworks to link against. - if !libraryBinaryPaths.isEmpty { - args += ["-F", buildParameters.buildPath.pathString] - } - - // Emit the ObjC compatibility header if enabled. - if shouldEmitObjCCompatibilityHeader { - args += ["-emit-objc-header", "-emit-objc-header-path", objCompatibilityHeaderPath.pathString] - } - - // Add arguments needed for code coverage if it is enabled. - if buildParameters.enableCodeCoverage { - args += ["-profile-coverage-mapping", "-profile-generate"] - } - - // Add arguments to colorize output if stdout is tty - if buildParameters.colorizedOutput { - args += ["-color-diagnostics"] - } - - // Add arguments from declared build settings. - args += try self.buildSettingsFlags() - - // Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other way. - if buildParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { - args += ["-emit-module-interface-path", parseableModuleInterfaceOutputPath.pathString] - } - - args += buildParameters.toolchain.extraFlags.swiftCompilerFlags - // User arguments (from -Xswiftc) should follow generated arguments to allow user overrides - args += buildParameters.swiftCompilerFlags - - // suppress warnings if the package is remote - if self.package.isRemote { - args += ["-suppress-warnings"] - // suppress-warnings and warnings-as-errors are mutually exclusive - if let index = args.firstIndex(of: "-warnings-as-errors") { - args.remove(at: index) - } - } - - return args - } - - /// When `scanInvocation` argument is set to `true`, omit the side-effect producing arguments - /// such as emitting a module or supplementary outputs. - public func emitCommandLine(scanInvocation: Bool = false) throws -> [String] { - var result: [String] = [] - result.append(buildParameters.toolchain.swiftCompilerPath.pathString) - - result.append("-module-name") - result.append(target.c99name) - - if !scanInvocation { - result.append("-emit-dependencies") - - // FIXME: Do we always have a module? - result.append("-emit-module") - result.append("-emit-module-path") - result.append(moduleOutputPath.pathString) - - result.append("-output-file-map") - // FIXME: Eliminate side effect. - result.append(try writeOutputFileMap().pathString) - } - - if buildParameters.useWholeModuleOptimization { - result.append("-whole-module-optimization") - result.append("-num-threads") - result.append(String(ProcessInfo.processInfo.activeProcessorCount)) - } else { - result.append("-incremental") - } - - result.append("-c") - result.append(contentsOf: sources.map { $0.pathString }) - - result.append("-I") - result.append(buildParameters.buildPath.pathString) - - result += try self.compileArguments() - return result - } - - /// Command-line for emitting just the Swift module. - public func emitModuleCommandLine() throws -> [String] { - guard buildParameters.emitSwiftModuleSeparately else { - throw InternalError("expecting emitSwiftModuleSeparately in build parameters") - } - - var result: [String] = [] - result.append(buildParameters.toolchain.swiftCompilerPath.pathString) - - result.append("-module-name") - result.append(target.c99name) - result.append("-emit-module") - result.append("-emit-module-path") - result.append(moduleOutputPath.pathString) - result += buildParameters.toolchain.extraFlags.swiftCompilerFlags - - result.append("-Xfrontend") - result.append("-experimental-skip-non-inlinable-function-bodies") - result.append("-force-single-frontend-invocation") - - // FIXME: Handle WMO - - for source in target.sources.paths { - result.append(source.pathString) - } - - result.append("-I") - result.append(buildParameters.buildPath.pathString) - - // FIXME: Maybe refactor these into "common args". - result += try buildParameters.targetTripleArgs(for: target) - result += ["-swift-version", swiftVersion.rawValue] - result += optimizationArguments - result += testingArguments - result += ["-g"] - result += ["-j\(buildParameters.jobs)"] - result += activeCompilationConditions - result += additionalFlags - result += try moduleCacheArgs - result += stdlibArguments - result += try self.buildSettingsFlags() - - return result - } - - /// Command-line for emitting the object files. - /// - /// Note: This doesn't emit the module. - public func emitObjectsCommandLine() throws -> [String] { - guard buildParameters.emitSwiftModuleSeparately else { - throw InternalError("expecting emitSwiftModuleSeparately in build parameters") - } - - var result: [String] = [] - result.append(buildParameters.toolchain.swiftCompilerPath.pathString) - - result.append("-module-name") - result.append(target.c99name) - result.append("-incremental") - result.append("-emit-dependencies") - - result.append("-output-file-map") - // FIXME: Eliminate side effect. - result.append(try writeOutputFileMap().pathString) - - // FIXME: Handle WMO - - result.append("-c") - for source in target.sources.paths { - result.append(source.pathString) - } - - result.append("-I") - result.append(buildParameters.buildPath.pathString) - - result += try buildParameters.targetTripleArgs(for: target) - result += ["-swift-version", swiftVersion.rawValue] - - result += buildParameters.indexStoreArguments(for: target) - result += optimizationArguments - result += testingArguments - result += ["-g"] - result += ["-j\(buildParameters.jobs)"] - result += activeCompilationConditions - result += additionalFlags - result += try moduleCacheArgs - result += stdlibArguments - result += buildParameters.sanitizers.compileSwiftFlags() - result += ["-parseable-output"] - result += try self.buildSettingsFlags() - result += buildParameters.toolchain.extraFlags.swiftCompilerFlags - result += buildParameters.swiftCompilerFlags - return result - } - - func appendClangFlags(_ flags: String...) { - flags.forEach { flag in - additionalFlags.append("-Xcc") - additionalFlags.append(flag) - } - } - - /// Returns true if ObjC compatibility header should be emitted. - private var shouldEmitObjCCompatibilityHeader: Bool { - return buildParameters.triple.isDarwin() && - // Emitting the interop header for mixed test targets enables the - // sharing of Objective-C compatible Swift test helpers between - // Swift and Objective-C test files. - (target.type == .library || target.type == .test && isWithinMixedTarget) - } - - private func writeOutputFileMap() throws -> AbsolutePath { - let path = tempsPath.appending(component: "output-file-map.json") - let stream = BufferedOutputByteStream() - - stream <<< "{\n" - - let masterDepsPath = tempsPath.appending(component: "master.swiftdeps") - stream <<< " \"\": {\n" - if buildParameters.useWholeModuleOptimization { - let moduleName = target.c99name - stream <<< " \"dependencies\": \"" <<< tempsPath.appending(component: moduleName + ".d").nativePathString(escaped: true) <<< "\",\n" - // FIXME: Need to record this deps file for processing it later. - stream <<< " \"object\": \"" <<< tempsPath.appending(component: moduleName + ".o").nativePathString(escaped: true) <<< "\",\n" - } - stream <<< " \"swift-dependencies\": \"" <<< masterDepsPath.nativePathString(escaped: true) <<< "\"\n" - - stream <<< " },\n" - - // Write out the entries for each source file. - let sources = target.sources.paths + derivedSources.paths + pluginDerivedSources.paths - for (idx, source) in sources.enumerated() { - let object = try objects[idx] - let objectDir = object.parentDirectory - - let sourceFileName = source.basenameWithoutExt - - let swiftDepsPath = objectDir.appending(component: sourceFileName + ".swiftdeps") - - stream <<< " \"" <<< source.nativePathString(escaped: true) <<< "\": {\n" - - if (!buildParameters.useWholeModuleOptimization) { - let depsPath = objectDir.appending(component: sourceFileName + ".d") - stream <<< " \"dependencies\": \"" <<< depsPath.nativePathString(escaped: true) <<< "\",\n" - // FIXME: Need to record this deps file for processing it later. - } - - stream <<< " \"object\": \"" <<< object.nativePathString(escaped: true) <<< "\",\n" - - let partialModulePath = objectDir.appending(component: sourceFileName + "~partial.swiftmodule") - stream <<< " \"swiftmodule\": \"" <<< partialModulePath.nativePathString(escaped: true) <<< "\",\n" - stream <<< " \"swift-dependencies\": \"" <<< swiftDepsPath.nativePathString(escaped: true) <<< "\"\n" - stream <<< " }" <<< ((idx + 1) < sources.count ? "," : "") <<< "\n" - } - - stream <<< "}\n" - - try self.fileSystem.createDirectory(path.parentDirectory, recursive: true) - try self.fileSystem.writeFileContents(path, bytes: stream.bytes) - return path - } - - /// Generates the module map for the Swift target and returns its path. - private func generateModuleMap() throws -> AbsolutePath { - let path = tempsPath.appending(component: moduleMapFilename) - - let stream = BufferedOutputByteStream() - stream <<< "module \(target.c99name) {\n" - stream <<< " header \"" <<< objCompatibilityHeaderPath.pathString <<< "\"\n" - stream <<< " requires objc\n" - stream <<< "}\n" - - // Return early if the contents are identical. - if self.fileSystem.isFile(path), try self.fileSystem.readFileContents(path) == stream.bytes { - return path - } - - try self.fileSystem.createDirectory(path.parentDirectory, recursive: true) - try self.fileSystem.writeFileContents(path, bytes: stream.bytes) - - return path - } - - /// Returns the path to the ObjC compatibility header for this Swift target. - var objCompatibilityHeaderPath: AbsolutePath { - return tempsPath.appending(component: "\(target.name)-Swift.h") - } - - /// Returns the build flags from the declared build settings. - private func buildSettingsFlags() throws -> [String] { - let scope = buildParameters.createScope(for: target) - var flags: [String] = [] - - // Swift defines. - let swiftDefines = scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) - flags += swiftDefines.map({ "-D" + $0 }) - - // Other Swift flags. - flags += scope.evaluate(.OTHER_SWIFT_FLAGS) - - // Add C flags by prefixing them with -Xcc. - // - // C defines. - let cDefines = scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) - flags += cDefines.flatMap({ ["-Xcc", "-D" + $0] }) - - // Header search paths. - let headerSearchPaths = scope.evaluate(.HEADER_SEARCH_PATHS) - flags += try headerSearchPaths.flatMap({ path -> [String] in - return ["-Xcc", "-I\(try AbsolutePath(validating: path, relativeTo: target.sources.root).pathString)"] - }) - - // Other C flags. - flags += scope.evaluate(.OTHER_CFLAGS).flatMap({ ["-Xcc", $0] }) - - return flags - } - - /// A list of compilation conditions to enable for conditional compilation expressions. - private var activeCompilationConditions: [String] { - var compilationConditions = ["-DSWIFT_PACKAGE"] - - switch buildParameters.configuration { - case .debug: - compilationConditions += ["-DDEBUG"] - case .release: - break - } - - return compilationConditions - } - - /// Optimization arguments according to the build configuration. - private var optimizationArguments: [String] { - switch buildParameters.configuration { - case .debug: - return ["-Onone"] - case .release: - return ["-O"] - } - } - - /// Testing arguments according to the build configuration. - private var testingArguments: [String] { - if self.isTestTarget { - // test targets must be built with -enable-testing - // since its required for test discovery (the non objective-c reflection kind) - return ["-enable-testing"] - } else if buildParameters.enableTestability { - return ["-enable-testing"] - } else { - return [] - } - } - - /// Module cache arguments. - private var moduleCacheArgs: [String] { - get throws { - return ["-module-cache-path", try buildParameters.moduleCache.pathString] - } - } - - private var stdlibArguments: [String] { - if buildParameters.shouldLinkStaticSwiftStdlib && - buildParameters.triple.isSupportingStaticStdlib { - return ["-static-stdlib"] - } else { - return [] - } - } -} - -public final class MixedTargetBuildDescription { - - /// The target described by this target. - let target: ResolvedTarget - - /// The list of all resource files in the target. - var resources: [Resource] { target.underlyingTarget.resources } - - /// If this target is a test target. - var isTestTarget: Bool { target.underlyingTarget.type == .test } - - /// The objects in this target. This includes both the Swift and Clang object files. - var objects: [AbsolutePath] { - get throws { - try swiftTargetBuildDescription.objects + - clangTargetBuildDescription.objects - } - } - - /// Path to the bundle generated for this module (if any). - var bundlePath: AbsolutePath? { swiftTargetBuildDescription.bundlePath } - - /// Path to the resource Info.plist file, if generated. - var resourceBundleInfoPlistPath: AbsolutePath? { - swiftTargetBuildDescription.resourceBundleInfoPlistPath - } - - /// The path to the VFS overlay file that overlays the public headers of - /// the Clang part of the target over the target's build directory. - let allProductHeadersOverlay: AbsolutePath - - /// The paths to the targets's public headers. - let publicHeaderPaths: [AbsolutePath] - - /// The modulemap file for this target. - let moduleMap: AbsolutePath - - /// Paths to the binary libraries the target depends on. - var libraryBinaryPaths: Set { - swiftTargetBuildDescription.libraryBinaryPaths - .union(clangTargetBuildDescription.libraryBinaryPaths) - } - - /// The build description for the Clang sources. - let clangTargetBuildDescription: ClangTargetBuildDescription - - /// The build description for the Swift sources. - let swiftTargetBuildDescription: SwiftTargetBuildDescription - - init( - package: ResolvedPackage, - target: ResolvedTarget, - toolsVersion: ToolsVersion, - additionalFileRules: [FileRuleDescription] = [], - buildParameters: BuildParameters, - buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], - prebuildCommandResults: [PrebuildCommandResult] = [], - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) throws { - guard let mixedTarget = target.underlyingTarget as? MixedTarget else { - throw InternalError("underlying target type mismatch \(target)") - } - - guard buildParameters.triple.isDarwin() else { - throw StringError("Targets with mixed language sources are only " + - "supported on Apple platforms.") - } - - self.target = target - - let clangResolvedTarget = ResolvedTarget( - target: mixedTarget.clangTarget, - dependencies: target.dependencies, - defaultLocalization: target.defaultLocalization, - platforms: target.platforms - ) - self.clangTargetBuildDescription = try ClangTargetBuildDescription( - target: clangResolvedTarget, - toolsVersion: toolsVersion, - buildParameters: buildParameters, - fileSystem: fileSystem, - isWithinMixedTarget: true - ) - - let swiftResolvedTarget = ResolvedTarget( - target: mixedTarget.swiftTarget, - dependencies: target.dependencies, - defaultLocalization: target.defaultLocalization, - platforms: target.platforms - ) - self.swiftTargetBuildDescription = try SwiftTargetBuildDescription( - package: package, - target: swiftResolvedTarget, - toolsVersion: toolsVersion, - additionalFileRules: additionalFileRules, - buildParameters: buildParameters, - buildToolPluginInvocationResults: buildToolPluginInvocationResults, - prebuildCommandResults: prebuildCommandResults, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - isWithinMixedTarget: true - ) - - let interopHeaderPath = swiftTargetBuildDescription.objCompatibilityHeaderPath - - // A mixed target's build directory uses three subdirectories to - // distinguish between build artifacts: - // - Intermediates: Stores artifacts used during the target's build. - // - Product: Stores artifacts used by clients of the target. - // - InteropSupport: If needed, stores a generated umbrella header - // for use during the target's build and by clients of the target. - let tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") - let intermediatesDirectory = tempsPath.appending(component: "Intermediates") - let productDirectory = tempsPath.appending(component: "Product") - let interopSupportDirectory: AbsolutePath? - - // Filenames for VFS overlay files. - let allProductHeadersFilename = "all-product-headers.yaml" - let unextendedModuleOverlayFilename = "unextended-module-overlay.yaml" - - // Used to generate both product and intermediate artifacts for the - // target. - let moduleMapGenerator = ModuleMapGenerator( - targetName: mixedTarget.clangTarget.name, - moduleName: mixedTarget.clangTarget.c99name, - publicHeadersDir: mixedTarget.clangTarget.includeDir, - fileSystem: fileSystem - ) - - // MARK: Conditionally generate an umbrella header for interoptability - - // When the Swift compiler creates the generated interop header - // (`$(ModuleName)-Swift.h`) for Objective-C compatible Swift API - // (via `-emit-objc-header`), any Objective-C symbol that cannot be - // forward declared (e.g. superclass, protocol, etc.) will attempt to - // be imported via a bridging or umbrella header. Since the compiler - // evaluates the target as a framework (as opposed to an app), the - // compiler assumes* an umbrella header exists in a subdirectory (named - // after the module) within the public headers directory: - // - // #import <$(ModuleName)/$(ModuleName).h> - // - // The compiler assumes that the above path can be resolved relative to - // the public header directory. Instead of forcing package authors to - // structure their packages around that constraint, the package manager - // generates an umbrella header if needed and will pass it along as a - // header search path when building the target. - // - // *: https://developer.apple.com/documentation/swift/importing-objective-c-into-swift - let umbrellaHeaderPathComponents = [mixedTarget.c99name, "\(mixedTarget.c99name).h"] - let potentialUmbrellaHeadersPath = mixedTarget.clangTarget.includeDir - .appending(components: umbrellaHeaderPathComponents) - // Check if an umbrella header at - // `PUBLIC_HDRS_DIR/$(ModuleName)/$(ModuleName).h` already exists. - if !fileSystem.isFile(potentialUmbrellaHeadersPath) { - interopSupportDirectory = tempsPath.appending(component: "InteropSupport") - let generatedUmbrellaHeaderPath = interopSupportDirectory! - .appending(components: umbrellaHeaderPathComponents) - // Populate a stream that will become the generated umbrella header. - let stream = BufferedOutputByteStream() - mixedTarget.clangTarget.headers - // One of the requirements for a Swift API to be Objective-C - // compatible and therefore included in the generated interop - // header is that it has `public` or `open` visbility. This - // means that such Swift API can only reference (e.g. subclass) - // Objective-C types defined in the target's public headers. - // Because of this, the generated umbrella header will only - // include public headers so all other can be filtered out. - .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } - // Filter out non-Objective-C/C headers. - // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? - .filter { $0.basename.hasSuffix(".h") } - // Add each remaining header to the generated umbrella header. - .forEach { - // Import the header, followed by a newline. - stream <<< "#import \"\($0)\"\n" - } - - try fileSystem.writeFileContentsIfNeeded( - generatedUmbrellaHeaderPath, - bytes: stream.bytes - ) - } else { - // An umbrella header in the desired format already exists so the - // interop support directory is not needed. - interopSupportDirectory = nil - } - - // Clients will later depend on the public header directory, and, if an - // umbrella header was created, the header's root directory. - self.publicHeaderPaths = interopSupportDirectory != nil ? - [mixedTarget.clangTarget.includeDir, interopSupportDirectory!] : - [mixedTarget.clangTarget.includeDir] - - // MARK: Generate products to be used by client of the target. - - // Path to the module map used by clients to access the mixed target's - // public API. - let productModuleMapPath = productDirectory.appending(component: moduleMapFilename) - - switch mixedTarget.clangTarget.moduleMapType { - // When the mixed target has a custom module map, clients of the target - // will be passed a module map *and* VFS overlay at buildtime to access - // the mixed target's public API. The following is therefore needed: - // - Create a copy of the custom module map, adding a submodule to - // expose the target's generated interop header. This allows clients - // of the target to consume the mixed target's public Objective-C - // compatible Swift API in a Clang context. - // - Create a VFS overlay to swap in the modified module map for the - // original custom module map. This is done so relative paths in the - // modified module map can be resolved as they would have been in the - // original module map. - case .custom(let customModuleMapPath): - let customModuleMapContents: String = - try fileSystem.readFileContents(customModuleMapPath) - - // Check that custom module map does not contain a Swift submodule. - if customModuleMapContents.contains("\(target.c99name).Swift") { - throw StringError( - "The target's module map may not contain a Swift " + - "submodule for the module \(target.c99name)." - ) - } - - // Extend the contents and write it to disk, if needed. - let stream = BufferedOutputByteStream() - stream <<< customModuleMapContents - stream <<< """ - module \(target.c99name).Swift { - header "\(interopHeaderPath)" - requires objc - } - """ - try fileSystem.writeFileContentsIfNeeded( - productModuleMapPath, - bytes: stream.bytes - ) - - // Set the original custom module map path as the module map path - // for the target. The below VFS overlay will redirect to the - // contents of the modified module map. - self.moduleMap = customModuleMapPath - self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) - - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: customModuleMapPath.parentDirectory.pathString, - contents: [ - // Redirect the custom `module.modulemap` to the - // modified module map in the product directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: productModuleMapPath.pathString - ), - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - ] - ) - ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) - - // When the mixed target does not have a custom module map, one will be - // generated as a product for use by clients. - // - Note: When `.none`, the mixed target has no public headers. Even - // then, a module map is created to expose the generated interop - // header so clients can access the public Objective-C compatible - // Swift API in a Clang context. - case .umbrellaHeader, .umbrellaDirectory, .none: - let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType - try moduleMapGenerator.generateModuleMap( - type: generatedModuleMapType, - at: productModuleMapPath, - interopHeaderPath: interopHeaderPath - ) - - // Set the generated module map as the module map for the target. - self.moduleMap = productModuleMapPath - self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) - - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: mixedTarget.clangTarget.includeDir.pathString, - contents: [ - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - ] - ) - ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) - } - - // MARK: Generate intermediate artifacts used to build the target. - // Building a mixed target uses intermediate module maps to expose - // private headers to the Swift part of the module. - - // 1. Generate an intermediate module map that exposes all headers, - // including the submodule with the generated Swift header. - let intermediateModuleMapPath = intermediatesDirectory.appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(mixedTarget.clangTarget.path), - at: intermediateModuleMapPath, - interopHeaderPath: interopHeaderPath - ) - - // 2. Generate an intermediate module map that exposes all headers. - // When building the Swift part of the mixed target, a module map will - // be needed to access types from the Objective-C part of the target. - // However, this module map should not expose the generated Swift - // header since it will not exist yet. - let unextendedModuleMapPath = intermediatesDirectory.appending(component: unextendedModuleMapFilename) - // Generating module maps that include non-Objective-C headers is not - // supported. - // FIXME(ncooke3): Link to evolution post. - // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? - let nonObjcHeaders: [AbsolutePath] = mixedTarget.clangTarget.headers - .filter { $0.extension != "h" } - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(mixedTarget.clangTarget.path), - at: unextendedModuleMapPath, - excludeHeaders: nonObjcHeaders - ) - - // 3. Use VFS overlays to purposefully expose specific resources (e.g. - // module map) during the build. The directory to add a VFS overlay in - // depends on the presence of a custom module map. - let rootOverlayResourceDirectory: AbsolutePath - if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { - // To avoid the custom module map causing a module redeclaration - // error, a VFS overlay is used when building the target to - // redirect the custom module map to the modified module map in the - // build directory. This redirecting overlay is placed in the - // custom module map's parent directory, as to replace it. - rootOverlayResourceDirectory = customModuleMapPath.parentDirectory - } else { - // Since no custom module map exists, the build directory can - // be used as the root of the VFS overlay. In this case, the - // VFS overlay's sole purpose is to expose the generated Swift - // header. - rootOverlayResourceDirectory = intermediatesDirectory - } - - let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the modified - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: intermediateModuleMapPath.pathString - ), - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ) - ] - ) - ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - - let unextendedModuleMapOverlayPath = intermediatesDirectory.appending(component: unextendedModuleOverlayFilename) - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the *unextended* - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: unextendedModuleMapPath.pathString - ) - ] - ) - ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) - - // 4. Tie everything together by passing build flags. - - // Importing the underlying module will build the Objective-C - // part of the module. In order to find the underlying module, - // a `module.modulemap` needs to be discoverable via a header - // search path. - swiftTargetBuildDescription.additionalFlags += [ - "-import-underlying-module", - "-I", rootOverlayResourceDirectory.pathString - ] - - swiftTargetBuildDescription.appendClangFlags( - // Pass both VFS overlays to the underlying Clang compiler. - "-ivfsoverlay", allProductHeadersPath.pathString, - "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, - // Adding the root of the target's source as a header search - // path allows for importing headers using paths relative to - // the root. - "-I", mixedTarget.path.pathString - ) - - clangTargetBuildDescription.additionalFlags += [ - // Adding the root of the target's source as a header search - // path allows for importing headers using paths relative to - // the root. - "-I", mixedTarget.path.pathString, - // Include overlay file to add interop header to overlay directory. - "-ivfsoverlay", allProductHeadersPath.pathString, - // The above two args add the interop header in the overlayed - // directory. Pass the overlay directory as a search path so the - // generated header can be imported. - "-I", intermediatesDirectory.pathString - ] - - // If a generated umbrella header was created, add its root directory - // as a header search path. This will resolve its import within the - // generated interop header. - if let interopSupportDirectory = interopSupportDirectory { - swiftTargetBuildDescription.appendClangFlags( - "-I", interopSupportDirectory.pathString - ) - clangTargetBuildDescription.additionalFlags += [ - "-I", interopSupportDirectory.pathString - ] - } - } -} - -/// The build description for a product. -public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription { - - /// The reference to the product. - public let package: ResolvedPackage - - /// The reference to the product. - public let product: ResolvedProduct - - /// The tools version of the package that declared the product. This can - /// can be used to conditionalize semantically significant changes in how - /// a target is built. - public let toolsVersion: ToolsVersion - - /// The build parameters. - public let buildParameters: BuildParameters - - /// All object files to link into this product. - /// - // Computed during build planning. - public fileprivate(set) var objects = SortedArray() - - /// The dynamic libraries this product needs to link with. - // Computed during build planning. - fileprivate(set) var dylibs: [ProductBuildDescription] = [] - - /// Any additional flags to be added. These flags are expected to be computed during build planning. - fileprivate var additionalFlags: [String] = [] - - /// The list of targets that are going to be linked statically in this product. - fileprivate var staticTargets: [ResolvedTarget] = [] - - /// The list of Swift modules that should be passed to the linker. This is required for debugging to work. - fileprivate var swiftASTs: SortedArray = .init() - - /// Paths to the binary libraries the product depends on. - fileprivate var libraryBinaryPaths: Set = [] - - /// Paths to tools shipped in binary dependencies - var availableTools: [String: AbsolutePath] = [:] - - /// Path to the temporary directory for this product. - var tempsPath: AbsolutePath { - return buildParameters.buildPath.appending(component: product.name + ".product") - } - - /// Path to the link filelist file. - var linkFileListPath: AbsolutePath { - return tempsPath.appending(component: "Objects.LinkFileList") - } - - /// File system reference. - private let fileSystem: FileSystem - - /// ObservabilityScope with which to emit diagnostics - private let observabilityScope: ObservabilityScope - - /// Create a build description for a product. - init( - package: ResolvedPackage, - product: ResolvedProduct, - toolsVersion: ToolsVersion, - buildParameters: BuildParameters, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) throws { - guard product.type != .library(.automatic) else { - throw InternalError("Automatic type libraries should not be described.") - } - - self.package = package - self.product = product - self.toolsVersion = toolsVersion - self.buildParameters = buildParameters - self.fileSystem = fileSystem - self.observabilityScope = observabilityScope - } - - /// Strips the arguments which should *never* be passed to Swift compiler - /// when we're linking the product. - /// - /// We might want to get rid of this method once Swift driver can strip the - /// flags itself, . - private func stripInvalidArguments(_ args: [String]) -> [String] { - let invalidArguments: Set = ["-wmo", "-whole-module-optimization"] - return args.filter({ !invalidArguments.contains($0) }) - } - - private var deadStripArguments: [String] { - if !buildParameters.linkerDeadStrip { - return [] - } - - switch buildParameters.configuration { - case .debug: - return [] - case .release: - if buildParameters.triple.isDarwin() { - return ["-Xlinker", "-dead_strip"] - } else if buildParameters.triple.isWindows() { - return ["-Xlinker", "/OPT:REF"] - } else if buildParameters.triple.arch == .wasm32 { - // FIXME: wasm-ld strips data segments referenced through __start/__stop symbols - // during GC, and it removes Swift metadata sections like swift5_protocols - // We should add support of SHF_GNU_RETAIN-like flag for __attribute__((retain)) - // to LLVM and wasm-ld - // This workaround is required for not only WASI but also all WebAssembly archs - // using wasm-ld (e.g. wasm32-unknown-unknown). So this branch is conditioned by - // arch == .wasm32 - return [] - } else { - return ["-Xlinker", "--gc-sections"] - } - } - } - - /// The arguments to the librarian to create a static library. - public func archiveArguments() throws -> [String] { - let librarian = buildParameters.toolchain.librarianPath.pathString - let triple = buildParameters.triple - if triple.isWindows(), librarian.hasSuffix("link") || librarian.hasSuffix("link.exe") { - return [librarian, "/LIB", "/OUT:\(binaryPath.pathString)", "@\(linkFileListPath.pathString)"] - } - if triple.isDarwin(), librarian.hasSuffix("libtool") { - return [librarian, "-static", "-o", binaryPath.pathString, "@\(linkFileListPath.pathString)"] - } - return [librarian, "crs", binaryPath.pathString, "@\(linkFileListPath.pathString)"] - } - - /// The arguments to link and create this product. - public func linkArguments() throws -> [String] { - var args = [buildParameters.toolchain.swiftCompilerPath.pathString] - args += buildParameters.sanitizers.linkSwiftFlags() - args += additionalFlags - - // Pass `-g` during a *release* build so the Swift driver emits a dSYM file for the binary. - if buildParameters.configuration == .release { - if buildParameters.triple.isWindows() { - args += ["-Xlinker", "-debug"] - } else { - args += ["-g"] - } - } - - // Only add the build path to the framework search path if there are binary frameworks to link against. - if !libraryBinaryPaths.isEmpty { - args += ["-F", buildParameters.buildPath.pathString] - } - - args += ["-L", buildParameters.buildPath.pathString] - args += ["-o", binaryPath.pathString] - args += ["-module-name", product.name.spm_mangledToC99ExtendedIdentifier()] - args += dylibs.map({ "-l" + $0.product.name }) - - // Add arguments needed for code coverage if it is enabled. - if buildParameters.enableCodeCoverage { - args += ["-profile-coverage-mapping", "-profile-generate"] - } - - let containsSwiftTargets = product.containsSwiftTargets - - switch product.type { - case .library(.automatic): - throw InternalError("automatic library not supported") - case .library(.static): - // No arguments for static libraries. - return [] - case .test: - // Test products are bundle when using objectiveC, executable when using test entry point. - switch buildParameters.testProductStyle { - case .loadableBundle: - args += ["-Xlinker", "-bundle"] - case .entryPointExecutable: - args += ["-emit-executable"] - } - args += deadStripArguments - case .library(.dynamic): - args += ["-emit-library"] - if buildParameters.triple.isDarwin() { - let relativePath = "@rpath/\(buildParameters.binaryRelativePath(for: product).pathString)" - args += ["-Xlinker", "-install_name", "-Xlinker", relativePath] - } - args += deadStripArguments - case .executable, .snippet: - // Link the Swift stdlib statically, if requested. - if buildParameters.shouldLinkStaticSwiftStdlib { - if buildParameters.triple.isDarwin() { - self.observabilityScope.emit(.swiftBackDeployError) - } else if buildParameters.triple.isSupportingStaticStdlib { - args += ["-static-stdlib"] - } - } - args += ["-emit-executable"] - args += deadStripArguments - - // If we're linking an executable whose main module is implemented in Swift, - // we rename the `__main` entry point symbol to `_main` again. - // This is because executable modules implemented in Swift are compiled with - // a main symbol named that way to allow tests to link against it without - // conflicts. If we're using a linker that doesn't support symbol renaming, - // we will instead have generated a source file containing the redirect. - // Support for linking tests against executables is conditional on the tools - // version of the package that defines the executable product. - let executableTarget = try product.executableTarget - if executableTarget.underlyingTarget is SwiftTarget, toolsVersion >= .v5_5, - buildParameters.canRenameEntrypointFunctionName { - if let flags = buildParameters.linkerFlagsForRenamingMainFunction(of: executableTarget) { - args += flags - } - } - case .plugin: - throw InternalError("unexpectedly asked to generate linker arguments for a plugin product") - } - - // Set rpath such that dynamic libraries are looked up - // adjacent to the product. - if buildParameters.triple.isLinux() { - args += ["-Xlinker", "-rpath=$ORIGIN"] - } else if buildParameters.triple.isDarwin() { - let rpath = product.type == .test ? "@loader_path/../../../" : "@loader_path" - args += ["-Xlinker", "-rpath", "-Xlinker", rpath] - } - args += ["@\(linkFileListPath.pathString)"] - - // Embed the swift stdlib library path inside tests and executables on Darwin. - if containsSwiftTargets { - let useStdlibRpath: Bool - switch product.type { - case .library(let type): - useStdlibRpath = type == .dynamic - case .test, .executable, .snippet: - useStdlibRpath = true - case .plugin: - throw InternalError("unexpectedly asked to generate linker arguments for a plugin product") - } - - // When deploying to macOS prior to macOS 12, add an rpath to the - // back-deployed concurrency libraries. - if useStdlibRpath, buildParameters.triple.isDarwin(), - let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS), - macOSSupportedPlatform.version.major < 12 { - let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib - .parentDirectory - .parentDirectory - .appending(component: "swift-5.5") - .appending(component: "macosx") - args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString] - } - } - - // Don't link runtime compatibility patch libraries if there are no - // Swift sources in the target. - if !containsSwiftTargets { - args += ["-runtime-compatibility-version", "none"] - } - - // Add the target triple from the first target in the product. - // - // We can just use the first target of the product because the deployment target - // setting is the package-level right now. We might need to figure out a better - // answer for libraries if/when we support specifying deployment target at the - // target-level. - args += try buildParameters.targetTripleArgs(for: product.targets[0]) - - // Add arguments from declared build settings. - args += self.buildSettingsFlags() - - // Add AST paths to make the product debuggable. This array is only populated when we're - // building for Darwin in debug configuration. - args += swiftASTs.flatMap{ ["-Xlinker", "-add_ast_path", "-Xlinker", $0.pathString] } - - args += buildParameters.toolchain.extraFlags.swiftCompilerFlags - // User arguments (from -Xlinker and -Xswiftc) should follow generated arguments to allow user overrides - args += buildParameters.linkerFlags - args += stripInvalidArguments(buildParameters.swiftCompilerFlags) - - // Add toolchain's libdir at the very end (even after the user -Xlinker arguments). - // - // This will allow linking to libraries shipped in the toolchain. - let toolchainLibDir = try buildParameters.toolchain.toolchainLibDir - if self.fileSystem.isDirectory(toolchainLibDir) { - args += ["-L", toolchainLibDir.pathString] - } - - return args - } - - /// Writes link filelist to the filesystem. - func writeLinkFilelist(_ fs: FileSystem) throws { - let stream = BufferedOutputByteStream() - - for object in objects { - stream <<< object.pathString.spm_shellEscaped() <<< "\n" - } - - try fs.createDirectory(linkFileListPath.parentDirectory, recursive: true) - try fs.writeFileContents(linkFileListPath, bytes: stream.bytes) - } - - /// Returns the build flags from the declared build settings. - private func buildSettingsFlags() -> [String] { - var flags: [String] = [] - - // Linked libraries. - let libraries = OrderedSet(staticTargets.reduce([]) { - $0 + buildParameters.createScope(for: $1).evaluate(.LINK_LIBRARIES) - }) - flags += libraries.map({ "-l" + $0 }) - - // Linked frameworks. - let frameworks = OrderedSet(staticTargets.reduce([]) { - $0 + buildParameters.createScope(for: $1).evaluate(.LINK_FRAMEWORKS) - }) - flags += frameworks.flatMap({ ["-framework", $0] }) - - // Other linker flags. - for target in staticTargets { - let scope = buildParameters.createScope(for: target) - flags += scope.evaluate(.OTHER_LDFLAGS) - } - - return flags - } -} - -/// Description for a plugin target. This is treated a bit differently from the -/// regular kinds of targets, and is not included in the LLBuild description. -/// But because the package graph and build plan are not loaded for incremental -/// builds, this information is included in the BuildDescription, and the plugin -/// targets are compiled directly. -public final class PluginDescription: Codable { - - /// The identity of the package in which the plugin is defined. - public let package: PackageIdentity - - /// The name of the plugin target in that package (this is also the name of - /// the plugin). - public let targetName: String - - /// The names of any plugin products in that package that vend the plugin - /// to other packages. - public let productNames: [String] - - /// The tools version of the package that declared the target. This affects - /// the API that is available in the PackagePlugin module. - public let toolsVersion: ToolsVersion - - /// Swift source files that comprise the plugin. - public let sources: Sources - - /// Initialize a new plugin target description. The target is expected to be - /// a `PluginTarget`. - init( - target: ResolvedTarget, - products: [ResolvedProduct], - package: ResolvedPackage, - toolsVersion: ToolsVersion, - testDiscoveryTarget: Bool = false, - fileSystem: FileSystem - ) throws { - guard target.underlyingTarget is PluginTarget else { - throw InternalError("underlying target type mismatch \(target)") - } - - self.package = package.identity - self.targetName = target.name - self.productNames = products.map{ $0.name } - self.toolsVersion = toolsVersion - self.sources = target.sources - } -} - /// A build plan for a package graph. public class BuildPlan: SPMBuildCore.BuildPlan { @@ -2124,7 +170,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { switch self { case .noBuildableTarget: return """ - The package does not contain a buildable target. + The package does not contain a buildable target. Add at least one `.target` or `.executableTarget` to your `Package.swift`. """ } From fe1f1f74441b6c82ab28ebe42e25ec14df3176c6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 17 Jan 2023 11:16:31 -0500 Subject: [PATCH 101/178] Corresponding test fix for f9ed8d5a99cee7a62ea0840250a6669dc418ad5f --- Tests/BuildTests/BuildPlanTests.swift | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index f2e1ed5da3f..6f22f9abf1a 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1516,6 +1516,10 @@ final class BuildPlanTests: XCTestCase { "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=/path/to/build/debug/lib.build/Product/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", + "-Xcc", "/path/to/build/debug/lib.build/InteropSupport", "-Xcc", + "-ivfsoverlay", "-Xcc", + "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence ]) @@ -1530,7 +1534,8 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-module-cache-path", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-Xcc", "-I", "-Xcc", + "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/lib.build/lib-Swift.h" @@ -1538,12 +1543,13 @@ final class BuildPlanTests: XCTestCase { let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) XCTAssertMatch(clangPartOfLib, [ - "-fobjc-arc", "-target", "x86_64-apple-macosx10.13", "-g", "-O0", + "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-g", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", "/Pkg/Sources/lib", "-ivfsoverlay", "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-I", "/path/to/build/debug/lib.build/Intermediates", + "-I", "/path/to/build/debug/lib.build/InteropSupport", "-fmodules-cache-path=/path/to/build/debug/ModuleCache" ]) @@ -1633,13 +1639,14 @@ final class BuildPlanTests: XCTestCase { result.checkTargetsCount(2) let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") - let exe = try result.target(for: "exe").swiftTarget().compileArguments() XCTAssertMatch(exe, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", - "-fmodule-map-file=/Pkg/Sources/lib/include/module.modulemap", "-Xcc", + "-fmodule-map-file=/Pkg/Sources/lib/include/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", + "-Xcc", "/path/to/build/debug/lib.build/InteropSupport", "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", "-module-cache-path", @@ -1655,7 +1662,8 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-module-cache-path", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-Xcc", "-I", "-Xcc", + "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/lib.build/lib-Swift.h" @@ -1663,12 +1671,13 @@ final class BuildPlanTests: XCTestCase { let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) XCTAssertMatch(clangPartOfLib, [ - "-fobjc-arc", "-target", "x86_64-apple-macosx10.13", "-g", "-O0", + "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-g", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", "/Pkg/Sources/lib", "-ivfsoverlay", "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", - "-I", "/path/to/build/debug/lib.build/Intermediates", + "-I", "/path/to/build/debug/lib.build/Intermediates", "-I", + "/path/to/build/debug/lib.build/InteropSupport", "-fmodules-cache-path=/path/to/build/debug/ModuleCache" ]) From db5f4d0c46bb3a97c4b0d3d0fb23d26af37a8eb4 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 17 Jan 2023 12:10:08 -0500 Subject: [PATCH 102/178] Revert "[Will erase] Make test build" - The fix was merged into 'main' in dd5d6b98f46fdc3618af954eb356623e5f62b313 This reverts commit b310f4e47d242c0c54abc1e0c388517430501a7b. --- Tests/FunctionalTests/PluginTests.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index dc4ad91623c..87ca4129256 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -657,14 +657,10 @@ class PluginTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) XCTAssert(packageGraph.packages.count == 1, "\(packageGraph.packages)") XCTAssert(packageGraph.rootPackages.count == 1, "\(packageGraph.rootPackages)") - let package: ResolvedPackage = try XCTUnwrap(packageGraph.rootPackages.first) + let package = try XCTUnwrap(packageGraph.rootPackages.first) // Find the regular target in our test package. - let libraryTarget = try XCTUnwrap( - package.targets - .map(\.underlyingTarget) - .first{ $0.name == "MyLibrary" } as? SwiftTarget - ) + let libraryTarget = try XCTUnwrap(package.targets.map(\.underlyingTarget).first{ $0.name == "MyLibrary" } as? SwiftTarget) XCTAssertEqual(libraryTarget.type, .library) // Set up a delegate to handle callbacks from the command plugin. In particular we want to know the process identifier. From 04e1bf0a8119ca8a4e5bdf4bebe91b3014431f07 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 17 Jan 2023 12:48:39 -0500 Subject: [PATCH 103/178] Fix BuildPlanTests.swift build failures on Linux --- Tests/BuildTests/BuildPlanTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6f22f9abf1a..e74620d27fa 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -2669,7 +2669,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertThrowsError( try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - graph: graph, + graph: g, fileSystem: fs, observabilityScope: observability.topScope )), @@ -2776,7 +2776,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertThrowsError( try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - graph: graph, + graph: g, fileSystem: fs, observabilityScope: observability.topScope )), From 8f71933db8ad9a98b89adc27c509362c0220de90 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 17 Jan 2023 14:19:12 -0500 Subject: [PATCH 104/178] Add new 'MixedTargetBuildDescription.swift' to 'Build/CMakeLists.txt' --- Sources/Build/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Build/CMakeLists.txt b/Sources/Build/CMakeLists.txt index d5d893053b2..8a975a4f7ea 100644 --- a/Sources/Build/CMakeLists.txt +++ b/Sources/Build/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(Build BuildDescription/ProductBuildDescription.swift BuildDescription/SharedTargetBuildDescription.swift BuildDescription/SwiftTargetBuildDescription.swift + BuildDescription/MixedTargetBuildDescription.swift BuildDescription/TargetBuildDescription.swift BuildOperationBuildSystemDelegateHandler.swift BuildOperation.swift From f12d8bd233c7a19633383f49f9a2867179a6bd6a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 3 Feb 2023 16:21:41 -0500 Subject: [PATCH 105/178] Fix bug where non-public headers could not import public headers --- .../Sources/BasicMixedTarget/FluxCapacitor.h | 9 +++++++++ .../Sources/BasicMixedTarget/FluxCapacitor.m | 13 +++++++++++++ .../MixedTargetBuildDescription.swift | 7 ++++++- 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.h new file mode 100644 index 00000000000..ed4b7708434 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.h @@ -0,0 +1,9 @@ +@import Foundation; + +#import +#import "CarPart.h" + +@interface FluxCapacitor : CarPart +@property (nonatomic, readonly) NSString *serialNumber; +- (instancetype)initWithSerialNumber:(NSString *)serialNumber; +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.m new file mode 100644 index 00000000000..1cc37a5960e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.m @@ -0,0 +1,13 @@ +#import "FluxCapacitor.h" + +@implementation FluxCapacitor + +- (instancetype)initWithSerialNumber:(NSString *)serialNumber { + self = [super init]; + if (self) { + _serialNumber = serialNumber; + } + return self; +} + +@end diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index abc7eb541c4..fb7a4e8db39 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -421,7 +421,12 @@ public final class MixedTargetBuildDescription { // Adding the root of the target's source as a header search // path allows for importing headers using paths relative to // the root. - "-I", mixedTarget.path.pathString + "-I", mixedTarget.path.pathString, + // TODO(ncooke3): When there are no public headers, what happens? + // What is exposed outside of the module? + // TODO(ncooke3): Add comment about below line. + // TODO(ncooke3): Think hard about further edge cases. + "-I", mixedTarget.clangTarget.includeDir.pathString ) self.clangTargetBuildDescription.additionalFlags += [ From 5af34497cd7d4310cf8f5b1f1f2e6cd746ed045d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 10 Feb 2023 17:39:57 -0500 Subject: [PATCH 106/178] Fix tests to accomodate previous commit's changes --- Tests/BuildTests/BuildPlanTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index e74620d27fa..4816a127086 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1535,6 +1535,7 @@ final class BuildPlanTests: XCTestCase { "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-Xcc", "-I", "-Xcc", + "/Pkg/Sources/lib/include", "-Xcc", "-I", "-Xcc", "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", @@ -1662,7 +1663,8 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-Xcc", "-I", "-Xcc", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib","-Xcc", "-I", "-Xcc", + "/Pkg/Sources/lib/include", "-Xcc", "-I", "-Xcc", "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", From 6b82912aedc8023f5abb8dcc1ccdde271010dde2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 10 Feb 2023 17:43:03 -0500 Subject: [PATCH 107/178] Fix typo in name of test --- Tests/FunctionalTests/MixedTargetTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 7ca8758ba3b..c7a60791375 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -204,7 +204,7 @@ final class MixedTargetTests: XCTestCase { } } - func testNonPublicHeadersAreVisibleFromSwiftPartfOfMixedTarget() throws { + func testNonPublicHeadersAreVisibleFromSwiftPartOfMixedTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, From cb7d874367a9c416a0164074e918ad49c053ec19 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 10 Feb 2023 18:10:49 -0500 Subject: [PATCH 108/178] Resolve comment TODOs --- .../MixedTargetBuildDescription.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index fb7a4e8db39..5eefa9c9b38 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -419,13 +419,14 @@ public final class MixedTargetBuildDescription { "-ivfsoverlay", allProductHeadersPath.pathString, "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, // Adding the root of the target's source as a header search - // path allows for importing headers using paths relative to - // the root. + // path allows for importing headers (within the mixed target's + // headers) using paths relative to the root. "-I", mixedTarget.path.pathString, - // TODO(ncooke3): When there are no public headers, what happens? - // What is exposed outside of the module? - // TODO(ncooke3): Add comment about below line. - // TODO(ncooke3): Think hard about further edge cases. + // Adding the public headers directory as a header search + // path allows for importing public headers within the mixed + // target's headers. Note that this directory may not exist in the + // case that there are no public headers. In this case, adding this + // header search path is a no-op. "-I", mixedTarget.clangTarget.includeDir.pathString ) From 599fb6baeecc0fb3737a54a603574669aaf6ee04 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 10 Feb 2023 18:28:17 -0500 Subject: [PATCH 109/178] Add test coverage to ensure dependency headers can be imported in mixed target --- .../Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h index 4facb42d276..7b1124d827d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h @@ -1,6 +1,8 @@ #import @import ClangTarget; +#import "Vessel.h" +#import @interface OldBoat : Vessel @end From 9896c47434881644dc60df0cd7ea791095b3db80 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 24 May 2023 12:00:02 -0400 Subject: [PATCH 110/178] Resolve rebase on 'main' --- Sources/Basics/FileSystem+Extensions.swift | 363 ------------------ .../FileSystem/FileSystem+Extensions.swift | 20 + .../MixedTargetBuildDescription.swift | 19 +- .../TargetBuildDescription.swift | 3 + Sources/Build/LLBuildManifestBuilder.swift | 2 +- .../PackageLoading/ModuleMapGenerator.swift | 33 +- Sources/PackageLoading/PackageBuilder.swift | 6 +- Sources/PackageModel/Target.swift | 15 +- Tests/BuildTests/BuildPlanTests.swift | 28 +- Tests/FunctionalTests/MixedTargetTests.swift | 4 +- .../ModuleMapGenerationTests.swift | 2 +- .../PackageBuilderTests.swift | 12 +- 12 files changed, 94 insertions(+), 413 deletions(-) delete mode 100644 Sources/Basics/FileSystem+Extensions.swift diff --git a/Sources/Basics/FileSystem+Extensions.swift b/Sources/Basics/FileSystem+Extensions.swift deleted file mode 100644 index d1139fd1f73..00000000000 --- a/Sources/Basics/FileSystem+Extensions.swift +++ /dev/null @@ -1,363 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2020-2021 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 class Foundation.FileManager -import struct Foundation.Data -import struct Foundation.UUID -import SystemPackage -import TSCBasic - -// MARK: - user level - -extension FileSystem { - /// SwiftPM directory under user's home directory (~/.swiftpm) - public var dotSwiftPM: AbsolutePath { - get throws { - return try self.homeDirectory.appending(component: ".swiftpm") - } - } - - fileprivate var idiomaticSwiftPMDirectory: AbsolutePath? { - get throws { - return try FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first.flatMap { try AbsolutePath(validating: $0.path) }?.appending(component: "org.swift.swiftpm") - } - } -} - -// MARK: - cache - -extension FileSystem { - private var idiomaticUserCacheDirectory: AbsolutePath? { - // in TSC: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) - self.cachesDirectory - } - - /// SwiftPM cache directory under user's caches directory (if exists) - public var swiftPMCacheDirectory: AbsolutePath { - get throws { - if let path = self.idiomaticUserCacheDirectory { - return path.appending(component: "org.swift.swiftpm") - } else { - return try self.dotSwiftPMCachesDirectory - } - } - } - - fileprivate var dotSwiftPMCachesDirectory: AbsolutePath { - get throws { - return try self.dotSwiftPM.appending(component: "cache") - } - } -} - -extension FileSystem { - public func getOrCreateSwiftPMCacheDirectory() throws -> AbsolutePath { - let idiomaticCacheDirectory = try self.swiftPMCacheDirectory - // Create idiomatic if necessary - if !self.exists(idiomaticCacheDirectory) { - try self.createDirectory(idiomaticCacheDirectory, recursive: true) - } - // Create ~/.swiftpm if necessary - if !self.exists(try self.dotSwiftPM) { - try self.createDirectory(self.dotSwiftPM, recursive: true) - } - // Create ~/.swiftpm/cache symlink if necessary - // locking ~/.swiftpm to protect from concurrent access - try self.withLock(on: self.dotSwiftPM, type: .exclusive) { - if !self.exists(try self.dotSwiftPMCachesDirectory, followSymlink: false) { - try self.createSymbolicLink(dotSwiftPMCachesDirectory, pointingAt: idiomaticCacheDirectory, relative: false) - } - } - return idiomaticCacheDirectory - } -} - -// MARK: - configuration - -extension FileSystem { - /// SwiftPM config directory under user's config directory (if exists) - public var swiftPMConfigurationDirectory: AbsolutePath { - get throws { - if let path = try self.idiomaticSwiftPMDirectory { - return path.appending(component: "configuration") - } else { - return try self.dotSwiftPMConfigurationDirectory - } - } - } - - fileprivate var dotSwiftPMConfigurationDirectory: AbsolutePath { - get throws { - return try self.dotSwiftPM.appending(component: "configuration") - } - } -} - -extension FileSystem { - public func getOrCreateSwiftPMConfigurationDirectory(warningHandler: @escaping (String) -> Void) throws -> AbsolutePath { - let idiomaticConfigurationDirectory = try self.swiftPMConfigurationDirectory - - // temporary 5.6, remove on next version: transition from previous configuration location - if !self.exists(idiomaticConfigurationDirectory) { - try self.createDirectory(idiomaticConfigurationDirectory, recursive: true) - } - - let handleExistingFiles = { (configurationFiles: [AbsolutePath]) in - for file in configurationFiles { - let destination = idiomaticConfigurationDirectory.appending(component: file.basename) - if !self.exists(destination) { - try self.copy(from: file, to: destination) - } else { - // Only emit a warning if source and destination file differ in their contents. - let srcContents = try? self.readFileContents(file) - let dstContents = try? self.readFileContents(destination) - if srcContents != dstContents { - warningHandler("Usage of \(file) has been deprecated. Please delete it and use the new \(destination) instead.") - } - } - } - } - - // in the case where ~/.swiftpm/configuration is not the idiomatic location (eg on macOS where its /Users//Library/org.swift.swiftpm/configuration) - if try idiomaticConfigurationDirectory != self.dotSwiftPMConfigurationDirectory { - // copy the configuration files from old location (eg /Users//Library/org.swift.swiftpm) to new one (eg /Users//Library/org.swift.swiftpm/configuration) - // but leave them there for backwards compatibility (eg older xcode) - let oldConfigDirectory = idiomaticConfigurationDirectory.parentDirectory - if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) { - let configurationFiles = try self.getDirectoryContents(oldConfigDirectory) - .map{ oldConfigDirectory.appending(component: $0) } - .filter{ self.isFile($0) && !self.isSymlink($0) && $0.extension != "lock" && ((try? self.readFileContents($0)) ?? []).count > 0 } - try handleExistingFiles(configurationFiles) - } - // in the case where ~/.swiftpm/configuration is the idiomatic location (eg on Linux) - } else { - // copy the configuration files from old location (~/.swiftpm/config) to new one (~/.swiftpm/configuration) - // but leave them there for backwards compatibility (eg older toolchain) - let oldConfigDirectory = try self.dotSwiftPM.appending(component: "config") - if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) { - let configurationFiles = try self.getDirectoryContents(oldConfigDirectory) - .map{ oldConfigDirectory.appending(component: $0) } - .filter{ self.isFile($0) && !self.isSymlink($0) && $0.extension != "lock" && ((try? self.readFileContents($0)) ?? []).count > 0 } - try handleExistingFiles(configurationFiles) - } - } - // ~temporary 5.6 migration - - // Create idiomatic if necessary - if !self.exists(idiomaticConfigurationDirectory) { - try self.createDirectory(idiomaticConfigurationDirectory, recursive: true) - } - // Create ~/.swiftpm if necessary - if !self.exists(try self.dotSwiftPM) { - try self.createDirectory(self.dotSwiftPM, recursive: true) - } - // Create ~/.swiftpm/configuration symlink if necessary - // locking ~/.swiftpm to protect from concurrent access - try self.withLock(on: self.dotSwiftPM, type: .exclusive) { - if !self.exists(try self.dotSwiftPMConfigurationDirectory, followSymlink: false) { - try self.createSymbolicLink(dotSwiftPMConfigurationDirectory, pointingAt: idiomaticConfigurationDirectory, relative: false) - } - } - - return idiomaticConfigurationDirectory - } -} - -// MARK: - security - -extension FileSystem { - /// SwiftPM security directory under user's security directory (if exists) - public var swiftPMSecurityDirectory: AbsolutePath { - get throws { - if let path = try self.idiomaticSwiftPMDirectory { - return path.appending(component: "security") - } else { - return try self.dotSwiftPMSecurityDirectory - } - } - } - - fileprivate var dotSwiftPMSecurityDirectory: AbsolutePath { - get throws { - return try self.dotSwiftPM.appending(component: "security") - } - } -} - -extension FileSystem { - public func getOrCreateSwiftPMSecurityDirectory() throws -> AbsolutePath { - let idiomaticSecurityDirectory = try self.swiftPMSecurityDirectory - - // temporary 5.6, remove on next version: transition from ~/.swiftpm/security to idiomatic location + symbolic link - if try idiomaticSecurityDirectory != self.dotSwiftPMSecurityDirectory && - self.exists(try self.dotSwiftPMSecurityDirectory) && - self.isDirectory(try self.dotSwiftPMSecurityDirectory) { - try self.removeFileTree(self.dotSwiftPMSecurityDirectory) - } - // ~temporary 5.6 migration - - // Create idiomatic if necessary - if !self.exists(idiomaticSecurityDirectory) { - try self.createDirectory(idiomaticSecurityDirectory, recursive: true) - } - // Create ~/.swiftpm if necessary - if !self.exists(try self.dotSwiftPM) { - try self.createDirectory(self.dotSwiftPM, recursive: true) - } - // Create ~/.swiftpm/security symlink if necessary - // locking ~/.swiftpm to protect from concurrent access - try self.withLock(on: self.dotSwiftPM, type: .exclusive) { - if !self.exists(try self.dotSwiftPMSecurityDirectory, followSymlink: false) { - try self.createSymbolicLink(dotSwiftPMSecurityDirectory, pointingAt: idiomaticSecurityDirectory, relative: false) - } - } - return idiomaticSecurityDirectory - } -} - -// MARK: - cross-compilation destinations - -private let crossCompilationDestinationsDirectoryName = "destinations" - -extension FileSystem { - /// SwiftPM cross-compilation destinations directory (if exists) - public var swiftPMCrossCompilationDestinationsDirectory: AbsolutePath { - get throws { - if let path = try idiomaticSwiftPMDirectory { - return path.appending(component: crossCompilationDestinationsDirectoryName) - } else { - return try dotSwiftPMCrossCompilationDestinationsDirectory - } - } - } - - fileprivate var dotSwiftPMCrossCompilationDestinationsDirectory: AbsolutePath { - get throws { - return try dotSwiftPM.appending(component: crossCompilationDestinationsDirectoryName) - } - } - - public func getSharedCrossCompilationDestinationsDirectory( - explicitDirectory: AbsolutePath? - ) throws -> AbsolutePath? { - if let explicitDestinationsDirectory = explicitDirectory { - // Create the explicit SDKs path if necessary - if !exists(explicitDestinationsDirectory) { - try createDirectory(explicitDestinationsDirectory, recursive: true) - } - return explicitDestinationsDirectory - } else { - return try swiftPMCrossCompilationDestinationsDirectory - } - } - - public func getOrCreateSwiftPMCrossCompilationDestinationsDirectory() throws -> AbsolutePath { - let idiomaticDestinationsDirectory = try swiftPMCrossCompilationDestinationsDirectory - - // Create idiomatic if necessary - if !exists(idiomaticDestinationsDirectory) { - try createDirectory(idiomaticDestinationsDirectory, recursive: true) - } - // Create ~/.swiftpm if necessary - if !exists(try dotSwiftPM) { - try createDirectory(dotSwiftPM, recursive: true) - } - // Create ~/.swiftpm/destinations symlink if necessary - // locking ~/.swiftpm to protect from concurrent access - try withLock(on: dotSwiftPM, type: .exclusive) { - if !exists(try dotSwiftPMCrossCompilationDestinationsDirectory, followSymlink: false) { - try createSymbolicLink( - dotSwiftPMCrossCompilationDestinationsDirectory, - pointingAt: idiomaticDestinationsDirectory, - relative: false - ) - } - } - return idiomaticDestinationsDirectory - } -} - -// MARK: - Utilities - -extension FileSystem { - public func readFileContents(_ path: AbsolutePath) throws -> Data { - return try Data(self.readFileContents(path).contents) - } - - public func readFileContents(_ path: AbsolutePath) throws -> String { - return try String(decoding: self.readFileContents(path), as: UTF8.self) - } - - public func writeFileContents(_ path: AbsolutePath, data: Data) throws { - return try self.writeFileContents(path, bytes: .init(data)) - } - - public func writeFileContents(_ path: AbsolutePath, string: String) throws { - return try self.writeFileContents(path, bytes: .init(encodingAsUTF8: string)) - } - - public func writeFileContents(_ path: AbsolutePath, provider: () -> String) throws { - return try self.writeFileContents(path, string: provider()) - } -} - -extension FileSystem { - public func forceCreateDirectory(at path: AbsolutePath) throws { - try self.createDirectory(path.parentDirectory, recursive: true) - if self.exists(path) { - try self.removeFileTree(path) - } - try self.createDirectory(path, recursive: true) - } -} - -extension FileSystem { - public func stripFirstLevel(of path: AbsolutePath) throws { - let topLevelDirectories = try self.getDirectoryContents(path) - .map{ path.appending(component: $0) } - .filter{ self.isDirectory($0) } - - guard topLevelDirectories.count == 1, let rootDirectory = topLevelDirectories.first else { - throw StringError("stripFirstLevel requires single top level directory") - } - - let tempDirectory = path.parentDirectory.appending(component: UUID().uuidString) - try self.move(from: rootDirectory, to: tempDirectory) - - let rootContents = try self.getDirectoryContents(tempDirectory) - for entry in rootContents { - try self.move(from: tempDirectory.appending(component: entry), to: path.appending(component: entry)) - } - - try self.removeFileTree(tempDirectory) - } -} - -extension FileSystem { - /// Writes the given bytes to the given path. If the file at the given path already exists with the given - /// bytes, nothing is done. - /// - Parameters: - /// - path: The given path to write to. - /// - bytes: The given byte string to write. - /// - Note: If the given path's parent directory does not exist, the file system will recursively create - /// intermediate directories so the given path can be resolved. - public func writeFileContentsIfNeeded(_ path: AbsolutePath, bytes: ByteString) throws { - try createDirectory(path.parentDirectory, recursive: true) - - // If the file exists with the identical contents, there is no need to - // rewrite it. - if let contents = try? readFileContents(path), contents == bytes { - return - } - try writeFileContents(path, bytes: bytes) - } -} diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index 09583a5b65d..5cfed5aae7b 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -616,3 +616,23 @@ extension FileSystem { try self.removeFileTree(tempDirectory) } } + +extension FileSystem { + /// Writes the given string to the given path. If the file at the given path already exists with the given + /// string, nothing is done. + /// - Parameters: + /// - path: The given path to write to. + /// - string: The given string to write. + /// - Note: If the given path's parent directory does not exist, the file system will recursively create + /// intermediate directories so the given path can be resolved. + public func writeFileContentsIfNeeded(_ path: AbsolutePath, string: String) throws { + try createDirectory(path.parentDirectory, recursive: true) + + // If the file exists with the identical contents, there is no need to + // rewrite it. + if let contents: String = try? readFileContents(path), contents == string { + return + } + try writeFileContents(path, string: string) + } +} diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 5eefa9c9b38..d6dda864fd2 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -16,7 +16,6 @@ import PackageGraph import PackageLoading import PackageModel import SPMBuildCore -import TSCBasic public final class MixedTargetBuildDescription { /// The target described by this target. @@ -99,7 +98,8 @@ public final class MixedTargetBuildDescription { toolsVersion: toolsVersion, buildParameters: buildParameters, fileSystem: fileSystem, - isWithinMixedTarget: true + isWithinMixedTarget: true, + observabilityScope: observabilityScope ) let swiftResolvedTarget = ResolvedTarget( @@ -176,8 +176,8 @@ public final class MixedTargetBuildDescription { interopSupportDirectory = tempsPath.appending(component: "InteropSupport") let generatedUmbrellaHeaderPath = interopSupportDirectory! .appending(components: umbrellaHeaderPathComponents) - // Populate a stream that will become the generated umbrella header. - let stream = BufferedOutputByteStream() + + var generatedUmbrellaHeader = "" mixedTarget.clangTarget.headers // One of the requirements for a Swift API to be Objective-C // compatible and therefore included in the generated interop @@ -193,12 +193,12 @@ public final class MixedTargetBuildDescription { // Add each remaining header to the generated umbrella header. .forEach { // Import the header, followed by a newline. - stream <<< "#import \"\($0)\"\n" + generatedUmbrellaHeader.append("#import \"\($0)\"\n") } try fileSystem.writeFileContentsIfNeeded( generatedUmbrellaHeaderPath, - bytes: stream.bytes + string: generatedUmbrellaHeader ) } else { // An umbrella header in the desired format already exists so the @@ -243,9 +243,8 @@ public final class MixedTargetBuildDescription { } // Extend the contents and write it to disk, if needed. - let stream = BufferedOutputByteStream() - stream <<< customModuleMapContents - stream <<< """ + let productModuleMap = """ + \(customModuleMapContents) module \(target.c99name).Swift { header "\(interopHeaderPath)" requires objc @@ -253,7 +252,7 @@ public final class MixedTargetBuildDescription { """ try fileSystem.writeFileContentsIfNeeded( productModuleMapPath, - bytes: stream.bytes + string: productModuleMap ) // Set the original custom module map path as the module map path diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index 8d90b60dc32..5f0d62f578e 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -104,6 +104,9 @@ public enum TargetBuildDescription { return target.buildToolPluginInvocationResults case .clang(let target): return target.buildToolPluginInvocationResults + // TODO(ncooke3): How should we handle this for mixed-lang targets? + case .mixed: + return [] } } } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index e86e588669e..ec801225bfb 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -836,7 +836,7 @@ extension LLBuildManifestBuilder { // don't need to block building of a module until its resources are assembled but // we don't currently have a good way to express that resources should be built // whenever a module is being built. - if let resourcesNode = createResourcesBundle(for: .clang(target)) { + if let resourcesNode = try createResourcesBundle(for: .clang(target)) { inputs.append(resourcesNode) } } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 622ef70768c..54731c6c015 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -187,32 +187,41 @@ public struct ModuleMapGenerator { excludeHeaders: [AbsolutePath] = [], interopHeaderPath: AbsolutePath? = nil ) throws { - let stream = BufferedOutputByteStream() - stream <<< "module \(moduleName) {\n" + var moduleMap = "module \(moduleName) {\n" if let type = type { switch type { case .umbrellaHeader(let hdr): - stream <<< " umbrella header \"\(hdr.moduleEscapedPathString)\"\n" + moduleMap.append(" umbrella header \"\(hdr.moduleEscapedPathString)\"\n") case .umbrellaDirectory(let dir): - stream <<< " umbrella \"\(dir.moduleEscapedPathString)\"\n" + moduleMap.append(" umbrella \"\(dir.moduleEscapedPathString)\"\n") } excludeHeaders.forEach { - stream <<< " exclude header \"\($0.moduleEscapedPathString)\"\n" + moduleMap.append(" exclude header \"\($0.moduleEscapedPathString)\"\n") } } + moduleMap.append( + """ + export * + } + + """ + ) - stream <<< " export *\n" - stream <<< "}\n" if let interopHeaderPath = interopHeaderPath { - stream <<< "module \(moduleName).Swift {\n" - stream <<< " header \"\(interopHeaderPath.moduleEscapedPathString)\"\n" - stream <<< " requires objc\n" - stream <<< "}\n" + moduleMap.append( + """ + module \(moduleName).Swift { + header \"\(interopHeaderPath.moduleEscapedPathString)\" + requires objc + } + + """ + ) } // If the file exists with the identical contents, we don't need to rewrite it. // Otherwise, compiler will recompile even if nothing else has changed. - try fileSystem.writeFileContentsIfNeeded(path, bytes: stream.bytes) + try fileSystem.writeFileContentsIfNeeded(path, string: moduleMap) } } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index b752b1becf2..45358897c4a 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -992,8 +992,12 @@ public final class PackageBuilder { ignored: ignored, others: others, dependencies: dependencies, + packageAccess: potentialModule.packageAccess, swiftVersion: try swiftVersion(), - buildSettings: buildSettings) + buildSettings: buildSettings, + usesUnsafeFlags: manifestTarget.usesUnsafeFlags + + ) } else if sources.hasSwiftSources { return SwiftTarget( diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift index 5ce2a3f4d05..0f5400fd12b 100644 --- a/Sources/PackageModel/Target.swift +++ b/Sources/PackageModel/Target.swift @@ -630,9 +630,11 @@ public final class MixedTarget: Target { ignored: [AbsolutePath] = [], others: [AbsolutePath] = [], dependencies: [Target.Dependency] = [], + packageAccess: Bool, swiftVersion: SwiftLanguageVersion, buildSettings: BuildSettings.AssignmentTable = .init(), - pluginUsages: [PluginUsage] = [] + pluginUsages: [PluginUsage] = [], + usesUnsafeFlags: Bool ) throws { guard type == .library || type == .test else { throw StringError( @@ -660,9 +662,11 @@ public final class MixedTarget: Target { ignored: ignored, others: others, dependencies: dependencies, + packageAccess: packageAccess, swiftVersion: swiftVersion, buildSettings: buildSettings, - pluginUsages: pluginUsages + pluginUsages: pluginUsages, + usesUnsafeFlags: usesUnsafeFlags ) let clangSources = Sources( @@ -687,7 +691,8 @@ public final class MixedTarget: Target { resources: resources, ignored: ignored, others: others, - buildSettings: buildSettings + buildSettings: buildSettings, + usesUnsafeFlags: usesUnsafeFlags ) super.init( @@ -700,8 +705,10 @@ public final class MixedTarget: Target { ignored: ignored, others: others, dependencies: dependencies, + packageAccess: packageAccess, buildSettings: buildSettings, - pluginUsages: pluginUsages + pluginUsages: pluginUsages, + usesUnsafeFlags: usesUnsafeFlags ) } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 4816a127086..75524791f93 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1468,8 +1468,8 @@ final class BuildPlanTests: XCTestCase { fileSystem: fs, manifests: [ Manifest.createRootManifest( - name: "Pkg", - path: .init(path: "/Pkg"), + displayName: "Pkg", + path: "/Pkg", // FIXME(ncooke3): Update error message with support version. toolsVersion: .vNext, targets: [ @@ -1599,8 +1599,8 @@ final class BuildPlanTests: XCTestCase { fileSystem: fs, manifests: [ Manifest.createRootManifest( - name: "Pkg", - path: .init(path: "/Pkg"), + displayName: "Pkg", + path: "/Pkg", // FIXME(ncooke3): Update error message with support version. toolsVersion: .vNext, targets: [ @@ -2631,8 +2631,8 @@ final class BuildPlanTests: XCTestCase { fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( - name: "Bar", - path: .init(path: "/Bar"), + displayName: "Bar", + path: "/Bar", // FIXME(ncooke3): Update with next version of SPM. toolsVersion: .vNext, products: [ @@ -2646,11 +2646,11 @@ final class BuildPlanTests: XCTestCase { TargetDescription(name: "Bar"), ]), Manifest.createRootManifest( - name: "Foo", - path: .init(path: "/Foo"), + displayName: "Foo", + path: "/Foo", dependencies: [ .localSourceControl( - path: .init(path: "/Bar"), + path: "/Bar", requirement: .upToNextMajor(from: "1.0.0") ), ], @@ -2738,8 +2738,8 @@ final class BuildPlanTests: XCTestCase { fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( - name: "Bar", - path: .init(path: "/Bar"), + displayName: "Bar", + path: "/Bar", // FIXME(ncooke3): Update with next version of SPM. toolsVersion: .vNext, products: [ @@ -2753,11 +2753,11 @@ final class BuildPlanTests: XCTestCase { TargetDescription(name: "Bar"), ]), Manifest.createRootManifest( - name: "Foo", - path: .init(path: "/Foo"), + displayName: "Foo", + path: "/Foo", dependencies: [ .localSourceControl( - path: .init(path: "/Bar"), + path: "/Bar", requirement: .upToNextMajor(from: "1.0.0") ), ], diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index c7a60791375..0e76379f3b2 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -122,7 +122,9 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: [ "--filter", "MixedTargetWithCustomModuleMapAndResourcesTests" -// FIXME(ncooke3): Blocked by fix for #5728. +// FIXME(ncooke3): Blocked by fix for #5728. Even though #5728 regression was +// addressed in #6055, #5728 is guarded on Swift Tools Version `.vNext`– which +// is also how the mixed language support is guarded. // ], // // Surface warning where custom umbrella header does not // // include `resource_bundle_accessor.h` in `build` directory. diff --git a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift index b8707da745e..bd0d88a6731 100644 --- a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift +++ b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift @@ -63,7 +63,7 @@ class ModuleMapGeneration: XCTestCase { root.appending(components: "include", "Foo.h").pathString ) - let interopHeaderPath = AbsolutePath(path: "/path/to/Foo-Swift.h") + let interopHeaderPath = AbsolutePath("/path/to/Foo-Swift.h") ModuleMapTester("Foo", interopHeaderPath: interopHeaderPath, in: fs) { result in result.check(contents: """ diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index ceab9d8e161..56e6020bbb1 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -42,7 +42,7 @@ class PackageBuilderTests: XCTestCase { } func testMixedSourcesWhenUnsupportedToolsVersion() throws { - let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") + let foo: AbsolutePath = AbsolutePath("/Sources/foo") let fs = InMemoryFileSystem(emptyFiles: foo.appending(components: "Foo.swift").pathString, @@ -50,7 +50,7 @@ class PackageBuilderTests: XCTestCase { ) let manifest = Manifest.createRootManifest( - name: "pkg", + displayName: "pkg", path: .root, // Use older tools version where mixed targets are not supported. toolsVersion: .v5, @@ -98,7 +98,7 @@ class PackageBuilderTests: XCTestCase { } func testMixedSourcesWithCustomModuleMap() throws { - let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") + let foo: AbsolutePath = AbsolutePath("/Sources/foo") let fs = InMemoryFileSystem(emptyFiles: foo.appending(components: "Foo.swift").pathString, @@ -108,7 +108,7 @@ class PackageBuilderTests: XCTestCase { ) let manifest = Manifest.createRootManifest( - name: "pkg", + displayName: "pkg", path: .root, // FIXME(ncooke3): Update with next version of SPM. toolsVersion: .vNext, @@ -127,7 +127,7 @@ class PackageBuilderTests: XCTestCase { } func testMixedTargetsDoNotSupportExecutables() throws { - let foo: AbsolutePath = AbsolutePath(path: "/Sources/foo") + let foo: AbsolutePath = AbsolutePath("/Sources/foo") let fs = InMemoryFileSystem(emptyFiles: foo.appending(components: "Foo.swift").pathString, @@ -135,7 +135,7 @@ class PackageBuilderTests: XCTestCase { ) let manifest = Manifest.createRootManifest( - name: "pkg", + displayName: "pkg", path: .root, // FIXME(ncooke3): Update with next version of SPM. toolsVersion: .vNext, From 5c15bdb09227164bc625584fce20f7a0a6f6459f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 17 Jul 2023 18:25:51 -0400 Subject: [PATCH 111/178] Resolve incorrect code from rebase --- Sources/Build/LLBuildManifestBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index ec801225bfb..c3aa8d17c9d 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -629,7 +629,7 @@ extension LLBuildManifestBuilder { manifest.addSwiftCmd( name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], - outputs: mixedTarget ? cmdOutputs.dropLast() : cmdOutputs, + outputs: cmdOutputs, executable: target.buildParameters.toolchain.swiftCompilerPath, moduleName: target.target.c99name, moduleAliases: target.target.moduleAliases, From a9b0ba2b5d2d53aeae9f35992866c192dd81eddb Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 17 Jul 2023 18:26:14 -0400 Subject: [PATCH 112/178] Get tests rebased passing --- Tests/BuildTests/BuildPlanTests.swift | 42 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 75524791f93..1c26620ef1c 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1511,9 +1511,10 @@ final class BuildPlanTests: XCTestCase { let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", - "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=/path/to/build/debug/lib.build/Product/module.modulemap", "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", @@ -1521,13 +1522,14 @@ final class BuildPlanTests: XCTestCase { "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", "-module-cache-path", - "\(buildPath.appending(components: "ModuleCache"))", .anySequence + "\(buildPath.appending(components: "ModuleCache"))", + .anySequence, "-parseable-output", "-g", "-Xcc", "-g", ]) let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() XCTAssertMatch(swiftPartOfLib, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", - "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", "/path/to/build/debug/lib.build/Intermediates", "-Xcc", "-ivfsoverlay", "-Xcc", @@ -1539,19 +1541,19 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/lib.build/lib-Swift.h" + "/path/to/build/debug/lib.build/lib-Swift.h", "-g", "-Xcc", "-g" ]) let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) XCTAssertMatch(clangPartOfLib, [ - "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-g", "-O0", + "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", "/Pkg/Sources/lib", "-ivfsoverlay", "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-I", "/path/to/build/debug/lib.build/Intermediates", "-I", "/path/to/build/debug/lib.build/InteropSupport", - "-fmodules-cache-path=/path/to/build/debug/ModuleCache" + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" ]) XCTAssertEqual( @@ -1575,7 +1577,8 @@ final class BuildPlanTests: XCTestCase { "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", - "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString + "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, + "-g" ]) testDiagnostics(observability.diagnostics) { result in @@ -1643,7 +1646,7 @@ final class BuildPlanTests: XCTestCase { let exe = try result.target(for: "exe").swiftTarget().compileArguments() XCTAssertMatch(exe, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", - "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=/Pkg/Sources/lib/include/module.modulemap", "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", @@ -1651,13 +1654,14 @@ final class BuildPlanTests: XCTestCase { "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", "-module-cache-path", - "\(buildPath.appending(components: "ModuleCache"))", .anySequence + "\(buildPath.appending(components: "ModuleCache"))", .anySequence, + "-parseable-output", "-g", "-Xcc", "-g" ]) let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() XCTAssertMatch(swiftPartOfLib, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", - "-enable-batch-mode", "-Onone", "-enable-testing", "-g", .equal(j), + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", "/Pkg/Sources/lib/include", "-Xcc", "-ivfsoverlay", "-Xcc", "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", @@ -1668,19 +1672,19 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/lib.build/lib-Swift.h" + "/path/to/build/debug/lib.build/lib-Swift.h", "-g", "-Xcc", "-g" ]) let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) XCTAssertMatch(clangPartOfLib, [ - "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-g", "-O0", + "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", "/Pkg/Sources/lib", "-ivfsoverlay", "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-I", "/path/to/build/debug/lib.build/Intermediates", "-I", "/path/to/build/debug/lib.build/InteropSupport", - "-fmodules-cache-path=/path/to/build/debug/ModuleCache" + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" ]) XCTAssertEqual( @@ -1704,7 +1708,8 @@ final class BuildPlanTests: XCTestCase { "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", - "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString + "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, + "-g" ]) testDiagnostics(observability.diagnostics) { result in @@ -2708,7 +2713,8 @@ final class BuildPlanTests: XCTestCase { "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, - "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString, + "-g" ]) XCTAssertEqual(barLinkArgs, [ @@ -2720,7 +2726,8 @@ final class BuildPlanTests: XCTestCase { "-Xlinker", "-install_name", "-Xlinker", "@rpath/libBar-Baz.dylib", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "Bar-Baz.product", "Objects.LinkFileList"))", - "-runtime-compatibility-version", "none", "-target", defaultTargetTriple + "-runtime-compatibility-version", "none", "-target", defaultTargetTriple, + "-g" ]) #endif } @@ -2814,7 +2821,8 @@ final class BuildPlanTests: XCTestCase { "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, - "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString, + "-g" ]) // No arguments for linking static libraries. From 7796abe2d3ad59b0186ca754e365b41c38a6ebd1 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 22 Jul 2023 18:51:15 -0400 Subject: [PATCH 113/178] Rename fixture --- .../BasicMixedTargets/Package.swift | 12 ++++++------ .../CXXFactorialFinder.cpp | 0 .../CXXFactorialFinder.hpp | 0 .../Factorial.swift | 0 .../ObjcCalculator.mm | 0 .../Sum.swift | 0 .../SumFinder.cpp | 0 .../include/CXXSumFinder.hpp | 0 .../include/ObjcCalculator.h | 0 .../include/module.modulemap | 0 .../MixedTargetWithCXXPublicAPITests.swift} | 2 +- ...getWithCXXPublicAPITestsViaHeaderImport.mm} | 6 +++--- ...getWithCXXPublicAPITestsViaModuleImport.mm} | 6 +++--- .../MixedTargetBuildDescription.swift | 18 +++++++++++++++--- Tests/FunctionalTests/MixedTargetTests.swift | 6 +++--- 15 files changed, 31 insertions(+), 19 deletions(-) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/CXXFactorialFinder.cpp (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/CXXFactorialFinder.hpp (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/Factorial.swift (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/ObjcCalculator.mm (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/Sum.swift (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/SumFinder.cpp (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/include/CXXSumFinder.hpp (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/include/ObjcCalculator.h (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/{MixedTargetWithPublicCXXAPI => MixedTargetWithCXXPublicAPI}/include/module.modulemap (100%) rename Fixtures/MixedTargets/BasicMixedTargets/Tests/{MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift => MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift} (86%) rename Fixtures/MixedTargets/BasicMixedTargets/Tests/{MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm => MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm} (71%) rename Fixtures/MixedTargets/BasicMixedTargets/Tests/{MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm => MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm} (70%) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index c5146e3ebee..103c2f2806a 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -102,7 +102,7 @@ let package = Package( name: "MixedTargetWithCXXAndCustomModuleMap" ), - // MARK: - MixedTargetWithPublicCXXAPI + // MARK: - MixedTargetWithCXXPublicAPI // In order to import this target into downstream targets, two // additional things must be done (depending on whether the target is // being imported into a Clang vs. Swift context): @@ -114,7 +114,7 @@ let package = Package( // map that only exposes public CXX headers in a non-Swift context. // // // module.modulemap - // module MixedTargetWithPublicCXXAPI { + // module MixedTargetWithCXXPublicAPI { // umbrella header "PublicNonCXXHeaders.h" // // module CXX { @@ -127,13 +127,13 @@ let package = Package( // } // .target( - name: "MixedTargetWithPublicCXXAPI" + name: "MixedTargetWithCXXPublicAPI" ), .testTarget( - name: "MixedTargetWithPublicCXXAPITests", - dependencies: ["MixedTargetWithPublicCXXAPI"], + name: "MixedTargetWithCXXPublicAPITests", + dependencies: ["MixedTargetWithCXXPublicAPI"], cSettings: [ - // To get the `MixedTargetWithPublicCXXAPI` target to build for use in + // To get the `MixedTargetWithCXXPublicAPI` target to build for use in // an Objective-C context (e.g. Objective-C++ test file), the following // unsafe flags must be passed. .unsafeFlags(["-fcxx-modules", "-fmodules"]) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.cpp rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/CXXFactorialFinder.hpp rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Factorial.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/ObjcCalculator.mm b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/ObjcCalculator.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Sum.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Sum.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/Sum.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Sum.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/SumFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/SumFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/SumFinder.cpp rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/SumFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/CXXSumFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/CXXSumFinder.hpp rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/ObjcCalculator.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/ObjcCalculator.h rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithPublicCXXAPI/include/module.modulemap rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift similarity index 86% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift index 25da15aee36..6aea5db367c 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/MixedTargetWithPublicCXXAPITests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift @@ -1,5 +1,5 @@ import XCTest -import MixedTargetWithPublicCXXAPI +import MixedTargetWithCXXPublicAPI final class MixedTargetWithCXXTests: XCTestCase { func testFactorial() throws { diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm similarity index 71% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm index 96e42520cce..06f2d81ff05 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm @@ -2,12 +2,12 @@ #import "CXXSumFinder.hpp" #import "ObjcCalculator.h" -#import "MixedTargetWithPublicCXXAPI-Swift.h" +#import "MixedTargetWithCXXPublicAPI-Swift.h" -@interface ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport : XCTestCase +@interface ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport : XCTestCase @end -@implementation ObjcMixedTargetWithPublicCXXAPITestsViaHeaderImport +@implementation ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport - (void)testPublicObjcAPI { XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm similarity index 70% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm index 4a34d4cc752..f4e05dd95f4 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithPublicCXXAPITests/ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm @@ -1,11 +1,11 @@ #import -@import MixedTargetWithPublicCXXAPI; +@import MixedTargetWithCXXPublicAPI; -@interface ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport : XCTestCase +@interface ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport : XCTestCase @end -@implementation ObjcMixedTargetWithPublicCXXAPITestsViaModuleImport +@implementation ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport - (void)testPublicObjcAPI { XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index d6dda864fd2..b6944790871 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -177,6 +177,18 @@ public final class MixedTargetBuildDescription { let generatedUmbrellaHeaderPath = interopSupportDirectory! .appending(components: umbrellaHeaderPathComponents) + // Note(ncooke3): There are two cases: + // i) Has modularizable headers -> can use generated module map + // ii) Does not have modularizable headers -> can NOT use generated module map + // Note(ncooke3): The below code can therefor be simplified. + // 1. ~Remove it~ + // 2. ~Run C++ tests~ + // 3. Only targets with public C++ headers should fail + // 4. Then fix them with #if def and note requirement somewhere + // 5. Then, duplicate the failing tests and fix with custom module map + // + // Note(ncooke3): Is there a case where the generating the internal + // module map requires some custom configuration? var generatedUmbrellaHeader = "" mixedTarget.clangTarget.headers // One of the requirements for a Swift API to be Objective-C @@ -187,9 +199,9 @@ public final class MixedTargetBuildDescription { // Because of this, the generated umbrella header will only // include public headers so all other can be filtered out. .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } - // Filter out non-Objective-C/C headers. - // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? - .filter { $0.basename.hasSuffix(".h") } +// // Filter out non-Objective-C/C headers. +// // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? +// .filter { $0.basename.hasSuffix(".h") } // Add each remaining header to the generated umbrella header. .forEach { // Import the header, followed by a newline. diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 0e76379f3b2..1c84d64de60 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -153,15 +153,15 @@ final class MixedTargetTests: XCTestCase { } } - func testMixedTargetWithPublicCXXAPI() throws { + func testMixedTargetWithCXXPublicAPI() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( fixturePath, - extraArgs: ["--target", "MixedTargetWithPublicCXXAPI"] + extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] ) XCTAssertSwiftTest( fixturePath, - extraArgs: ["--filter", "MixedTargetWithPublicCXXAPITests"] + extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"] ) } } From 3e6ea0e9c57dd95f0ffa2319ce5e5814410adb2e Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 22 Jul 2023 18:58:21 -0400 Subject: [PATCH 114/178] Rename fixture (leftover from previous commit) --- .../MixedTargetWithCXXPublicAPI/include/module.modulemap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap index b9ac4b7d8dc..3abb4846c20 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap @@ -1,4 +1,4 @@ -module MixedTargetWithPublicCXXAPI { +module MixedTargetWithCXXPublicAPI { header "ObjcCalculator.h" module PublicCXXAPI { From 7923b023327938b9e9b204a318808eedaf44362d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sun, 23 Jul 2023 18:31:30 -0400 Subject: [PATCH 115/178] Rename some test fixtures --- .../BasicMixedTargets/Package.swift | 107 ++++++++++++------ .../{SumFinder.cpp => CXXSumFinder.cpp} | 2 +- .../ObjcCalculator.mm | 2 +- .../include/module.modulemap | 11 -- .../Factorial.swift | 11 ++ .../Sum.swift | 5 + .../XYZCxxFactorialFinder.cpp | 6 + .../XYZCxxFactorialFinder.hpp | 5 + .../XYZCxxSumFinder.cpp | 5 + .../XYZObjcCalculator.mm | 21 ++++ .../include/XYZCxxSumFinder.hpp | 5 + .../include/XYZObjcCalculator.h | 6 + .../include/module.modulemap | 11 ++ ...hCXXPublicAPIAndCustomModuleMapTests.swift | 12 ++ ...IAndCustomModuleMapTestsViaHeaderImport.mm | 26 +++++ ...IAndCustomModuleMapTestsViaModuleImport.mm | 24 ++++ .../MixedTargetWithCXXPublicAPITests.swift | 2 +- Tests/FunctionalTests/MixedTargetTests.swift | 76 ++++++++----- 18 files changed, 259 insertions(+), 78 deletions(-) rename Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/{SumFinder.cpp => CXXSumFinder.cpp} (97%) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 103c2f2806a..75be1900a93 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -69,7 +69,7 @@ let package = Package( .testTarget( name: "MixedTargetWithCustomModuleMapTests", dependencies: ["MixedTargetWithCustomModuleMap"] - ), + ), // MARK: - MixedTargetWithInvalidCustomModuleMap .target( @@ -86,23 +86,59 @@ let package = Package( .testTarget( name: "MixedTargetWithCustomModuleMapAndResourcesTests", dependencies: ["MixedTargetWithCustomModuleMapAndResources"] - ), - - // MARK: - MixedTargetWithC++ - .target( - name: "MixedTargetWithCXX" - ), - .testTarget( - name: "MixedTargetWithCXXTests", - dependencies: ["MixedTargetWithCXX"] ), - // MARK: - MixedTargetWithCXXAndCustomModuleMap - .target( - name: "MixedTargetWithCXXAndCustomModuleMap" - ), + ///// START + // TODO(ncooke3): Figure out how to support C++ when interop mode not enabled. + + // // MARK: - MixedTargetWithC++ + // .target( + // name: "MixedTargetWithCXX" + // ), + // .testTarget( + // name: "MixedTargetWithCXXTests", + // dependencies: ["MixedTargetWithCXX"] + // ), + + // // MARK: - MixedTargetWithCXXAndCustomModuleMap + // .target( + // name: "MixedTargetWithCXXAndCustomModuleMap" + // ), + // TODO(ncooke3): Add a test target for above target. + + // MARK: - MixedTargetWithCXXPublicAPI + // TODO(ncooke3): Adjust after getting guidance on Swift Forums. + // // In order to import this target into downstream targets, two + // // additional things must be done (depending on whether the target is + // // being imported into a Clang vs. Swift context): + // // - Clang context: If the client wants to import the module, client + // // must pass `-fcxx-modules` and `-fmodules` as unsafe flags in + // // the target's `cSettings`. Else, the client can just import + // // individual public headers without further configuring the target. + // // - Swift context: The mixed target needs to make a custom module + // // map that only exposes public CXX headers in a non-Swift context. + // // + // .target( + // name: "MixedTargetWithCXXPublicAPI", + // linkerSettings: [ + // .linkedLibrary("c++"), + // ] + // ), + // .testTarget( + // name: "MixedTargetWithCXXPublicAPITests", + // dependencies: ["MixedTargetWithCXXPublicAPI"], + // cSettings: [ + // // To get the `MixedTargetWithCXXPublicAPI` target to build for use in + // // an Objective-C context (e.g. Objective-C++ test file), the following + // // unsafe flags must be passed. + // .unsafeFlags(["-fcxx-modules", "-fmodules"]) + // ], + // linkerSettings: [ + // .linkedLibrary("c++"), + // ] + // ), - // MARK: - MixedTargetWithCXXPublicAPI + // MARK: - MixedTargetWithCXXPublicAPIAndCustomModuleMap // In order to import this target into downstream targets, two // additional things must be done (depending on whether the target is // being imported into a Clang vs. Swift context): @@ -112,36 +148,39 @@ let package = Package( // individual public headers without further configuring the target. // - Swift context: The mixed target needs to make a custom module // map that only exposes public CXX headers in a non-Swift context. - // + // // // module.modulemap // module MixedTargetWithCXXPublicAPI { // umbrella header "PublicNonCXXHeaders.h" - // + // // module CXX { // header "PublicCXXHeaders.h" // export * // requires !swift // } - // + // // export * // } // - .target( - name: "MixedTargetWithCXXPublicAPI" - ), - .testTarget( - name: "MixedTargetWithCXXPublicAPITests", - dependencies: ["MixedTargetWithCXXPublicAPI"], - cSettings: [ - // To get the `MixedTargetWithCXXPublicAPI` target to build for use in - // an Objective-C context (e.g. Objective-C++ test file), the following - // unsafe flags must be passed. - .unsafeFlags(["-fcxx-modules", "-fmodules"]) - ], - linkerSettings: [ - .linkedLibrary("c++"), - ] - ), + // .target( + // name: "MixedTargetWithCXXPublicAPIAndCustomModuleMap" + // ), + // .testTarget( + // name: "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests", + // dependencies: ["MixedTargetWithCXXPublicAPIAndCustomModuleMap"], + // cSettings: [ + // // To get the `MixedTargetWithCXXPublicAPIAndCustomModuleMap` + // // target to build for use in an Objective-C context (e.g. + // // Objective-C++ test file), the following unsafe flags must be + // // passed. + // .unsafeFlags(["-fcxx-modules", "-fmodules"]) + // ], + // linkerSettings: [ + // .linkedLibrary("c++"), + // ] + // ), + + ///// END // MARK: - MixedTargetWithC .target( diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/SumFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp similarity index 97% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/SumFinder.cpp rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp index fdba9f04ec2..2dca74255ec 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/SumFinder.cpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp @@ -2,4 +2,4 @@ long CXXSumFinder::sum(int x, int y) { return x + y; -} +} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm index 89b35f91fd8..fa79b95307a 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm @@ -18,4 +18,4 @@ + (long)sumX:(int)x andY:(int)y { return sf.sum(x, y); } -@end +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap deleted file mode 100644 index 3abb4846c20..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/module.modulemap +++ /dev/null @@ -1,11 +0,0 @@ -module MixedTargetWithCXXPublicAPI { - header "ObjcCalculator.h" - - module PublicCXXAPI { - header "CXXSumFinder.hpp" - requires !swift - export * - } - - export * -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift new file mode 100644 index 00000000000..08e13752ccf --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift @@ -0,0 +1,11 @@ +import Foundation + +@objc public class Factorial: NSObject { + @objc public static func text() -> String { + return "Hello, World!" + } +} + +public func factorial(_ x: Int32) -> Int { + return XYZObjcCalculator.factorial(for: x) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift new file mode 100644 index 00000000000..837bf1eb77e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift @@ -0,0 +1,5 @@ +import Foundation + +public func sum(x: Int32, y: Int32) -> Int { + return XYZObjcCalculator.sum(x:x, y:y) +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp new file mode 100644 index 00000000000..41af6ed34ab --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp @@ -0,0 +1,6 @@ +#include "XYZCxxFactorialFinder.hpp" + +long XYZCxxFactorialFinder::factorial(int n) { + if (n == 0 || n == 1) return 1; + return n * factorial(n-1); +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp new file mode 100644 index 00000000000..1e0ab6448f0 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp @@ -0,0 +1,5 @@ +class XYZCxxFactorialFinder +{ +public: + long factorial(int n); +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp new file mode 100644 index 00000000000..858cb8950a8 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp @@ -0,0 +1,5 @@ +#include "XYZCxxSumFinder.hpp" + +long XYZCxxSumFinder::sum(int x, int y) { + return x + y; +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm new file mode 100644 index 00000000000..ae3c8abb78d --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm @@ -0,0 +1,21 @@ +#import + +#import "include/XYZObjcCalculator.h" + +// Import C++ headers. +#import "XYZCxxFactorialFinder.hpp" +#import "XYZCxxSumFinder.hpp" + +@implementation XYZObjcCalculator + ++ (long)factorialForInt:(int)integer { + XYZCxxFactorialFinder ff; + return ff.factorial(integer); +} + ++ (long)sumX:(int)x andY:(int)y { + XYZCxxSumFinder sf; + return sf.sum(x, y); +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp new file mode 100644 index 00000000000..2ed9d055b9b --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp @@ -0,0 +1,5 @@ +class XYZCxxSumFinder +{ +public: + long sum(int x, int y); +}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h new file mode 100644 index 00000000000..7e12ee2e958 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h @@ -0,0 +1,6 @@ +#import + +@interface XYZObjcCalculator : NSObject ++ (long)factorialForInt:(int)integer; ++ (long)sumX:(int)x andY:(int)y NS_SWIFT_NAME(sum(x:y:)); +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap new file mode 100644 index 00000000000..e9aaea85bad --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap @@ -0,0 +1,11 @@ +module MixedTargetWithCXXPublicAPIAndCustomModuleMap { + header "XYZObjcCalculator.h" + + module PublicCXXAPI { + header "XYZCxxSumFinder.hpp" + requires !swift + export * + } + + export * +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift new file mode 100644 index 00000000000..e4801f27ea6 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift @@ -0,0 +1,12 @@ +import XCTest +import MixedTargetWithCXXPublicAPIAndCustomModuleMap + +final class MixedTargetWithCXXPublicAPIAndCustomModuleMapTests: XCTestCase { + func testFactorial() throws { + XCTAssertEqual(factorial(5), 120) + } + + func testSum() throws { + XCTAssertEqual(sum(x: 60, y: 40), 100) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm new file mode 100644 index 00000000000..ad6ea829a12 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm @@ -0,0 +1,26 @@ +#import + +#import "XYZCxxSumFinder.hpp" +#import "XYZObjcCalculator.h" +#import "MixedTargetWithCXXPublicAPIAndCustomModuleMap-Swift.h" + +@interface ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport : XCTestCase +@end + +@implementation ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport + +- (void)testPublicObjcAPI { + XCTAssertEqual([XYZObjcCalculator factorialForInt:5], 120); + XCTAssertEqual([XYZObjcCalculator sumX:1 andY:2], 3); +} + +- (void)testPublicSwiftAPI { + XCTAssertEqualObjects([Factorial text], @"Hello, World!"); +} + +- (void)testPublicCXXAPI { + XYZCxxSumFinder sf; + XCTAssertEqual(sf.sum(1,2), 3); +} + +@end \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm new file mode 100644 index 00000000000..70de39dd86f --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm @@ -0,0 +1,24 @@ +#import + +@import MixedTargetWithCXXPublicAPIAndCustomModuleMap; + +@interface ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport : XCTestCase +@end + +@implementation ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport + +- (void)testPublicObjcAPI { + XCTAssertEqual([XYZObjcCalculator factorialForInt:5], 120); + XCTAssertEqual([XYZObjcCalculator sumX:1 andY:2], 3); +} + +- (void)testPublicSwiftAPI { + XCTAssertEqualObjects([Factorial text], @"Hello, World!"); +} + +- (void)testPublicCXXAPI { + XYZCxxSumFinder sf; + XCTAssertEqual(sf.sum(1,2), 3); +} + +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift index 6aea5db367c..79b6100d78f 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift @@ -1,7 +1,7 @@ import XCTest import MixedTargetWithCXXPublicAPI -final class MixedTargetWithCXXTests: XCTestCase { +final class MixedTargetWithCXXPublicAPITests: XCTestCase { func testFactorial() throws { XCTAssertEqual(factorial(5), 120) } diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 1c84d64de60..ac634f5720c 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -135,36 +135,52 @@ final class MixedTargetTests: XCTestCase { } } - func testMixedTargetWithCXX() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "MixedTargetWithCXXTests"] - ) - } - } - - func testMixedTargetWithCXXAndCustomModuleMap() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "MixedTargetWithCXXAndCustomModuleMap"] - ) - } - } - - func testMixedTargetWithCXXPublicAPI() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] - ) - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"] - ) - } - } +// TODO(ncooke3): Make the below test target more robust? +// func testMixedTargetWithCXX() throws { +// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// XCTAssertSwiftTest( +// fixturePath, +// extraArgs: ["--filter", "MixedTargetWithCXXTests"] +// ) +// } +// } +// +// func testMixedTargetWithCXXAndCustomModuleMap() throws { +// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// XCTAssertBuilds( +// fixturePath, +// extraArgs: ["--target", "MixedTargetWithCXXAndCustomModuleMap"] +// ) +// } +// } + +// TODO(ncooke3): Using `#ifdef __cplusplus` to guard the C++ code prevents it +// from being referenced by Objective-C. Is this expectected? +// func testMixedTargetWithCXXPublicAPI() throws { +// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// XCTAssertBuilds( +// fixturePath, +// extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] +// ) +// XCTAssertSwiftTest( +// fixturePath, +// extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"] +// ) +// } +// } + +// func testMixedTargetWithCXXPublicAPIAndCustomModuleMap() throws { +// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// XCTAssertBuilds( +// fixturePath, +// extraArgs: ["--target", "MixedTargetWithCXXPublicAPIAndCustomModuleMap"] +// ) +// XCTAssertSwiftTest( +// fixturePath, +// extraArgs: ["--filter", "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests"] +// ) +// } +// } func testMixedTargetWithC() throws { From 7f7aee91e77c4951d36f18549b8d79c943753dfb Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sun, 23 Jul 2023 18:32:44 -0400 Subject: [PATCH 116/178] C++ interop support (1) --- .../BasicMixedTargets/Package.swift | 4 ++- .../CxxCountdown.cpp | 21 +++++++++++++++ .../SwiftCountdown.swift | 26 ++++++++++++++++++ .../include/CxxCountdown.hpp | 9 +++++++ .../MixedTargetBuildDescription.swift | 27 +++---------------- Tests/FunctionalTests/MixedTargetTests.swift | 9 +++++++ 6 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/SwiftCountdown.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/include/CxxCountdown.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 75be1900a93..85f8b10f16f 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -284,5 +284,7 @@ let package = Package( .target(name: "SwiftTarget"), .target(name: "ClangTarget") - ] + ], + // TODO(ncooke3): Is this really neccessary? + cxxLanguageStandard: .cxx11 ) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp new file mode 100644 index 00000000000..daefacd6f5d --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp @@ -0,0 +1,21 @@ +#include "CxxCountdown.hpp" +// #include "MixedTargetWithCXX_CXXInteropEnabled-Swift.h" +// #include +#include +#include + +CxxCountdown::CxxCountdown(bool printCount) : printCount(printCount) {} + +void CxxCountdown::countdown(int x )const { + if (x < 0) + std::cout << "[c++] Cannot count down from a negative number.\n"; + + if (printCount) + std::cout << "[c++] T-minus " << x << "... \n"; + + if (x == 0) + std::cout << "[c++] We have liftoff!"; + + auto swiftCountdown = MixedTargetWithCXX_CXXInteropEnabled::SwiftCountdown::init(printCount); + swiftCountdown.countdown(x - 1); +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/SwiftCountdown.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/SwiftCountdown.swift new file mode 100644 index 00000000000..e2a3e80a236 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/SwiftCountdown.swift @@ -0,0 +1,26 @@ +import Foundation + +public struct SwiftCountdown { + private let printCount: Bool + + public init(printCount: Bool) { + self.printCount = printCount + } + + public func countdown(x: Int) { + if x < 0 { + print("[swift] Cannot count down from a negative number.") + } + + if printCount { + print("[swift] T-minus \(x)...") + } + + if x == 0 { + print("[swift] We have liftoff!") + } + + let cxxCountdown = CxxCountdown(printCount) + cxxCountdown.countdown(Int32(x) - 1) + } +} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/include/CxxCountdown.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/include/CxxCountdown.hpp new file mode 100644 index 00000000000..b179673130e --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/include/CxxCountdown.hpp @@ -0,0 +1,9 @@ +#pragma once +class CxxCountdown +{ +public: + CxxCountdown(bool printCount); + void countdown(int x) const; +private: + bool printCount; +}; diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index b6944790871..03eabee61f2 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -177,18 +177,9 @@ public final class MixedTargetBuildDescription { let generatedUmbrellaHeaderPath = interopSupportDirectory! .appending(components: umbrellaHeaderPathComponents) - // Note(ncooke3): There are two cases: - // i) Has modularizable headers -> can use generated module map - // ii) Does not have modularizable headers -> can NOT use generated module map - // Note(ncooke3): The below code can therefor be simplified. - // 1. ~Remove it~ - // 2. ~Run C++ tests~ - // 3. Only targets with public C++ headers should fail - // 4. Then fix them with #if def and note requirement somewhere - // 5. Then, duplicate the failing tests and fix with custom module map - // - // Note(ncooke3): Is there a case where the generating the internal - // module map requires some custom configuration? + // TODO(ncooke3): Can simplify this to just use `includeDir` as + // umbrella directory if it is non-empty. + // TODO(ncooke3): What if there are no headers in `includeDir`? var generatedUmbrellaHeader = "" mixedTarget.clangTarget.headers // One of the requirements for a Swift API to be Objective-C @@ -199,9 +190,6 @@ public final class MixedTargetBuildDescription { // Because of this, the generated umbrella header will only // include public headers so all other can be filtered out. .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } -// // Filter out non-Objective-C/C headers. -// // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? -// .filter { $0.basename.hasSuffix(".h") } // Add each remaining header to the generated umbrella header. .forEach { // Import the header, followed by a newline. @@ -346,16 +334,9 @@ public final class MixedTargetBuildDescription { // However, this module map should not expose the generated Swift // header since it will not exist yet. let unextendedModuleMapPath = intermediatesDirectory.appending(component: unextendedModuleMapFilename) - // Generating module maps that include non-Objective-C headers is not - // supported. - // FIXME(ncooke3): Link to evolution post. - // TODO(ncooke3): C++ headers can be ".h". How else can we rule them out? - let nonObjcHeaders: [AbsolutePath] = mixedTarget.clangTarget.headers - .filter { $0.extension != "h" } try moduleMapGenerator.generateModuleMap( type: .umbrellaDirectory(mixedTarget.clangTarget.path), - at: unextendedModuleMapPath, - excludeHeaders: nonObjcHeaders + at: unextendedModuleMapPath ) // 3. Use VFS overlays to purposefully expose specific resources (e.g. diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index ac634f5720c..95041c08e8d 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -135,6 +135,15 @@ final class MixedTargetTests: XCTestCase { } } + func testMixedTargetWithCXX_CXXInteropEnabled() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCXX_CXXInteropEnabled"] + ) + } + } + // TODO(ncooke3): Make the below test target more robust? // func testMixedTargetWithCXX() throws { // try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in From fa6a0e2bcdacfdf084597899cb5a508ef06b35af Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sun, 23 Jul 2023 19:19:38 -0400 Subject: [PATCH 117/178] Adding build settings assertions to test --- Tests/BuildTests/BuildPlanTests.swift | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 1c26620ef1c..7ada8870c28 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1474,7 +1474,17 @@ final class BuildPlanTests: XCTestCase { toolsVersion: .vNext, targets: [ TargetDescription(name: "exe", dependencies: ["lib"], type: .executable), - TargetDescription(name: "lib") + TargetDescription( + name: "lib", + // Configure some settings to verify they are + // passed to mixed target's subtargets. + settings: [ + .init(tool: .swift, kind: .unsafeFlags(["-Xfrontend", "-super-cool-swift-only-flag"]), condition: .init(config: "debug")), + .init(tool: .linker, kind: .linkedFramework("OtherFramework")), + .init(tool: .c, kind: .define("HELLO_CLANG=1")), + .init(tool: .swift, kind: .define("HELLO_SWIFT=1")) + ] + ) ] ) ], @@ -1541,7 +1551,8 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/lib.build/lib-Swift.h", "-g", "-Xcc", "-g" + "/path/to/build/debug/lib.build/lib-Swift.h", "-DHELLO_SWIFT=1", "-Xfrontend", + "-super-cool-swift-only-flag", "-Xcc", "-DHELLO_CLANG=1", "-g", "-Xcc", "-g" ]) let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) @@ -1553,7 +1564,8 @@ final class BuildPlanTests: XCTestCase { "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", "-I", "/path/to/build/debug/lib.build/Intermediates", "-I", "/path/to/build/debug/lib.build/InteropSupport", - "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-DHELLO_CLANG=1", + "-g" ]) XCTAssertEqual( @@ -1576,7 +1588,7 @@ final class BuildPlanTests: XCTestCase { "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", + "-target", defaultTargetTriple, "-framework", "OtherFramework", "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, "-g" ]) From f2af5c0519dcfb4ff9169f49481c4738062fce91 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 24 Jul 2023 14:26:18 -0400 Subject: [PATCH 118/178] C++ interop support (2) --- .../BasicMixedTargets/Package.swift | 53 ++++++++----------- .../CXXFactorialFinder.hpp | 4 ++ .../include/CXXSumFinder.hpp | 4 ++ Tests/FunctionalTests/MixedTargetTests.swift | 31 +++++------ 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 85f8b10f16f..a880150e425 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -88,6 +88,12 @@ let package = Package( dependencies: ["MixedTargetWithCustomModuleMapAndResources"] ), + // MARK: - MixedTargetWithCXX_CXXInteropEnabled + .target( + name: "MixedTargetWithCXX_CXXInteropEnabled", + swiftSettings: [.interoperabilityMode(.Cxx)] + ), + ///// START // TODO(ncooke3): Figure out how to support C++ when interop mode not enabled. @@ -107,36 +113,19 @@ let package = Package( // TODO(ncooke3): Add a test target for above target. // MARK: - MixedTargetWithCXXPublicAPI - // TODO(ncooke3): Adjust after getting guidance on Swift Forums. - // // In order to import this target into downstream targets, two - // // additional things must be done (depending on whether the target is - // // being imported into a Clang vs. Swift context): - // // - Clang context: If the client wants to import the module, client - // // must pass `-fcxx-modules` and `-fmodules` as unsafe flags in - // // the target's `cSettings`. Else, the client can just import - // // individual public headers without further configuring the target. - // // - Swift context: The mixed target needs to make a custom module - // // map that only exposes public CXX headers in a non-Swift context. - // // - // .target( - // name: "MixedTargetWithCXXPublicAPI", - // linkerSettings: [ - // .linkedLibrary("c++"), - // ] - // ), - // .testTarget( - // name: "MixedTargetWithCXXPublicAPITests", - // dependencies: ["MixedTargetWithCXXPublicAPI"], - // cSettings: [ - // // To get the `MixedTargetWithCXXPublicAPI` target to build for use in - // // an Objective-C context (e.g. Objective-C++ test file), the following - // // unsafe flags must be passed. - // .unsafeFlags(["-fcxx-modules", "-fmodules"]) - // ], - // linkerSettings: [ - // .linkedLibrary("c++"), - // ] - // ), + .target( + name: "MixedTargetWithCXXPublicAPI" + ), + .testTarget( + name: "MixedTargetWithCXXPublicAPITests", + dependencies: ["MixedTargetWithCXXPublicAPI"], + cSettings: [ + // To import `MixedTargetWithCXXPublicAPI` via a module style + // import, the following unsafe flags must be passed. See + // the Objective-C++ test file in the test target. + .unsafeFlags(["-fcxx-modules", "-fmodules"]) + ] + ), // MARK: - MixedTargetWithCXXPublicAPIAndCustomModuleMap // In order to import this target into downstream targets, two @@ -285,6 +274,8 @@ let package = Package( .target(name: "ClangTarget") ], - // TODO(ncooke3): Is this really neccessary? + // TODO(ncooke3): Is the below note behavior that we want to be intended? + // This is needed for targets with that have + // `swiftSettings: [.interoperabilityMode(.Cxx)]` set. cxxLanguageStandard: .cxx11 ) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp index 3308e76415b..e5f863d4d22 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp @@ -1,5 +1,9 @@ +#ifdef __cplusplus + class CXXFactorialFinder { public: long factorial(int n); }; + +#endif // __cplusplus \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp index 540134e63be..72346a8f4a3 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp @@ -1,5 +1,9 @@ +#ifdef __cplusplus + class CXXSumFinder { public: long sum(int x, int y); }; + +#endif // __cplusplus \ No newline at end of file diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 95041c08e8d..c4f571db502 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -124,7 +124,10 @@ final class MixedTargetTests: XCTestCase { "--filter", "MixedTargetWithCustomModuleMapAndResourcesTests" // FIXME(ncooke3): Blocked by fix for #5728. Even though #5728 regression was // addressed in #6055, #5728 is guarded on Swift Tools Version `.vNext`– which -// is also how the mixed language support is guarded. +// is also how the mixed language support is guarded. This comment should be +// resolved once the mixed language test feature is staged for release and the +// mixed language Fixture test targets have a swift-tools-version matching the +// expected tools version that the mixed language test targets will release in. // ], // // Surface warning where custom umbrella header does not // // include `resource_bundle_accessor.h` in `build` directory. @@ -163,20 +166,18 @@ final class MixedTargetTests: XCTestCase { // } // } -// TODO(ncooke3): Using `#ifdef __cplusplus` to guard the C++ code prevents it -// from being referenced by Objective-C. Is this expectected? -// func testMixedTargetWithCXXPublicAPI() throws { -// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in -// XCTAssertBuilds( -// fixturePath, -// extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] -// ) -// XCTAssertSwiftTest( -// fixturePath, -// extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"] -// ) -// } -// } + func testMixedTargetWithCXXPublicAPI() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] + ) + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"] + ) + } + } // func testMixedTargetWithCXXPublicAPIAndCustomModuleMap() throws { // try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in From b753b51ad26007ec9b499f883fea92bca3e1fecd Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 24 Jul 2023 15:26:44 -0400 Subject: [PATCH 119/178] C++ interop support (3) --- .../BasicMixedTargets/Package.swift | 67 +++++++++---------- .../MixedTargetWithCXX/FactorialFinder.hpp | 4 ++ .../FactorialFinder.hpp | 4 ++ .../include/CXXSumFinder.hpp | 2 +- .../XYZCxxFactorialFinder.hpp | 4 ++ .../include/XYZCxxSumFinder.hpp | 4 ++ Tests/FunctionalTests/MixedTargetTests.swift | 59 ++++++++-------- 7 files changed, 76 insertions(+), 68 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index a880150e425..b50fc30c727 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -94,23 +94,19 @@ let package = Package( swiftSettings: [.interoperabilityMode(.Cxx)] ), - ///// START - // TODO(ncooke3): Figure out how to support C++ when interop mode not enabled. - - // // MARK: - MixedTargetWithC++ - // .target( - // name: "MixedTargetWithCXX" - // ), - // .testTarget( - // name: "MixedTargetWithCXXTests", - // dependencies: ["MixedTargetWithCXX"] - // ), + // MARK: - MixedTargetWithCXX + .target( + name: "MixedTargetWithCXX" + ), + .testTarget( + name: "MixedTargetWithCXXTests", + dependencies: ["MixedTargetWithCXX"] + ), - // // MARK: - MixedTargetWithCXXAndCustomModuleMap - // .target( - // name: "MixedTargetWithCXXAndCustomModuleMap" - // ), - // TODO(ncooke3): Add a test target for above target. + // MARK: - MixedTargetWithCXXAndCustomModuleMap + .target( + name: "MixedTargetWithCXXAndCustomModuleMap" + ), // MARK: - MixedTargetWithCXXPublicAPI .target( @@ -122,7 +118,7 @@ let package = Package( cSettings: [ // To import `MixedTargetWithCXXPublicAPI` via a module style // import, the following unsafe flags must be passed. See - // the Objective-C++ test file in the test target. + // the Objective-C++ file in the test target. .unsafeFlags(["-fcxx-modules", "-fmodules"]) ] ), @@ -139,7 +135,7 @@ let package = Package( // map that only exposes public CXX headers in a non-Swift context. // // // module.modulemap - // module MixedTargetWithCXXPublicAPI { + // module MixedTargetWithCXXPublicAPIAndCustomModuleMap { // umbrella header "PublicNonCXXHeaders.h" // // module CXX { @@ -151,25 +147,22 @@ let package = Package( // export * // } // - // .target( - // name: "MixedTargetWithCXXPublicAPIAndCustomModuleMap" - // ), - // .testTarget( - // name: "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests", - // dependencies: ["MixedTargetWithCXXPublicAPIAndCustomModuleMap"], - // cSettings: [ - // // To get the `MixedTargetWithCXXPublicAPIAndCustomModuleMap` - // // target to build for use in an Objective-C context (e.g. - // // Objective-C++ test file), the following unsafe flags must be - // // passed. - // .unsafeFlags(["-fcxx-modules", "-fmodules"]) - // ], - // linkerSettings: [ - // .linkedLibrary("c++"), - // ] - // ), - - ///// END + .target( + name: "MixedTargetWithCXXPublicAPIAndCustomModuleMap" + ), + .testTarget( + name: "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests", + dependencies: ["MixedTargetWithCXXPublicAPIAndCustomModuleMap"], + cSettings: [ + // To import `MixedTargetWithCXXPublicAPIAndCustomModuleMap` + // via a module style import, the following unsafe flags must + // be passed. See the Objective-C++ file in the test target. + .unsafeFlags(["-fcxx-modules", "-fmodules"]) + ], + linkerSettings: [ + .linkedLibrary("c++"), + ] + ), // MARK: - MixedTargetWithC .target( diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp index 533730e7c97..dfb6e36793b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp @@ -1,5 +1,9 @@ +#ifdef __cplusplus + class FactorialFinder { public: long factorial(int n); }; + +#endif // __cplusplus diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp index 533730e7c97..dfb6e36793b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp @@ -1,5 +1,9 @@ +#ifdef __cplusplus + class FactorialFinder { public: long factorial(int n); }; + +#endif // __cplusplus diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp index 72346a8f4a3..39bd407e74e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp @@ -6,4 +6,4 @@ class CXXSumFinder long sum(int x, int y); }; -#endif // __cplusplus \ No newline at end of file +#endif // __cplusplus diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp index 1e0ab6448f0..71bec8bc8e4 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp @@ -1,5 +1,9 @@ +#ifdef __cplusplus + class XYZCxxFactorialFinder { public: long factorial(int n); }; + +#endif // __cplusplus \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp index 2ed9d055b9b..87b3dbe24a7 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp @@ -1,5 +1,9 @@ +#ifdef __cplusplus + class XYZCxxSumFinder { public: long sum(int x, int y); }; + +#endif // __cplusplus diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index c4f571db502..c4635d4cca9 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -147,24 +147,23 @@ final class MixedTargetTests: XCTestCase { } } -// TODO(ncooke3): Make the below test target more robust? -// func testMixedTargetWithCXX() throws { -// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in -// XCTAssertSwiftTest( -// fixturePath, -// extraArgs: ["--filter", "MixedTargetWithCXXTests"] -// ) -// } -// } -// -// func testMixedTargetWithCXXAndCustomModuleMap() throws { -// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in -// XCTAssertBuilds( -// fixturePath, -// extraArgs: ["--target", "MixedTargetWithCXXAndCustomModuleMap"] -// ) -// } -// } + func testMixedTargetWithCXX() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCXXTests"] + ) + } + } + + func testMixedTargetWithCXXAndCustomModuleMap() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCXXAndCustomModuleMap"] + ) + } + } func testMixedTargetWithCXXPublicAPI() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in @@ -179,18 +178,18 @@ final class MixedTargetTests: XCTestCase { } } -// func testMixedTargetWithCXXPublicAPIAndCustomModuleMap() throws { -// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in -// XCTAssertBuilds( -// fixturePath, -// extraArgs: ["--target", "MixedTargetWithCXXPublicAPIAndCustomModuleMap"] -// ) -// XCTAssertSwiftTest( -// fixturePath, -// extraArgs: ["--filter", "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests"] -// ) -// } -// } + func testMixedTargetWithCXXPublicAPIAndCustomModuleMap() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetWithCXXPublicAPIAndCustomModuleMap"] + ) + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests"] + ) + } + } func testMixedTargetWithC() throws { From 0dd4540debed02e54c0c635aa2b02175a54c7dd9 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 24 Jul 2023 16:54:26 -0400 Subject: [PATCH 120/178] Resolve deprecation --- .../Build/BuildDescription/MixedTargetBuildDescription.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 03eabee61f2..c0fcf2436d4 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -80,7 +80,7 @@ public final class MixedTargetBuildDescription { throw InternalError("underlying target type mismatch \(target)") } - guard buildParameters.triple.isDarwin() else { + guard buildParameters.targetTriple.isDarwin() else { throw StringError("Targets with mixed language sources are only " + "supported on Apple platforms.") } From 735d37b5e1ae6dced4ac31bcddc1ccdc357089e9 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 25 Jul 2023 17:41:23 -0400 Subject: [PATCH 121/178] Guard tests and targets that require Swift 5.9 --- .../BasicMixedTargets/Package.swift | 25 +++++++++++++------ Tests/FunctionalTests/MixedTargetTests.swift | 4 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index b50fc30c727..9684d7be51b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -88,12 +88,6 @@ let package = Package( dependencies: ["MixedTargetWithCustomModuleMapAndResources"] ), - // MARK: - MixedTargetWithCXX_CXXInteropEnabled - .target( - name: "MixedTargetWithCXX_CXXInteropEnabled", - swiftSettings: [.interoperabilityMode(.Cxx)] - ), - // MARK: - MixedTargetWithCXX .target( name: "MixedTargetWithCXX" @@ -265,10 +259,25 @@ let package = Package( // The below two targets are used for testing the above targets. .target(name: "SwiftTarget"), .target(name: "ClangTarget") - - ], + ] + targetsWithCxxInteropEnabled(), // TODO(ncooke3): Is the below note behavior that we want to be intended? // This is needed for targets with that have // `swiftSettings: [.interoperabilityMode(.Cxx)]` set. cxxLanguageStandard: .cxx11 ) + +func targetsWithCxxInteropEnabled() -> [Target] { +// The below targets have C++ interoperability mode enabled, a feature that +// requires Swift 5.9 or greater. +#if swift(>=5.9) + // MARK: - MixedTargetWithCXX_CXXInteropEnabled + return [ + .target( + name: "MixedTargetWithCXX_CXXInteropEnabled", + swiftSettings: [.interoperabilityMode(.Cxx)] + ) + ] +#else + return [] +#endif // swift(>=5.9) +} diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index c4635d4cca9..edf6dc27242 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -138,6 +138,9 @@ final class MixedTargetTests: XCTestCase { } } +// The below tests build targets with C++ interoperability mode enabled, a +// feature that requires Swift 5.9 or greater. +#if swift(>=5.9) func testMixedTargetWithCXX_CXXInteropEnabled() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in XCTAssertBuilds( @@ -146,6 +149,7 @@ final class MixedTargetTests: XCTestCase { ) } } +#endif // swift(>=5.9) func testMixedTargetWithCXX() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in From ecb3747c7dd219a7343ba7a5cd172876548dc320 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 25 Jul 2023 19:03:21 -0400 Subject: [PATCH 122/178] Remove unneeded build artifacts --- .../MixedTargetBuildDescription.swift | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index c0fcf2436d4..3aba2916521 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -319,16 +319,7 @@ public final class MixedTargetBuildDescription { // Building a mixed target uses intermediate module maps to expose // private headers to the Swift part of the module. - // 1. Generate an intermediate module map that exposes all headers, - // including the submodule with the generated Swift header. - let intermediateModuleMapPath = intermediatesDirectory.appending(component: moduleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(mixedTarget.clangTarget.path), - at: intermediateModuleMapPath, - interopHeaderPath: interopHeaderPath - ) - - // 2. Generate an intermediate module map that exposes all headers. + // 1. Generate an intermediate module map that exposes all headers. // When building the Swift part of the mixed target, a module map will // be needed to access types from the Objective-C part of the target. // However, this module map should not expose the generated Swift @@ -339,7 +330,7 @@ public final class MixedTargetBuildDescription { at: unextendedModuleMapPath ) - // 3. Use VFS overlays to purposefully expose specific resources (e.g. + // 2. Use VFS overlays to purposefully expose specific resources (e.g. // module map) during the build. The directory to add a VFS overlay in // depends on the presence of a custom module map. let rootOverlayResourceDirectory: AbsolutePath @@ -363,12 +354,6 @@ public final class MixedTargetBuildDescription { VFSOverlay.Directory( name: rootOverlayResourceDirectory.pathString, contents: [ - // Redirect the `module.modulemap` to the modified - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: intermediateModuleMapPath.pathString - ), // Add a generated Swift header that redirects to the // generated header in the build directory's root. VFSOverlay.File( @@ -395,7 +380,7 @@ public final class MixedTargetBuildDescription { ), ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) - // 4. Tie everything together by passing build flags. + // 3. Tie everything together by passing build flags. // Importing the underlying module will build the Objective-C // part of the module. In order to find the underlying module, From 4a524e19f19b372dcc20bc33f5926276c720489c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 25 Jul 2023 19:33:18 -0400 Subject: [PATCH 123/178] Remove 'requires objc' --- Sources/PackageLoading/ModuleMapGenerator.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 54731c6c015..861d2bf5563 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -212,7 +212,6 @@ public struct ModuleMapGenerator { """ module \(moduleName).Swift { header \"\(interopHeaderPath.moduleEscapedPathString)\" - requires objc } """ From b01c677869395b257c3921c426a63eb6e659cae3 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sun, 30 Jul 2023 11:24:03 -0400 Subject: [PATCH 124/178] Update test target Swift code to not reference non-public types --- .../Sources/BasicMixedTarget/Engine.swift | 5 ++++- .../Sources/BasicMixedTarget/NewCar.swift | 9 +++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift index bd269a81124..b22cbc46879 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift @@ -1,4 +1,7 @@ import Foundation // This type is Objective-C compatible and used in `OldCar`. -@objc public class Engine: CarPart {} +// FIXME(ncooke3): When Swift compiler's header generation logic is updated, +// subclass type from Objective-C. +// @objc public class Engine: CarPart {} +@objc public class Engine: NDObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift index fbf2e453b4f..26195a50c56 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift @@ -5,10 +5,11 @@ public class NewCar { var engine: Engine? = nil // The following types are defined in Objective-C. var driver: Driver? = nil - var transmission: Transmission? = nil - var hasStickShift: Bool { - return transmission != nil && transmission!.transmissionKind == .manual - } + // TODO(ncooke3): Update when non-public headers are exposed to Swift. + // var transmission: Transmission? = nil + // var hasStickShift: Bool { + // return transmission != nil && transmission!.transmissionKind == .manual + // } public init() {} } From 5f0054bade069d919ba7b8f72f26611a76da5f82 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sun, 30 Jul 2023 15:11:55 -0400 Subject: [PATCH 125/178] Post FR Re-work (1) - Removed Intermediates build directory and its associated logic. - Mixed target builds (only case where module map not supported) --- .../Sources/BasicMixedTarget/Engine.swift | 2 +- .../MixedTargetBuildDescription.swift | 166 ++---------------- 2 files changed, 11 insertions(+), 157 deletions(-) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift index b22cbc46879..ef5bfd0f69d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift @@ -4,4 +4,4 @@ import Foundation // FIXME(ncooke3): When Swift compiler's header generation logic is updated, // subclass type from Objective-C. // @objc public class Engine: CarPart {} -@objc public class Engine: NDObject {} +@objc public class Engine: NSObject {} diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 3aba2916521..8d5cf0311c9 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -125,14 +125,9 @@ public final class MixedTargetBuildDescription { // A mixed target's build directory uses three subdirectories to // distinguish between build artifacts: - // - Intermediates: Stores artifacts used during the target's build. // - Product: Stores artifacts used by clients of the target. - // - InteropSupport: If needed, stores a generated umbrella header - // for use during the target's build and by clients of the target. let tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") - let intermediatesDirectory = tempsPath.appending(component: "Intermediates") let productDirectory = tempsPath.appending(component: "Product") - let interopSupportDirectory: AbsolutePath? // Filenames for VFS overlay files. let allProductHeadersFilename = "all-product-headers.yaml" @@ -147,70 +142,9 @@ public final class MixedTargetBuildDescription { fileSystem: fileSystem ) - // MARK: Conditionally generate an umbrella header for interoptability - - // When the Swift compiler creates the generated interop header - // (`$(ModuleName)-Swift.h`) for Objective-C compatible Swift API - // (via `-emit-objc-header`), any Objective-C symbol that cannot be - // forward declared (e.g. superclass, protocol, etc.) will attempt to - // be imported via a bridging or umbrella header. Since the compiler - // evaluates the target as a framework (as opposed to an app), the - // compiler assumes* an umbrella header exists in a subdirectory (named - // after the module) within the public headers directory: - // - // #import <$(ModuleName)/$(ModuleName).h> - // - // The compiler assumes that the above path can be resolved relative to - // the public header directory. Instead of forcing package authors to - // structure their packages around that constraint, the package manager - // generates an umbrella header if needed and will pass it along as a - // header search path when building the target. - // - // *: https://developer.apple.com/documentation/swift/importing-objective-c-into-swift - let umbrellaHeaderPathComponents = [mixedTarget.c99name, "\(mixedTarget.c99name).h"] - let potentialUmbrellaHeadersPath = mixedTarget.clangTarget.includeDir - .appending(components: umbrellaHeaderPathComponents) - // Check if an umbrella header at - // `PUBLIC_HDRS_DIR/$(ModuleName)/$(ModuleName).h` already exists. - if !fileSystem.isFile(potentialUmbrellaHeadersPath) { - interopSupportDirectory = tempsPath.appending(component: "InteropSupport") - let generatedUmbrellaHeaderPath = interopSupportDirectory! - .appending(components: umbrellaHeaderPathComponents) - - // TODO(ncooke3): Can simplify this to just use `includeDir` as - // umbrella directory if it is non-empty. - // TODO(ncooke3): What if there are no headers in `includeDir`? - var generatedUmbrellaHeader = "" - mixedTarget.clangTarget.headers - // One of the requirements for a Swift API to be Objective-C - // compatible and therefore included in the generated interop - // header is that it has `public` or `open` visbility. This - // means that such Swift API can only reference (e.g. subclass) - // Objective-C types defined in the target's public headers. - // Because of this, the generated umbrella header will only - // include public headers so all other can be filtered out. - .filter { $0.isDescendant(of: mixedTarget.clangTarget.includeDir) } - // Add each remaining header to the generated umbrella header. - .forEach { - // Import the header, followed by a newline. - generatedUmbrellaHeader.append("#import \"\($0)\"\n") - } - - try fileSystem.writeFileContentsIfNeeded( - generatedUmbrellaHeaderPath, - string: generatedUmbrellaHeader - ) - } else { - // An umbrella header in the desired format already exists so the - // interop support directory is not needed. - interopSupportDirectory = nil - } - // Clients will later depend on the public header directory, and, if an // umbrella header was created, the header's root directory. - self.publicHeaderPaths = interopSupportDirectory != nil ? - [mixedTarget.clangTarget.includeDir, interopSupportDirectory!] : - [mixedTarget.clangTarget.includeDir] + self.publicHeaderPaths = [mixedTarget.clangTarget.includeDir] // MARK: Generate products to be used by client of the target. @@ -292,7 +226,7 @@ public final class MixedTargetBuildDescription { try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: productModuleMapPath, - interopHeaderPath: interopHeaderPath + interopHeaderPath: nil//interopHeaderPath ) // Set the generated module map as the module map for the target. @@ -312,89 +246,25 @@ public final class MixedTargetBuildDescription { ] ), ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) + + } - // MARK: Generate intermediate artifacts used to build the target. - - // Building a mixed target uses intermediate module maps to expose - // private headers to the Swift part of the module. - - // 1. Generate an intermediate module map that exposes all headers. - // When building the Swift part of the mixed target, a module map will - // be needed to access types from the Objective-C part of the target. - // However, this module map should not expose the generated Swift - // header since it will not exist yet. - let unextendedModuleMapPath = intermediatesDirectory.appending(component: unextendedModuleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: .umbrellaDirectory(mixedTarget.clangTarget.path), - at: unextendedModuleMapPath - ) - - // 2. Use VFS overlays to purposefully expose specific resources (e.g. - // module map) during the build. The directory to add a VFS overlay in - // depends on the presence of a custom module map. - let rootOverlayResourceDirectory: AbsolutePath - if case .custom(let customModuleMapPath) = mixedTarget.clangTarget.moduleMapType { - // To avoid the custom module map causing a module redeclaration - // error, a VFS overlay is used when building the target to - // redirect the custom module map to the modified module map in the - // build directory. This redirecting overlay is placed in the - // custom module map's parent directory, as to replace it. - rootOverlayResourceDirectory = customModuleMapPath.parentDirectory - } else { - // Since no custom module map exists, the build directory can - // be used as the root of the VFS overlay. In this case, the - // VFS overlay's sole purpose is to expose the generated Swift - // header. - rootOverlayResourceDirectory = intermediatesDirectory - } - - let allProductHeadersPath = intermediatesDirectory.appending(component: allProductHeadersFilename) - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. - VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString - ), - ] - ), - ]).write(to: allProductHeadersPath, fileSystem: fileSystem) - - let unextendedModuleMapOverlayPath = intermediatesDirectory - .appending(component: unextendedModuleOverlayFilename) - try VFSOverlay(roots: [ - VFSOverlay.Directory( - name: rootOverlayResourceDirectory.pathString, - contents: [ - // Redirect the `module.modulemap` to the *unextended* - // module map in the intermediates directory. - VFSOverlay.File( - name: moduleMapFilename, - externalContents: unextendedModuleMapPath.pathString - ), - ] - ), - ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) - // 3. Tie everything together by passing build flags. // Importing the underlying module will build the Objective-C // part of the module. In order to find the underlying module, - // a `module.modulemap` needs to be discoverable via a header - // search path. + // a `module.modulemap` needs to be discoverable via directory passed + // as a header search path. self.swiftTargetBuildDescription.additionalFlags += [ "-import-underlying-module", - "-I", rootOverlayResourceDirectory.pathString, + "-I", tempsPath.pathString, ] self.swiftTargetBuildDescription.appendClangFlags( // Pass both VFS overlays to the underlying Clang compiler. - "-ivfsoverlay", allProductHeadersPath.pathString, - "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, +// "-ivfsoverlay", allProductHeadersPath.pathString, +// "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, // Adding the root of the target's source as a header search // path allows for importing headers (within the mixed target's // headers) using paths relative to the root. @@ -413,23 +283,7 @@ public final class MixedTargetBuildDescription { // the root. "-I", mixedTarget.path.pathString, // Include overlay file to add interop header to overlay directory. - "-ivfsoverlay", allProductHeadersPath.pathString, - // The above two args add the interop header in the overlayed - // directory. Pass the overlay directory as a search path so the - // generated header can be imported. - "-I", intermediatesDirectory.pathString, + "-I", interopHeaderPath.parentDirectory.pathString ] - - // If a generated umbrella header was created, add its root directory - // as a header search path. This will resolve its import within the - // generated interop header. - if let interopSupportDirectory = interopSupportDirectory { - self.swiftTargetBuildDescription.appendClangFlags( - "-I", interopSupportDirectory.pathString - ) - self.clangTargetBuildDescription.additionalFlags += [ - "-I", interopSupportDirectory.pathString, - ] - } } } From 990908ad8c3b78dec839b38bc33bdd9b0305c7be Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Aug 2023 15:22:07 -0400 Subject: [PATCH 126/178] Post FR Re-work (2) - Case where custom module map is provided --- .../MixedTargetBuildDescription.swift | 75 +++++++++++++------ Sources/Build/BuildPlan.swift | 5 -- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 8d5cf0311c9..32554446980 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -127,7 +127,6 @@ public final class MixedTargetBuildDescription { // distinguish between build artifacts: // - Product: Stores artifacts used by clients of the target. let tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") - let productDirectory = tempsPath.appending(component: "Product") // Filenames for VFS overlay files. let allProductHeadersFilename = "all-product-headers.yaml" @@ -148,10 +147,6 @@ public final class MixedTargetBuildDescription { // MARK: Generate products to be used by client of the target. - // Path to the module map used by clients to access the mixed target's - // public API. - let productModuleMapPath = productDirectory.appending(component: moduleMapFilename) - switch mixedTarget.clangTarget.moduleMapType { // When the mixed target has a custom module map, clients of the target // will be passed a module map *and* VFS overlay at buildtime to access @@ -175,6 +170,12 @@ public final class MixedTargetBuildDescription { "submodule for the module \(target.c99name)." ) } + + // If it's named 'module.modulemap', there will be a module + // redeclaration error as both the public headers dir. and the + // build dir. are passed as import paths and there will be a + // `module.modulemap` in each directory. + let productModuleMapPath = tempsPath.appending(component: "extended-custom-module.modulemap") // Extend the contents and write it to disk, if needed. let productModuleMap = """ @@ -193,7 +194,7 @@ public final class MixedTargetBuildDescription { // for the target. The below VFS overlay will redirect to the // contents of the modified module map. self.moduleMap = customModuleMapPath - self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) + self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) try VFSOverlay(roots: [ VFSOverlay.Directory( @@ -214,7 +215,16 @@ public final class MixedTargetBuildDescription { ] ), ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) - + + // Importing the underlying module will build the Objective-C + // part of the module. In order to find the underlying module, + // a `module.modulemap` needs to be discoverable via directory passed + // as a header search path. + self.swiftTargetBuildDescription.additionalFlags += [ + "-import-underlying-module", + "-I", mixedTarget.clangTarget.includeDir.pathString + ] + // When the mixed target does not have a custom module map, one will be // generated as a product for use by clients. // - Note: When `.none`, the mixed target has no public headers. Even @@ -223,15 +233,16 @@ public final class MixedTargetBuildDescription { // Swift API in a Clang context. case .umbrellaHeader, .umbrellaDirectory, .none: let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType + let productModuleMapPath = tempsPath.appending(component: moduleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: productModuleMapPath, - interopHeaderPath: nil//interopHeaderPath + interopHeaderPath: interopHeaderPath ) // Set the generated module map as the module map for the target. self.moduleMap = productModuleMapPath - self.allProductHeadersOverlay = productDirectory.appending(component: allProductHeadersFilename) + self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) try VFSOverlay(roots: [ VFSOverlay.Directory( @@ -246,25 +257,41 @@ public final class MixedTargetBuildDescription { ] ), ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) - - - } - // 3. Tie everything together by passing build flags. + let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) + try moduleMapGenerator.generateModuleMap( + type: generatedModuleMapType, + at: unextendedModuleMapPath, + interopHeaderPath: nil + ) + let unextendedModuleMapOverlayPath = tempsPath.appending(component: unextendedModuleOverlayFilename) - // Importing the underlying module will build the Objective-C - // part of the module. In order to find the underlying module, - // a `module.modulemap` needs to be discoverable via directory passed - // as a header search path. - self.swiftTargetBuildDescription.additionalFlags += [ - "-import-underlying-module", - "-I", tempsPath.pathString, - ] + try VFSOverlay(roots: [ + VFSOverlay.Directory( + name: tempsPath.pathString, + contents: [ + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. + VFSOverlay.File( + name: moduleMapFilename, + externalContents: unextendedModuleMapPath.pathString + ), + ] + ), + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) + + // Importing the underlying module will build the Objective-C + // part of the module. In order to find the underlying module, + // a `module.modulemap` needs to be discoverable via directory passed + // as a header search path. + self.swiftTargetBuildDescription.additionalFlags += [ + "-import-underlying-module", + "-I", tempsPath.pathString, + "-Xcc", "-ivfsoverlay", "-Xcc", unextendedModuleMapOverlayPath.pathString + ] + } self.swiftTargetBuildDescription.appendClangFlags( - // Pass both VFS overlays to the underlying Clang compiler. -// "-ivfsoverlay", allProductHeadersPath.pathString, -// "-ivfsoverlay", unextendedModuleMapOverlayPath.pathString, // Adding the root of the target's source as a header search // path allows for importing headers (within the mixed target's // headers) using paths relative to the root. diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index e4d9ecc6659..ba906090f85 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -952,11 +952,6 @@ public class BuildPlan: SPMBuildCore.BuildPlan { swiftTarget.appendClangFlags("-I", $0.pathString) } - // Add the dependency's public VFS overlay. - swiftTarget.appendClangFlags( - "-ivfsoverlay", target.allProductHeadersOverlay.pathString - ) - default: break } From 7b0f582fe6499bdc04fa2717c31f7a234a1e2cf3 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Aug 2023 20:43:13 -0400 Subject: [PATCH 127/178] Post FR Re-work (3) - 'Fixtures' tests pass locally --- .../Sources/NewCar.swift | 4 - .../BasicMixedTargets/Package.swift | 34 ++---- .../CabinClass.h | 7 -- .../Engine.swift | 4 - .../NewPlane.swift | 14 --- .../OldPlane.m | 21 ---- .../Pilot.m | 12 -- .../PlanePart.m | 12 -- ...BasicMixedTargetWithManualBridgingHeader.h | 5 - .../OldPlane.h | 14 --- .../Pilot.h | 6 - .../PlanePart.h | 5 - .../MixedTargetWithCustomModuleMap.h | 1 + .../NewCar.swift | 2 - .../NewCar.swift | 4 - .../NewCar.swift | 4 - .../NewCar.swift | 4 - .../BarBaz.swift | 16 +++ .../Engine.swift | 4 - .../NewCar.swift | 10 -- .../OnLoadHook.h | 4 + .../{XYZOldCar.m => OnLoadHook.m} | 9 +- .../XYZDriver.h | 12 -- .../XYZDriver.m | 6 - .../XYZOldCar.h | 18 --- .../MixedTargetWithNonPublicHeaders/Bat.swift | 2 + ...leImport.m => ObjcBasicMixedTargetTests.m} | 9 +- ...xedTargetTestsViaGeneratedBridgingHeader.m | 26 ----- ...ObjcBasicMixedTargetTestsViaHeaderImport.m | 23 ---- ...dTargetWithManualBridgingHeaderTests.swift | 25 ----- ...ManualBridgingHeaderTestsViaHeaderImport.m | 27 ----- ...ManualBridgingHeaderTestsViaModuleImport.m | 23 ---- ...ithCXXPublicAPIAndCustomModuleMapTests.mm} | 10 +- ...IAndCustomModuleMapTestsViaModuleImport.mm | 24 ---- ...> ObjcMixedTargetWithCXXPublicAPITests.mm} | 8 +- ...getWithCXXPublicAPITestsViaModuleImport.mm | 24 ---- ...ObjcMixedTargetWithCustomModuleMapTests.m} | 8 +- ...ModuleMapTestsViaGeneratedBridgingHeader.m | 25 ----- ...tWithCustomModuleMapTestsViaModuleImport.m | 22 ---- ...etWithNoPublicObjectiveCHeadersTests.swift | 11 +- ...TargetWithNoPublicObjectiveCHeadersTests.m | 5 +- ...MixedTargetWithNonPublicHeadersTests.swift | 17 --- Tests/FunctionalTests/MixedTargetTests.swift | 105 ++++++++++-------- 43 files changed, 133 insertions(+), 493 deletions(-) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/CabinClass.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Engine.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/NewPlane.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/Pilot.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift create mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h rename Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/{XYZOldCar.m => OnLoadHook.m} (58%) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h rename Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/{ObjcBasicMixedTargetTestsViaModuleImport.m => ObjcBasicMixedTargetTests.m} (73%) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithManualBridgingHeaderTests.swift delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m rename Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/{ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm => ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm} (81%) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm rename Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/{ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm => ObjcMixedTargetWithCXXPublicAPITests.mm} (68%) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm rename Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/{ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m => ObjcMixedTargetWithCustomModuleMapTests.m} (71%) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift index fbf2e453b4f..d50c1a21185 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift @@ -5,10 +5,6 @@ public class NewCar { var engine: Engine? = nil // The following types are defined in Objective-C. var driver: Driver? = nil - var transmission: Transmission? = nil - var hasStickShift: Bool { - return transmission != nil && transmission!.transmissionKind == .manual - } public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift index 9684d7be51b..1724d001a9a 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Package.swift @@ -32,23 +32,15 @@ let package = Package( dependencies: ["BasicMixedTarget"] ), - // MARK: - BasicMixedTargetWithManualBridgingHeader - .target( - name: "BasicMixedTargetWithManualBridgingHeader" - ), - .testTarget( - name: "BasicMixedTargetWithManualBridgingHeaderTests", - dependencies: ["BasicMixedTargetWithManualBridgingHeader"] - ), - - // MARK: - BasicMixedTargetWithUmbrellaHeader - .target( - name: "BasicMixedTargetWithUmbrellaHeader" - ), - .testTarget( - name: "BasicMixedTargetWithUmbrellaHeaderTests", - dependencies: ["BasicMixedTargetWithUmbrellaHeader"] - ), + // FIXME(ncooke3): Re-enable with Swift compiler change (see proposal). + // // MARK: - BasicMixedTargetWithUmbrellaHeader + // .target( + // name: "BasicMixedTargetWithUmbrellaHeader" + // ), + // .testTarget( + // name: "BasicMixedTargetWithUmbrellaHeaderTests", + // dependencies: ["BasicMixedTargetWithUmbrellaHeader"] + // ), // MARK: - MixedTargetWithResources .target( @@ -171,12 +163,6 @@ let package = Package( .target( name: "MixedTargetWithNonPublicHeaders" ), - // This test target should fail to build. See - // `testNonPublicHeadersAreNotVisibleFromOutsideOfTarget`. - .testTarget( - name: "MixedTargetWithNonPublicHeadersTests", - dependencies: ["MixedTargetWithNonPublicHeaders"] - ), // MARK: - MixedTargetWithCustomPaths .target( @@ -201,6 +187,8 @@ let package = Package( ), // MARK: - MixedTargetWithNoPublicObjectiveCHeaders + // This target is an edge case. It's purpose may or may not be + // useful, but it shouldn't fail to build. .target( name: "MixedTargetWithNoPublicObjectiveCHeaders" ), diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/CabinClass.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/CabinClass.h deleted file mode 100644 index a2dd7552178..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/CabinClass.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -typedef NS_ENUM(NSInteger, CabinClass) { - CabinClassFirstClass, - CabinClassBusinessClass, - CabinClassEconomyClass -}; diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Engine.swift deleted file mode 100644 index 222c4b45157..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Engine.swift +++ /dev/null @@ -1,4 +0,0 @@ -import Foundation - -// This type is Objective-C compatible and used in `OldPlane`. -@objc public class Engine: PlanePart {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/NewPlane.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/NewPlane.swift deleted file mode 100644 index 8cc1eff3a0b..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/NewPlane.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -public class NewPlane { - // `Engine` is defined in Swift. - var engine: Engine? = nil - // The following types are defined in Objective-C. - var pilot: Pilot? = nil - var cabinClass: CabinClass? = nil - var hasTrolleyService: Bool { - return cabinClass != nil && cabinClass != .economyClass - } - - public init() {} -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m deleted file mode 100644 index eb61e9e523f..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/OldPlane.m +++ /dev/null @@ -1,21 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetWithManualBridgingHeader/OldPlane.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `OldPlane` can be imported via: -#import "include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h" -#import "BasicMixedTargetWithManualBridgingHeader/OldPlane.h" - -// Import the Swift part of the module. -#import "BasicMixedTargetWithManualBridgingHeader-Swift.h" - -#import "CabinClass.h" - -@interface OldPlane () -@property(nonatomic, assign) CabinClass cabinClass; -@end - -@implementation OldPlane -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m deleted file mode 100644 index 71f34a9031e..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/Pilot.m +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetWithManualBridgingHeader/Pilot.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `Pilot` can be imported via: -#import "include/BasicMixedTargetWithManualBridgingHeader/Pilot.h" -#import "BasicMixedTargetWithManualBridgingHeader/Pilot.h" - -@implementation Pilot -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m deleted file mode 100644 index dac124120ff..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/PlanePart.m +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// The below import syntax resolves to the `"BasicMixedTargetWithManualBridgingHeader/PlanePart.h"` -// path within the public headers directory. It is not related to a -// framework style import. -#import -// Alternatively, the above `PlanePart` can be imported via: -#import "include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h" -#import "BasicMixedTargetWithManualBridgingHeader/PlanePart.h" - -@implementation PlanePart -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h deleted file mode 100644 index 30eb5f50868..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/BasicMixedTargetWithManualBridgingHeader.h +++ /dev/null @@ -1,5 +0,0 @@ -// Note: Importing `#import ` is not supported -// within this directory. -#import "PlanePart.h" -#import "Pilot.h" -#import "OldPlane.h" diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h deleted file mode 100644 index 30be4cb8f00..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/OldPlane.h +++ /dev/null @@ -1,14 +0,0 @@ -#import - -#import "Pilot.h" - -// The `Engine` type is declared in the Swift part of the module. Such types -// must be forward declared in headers. -@class Engine; - -@interface OldPlane : NSObject -// `Engine` is defined in Swift. -@property(nullable) Engine *engine; -// `Pilot` is defined in Objective-C. -@property(nullable) Pilot *pilot; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/Pilot.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/Pilot.h deleted file mode 100644 index 2bd5b9b60ea..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/Pilot.h +++ /dev/null @@ -1,6 +0,0 @@ -#import - -// This type is Swift compatible and used in `NewPlane`. -@interface Pilot : NSObject -@property(nonnull) NSString* name; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h deleted file mode 100644 index 6ee9ef215ed..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithManualBridgingHeader/include/BasicMixedTargetWithManualBridgingHeader/PlanePart.h +++ /dev/null @@ -1,5 +0,0 @@ -#import - -// This type is subclassed by the Objective-C compatible Swift `Engine` type. -@interface PlanePart : NSObject -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h new file mode 100644 index 00000000000..09a635d7856 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h @@ -0,0 +1 @@ +#import "../MixedTarget.h" \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift index 6f3fc717999..07ea404b85c 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift @@ -5,8 +5,6 @@ public class NewCar { var engine: Engine? = nil // `Driver` is defined in Objective-C. var driver: Driver? = nil - // `Transmission` is defined ina private header. - var transmission: Transmission? = nil public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift index fbf2e453b4f..d50c1a21185 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift @@ -5,10 +5,6 @@ public class NewCar { var engine: Engine? = nil // The following types are defined in Objective-C. var driver: Driver? = nil - var transmission: Transmission? = nil - var hasStickShift: Bool { - return transmission != nil && transmission!.transmissionKind == .manual - } public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift index fbf2e453b4f..d50c1a21185 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift @@ -5,10 +5,6 @@ public class NewCar { var engine: Engine? = nil // The following types are defined in Objective-C. var driver: Driver? = nil - var transmission: Transmission? = nil - var hasStickShift: Bool { - return transmission != nil && transmission!.transmissionKind == .manual - } public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift index 8beaa7f21dd..80b725b488e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift @@ -6,10 +6,6 @@ public class NewCar { var engine: Engine? = nil // The following types are defined in Objective-C. var driver: Driver? = nil - var transmission: Transmission? = nil - var hasStickShift: Bool { - return transmission != nil && transmission!.transmissionKind == .manual - } public init() {} } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift new file mode 100644 index 00000000000..4ad0a39918a --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift @@ -0,0 +1,16 @@ +import Foundation + +// NOTE: This target is an edge case. It's purpose may or may not be useful, +// but it shouldn't fail to build. + +// This type is Objective-C compatible and used in `OldCar`. +@objc public class Bar: NSObject { + @objc public func doStuff() {} +} + +public struct Baz { + public let bar: Bar? + public init(bar: Bar? = nil) { + self.bar = bar + } +} \ No newline at end of file diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift deleted file mode 100644 index da1ee757331..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/Engine.swift +++ /dev/null @@ -1,4 +0,0 @@ -import Foundation - -// This type is Objective-C compatible and used in `OldCar`. -@objc public class Engine: NSObject {} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift deleted file mode 100644 index 07ea404b85c..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/NewCar.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -public class NewCar { - // `Engine` is defined in Swift. - var engine: Engine? = nil - // `Driver` is defined in Objective-C. - var driver: Driver? = nil - - public init() {} -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h new file mode 100644 index 00000000000..67544ebf664 --- /dev/null +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h @@ -0,0 +1,4 @@ +#import + +@interface OnLoadHook : NSObject +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m similarity index 58% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m rename to Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m index f8c80ac2afe..2cc681ba38d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m @@ -1,9 +1,14 @@ #import -#import "XYZOldCar.h" +#import "OnLoadHook.h" // Import the Swift part of the module. #import "MixedTargetWithNoPublicObjectiveCHeaders-Swift.h" -@implementation XYZOldCar +@implementation OnLoadHook + ++ (void)load { + [[[Bar alloc] init] doStuff]; +} + @end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h deleted file mode 100644 index 1921ac7046f..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.h +++ /dev/null @@ -1,12 +0,0 @@ -#import - -// This type is Swift compatible and used in `NewCar`. -// -// Class prefix is needed to avoid conflict with identical type when building -// the test executable. This should not be needed in real packages as they -// likely would not have multiple types with the same name like in this -// test package. -NS_SWIFT_NAME(Driver) -@interface XYZDriver : NSObject -@property(nonnull) NSString* name; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m deleted file mode 100644 index 97d3de51f0e..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZDriver.m +++ /dev/null @@ -1,6 +0,0 @@ -#import - -#import "XYZDriver.h" - -@implementation XYZDriver -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h deleted file mode 100644 index 1324aa18b7d..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/XYZOldCar.h +++ /dev/null @@ -1,18 +0,0 @@ -#import - -#import "XYZDriver.h" - -// The `Engine` type is declared in the Swift part of the module. Such types -// must be forward declared in headers. -@class Engine; - -// Class prefix is needed to avoid conflict with identical type when building -// the test executable. This should not be needed in real packages as they -// likely would not have multiple types with the same name like in this -// test package. -@interface XYZOldCar : NSObject -// `Engine` is defined in Swift. -@property(nullable) Engine *engine; -// `Driver` is defined in Objective-C. -@property(nullable) XYZDriver *driver; -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift index d02a706f703..d44d379724e 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift @@ -1,7 +1,9 @@ import Foundation public class Bat { + #if EXPECT_FAILURE // The following Objective-C types are defined in non-public headers. let foo: Foo? = nil let bar: Bar? = nil + #endif // EXPECT_FAILURE } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m similarity index 73% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m index ad484324347..2f48e888865 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaModuleImport.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m @@ -1,15 +1,18 @@ #import +#if TEST_MODULE_IMPORTS +@import BasicMixedTarget; +#else #import "CarPart.h" #import "Driver.h" #import "OldCar.h" #import "BasicMixedTarget-Swift.h" +#endif // TEST_MODULE_IMPORTS -@interface ObjcBasicMixedTargetTestsViaModuleImport : XCTestCase - +@interface ObjcBasicMixedTargetTests : XCTestCase @end -@implementation ObjcBasicMixedTargetTestsViaModuleImport +@implementation ObjcBasicMixedTargetTests - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m deleted file mode 100644 index 93a8db46a91..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader.m +++ /dev/null @@ -1,26 +0,0 @@ -#import - -// IMPORTANT: The generated "bridging" header must be imported before the -// generated interop header. -#import "BasicMixedTarget/BasicMixedTarget.h" -#import "BasicMixedTarget-Swift.h" - -@interface ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader : XCTestCase - -@end - -@implementation ObjcBasicMixedTargetTestsViaGeneratedBridgingHeader - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - OldCar *oldCar = [[OldCar alloc] init]; - Driver *driver = [[Driver alloc] init]; - CarPart *carPart = [[CarPart alloc] init]; -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m deleted file mode 100644 index 36738c8bb5a..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTestsViaHeaderImport.m +++ /dev/null @@ -1,23 +0,0 @@ -#import - -@import BasicMixedTarget; - -@interface ObjcBasicMixedTargetTestsViaHeaderImport : XCTestCase - -@end - -@implementation ObjcBasicMixedTargetTestsViaHeaderImport - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - OldCar *oldCar = [[OldCar alloc] init]; - Driver *driver = [[Driver alloc] init]; - CarPart *carPart = [[CarPart alloc] init]; -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithManualBridgingHeaderTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithManualBridgingHeaderTests.swift deleted file mode 100644 index 2a1f04ee384..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/BasicMixedTargetWithManualBridgingHeaderTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -import XCTest -import BasicMixedTargetWithManualBridgingHeader - -final class BasicMixedTargetWithManualBridgingHeaderTests: XCTestCase { - - func testPublicSwiftAPI() throws { - // Check that Swift API surface is exposed... - let _ = NewPlane() - let _ = Engine() - } - - func testPublicObjcAPI() throws { - // Check that Objective-C API surface is exposed... - let _ = OldPlane() - let _ = Pilot() - } - - func testModulePrefixingWorks() throws { - let _ = BasicMixedTargetWithManualBridgingHeader.NewPlane() - let _ = BasicMixedTargetWithManualBridgingHeader.Engine() - let _ = BasicMixedTargetWithManualBridgingHeader.OldPlane() - let _ = BasicMixedTargetWithManualBridgingHeader.Pilot() - } - -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m deleted file mode 100644 index 14ce94e63b6..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport.m +++ /dev/null @@ -1,27 +0,0 @@ -#import - -// Import the target via header imports. -#import -#import -#import -#import -#import - -@interface ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport : XCTestCase - -@end - -@implementation ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaHeaderImport - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - OldPlane *oldPlane = [[OldPlane alloc] init]; - Pilot *pilot = [[Pilot alloc] init]; -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m deleted file mode 100644 index 61f8bd7d34b..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithManualBridgingHeaderTests/ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport.m +++ /dev/null @@ -1,23 +0,0 @@ -#import - -// Import the target via a module import. -@import BasicMixedTargetWithManualBridgingHeader; - -@interface ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport : XCTestCase - -@end - -@implementation ObjcBasicMixedTargetWithManualBridgingHeaderTestsViaModuleImport - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - OldPlane *oldPlane = [[OldPlane alloc] init]; - Pilot *pilot = [[Pilot alloc] init]; -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm similarity index 81% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm index ad6ea829a12..446094e4314 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm @@ -1,13 +1,17 @@ #import +#if TEST_MODULE_IMPORTS +@import MixedTargetWithCXXPublicAPIAndCustomModuleMap; +#else #import "XYZCxxSumFinder.hpp" #import "XYZObjcCalculator.h" #import "MixedTargetWithCXXPublicAPIAndCustomModuleMap-Swift.h" +#endif // TEST_MODULE_IMPORTS -@interface ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport : XCTestCase +@interface ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests : XCTestCase @end -@implementation ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaHeaderImport +@implementation ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests - (void)testPublicObjcAPI { XCTAssertEqual([XYZObjcCalculator factorialForInt:5], 120); @@ -23,4 +27,4 @@ - (void)testPublicCXXAPI { XCTAssertEqual(sf.sum(1,2), 3); } -@end \ No newline at end of file +@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm deleted file mode 100644 index 70de39dd86f..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport.mm +++ /dev/null @@ -1,24 +0,0 @@ -#import - -@import MixedTargetWithCXXPublicAPIAndCustomModuleMap; - -@interface ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport : XCTestCase -@end - -@implementation ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTestsViaModuleImport - -- (void)testPublicObjcAPI { - XCTAssertEqual([XYZObjcCalculator factorialForInt:5], 120); - XCTAssertEqual([XYZObjcCalculator sumX:1 andY:2], 3); -} - -- (void)testPublicSwiftAPI { - XCTAssertEqualObjects([Factorial text], @"Hello, World!"); -} - -- (void)testPublicCXXAPI { - XYZCxxSumFinder sf; - XCTAssertEqual(sf.sum(1,2), 3); -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm similarity index 68% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm index 06f2d81ff05..c8113f2f951 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport.mm +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm @@ -1,13 +1,17 @@ #import +#if TEST_MODULE_IMPORTS +@import MixedTargetWithCXXPublicAPI; +#else #import "CXXSumFinder.hpp" #import "ObjcCalculator.h" #import "MixedTargetWithCXXPublicAPI-Swift.h" +#endif // TEST_MODULE_IMPORTS -@interface ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport : XCTestCase +@interface ObjcMixedTargetWithCXXPublicAPITests : XCTestCase @end -@implementation ObjcMixedTargetWithCXXPublicAPITestsViaHeaderImport +@implementation ObjcMixedTargetWithCXXPublicAPITests - (void)testPublicObjcAPI { XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm deleted file mode 100644 index f4e05dd95f4..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport.mm +++ /dev/null @@ -1,24 +0,0 @@ -#import - -@import MixedTargetWithCXXPublicAPI; - -@interface ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport : XCTestCase -@end - -@implementation ObjcMixedTargetWithCXXPublicAPITestsViaModuleImport - -- (void)testPublicObjcAPI { - XCTAssertEqual([ObjcCalculator factorialForInt:5], 120); - XCTAssertEqual([ObjcCalculator sumX:1 andY:2], 3); -} - -- (void)testPublicSwiftAPI { - XCTAssertEqualObjects([Factorial text], @"Hello, World!"); -} - -- (void)testPublicCXXAPI { - CXXSumFinder sf; - XCTAssertEqual(sf.sum(1,2), 3); -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m similarity index 71% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m rename to Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m index b77ee46f2e4..82c4a15f0e7 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m @@ -1,15 +1,19 @@ #import +#if TEST_MODULE_IMPORTS +@import MixedTargetWithCustomModuleMap; +#else #import "Machine.h" #import "MixedTarget.h" #import "Driver.h" #import "OldCar.h" #import "MixedTargetWithCustomModuleMap-Swift.h" +#endif // TEST_MODULE_IMPORTS -@interface ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport : XCTestCase +@interface ObjcMixedTargetWithCustomModuleMapTests : XCTestCase @end -@implementation ObjcMixedTargetWithCustomModuleMapTestsViaHeaderImport +@implementation ObjcMixedTargetWithCustomModuleMapTests - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m deleted file mode 100644 index f49d5f3f0ec..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader.m +++ /dev/null @@ -1,25 +0,0 @@ -#import - -// IMPORTANT: The generated "bridging" header must be imported before the -// generated interop header. -#import "MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h" -#import "MixedTargetWithCustomModuleMap-Swift.h" - -@interface ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader : XCTestCase -@end - -@implementation ObjcMixedTargetWithCustomModuleMapTestsViaGeneratedBridgingHeader - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - MyMachine *machine = [[MyMachine alloc] init]; - MyOldCar *oldCar = [[MyOldCar alloc] init]; - MyDriver *driver = [[MyDriver alloc] init]; -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m deleted file mode 100644 index 4666c1b8efe..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport.m +++ /dev/null @@ -1,22 +0,0 @@ -#import - -@import MixedTargetWithCustomModuleMap; - -@interface ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport : XCTestCase -@end - -@implementation ObjcMixedTargetWithCustomModuleMapTestsViaModuleImport - -- (void)testPublicSwiftAPI { - // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; -} - -- (void)testPublicObjcAPI { - // Check that Objective-C API surface is exposed... - MyMachine *machine = [[MyMachine alloc] init]; - MyOldCar *oldCar = [[MyOldCar alloc] init]; - MyDriver *driver = [[MyDriver alloc] init]; -} - -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift index 872d700f911..7077f77f1fb 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift @@ -5,17 +5,16 @@ final class MixedTargetWithNoPublicObjectiveCHeadersTests: XCTestCase { func testPublicSwiftAPI() throws { // Check that Swift API surface is exposed... - let _ = NewCar() - let _ = Engine() - let _ = MixedTargetWithNoPublicObjectiveCHeaders.NewCar() - let _ = MixedTargetWithNoPublicObjectiveCHeaders.Engine() + let _ = Bar() + let _ = Baz() + let _ = MixedTargetWithNoPublicObjectiveCHeaders.Bar() + let _ = MixedTargetWithNoPublicObjectiveCHeaders.Baz() } #if EXPECT_FAILURE func testObjcAPI() throws { // No Objective-C API should be exposed... - let _ = OldCar() - let _ = Driver() + let _ = OnLoadHook() } #endif diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m index a2dffe5b5e5..ed4e7b317b5 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m @@ -10,14 +10,13 @@ @implementation ObjcMixedTargetWithNoPublicObjectiveCHeadersTests - (void)testPublicSwiftAPI { // Check that Objective-C compatible Swift API surface is exposed... - Engine *engine = [[Engine alloc] init]; + Bar *bar = [[Bar alloc] init]; } #if EXPECT_FAILURE - (void)testObjcAPI { // No Objective-C API surface should be exposed... - OldCar *oldCar = [[OldCar alloc] init]; - Driver *driver = [[Driver alloc] init]; + OnLoadHook *onLoadHook = [[OnLoadHook alloc] init]; } #endif diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift b/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift deleted file mode 100644 index 051bcad91ff..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNonPublicHeadersTests/MixedTargetWithNonPublicHeadersTests.swift +++ /dev/null @@ -1,17 +0,0 @@ -import XCTest -import MixedTargetWithNonPublicHeaders - -#if EXPECT_FAILURE - -final class MixedTargetWithCTests: XCTestCase { - func testInternalObjcTypesAreNotExposed() throws { - // The following Objective-C types are defined in non-public headers - // within the `MixedTargetWithNonPublicHeaders` target. They should not be - // visible in this context and should cause a failure when building the - // test target associated with this file. - let _ = Foo() - let _ = Bar() - } -} - -#endif // EXPECT_FAILURE diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index edf6dc27242..92e03f47635 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -31,31 +31,19 @@ final class MixedTargetTests: XCTestCase { } } - func testMixedTargetWithUmbrellaHeader() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "BasicMixedTargetWithUmbrellaHeader"] - ) - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "BasicMixedTargetWithUmbrellaHeaderTests"] - ) - } - } - - func testMixedTargetWithManualBridgingHeader() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "BasicMixedTargetWithManualBridgingHeader"] - ) - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "BasicMixedTargetWithManualBridgingHeaderTests"] - ) - } - } +// FIXME(ncooke3): Re-enable with Swift compiler change (see proposal). +// func testMixedTargetWithUmbrellaHeader() throws { +// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// XCTAssertBuilds( +// fixturePath, +// extraArgs: ["--target", "BasicMixedTargetWithUmbrellaHeader"] +// ) +// XCTAssertSwiftTest( +// fixturePath, +// extraArgs: ["--filter", "BasicMixedTargetWithUmbrellaHeaderTests"] +// ) +// } +// } func testMixedTargetWithResources() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in @@ -72,11 +60,16 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: ["--target", "MixedTargetWithCustomModuleMap"] ) - - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "MixedTargetWithCustomModuleMapTests"] - ) + + // Test importing the mixed target into Clang source via a module + // import or textual imports. + for value in [0, 1] { + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCustomModuleMapTests"], + Xcc: ["-DTEST_MODULE_IMPORTS=\(value)"] + ) + } } } @@ -175,10 +168,15 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] ) - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"] - ) + // Test importing the mixed target into Clang source via a module + // import or textual imports. + for value in [0, 1] { + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCXXPublicAPITests"], + Xcc: ["-DTEST_MODULE_IMPORTS=\(value)"] + ) + } } } @@ -188,10 +186,15 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: ["--target", "MixedTargetWithCXXPublicAPIAndCustomModuleMap"] ) - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests"] - ) + // Test importing the mixed target into Clang source via a module + // import or textual imports. + for value in [0, 1] { + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "MixedTargetWithCXXPublicAPIAndCustomModuleMapTests"], + Xcc: ["-DTEST_MODULE_IMPORTS=\(value)"] + ) + } } } @@ -237,14 +240,21 @@ final class MixedTargetTests: XCTestCase { func testNonPublicHeadersAreVisibleFromSwiftPartOfMixedTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( + XCTAssertBuildFails( fixturePath, - extraArgs: ["--target", "MixedTargetWithNonPublicHeaders"] + extraArgs: ["--target", "MixedTargetWithNonPublicHeaders"], + // Without selectively enabling the tests with the below macro, + // the intentional build failure will break other unit tests + // since all targets in the package are build when running + // `swift test`. + Xswiftc: ["EXPECT_FAILURE"] ) } } - func testNonPublicHeadersAreNotVisibleFromOutsideOfTarget() throws { + // TODO(ncooke3): Use a different target to test this as the target below + // has been deleted. + func SKIP_testNonPublicHeadersAreNotVisibleFromOutsideOfTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in // The test target tries to access non-public headers so the build // should fail. @@ -395,10 +405,15 @@ final class MixedTargetTests: XCTestCase { func testMixedTestTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertSwiftTest( - fixturePath, - extraArgs: ["--filter", "BasicMixedTargetTests"] - ) + // Test importing the mixed target into Clang source via a module + // import or textual imports. + for value in [0, 1] { + XCTAssertSwiftTest( + fixturePath, + extraArgs: ["--filter", "BasicMixedTargetTests"], + Xcc: ["-DTEST_MODULE_IMPORTS=\(value)"] + ) + } } } From 1da62e7bbdfd5aa9d3dc94ea329735d30d1cbdcf Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 12 Aug 2023 21:41:13 -0400 Subject: [PATCH 128/178] Post FR Re-work (4) - Simplify some more tests in 'Fixtures' --- .../JunkYard.m | 13 ++++++++ .../RecyclingCenter.h | 4 --- .../RecyclingCenter.m | 25 ---------------- .../MixedTargetDependsOnMixedTarget/OldBoat.m | 13 ++++++++ .../SpeedBoat.h | 4 --- .../SpeedBoat.m | 25 ---------------- Tests/FunctionalTests/MixedTargetTests.swift | 30 +++++++++---------- 7 files changed, 40 insertions(+), 74 deletions(-) delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h delete mode 100644 Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m index 86fdb76bef1..476fd61352b 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m @@ -2,8 +2,21 @@ #import "include/JunkYard.h" +#if TEST_MODULE_IMPORTS // Import the mixed target's module. @import BasicMixedTarget; +#else +// Import the mixed target's public headers. Both "..." and <...> style imports +// should resolve. +#import "CarPart.h" +#import +#import "Driver.h" +#import +#import "OldCar.h" +#import +#import "BasicMixedTarget-Swift.h" +#import +#endif // TEST_MODULE_IMPORTS @interface JunkYard () // The below types come from the `BasicMixedTarget` module. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h deleted file mode 100644 index f616cef1144..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface RecyclingCenter : NSObject -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m deleted file mode 100644 index 9a0ff86e15f..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/RecyclingCenter.m +++ /dev/null @@ -1,25 +0,0 @@ -#import - -#import "RecyclingCenter.h" - -// Import the mixed target's public headers. Both "..." and <...> style imports -// should resolve. -#import "CarPart.h" -#import -#import "Driver.h" -#import -#import "OldCar.h" -#import -#import "BasicMixedTarget-Swift.h" -#import - -@interface RecyclingCenter () -// The below types come from the `BasicMixedTarget` module. -@property(nullable) Engine *engine; -@property(nullable) Driver *driver; -@property(nullable) OldCar *oldCar; -@property(nullable) CarPart *carPart; -@end - -@implementation RecyclingCenter -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m index b6aefd10139..990e705ba6a 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m @@ -2,8 +2,21 @@ #import "include/OldBoat.h" +#if TEST_MODULE_IMPORTS // Import the mixed target's module. @import BasicMixedTarget; +#else +// Import the mixed target's public headers. Both "..." and <...> style imports +// should resolve. +#import "CarPart.h" +#import +#import "Driver.h" +#import +#import "OldCar.h" +#import +#import "BasicMixedTarget-Swift.h" +#import +#endif // TEST_MODULE_IMPORTS @interface OldBoat () // The below types comes from the `BasicMixedTarget` module`. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h deleted file mode 100644 index 25feaedf3e7..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface SpeedBoat : NSObject -@end diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m deleted file mode 100644 index fe9775a6fb1..00000000000 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/SpeedBoat.m +++ /dev/null @@ -1,25 +0,0 @@ -#import - -#import "SpeedBoat.h" -#import - -// Import the mixed target's public headers. Both "..." and <...> style imports -// should resolve. -#import "CarPart.h" -#import -#import "Driver.h" -#import -#import "OldCar.h" -#import -#import "BasicMixedTarget-Swift.h" -#import - -@interface SpeedBoat () - -// The below types comes from the `BasicMixedTarget` module`. -@property(nonatomic, strong) Engine *engine; -@property(nonatomic, strong) Driver *driver; -@end - -@implementation SpeedBoat -@end diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 92e03f47635..3f4381a882c 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -61,8 +61,6 @@ final class MixedTargetTests: XCTestCase { extraArgs: ["--target", "MixedTargetWithCustomModuleMap"] ) - // Test importing the mixed target into Clang source via a module - // import or textual imports. for value in [0, 1] { XCTAssertSwiftTest( fixturePath, @@ -168,8 +166,6 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] ) - // Test importing the mixed target into Clang source via a module - // import or textual imports. for value in [0, 1] { XCTAssertSwiftTest( fixturePath, @@ -186,8 +182,6 @@ final class MixedTargetTests: XCTestCase { fixturePath, extraArgs: ["--target", "MixedTargetWithCXXPublicAPIAndCustomModuleMap"] ) - // Test importing the mixed target into Clang source via a module - // import or textual imports. for value in [0, 1] { XCTAssertSwiftTest( fixturePath, @@ -405,8 +399,6 @@ final class MixedTargetTests: XCTestCase { func testMixedTestTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - // Test importing the mixed target into Clang source via a module - // import or textual imports. for value in [0, 1] { XCTAssertSwiftTest( fixturePath, @@ -439,10 +431,13 @@ final class MixedTargetTests: XCTestCase { func testClangTargetDependsOnMixedTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "ClangTargetDependsOnMixedTarget"] - ) + for value in [0, 1] { + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "ClangTargetDependsOnMixedTarget"], + Xcc: ["-DTEST_MODULE_IMPORTS=\(value)"] + ) + } } } @@ -457,10 +452,13 @@ final class MixedTargetTests: XCTestCase { func testMixedTargetDependsOnOtherMixedTarget() throws { try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "MixedTargetDependsOnMixedTarget"] - ) + for value in [0, 1] { + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTargetDependsOnMixedTarget"], + Xcc: ["-DTEST_MODULE_IMPORTS=\(value)"] + ) + } } } From f00a88322f5af711f84128e7acea335f09083305 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Aug 2023 17:12:00 -0400 Subject: [PATCH 129/178] Post FR Re-work (5): Plugin Support - Added support for plugins to process mixed lang. targets - Added tests for using build & command plugin on mixed lang. target --- Sources/PackagePlugin/PackageModel.swift | 51 ++++++ .../PluginContextDeserializer.swift | 34 ++++ Sources/PackagePlugin/PluginMessages.swift | 11 ++ .../PluginContextSerializer.swift | 12 ++ Tests/CommandsTests/PackageToolTests.swift | 149 ++++++++++++++++++ 5 files changed, 257 insertions(+) diff --git a/Sources/PackagePlugin/PackageModel.swift b/Sources/PackagePlugin/PackageModel.swift index 2dd84db4f80..8b7dd0a3bbb 100644 --- a/Sources/PackagePlugin/PackageModel.swift +++ b/Sources/PackagePlugin/PackageModel.swift @@ -318,6 +318,57 @@ public struct ClangSourceModuleTarget: SourceModuleTarget { public let linkedFrameworks: [String] } +/// Represents a target consisting of a source code module compiled using both the Clang and Swift compiler. +public struct MixedSourceModuleTarget: SourceModuleTarget { + /// Unique identifier for the target. + public let id: ID + + /// The name of the target, as defined in the package manifest. This name + /// is unique among the targets of the package in which it is defined. + public let name: String + + /// The kind of module, describing whether it contains unit tests, contains + /// the main entry point of an executable, or neither. + public let kind: ModuleKind + + /// The absolute path of the target directory in the local file system. + public let directory: Path + + /// Any other targets on which this target depends, in the same order as + /// they are specified in the package manifest. Conditional dependencies + /// that do not apply have already been filtered out. + public let dependencies: [TargetDependency] + + /// The name of the module produced by the target (derived from the target + /// name, though future SwiftPM versions may allow this to be customized). + public let moduleName: String + + /// The source files that are associated with this target (any files that + /// have been excluded in the manifest have already been filtered out). + public let sourceFiles: FileList + + /// Any custom compilation conditions specified for the target's Swift sources. + public let swiftCompilationConditions: [String] + + /// Any preprocessor definitions specified for the target's Clang sources. + public let clangPreprocessorDefinitions: [String] + + /// Any custom header search paths specified for the Clang target. + public let headerSearchPaths: [String] + + /// The directory containing public C headers, if applicable. This will + /// only be set for targets that have a directory of a public headers. + public let publicHeadersDirectory: Path? + + /// Any custom linked libraries required by the module, as specified in the + /// package manifest. + public let linkedLibraries: [String] + + /// Any custom linked frameworks required by the module, as specified in the + /// package manifest. + public let linkedFrameworks: [String] +} + /// Represents a target describing an artifact (e.g. a library or executable) /// that is distributed as a binary. public struct BinaryArtifactTarget: Target { diff --git a/Sources/PackagePlugin/PluginContextDeserializer.swift b/Sources/PackagePlugin/PluginContextDeserializer.swift index 22fbd3dcfc5..a0e9dde8c0f 100644 --- a/Sources/PackagePlugin/PluginContextDeserializer.swift +++ b/Sources/PackagePlugin/PluginContextDeserializer.swift @@ -133,6 +133,40 @@ internal struct PluginContextDeserializer { linkedLibraries: linkedLibraries, linkedFrameworks: linkedFrameworks) + + case let .mixedSourceModuleInfo(moduleName, kind, sourceFiles, compilationConditions, preprocessorDefinitions, headerSearchPaths, publicHeadersDirId, linkedLibraries, linkedFrameworks): + let publicHeadersDir = try publicHeadersDirId.map { try self.path(for: $0) } + let sourceFiles = FileList(try sourceFiles.map { + let path = try self.path(for: $0.basePathId).appending($0.name) + let type: FileType + switch $0.type { + case .source: + type = .source + case .header: + type = .header + case .resource: + type = .resource + case .unknown: + type = .unknown + } + return File(path: path, type: type) + }) + target = MixedSourceModuleTarget( + id: String(id), + name: wireTarget.name, + kind: .init(kind), + directory: directory, + dependencies: dependencies, + moduleName: moduleName, + sourceFiles: sourceFiles, + swiftCompilationConditions: compilationConditions, + clangPreprocessorDefinitions: preprocessorDefinitions, + headerSearchPaths: headerSearchPaths, + publicHeadersDirectory: publicHeadersDir, + linkedLibraries: linkedLibraries, + linkedFrameworks: linkedFrameworks + ) + case let .binaryArtifactInfo(kind, origin, artifactId): let artifact = try self.path(for: artifactId) let artifactKind: BinaryArtifactTarget.Kind diff --git a/Sources/PackagePlugin/PluginMessages.swift b/Sources/PackagePlugin/PluginMessages.swift index b65307e9af3..7964c21fb38 100644 --- a/Sources/PackagePlugin/PluginMessages.swift +++ b/Sources/PackagePlugin/PluginMessages.swift @@ -145,6 +145,17 @@ enum HostToPluginMessage: Codable { linkedLibraries: [String], linkedFrameworks: [String]) + case mixedSourceModuleInfo( + moduleName: String, + kind: SourceModuleKind, + sourceFiles: [File], + compilationConditions: [String], + preprocessorDefinitions: [String], + headerSearchPaths: [String], + publicHeadersDirId: Path.Id?, + linkedLibraries: [String], + linkedFrameworks: [String]) + case binaryArtifactInfo( kind: BinaryArtifactKind, origin: BinaryArtifactOrigin, diff --git a/Sources/SPMBuildCore/PluginContextSerializer.swift b/Sources/SPMBuildCore/PluginContextSerializer.swift index 7af829e9f93..f7834943942 100644 --- a/Sources/SPMBuildCore/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/PluginContextSerializer.swift @@ -100,6 +100,18 @@ internal struct PluginContextSerializer { linkedLibraries: scope.evaluate(.LINK_LIBRARIES), linkedFrameworks: scope.evaluate(.LINK_FRAMEWORKS)) + case let target as MixedTarget: + targetInfo = .mixedSourceModuleInfo( + moduleName: target.c99name, + kind: try .init(target.type), + sourceFiles: targetFiles, + compilationConditions: scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS), + preprocessorDefinitions: scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS), + headerSearchPaths: scope.evaluate(.HEADER_SEARCH_PATHS), + publicHeadersDirId: try serialize(path: target.clangTarget.includeDir), + linkedLibraries: scope.evaluate(.LINK_LIBRARIES), + linkedFrameworks: scope.evaluate(.LINK_FRAMEWORKS)) + case let target as SystemLibraryTarget: var cFlags: [String] = [] var ldFlags: [String] = [] diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index 0fad9d05a53..a277078d791 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -2936,4 +2936,153 @@ final class PackageToolTests: CommandsTestCase { XCTAssertNoDiagnostics(observability.diagnostics) } } + + func testPluginsOnMixedLanguageTargets() throws { + // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). + try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") + + try testWithTemporaryDirectory { tmpPath in + // Create a sample package with a library target and a plugin. + let packageDir = tmpPath.appending(components: "MyPackage") + // FIXME(ncooke3): Update with next version of SPM. + try localFileSystem.writeFileContents(packageDir.appending("Package.swift"), string: + """ + // swift-tools-version: 999.0 + import PackageDescription + let package = Package( + name: "MyPackage", + targets: [ + .target( + name: "MyLibrary", + plugins: [ + "MyPlugin", + ] + ), + .plugin( + name: "MyPlugin", + capability: .buildTool() + ), + .plugin( + name: "CmdPlugin", + capability: .command(intent: .custom(verb: "mycmd", description: "Determine if a target has mixed language sources.")) + ) + ] + ) + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Foo.swift"), string: + """ + public struct Foo { } + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "include", "Bar.h"), string: + """ + #import + @interface Bar: NSObject + @end + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Bar.m"), string: + """ + #import "Bar.h" + @implementation Bar + @end + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "library.foo"), string: + """ + a file with a filename suffix handled by the plugin + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "library.bar"), string: + """ + a file with a filename suffix not handled by the plugin + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Plugins", "MyPlugin", "plugin.swift"), string: + """ + import PackagePlugin + import Foundation + @main + struct MyBuildToolPlugin: BuildToolPlugin { + func createBuildCommands( + context: PackagePlugin.PluginContext, + target: PackagePlugin.Target + ) async throws -> [PackagePlugin.Command] { + // Expect the initial working directory for build tool plugins is the package directory. + guard FileManager.default.currentDirectoryPath == context.package.directory.string else { + throw "expected initial working directory ‘\\(FileManager.default.currentDirectoryPath)’" + } + + // Check that the package display name is what we expect. + guard context.package.displayName == "MyPackage" else { + throw "expected display name to be ‘MyPackage’ but found ‘\\(context.package.displayName)’" + } + + guard let mixedTarget = target as? MixedSourceModuleTarget else { + throw "target \\(target.name) is not a Mixed Language target" + } + + // Create and return a build command that uses all the `.baz` files in the target as inputs, so they get counted as having been handled. + let fooFiles = mixedTarget.sourceFiles.compactMap { $0.path.extension == "foo" ? $0.path : nil } + return [ .buildCommand(displayName: "A command", executable: Path("/bin/echo"), arguments: fooFiles, inputFiles: fooFiles) ] + } + } + + extension String : Error {} + """ + ) + try localFileSystem.writeFileContents(packageDir.appending(components: "Plugins", "CmdPlugin", "CmdPlugin.swift"), string: + """ + import PackagePlugin + import Foundation + + @main + struct CmdPlugin: CommandPlugin { + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + var argExtractor = ArgumentExtractor(arguments) + let targetNames = argExtractor.extractOption(named: "target") + guard targetNames.count == 1 else { + throw "Only one target may be specified." + } + + let targets = try context.package.targets(named: targetNames) + guard targets.count == 1 else { + throw "Multiple targets named \\(targetNames[0]) exist." + } + + let target = targets[0] + + guard let rootURL = URL(string: target.directory.string) else { + throw "Could not create URL from \\(target.directory.string)" + } + let subpaths = try FileManager.default.subpathsOfDirectory(atPath: rootURL.absoluteString) + let extensionsSet = Set(subpaths.compactMap { Path($0).extension }) + + if extensionsSet.contains("swift") && extensionsSet.contains("m") { + print("This target contains mixed language sources.") + } + } + } + + extension String : Error {} + """ + ) + + // Test build plugin... + // Invoke it, and check the results. + let (stdout_build, stderr) = try SwiftPM.Build.execute(packagePath: packageDir) + XCTAssert(stdout_build.contains("Build complete!")) + + // We expect a warning about `library.bar` but not about `library.foo`. + XCTAssertMatch(stderr, .contains("found 1 file(s) which are unhandled")) + XCTAssertNoMatch(stderr, .contains("Sources/MyLibrary/library.foo")) + XCTAssertMatch(stderr, .contains("Sources/MyLibrary/library.bar")) + + // Test command plugin... + // Invoke it, and check the results. + let (stdout_cmd, _) = try SwiftPM.Package.execute(["mycmd", "--target", "MyLibrary"], packagePath: packageDir) + XCTAssertMatch(stdout_cmd, .contains("This target contains mixed language sources.")) + } + } } From 6090b912dcb41628abc678326342a47e9c251be7 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 15 Aug 2023 18:31:15 -0400 Subject: [PATCH 130/178] Add newlines where needed --- .../Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m | 1 + .../Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift | 1 + .../Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h | 1 + 3 files changed, 3 insertions(+) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m index e69de29bb2d..5c41623fa3d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m @@ -0,0 +1 @@ +// File is intentionally left blank. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift index e69de29bb2d..5c41623fa3d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift @@ -0,0 +1 @@ +// File is intentionally left blank. diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h index e69de29bb2d..5c41623fa3d 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h +++ b/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h @@ -0,0 +1 @@ +// File is intentionally left blank. From eb19c0f63f8fdb5f586bdae68e9017341cf96ea3 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 12:51:08 -0400 Subject: [PATCH 131/178] Cleanup unneeed module map generator code --- Sources/PackageLoading/ModuleMapGenerator.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 861d2bf5563..6f5e8288644 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -179,12 +179,10 @@ public struct ModuleMapGenerator { return .umbrellaDirectory(publicHeadersDir) } - /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any - /// diagnostics are added to the receiver's diagnostics engine.. + /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any diagnostics are added to the receiver's diagnostics engine. public func generateModuleMap( type: GeneratedModuleMapType?, at path: AbsolutePath, - excludeHeaders: [AbsolutePath] = [], interopHeaderPath: AbsolutePath? = nil ) throws { var moduleMap = "module \(moduleName) {\n" @@ -195,9 +193,6 @@ public struct ModuleMapGenerator { case .umbrellaDirectory(let dir): moduleMap.append(" umbrella \"\(dir.moduleEscapedPathString)\"\n") } - excludeHeaders.forEach { - moduleMap.append(" exclude header \"\($0.moduleEscapedPathString)\"\n") - } } moduleMap.append( """ From 33360bf1a5ef6dfadbb9367d70b119eb8a6c9ca4 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 12:53:57 -0400 Subject: [PATCH 132/178] Post FR Re-work (6): Remove 'objc' from module map - See https://forums.swift.org/t/se-0403-package-manager-mixed-language-target-support/66202/17 --- Sources/Build/BuildDescription/MixedTargetBuildDescription.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 32554446980..13f73021f5f 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -182,7 +182,6 @@ public final class MixedTargetBuildDescription { \(customModuleMapContents) module \(target.c99name).Swift { header "\(interopHeaderPath)" - requires objc } """ try fileSystem.writeFileContentsIfNeeded( From 044712c56c89c28f2388783a8b99b40b29a9a455 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 14:50:29 -0400 Subject: [PATCH 133/178] Post FR Re-work (7): Use relative path for modularized Swift header - https://forums.swift.org/t/se-0403-package-manager-mixed-language-target-support/66202/17 --- .../MixedTargetBuildDescription.swift | 17 +++++++++-------- .../SwiftTargetBuildDescription.swift | 1 + Sources/PackageLoading/ModuleMapGenerator.swift | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 13f73021f5f..7fd944c044d 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -151,11 +151,11 @@ public final class MixedTargetBuildDescription { // When the mixed target has a custom module map, clients of the target // will be passed a module map *and* VFS overlay at buildtime to access // the mixed target's public API. The following is therefore needed: - // - Create a copy of the custom module map, adding a submodule to - // expose the target's generated interop header. This allows clients - // of the target to consume the mixed target's public Objective-C - // compatible Swift API in a Clang context. - // - Create a VFS overlay to swap in the modified module map for the + // - Create a copy of the custom module map, and extend its contents by + // adding a submodule that modularizes the target's generated interop + // header. This allows clients of the target to consume the mixed + // target's public Obj-C compatible Swift API in a Clang context. + // - Create a VFS overlay to swap in the extended module map for the // original custom module map. This is done so relative paths in the // modified module map can be resolved as they would have been in the // original module map. @@ -181,7 +181,7 @@ public final class MixedTargetBuildDescription { let productModuleMap = """ \(customModuleMapContents) module \(target.c99name).Swift { - header "\(interopHeaderPath)" + header "\(interopHeaderPath.basename)" } """ try fileSystem.writeFileContentsIfNeeded( @@ -190,8 +190,9 @@ public final class MixedTargetBuildDescription { ) // Set the original custom module map path as the module map path - // for the target. The below VFS overlay will redirect to the - // contents of the modified module map. + // for the target. With the below VFS overlay, evaluating the + // custom module map path will redirect to the extended module map + // path. self.moduleMap = customModuleMapPath self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index bf74b61517f..5779e1f2680 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -705,6 +705,7 @@ public final class SwiftTargetBuildDescription { private func generateModuleMap() throws -> AbsolutePath { let path = self.tempsPath.appending(component: moduleMapFilename) + // TODO(ncooke3): Is the `requires objc` an issue for C++ interop? let bytes = ByteString( #""" module \#(self.target.c99name) { diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 6f5e8288644..7094d2312fe 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -206,7 +206,7 @@ public struct ModuleMapGenerator { moduleMap.append( """ module \(moduleName).Swift { - header \"\(interopHeaderPath.moduleEscapedPathString)\" + header \"\(interopHeaderPath.basename)\" } """ From 978c5f7fd89c255ac03f6eb78d5707be366cf284 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 15:22:26 -0400 Subject: [PATCH 134/178] Add TODO question wrt C++ public hdrs dir --- Sources/PackageLoading/ModuleMapGenerator.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 7094d2312fe..765dbe21549 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -128,6 +128,8 @@ public struct ModuleMapGenerator { // Filter out headers and directories at the top level of the public-headers directory. // FIXME: What about .hh files, or .hpp, etc? We should centralize the detection of file types based on names (and ideally share with SwiftDriver). + // TODO(ncooke3): Per above FIXME and last line in function, public header + // directories with only C++ headers will default to umbrella directory. let headers = entries.filter({ fileSystem.isFile($0) && $0.suffix == ".h" }) let directories = entries.filter({ fileSystem.isDirectory($0) }) From 9c42e9997bce9290a03b638da8a7fc5ff10fc9be Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 17:23:56 -0400 Subject: [PATCH 135/178] Consolidate module map logic --- .../MixedTargetBuildDescription.swift | 127 ++++++++++-------- Sources/Build/LLBuildManifestBuilder.swift | 1 + .../PackageLoading/ModuleMapGenerator.swift | 11 -- 3 files changed, 71 insertions(+), 68 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 7fd944c044d..4a53a98d895 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -160,35 +160,6 @@ public final class MixedTargetBuildDescription { // modified module map can be resolved as they would have been in the // original module map. case .custom(let customModuleMapPath): - let customModuleMapContents: String = - try fileSystem.readFileContents(customModuleMapPath) - - // Check that custom module map does not contain a Swift submodule. - if customModuleMapContents.contains("\(target.c99name).Swift") { - throw StringError( - "The target's module map may not contain a Swift " + - "submodule for the module \(target.c99name)." - ) - } - - // If it's named 'module.modulemap', there will be a module - // redeclaration error as both the public headers dir. and the - // build dir. are passed as import paths and there will be a - // `module.modulemap` in each directory. - let productModuleMapPath = tempsPath.appending(component: "extended-custom-module.modulemap") - - // Extend the contents and write it to disk, if needed. - let productModuleMap = """ - \(customModuleMapContents) - module \(target.c99name).Swift { - header "\(interopHeaderPath.basename)" - } - """ - try fileSystem.writeFileContentsIfNeeded( - productModuleMapPath, - string: productModuleMap - ) - // Set the original custom module map path as the module map path // for the target. With the below VFS overlay, evaluating the // custom module map path will redirect to the extended module map @@ -196,6 +167,17 @@ public final class MixedTargetBuildDescription { self.moduleMap = customModuleMapPath self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) + // If it's named 'module.modulemap', there will be a module + // redeclaration error as both the public headers dir. and the + // build dir. are passed as import paths and there will be a + // `module.modulemap` in each directory. + let extendedCustomModuleMapPath = tempsPath.appending(component: "extended-custom-module.modulemap") + try generateExtendedModuleMap( + from: customModuleMapPath, + at: extendedCustomModuleMapPath, + fileSystem: fileSystem + ) + try VFSOverlay(roots: [ VFSOverlay.Directory( name: customModuleMapPath.parentDirectory.pathString, @@ -204,7 +186,7 @@ public final class MixedTargetBuildDescription { // modified module map in the product directory. VFSOverlay.File( name: moduleMapFilename, - externalContents: productModuleMapPath.pathString + externalContents: extendedCustomModuleMapPath.pathString ), // Add a generated Swift header that redirects to the // generated header in the build directory's root. @@ -224,7 +206,7 @@ public final class MixedTargetBuildDescription { "-import-underlying-module", "-I", mixedTarget.clangTarget.includeDir.pathString ] - + // When the mixed target does not have a custom module map, one will be // generated as a product for use by clients. // - Note: When `.none`, the mixed target has no public headers. Even @@ -233,53 +215,53 @@ public final class MixedTargetBuildDescription { // Swift API in a Clang context. case .umbrellaHeader, .umbrellaDirectory, .none: let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType - let productModuleMapPath = tempsPath.appending(component: moduleMapFilename) + let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, - at: productModuleMapPath, - interopHeaderPath: interopHeaderPath + at: unextendedModuleMapPath ) - // Set the generated module map as the module map for the target. - self.moduleMap = productModuleMapPath - self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) - + let unextendedModuleMapOverlayPath = tempsPath.appending(component: unextendedModuleOverlayFilename) try VFSOverlay(roots: [ VFSOverlay.Directory( - name: mixedTarget.clangTarget.includeDir.pathString, + name: tempsPath.pathString, contents: [ - // Add a generated Swift header that redirects to the - // generated header in the build directory's root. + // Redirect the `module.modulemap` to the *unextended* + // module map in the intermediates directory. VFSOverlay.File( - name: interopHeaderPath.basename, - externalContents: interopHeaderPath.pathString + name: moduleMapFilename, + externalContents: unextendedModuleMapPath.pathString ), ] ), - ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) + ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) - let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) - try moduleMapGenerator.generateModuleMap( - type: generatedModuleMapType, - at: unextendedModuleMapPath, - interopHeaderPath: nil + let extendedModuleMapPath = tempsPath.appending(component: moduleMapFilename) + + // Set the generated module map as the module map for the target. + self.moduleMap = extendedModuleMapPath + self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) + + try generateExtendedModuleMap( + from: unextendedModuleMapPath, + at: extendedModuleMapPath, + fileSystem: fileSystem ) - let unextendedModuleMapOverlayPath = tempsPath.appending(component: unextendedModuleOverlayFilename) try VFSOverlay(roots: [ VFSOverlay.Directory( - name: tempsPath.pathString, + name: mixedTarget.clangTarget.includeDir.pathString, contents: [ - // Redirect the `module.modulemap` to the *unextended* - // module map in the intermediates directory. + // Add a generated Swift header that redirects to the + // generated header in the build directory's root. VFSOverlay.File( - name: moduleMapFilename, - externalContents: unextendedModuleMapPath.pathString + name: interopHeaderPath.basename, + externalContents: interopHeaderPath.pathString ), ] ), - ]).write(to: unextendedModuleMapOverlayPath, fileSystem: fileSystem) - + ]).write(to: self.allProductHeadersOverlay, fileSystem: fileSystem) + // Importing the underlying module will build the Objective-C // part of the module. In order to find the underlying module, // a `module.modulemap` needs to be discoverable via directory passed @@ -313,4 +295,35 @@ public final class MixedTargetBuildDescription { "-I", interopHeaderPath.parentDirectory.pathString ] } + + /// Extends the contents of the given module map to contain the target's generated Swift header + /// modularized in a submodule. This "extended" module map is written at the given `path`. + private func generateExtendedModuleMap( + from unextendedModuleMapPath: AbsolutePath, + at path: AbsolutePath, + fileSystem: FileSystem + ) throws { + let unextendedModuleMapContents: String = + try fileSystem.readFileContents(unextendedModuleMapPath) + + // Check that custom module map does not contain a Swift submodule. + if unextendedModuleMapContents.contains("\(target.c99name).Swift") { + throw StringError( + "The target's module map may not contain a Swift " + + "submodule for the module \(target.c99name)." + ) + } + + // Extend the contents and write it to disk, if needed. + let productModuleMap = """ + \(unextendedModuleMapContents) + module \(target.c99name).Swift { + header "\(target.c99name)-Swift.h" + } + """ + try fileSystem.writeFileContentsIfNeeded( + path, + string: productModuleMap + ) + } } diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index c3aa8d17c9d..b76f65ad814 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -845,6 +845,7 @@ extension LLBuildManifestBuilder { // the Swift part of the mixed target generates. This header acts as an // input to the Clang compile command, which therefore forces the // Swift part of the mixed target to be built first. + // TODO(ncooke3): I think this can be passed in via a param. if target.isWithinMixedTarget { inputs += [ .file(target.tempsPath.appending(component: "\(target.target.c99name)-Swift.h")) diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 765dbe21549..16734606fa8 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -204,17 +204,6 @@ public struct ModuleMapGenerator { """ ) - if let interopHeaderPath = interopHeaderPath { - moduleMap.append( - """ - module \(moduleName).Swift { - header \"\(interopHeaderPath.basename)\" - } - - """ - ) - } - // If the file exists with the identical contents, we don't need to rewrite it. // Otherwise, compiler will recompile even if nothing else has changed. try fileSystem.writeFileContentsIfNeeded(path, string: moduleMap) From 8575dbf3a2e2bc2ece65b0aafdca709d1d6d3485 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 17:29:22 -0400 Subject: [PATCH 136/178] Replace unneeded property --- .../BuildDescription/MixedTargetBuildDescription.swift | 10 ++++------ Sources/Build/BuildPlan.swift | 8 ++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 4a53a98d895..533113a5ed1 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -48,7 +48,9 @@ public final class MixedTargetBuildDescription { let allProductHeadersOverlay: AbsolutePath /// The paths to the targets's public headers. - let publicHeaderPaths: [AbsolutePath] + var publicHeadersDir: AbsolutePath { + clangTargetBuildDescription.clangTarget.includeDir + } /// The modulemap file for this target. let moduleMap: AbsolutePath @@ -141,10 +143,6 @@ public final class MixedTargetBuildDescription { fileSystem: fileSystem ) - // Clients will later depend on the public header directory, and, if an - // umbrella header was created, the header's root directory. - self.publicHeaderPaths = [mixedTarget.clangTarget.includeDir] - // MARK: Generate products to be used by client of the target. switch mixedTarget.clangTarget.moduleMapType { @@ -241,7 +239,7 @@ public final class MixedTargetBuildDescription { // Set the generated module map as the module map for the target. self.moduleMap = extendedModuleMapPath self.allProductHeadersOverlay = tempsPath.appending(component: allProductHeadersFilename) - + try generateExtendedModuleMap( from: unextendedModuleMapPath, at: extendedModuleMapPath, diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index ba906090f85..05e5a313838 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -878,9 +878,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { ) // Add the dependency's public headers. - dependencyTargetDescription.publicHeaderPaths.forEach { - clangTarget.additionalFlags += [ "-I", $0.pathString ] - } + clangTarget.additionalFlags += [ "-I", dependencyTargetDescription.publicHeadersDir.pathString ] // Add the dependency's public VFS overlay. clangTarget.additionalFlags += [ @@ -948,9 +946,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { ) // Add the dependency's public headers. - target.publicHeaderPaths.forEach { - swiftTarget.appendClangFlags("-I", $0.pathString) - } + swiftTarget.appendClangFlags("-I", target.publicHeadersDir.pathString) default: break From 219b92e732b795cb8326707eba43839331149bfc Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 17:32:43 -0400 Subject: [PATCH 137/178] Consolidate module map logic (2) --- .../PackageLoading/ModuleMapGenerator.swift | 3 +- .../ModuleMapGenerationTests.swift | 34 +++---------------- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 16734606fa8..59d6f497962 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -184,8 +184,7 @@ public struct ModuleMapGenerator { /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any diagnostics are added to the receiver's diagnostics engine. public func generateModuleMap( type: GeneratedModuleMapType?, - at path: AbsolutePath, - interopHeaderPath: AbsolutePath? = nil + at path: AbsolutePath ) throws { var moduleMap = "module \(moduleName) {\n" if let type = type { diff --git a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift index bd0d88a6731..80834a55e78 100644 --- a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift +++ b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift @@ -56,30 +56,6 @@ class ModuleMapGeneration: XCTestCase { } } - func testSwiftSubmoduleWhenGivenInteropHeaderPath() throws { - let root: AbsolutePath = AbsolutePath.root - - let fs = InMemoryFileSystem(emptyFiles: - root.appending(components: "include", "Foo.h").pathString - ) - - let interopHeaderPath = AbsolutePath("/path/to/Foo-Swift.h") - - ModuleMapTester("Foo", interopHeaderPath: interopHeaderPath, in: fs) { result in - result.check(contents: """ - module Foo { - umbrella header "\(root.appending(components: "include", "Foo.h"))" - export * - } - module Foo.Swift { - header "/path/to/Foo-Swift.h" - requires objc - } - - """) - } - } - func testOtherCases() throws { let root: AbsolutePath = .root var fs: InMemoryFileSystem @@ -204,24 +180,24 @@ class ModuleMapGeneration: XCTestCase { } /// Helper function to test module map generation. Given a target name and optionally the name of a public-headers directory, this function determines the module map type of the public-headers directory by examining the contents of a file system and invokes a given block to check the module result (including any diagnostics). -func ModuleMapTester(_ targetName: String, includeDir: String = "include", interopHeaderPath: AbsolutePath? = nil, in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { +func ModuleMapTester(_ targetName: String, includeDir: String = "include", in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) { let observability = ObservabilitySystem.makeForTesting() // Create a module map generator, and determine the type of module map to use for the header directory. This may emit diagnostics. let moduleMapGenerator = ModuleMapGenerator(targetName: targetName, moduleName: targetName.spm_mangledToC99ExtendedIdentifier(), publicHeadersDir: AbsolutePath.root.appending(component: includeDir), fileSystem: fileSystem) let moduleMapType = moduleMapGenerator.determineModuleMapType(observabilityScope: observability.topScope) - + // Generate a module map and capture any emitted diagnostics. let generatedModuleMapPath = AbsolutePath.root.appending(components: "module.modulemap") observability.topScope.trap { if let generatedModuleMapType = moduleMapType.generatedModuleMapType { - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath, interopHeaderPath: interopHeaderPath) + try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath) } } - + // Invoke the closure to check the results. let result = ModuleMapResult(diagnostics: observability.diagnostics, path: generatedModuleMapPath, fs: fileSystem) body(result) - + // Check for any unexpected diagnostics (the ones the closure didn't check for). result.validateDiagnostics() } From 008f2940613368dad817b6ab9f2bf0d9f355c976 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 17:34:08 -0400 Subject: [PATCH 138/178] Consolidate module map logic (3): Remove dated docs --- Sources/PackageLoading/ModuleMapGenerator.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 59d6f497962..3b40b16e3ea 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -69,9 +69,6 @@ extension ClangTarget: ModuleMapProtocol { /// These rules are documented at https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#creating-c-language-targets. To avoid breaking existing packages, do not change the semantics here without making any change conditional on the tools version of the package that defines the target. /// /// Note that a module map generator doesn't require a target to already have been instantiated; it can operate on information that will later be used to instantiate a target. -/// -/// For Mixed language targets, the module map generator will generate a module map that includes a -/// module declaration for the Mixed target's C-language API and a submodule declaration for the Swift API. public struct ModuleMapGenerator { /// The name of the Clang target (for diagnostics). From 159840e186b7f5259ad745069447fafb13ddba4c Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 16 Aug 2023 17:44:43 -0400 Subject: [PATCH 139/178] Add TODO to investigate --- .../Build/BuildDescription/MixedTargetBuildDescription.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 533113a5ed1..3bf023d0b36 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -214,6 +214,8 @@ public final class MixedTargetBuildDescription { case .umbrellaHeader, .umbrellaDirectory, .none: let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) + + // TODO(ncooke3): We might be able to shift this to ClangTargetBuildDescription. try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: unextendedModuleMapPath From 6f9a0c5d5c20fa4c9e30be7c6f5fa7f56438d6a8 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 17 Aug 2023 00:14:45 -0400 Subject: [PATCH 140/178] Simplify some manifest builder logic - While minor, this makes things a bit more explicit. I didn't run the full test suite so it's possible there is a regression. The main difference is that all of the Swift outputs are fed to the Clang compile command, whereas before it was only the generated swift header. --- Sources/Build/LLBuildManifestBuilder.swift | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index b76f65ad814..eb24a762200 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -822,6 +822,7 @@ extension LLBuildManifestBuilder { private func createClangCompileCommand( _ target: ClangTargetBuildDescription, addTargetCmd: Bool = true, + inputs: [Node] = [], createResourceBundle: Bool = true ) throws -> [Node] { let standards = [ @@ -829,7 +830,7 @@ extension LLBuildManifestBuilder { (target.clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions), ] - var inputs: [Node] = [] + var inputs: [Node] = inputs if createResourceBundle { // Add resources node as the input to the target. This isn't great because we @@ -841,17 +842,6 @@ extension LLBuildManifestBuilder { } } - // If it's a mixed target, add the Objective-C compatibility header that - // the Swift part of the mixed target generates. This header acts as an - // input to the Clang compile command, which therefore forces the - // Swift part of the mixed target to be built first. - // TODO(ncooke3): I think this can be passed in via a param. - if target.isWithinMixedTarget { - inputs += [ - .file(target.tempsPath.appending(component: "\(target.target.c99name)-Swift.h")) - ] - } - func addStaticTargetInputs(_ target: ResolvedTarget) { if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { inputs.append(file: desc.moduleOutputPath) @@ -954,6 +944,10 @@ extension LLBuildManifestBuilder { let clangOutputs = try createClangCompileCommand( target.clangTargetBuildDescription, addTargetCmd: false, + // This forces the Swift sub-target to build first. This is needed + // since the Clang sub-target depends on build artifacts from the + // Swift sub-target (e.g. generated Swift header). + inputs: swiftOutputs, // The Swift compile command already created the resource bundle. createResourceBundle: false ) From 1dd2578e26764017ce520704ae577bcb29812d29 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 17 Aug 2023 00:34:57 -0400 Subject: [PATCH 141/178] Post FR Re-work (8): Fix up Build Plan unit tests --- Tests/BuildTests/BuildPlanTests.swift | 44 ++++++++------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 7ada8870c28..5569c0f0146 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1521,16 +1521,13 @@ final class BuildPlanTests: XCTestCase { let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") let exe = try result.target(for: "exe").swiftTarget().compileArguments() - + XCTAssertMatch(exe, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", - "-fmodule-map-file=/path/to/build/debug/lib.build/Product/module.modulemap", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", - "-Xcc", "/path/to/build/debug/lib.build/InteropSupport", "-Xcc", - "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", + "-fmodule-map-file=/path/to/build/debug/lib.build/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parseable-output", "-g", "-Xcc", "-g", @@ -1541,14 +1538,10 @@ final class BuildPlanTests: XCTestCase { "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", - "/path/to/build/debug/lib.build/Intermediates", "-Xcc", - "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", - "-Xcc", "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", + "/path/to/build/debug/lib.build", "-Xcc", "-ivfsoverlay", + "-Xcc", "/path/to/build/debug/lib.build/unextended-module-overlay.yaml", "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib", "-Xcc", "-I", "-Xcc", - "/Pkg/Sources/lib/include", "-Xcc", "-I", "-Xcc", - "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", + "/Pkg/Sources/lib/include", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/lib.build/lib-Swift.h", "-DHELLO_SWIFT=1", "-Xfrontend", @@ -1560,10 +1553,7 @@ final class BuildPlanTests: XCTestCase { "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", - "/Pkg/Sources/lib", "-ivfsoverlay", - "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", - "-I", "/path/to/build/debug/lib.build/Intermediates", - "-I", "/path/to/build/debug/lib.build/InteropSupport", + "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-DHELLO_CLANG=1", "-g" ]) @@ -1578,7 +1568,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual( try result.target(for: "lib").mixedTarget().moduleMap.pathString, - buildPath.appending(components: "lib.build", "Product", "module.modulemap").pathString + buildPath.appending(components: "lib.build", "module.modulemap").pathString ) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ @@ -1661,10 +1651,7 @@ final class BuildPlanTests: XCTestCase { "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=/Pkg/Sources/lib/include/module.modulemap", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", - "-Xcc", "/path/to/build/debug/lib.build/InteropSupport", "-Xcc", - "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Product/all-product-headers.yaml", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parseable-output", "-g", "-Xcc", "-g" @@ -1675,13 +1662,9 @@ final class BuildPlanTests: XCTestCase { "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), "-DSWIFT_PACKAGE", "-DDEBUG", "-import-underlying-module", "-I", - "/Pkg/Sources/lib/include", "-Xcc", "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", - "-Xcc", "-ivfsoverlay", "-Xcc", - "/path/to/build/debug/lib.build/Intermediates/unextended-module-overlay.yaml", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib","-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-Xcc", "-I", "-Xcc", - "/path/to/build/debug/lib.build/InteropSupport", "-module-cache-path", + "/Pkg/Sources/lib", "-Xcc", "-I", "-Xcc", + "/Pkg/Sources/lib/include", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-parse-as-library", "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/lib.build/lib-Swift.h", "-g", "-Xcc", "-g" @@ -1692,10 +1675,7 @@ final class BuildPlanTests: XCTestCase { "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", - "/Pkg/Sources/lib", "-ivfsoverlay", - "/path/to/build/debug/lib.build/Intermediates/all-product-headers.yaml", - "-I", "/path/to/build/debug/lib.build/Intermediates", "-I", - "/path/to/build/debug/lib.build/InteropSupport", + "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" ]) From 3202a825c8afcd527e3d808c3be5c2a3c047f263 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 17 Aug 2023 00:41:13 -0400 Subject: [PATCH 142/178] Post FR Re-work (9): Enable Windows support - https://forums.swift.org/t/se-0403-package-manager-mixed-language-target-support/66202/31 - I'm expecting this to surface some issues on CI --- .../MixedTargetBuildDescription.swift | 5 -- .../SwiftTargetBuildDescription.swift | 9 ++- Tests/BuildTests/BuildPlanTests.swift | 72 ------------------- Tests/FunctionalTests/MixedTargetTests.swift | 27 ------- 4 files changed, 4 insertions(+), 109 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 3bf023d0b36..e08b9d315b0 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -82,11 +82,6 @@ public final class MixedTargetBuildDescription { throw InternalError("underlying target type mismatch \(target)") } - guard buildParameters.targetTriple.isDarwin() else { - throw StringError("Targets with mixed language sources are only " + - "supported on Apple platforms.") - } - self.target = target let clangResolvedTarget = ResolvedTarget( diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index 5779e1f2680..78eceb74e13 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -609,11 +609,10 @@ public final class SwiftTargetBuildDescription { /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { - self.buildParameters.targetTriple.isDarwin() && - // Emitting the interop header for mixed test targets enables the - // sharing of Objective-C compatible Swift test helpers between - // Swift and Objective-C test files. - (self.target.type == .library || self.target.type == .test && self.isWithinMixedTarget) + // Emitting the interop header for mixed test targets enables the + // sharing of Objective-C compatible Swift test helpers between + // Swift and Objective-C test files. + self.target.type == .library || self.target.type == .test && self.isWithinMixedTarget } func writeOutputFileMap() throws -> AbsolutePath { diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 5569c0f0146..8eb25c409fb 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1492,22 +1492,6 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) - #if !os(macOS) - XCTAssertThrowsError( - try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - graph: graph, - fileSystem: fs, - observabilityScope: observability.topScope - )), - "This should fail when run on non-Apple platforms." - ) { error in - XCTAssertEqual( - error.localizedDescription, - "Targets with mixed language sources are only supported on Apple platforms." - ) - } - #else let result = try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), graph: graph, @@ -1586,8 +1570,6 @@ final class BuildPlanTests: XCTestCase { testDiagnostics(observability.diagnostics) { result in result.check(diagnostic: .contains("can be downloaded"), severity: .warning) } - - #endif } func testBasicMixedLanguagesWithCustomModuleMap() throws { @@ -1618,22 +1600,6 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) - #if !os(macOS) - XCTAssertThrowsError( - try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - graph: graph, - fileSystem: fs, - observabilityScope: observability.topScope - )), - "This should fail when run on non-Apple platforms." - ) { error in - XCTAssertEqual( - error.localizedDescription, - "Targets with mixed language sources are only supported on Apple platforms." - ) - } - #else let result = try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), graph: graph, @@ -1707,12 +1673,8 @@ final class BuildPlanTests: XCTestCase { testDiagnostics(observability.diagnostics) { result in result.check(diagnostic: .contains("can be downloaded"), severity: .warning) } - - #endif } - - func testSwiftCMixed() throws { let Pkg: AbsolutePath = "/Pkg" @@ -2664,22 +2626,6 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) - #if !os(macOS) - XCTAssertThrowsError( - try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - graph: g, - fileSystem: fs, - observabilityScope: observability.topScope - )), - "This should fail when run on non-Apple platforms." - ) { error in - XCTAssertEqual( - error.localizedDescription, - "Targets with mixed language sources are only supported on Apple platforms." - ) - } - #else let result = try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters(), graph: g, @@ -2721,7 +2667,6 @@ final class BuildPlanTests: XCTestCase { "-runtime-compatibility-version", "none", "-target", defaultTargetTriple, "-g" ]) - #endif } func testStaticProductsForMixedTarget() throws { @@ -2773,22 +2718,6 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) - #if !os(macOS) - XCTAssertThrowsError( - try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - graph: g, - fileSystem: fs, - observabilityScope: observability.topScope - )), - "This should fail when run on non-Apple platforms." - ) { error in - XCTAssertEqual( - error.localizedDescription, - "Targets with mixed language sources are only supported on Apple platforms." - ) - } - #else let result = try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters(), graph: g, @@ -2819,7 +2748,6 @@ final class BuildPlanTests: XCTestCase { // No arguments for linking static libraries. XCTAssertEqual(barLinkArgs, []) - #endif } func testExecAsDependency() throws { diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 3f4381a882c..8720697f549 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -17,9 +17,6 @@ import SPMTestSupport final class MixedTargetTests: XCTestCase { - // Mixed language targets are only supported on macOS. - #if os(macOS) - // MARK: - Testing Mixed Targets func testMixedTarget() throws { @@ -479,28 +476,4 @@ final class MixedTargetTests: XCTestCase { ) } } - - #else - - // MARK: - Test Mixed Targets unsupported on non-macOS - - func testMixedTargetOnlySupportedOnMacOS() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - let commandExecutionError = try XCTUnwrap( - XCTAssertBuildFails( - fixturePath, - extraArgs: ["--target", "BasicMixedTarget"] - ) - ) - - XCTAssert( - commandExecutionError.stderr.contains( - "error: Targets with mixed language sources are only supported on Apple platforms." - ) - ) - } - } - - #endif - } From 67f7484fa5b5ed124175425dcd6be0bae8850694 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 17 Aug 2023 11:47:50 -0400 Subject: [PATCH 143/178] Post rebase fixes --- .../SwiftTargetBuildDescription.swift | 7 +++++++ Sources/Build/LLBuildManifestBuilder.swift | 18 +----------------- Tests/BuildTests/BuildPlanTests.swift | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index 78eceb74e13..de5b193a05a 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -607,6 +607,13 @@ public final class SwiftTargetBuildDescription { return result } + func appendClangFlags(_ flags: String...) { + flags.forEach { flag in + additionalFlags.append("-Xcc") + additionalFlags.append(flag) + } + } + /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { // Emitting the interop header for mixed test targets enables the diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index eb24a762200..1837796fda8 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -610,23 +610,7 @@ extension LLBuildManifestBuilder { let cmdName = target.target.getCommandName(config: self.buildConfig) self.manifest.addWriteSourcesFileListCommand(sources: target.sources, sourcesFileListPath: target.sourcesFileListPath) - - var otherArguments = try target.compileArguments() - if mixedTarget { - otherArguments += [ - "-import-underlying-module", - "-Xcc", - "-ivfsoverlay", - "-Xcc", - "\(target.tempsPath)/all-product-headers.yaml", - "-Xcc", - "-ivfsoverlay", - "-Xcc", - "\(target.tempsPath)/unextended-module-overlay.yaml" - ] - } - - manifest.addSwiftCmd( + self.manifest.addSwiftCmd( name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], outputs: cmdOutputs, diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 8eb25c409fb..c95a8948603 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -5299,7 +5299,7 @@ final class BuildPlanTests: XCTestCase { if #available(macOS 13, *) { // `.contains` is only available in macOS 13 or newer XCTAssertTrue(try swiftTarget.compileArguments().contains(["-user-module-version", "1.0.0"])) } - case .clang: + case .clang, .mixed: XCTFail("expected a Swift target") } } From 9c6c97ed542da720669e8f7a0db00c79d49db92b Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 17 Aug 2023 15:11:36 -0400 Subject: [PATCH 144/178] Resolve TODO in LLBuildManifestBuilder.swift --- .../ClangTargetBuildDescription.swift | 2 +- .../SwiftTargetBuildDescription.swift | 2 +- Sources/Build/LLBuildManifestBuilder.swift | 11 +---------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index 2a6c2f782d0..3c22fcb4c07 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -103,7 +103,7 @@ public final class ClangTargetBuildDescription { /// Whether or not the target belongs to a mixed language target. /// /// Mixed language targets consist of an underlying Swift and Clang target. - let isWithinMixedTarget: Bool + private let isWithinMixedTarget: Bool /// If this target is a test target. public var isTestTarget: Bool { diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index de5b193a05a..de3f9bd9d03 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -134,7 +134,7 @@ public final class SwiftTargetBuildDescription { /// Whether or not the target belongs to a mixed language target. /// /// Mixed language targets consist of an underlying Swift and Clang target. - let isWithinMixedTarget: Bool + private let isWithinMixedTarget: Bool /// The swift version for this target. var swiftVersion: SwiftLanguageVersion { diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/LLBuildManifestBuilder.swift index 1837796fda8..fd65ac2a7a1 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/LLBuildManifestBuilder.swift @@ -262,16 +262,7 @@ extension LLBuildManifestBuilder { let moduleNode = Node.file(target.moduleOutputPath) let cmdOutputs = objectNodes + [moduleNode] - // TODO(ncooke3): Do we need to support building mixed targets with the - // `--use-integrated-swiftdriver` or `--emit-swift-module-separately` - // flags? Ideally no because doing so does not look straightforward. - if target.isWithinMixedTarget { - try self.addCmdWithBuiltinSwiftTool( - target, - inputs: inputs, - cmdOutputs: cmdOutputs + [.file(target.objCompatibilityHeaderPath)] - ) - } else if buildParameters.useIntegratedSwiftDriver { + if buildParameters.useIntegratedSwiftDriver { try self.addSwiftCmdsViaIntegratedDriver( target, inputs: inputs, From f8771432eb32b48800307c0400d413329d14ef16 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 18 Aug 2023 15:19:01 -0400 Subject: [PATCH 145/178] Feedback w.r.t. Plugin API --- Sources/PackagePlugin/PackageModel.swift | 37 +++++++++++++------ .../PluginContextDeserializer.swift | 9 +++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Sources/PackagePlugin/PackageModel.swift b/Sources/PackagePlugin/PackageModel.swift index 8b7dd0a3bbb..aadd06033f9 100644 --- a/Sources/PackagePlugin/PackageModel.swift +++ b/Sources/PackagePlugin/PackageModel.swift @@ -318,8 +318,28 @@ public struct ClangSourceModuleTarget: SourceModuleTarget { public let linkedFrameworks: [String] } -/// Represents a target consisting of a source code module compiled using both the Clang and Swift compiler. +/// Represents a target consisting of a source code module containing both Swift and C-family language sources. public struct MixedSourceModuleTarget: SourceModuleTarget { + + /// Describes attributes specific to the target's Swift sources. + public struct SwiftSourceAttributes { + /// Any custom compilation conditions specified for the target's Swift sources. + public let compilationConditions: [String] + } + + /// Describes settings specific to the target's C-family language sources. + public struct ClangSourceAttributes { + /// Any preprocessor definitions specified for the Clang target. + public let preprocessorDefinitions: [String] + + /// Any custom header search paths specified for the Clang target. + public let headerSearchPaths: [String] + + /// The directory containing public C headers, if applicable. This will + /// only be set for targets that have a directory of a public headers. + public let publicHeadersDirectory: Path? + } + /// Unique identifier for the target. public let id: ID @@ -347,18 +367,11 @@ public struct MixedSourceModuleTarget: SourceModuleTarget { /// have been excluded in the manifest have already been filtered out). public let sourceFiles: FileList - /// Any custom compilation conditions specified for the target's Swift sources. - public let swiftCompilationConditions: [String] + /// Attributes specific to the target's Swift sources. + public let swift: SwiftSourceAttributes - /// Any preprocessor definitions specified for the target's Clang sources. - public let clangPreprocessorDefinitions: [String] - - /// Any custom header search paths specified for the Clang target. - public let headerSearchPaths: [String] - - /// The directory containing public C headers, if applicable. This will - /// only be set for targets that have a directory of a public headers. - public let publicHeadersDirectory: Path? + /// Attributes specific to the target's Clang sources. + public let clang: ClangSourceAttributes /// Any custom linked libraries required by the module, as specified in the /// package manifest. diff --git a/Sources/PackagePlugin/PluginContextDeserializer.swift b/Sources/PackagePlugin/PluginContextDeserializer.swift index a0e9dde8c0f..932d71c416a 100644 --- a/Sources/PackagePlugin/PluginContextDeserializer.swift +++ b/Sources/PackagePlugin/PluginContextDeserializer.swift @@ -159,10 +159,11 @@ internal struct PluginContextDeserializer { dependencies: dependencies, moduleName: moduleName, sourceFiles: sourceFiles, - swiftCompilationConditions: compilationConditions, - clangPreprocessorDefinitions: preprocessorDefinitions, - headerSearchPaths: headerSearchPaths, - publicHeadersDirectory: publicHeadersDir, + swift: .init(compilationConditions: compilationConditions), + clang: .init( + preprocessorDefinitions: preprocessorDefinitions, + headerSearchPaths: headerSearchPaths, + publicHeadersDirectory: publicHeadersDir), linkedLibraries: linkedLibraries, linkedFrameworks: linkedFrameworks ) From 652fb7ea07c4480a0b116e2ddc485b4f4b0f4f66 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 18 Aug 2023 17:49:01 -0400 Subject: [PATCH 146/178] Revert "Post FR Re-work (9): Enable Windows support" - Sleectively revert the parts that broke the Linux CI - Windows support will need to be manually tested - TODO: Selectively add back functional tests that work on Linux This reverts commit 3202a825c8afcd527e3d808c3be5c2a3c047f263. --- Tests/FunctionalTests/MixedTargetTests.swift | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 8720697f549..3f4381a882c 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -17,6 +17,9 @@ import SPMTestSupport final class MixedTargetTests: XCTestCase { + // Mixed language targets are only supported on macOS. + #if os(macOS) + // MARK: - Testing Mixed Targets func testMixedTarget() throws { @@ -476,4 +479,28 @@ final class MixedTargetTests: XCTestCase { ) } } + + #else + + // MARK: - Test Mixed Targets unsupported on non-macOS + + func testMixedTargetOnlySupportedOnMacOS() throws { + try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + let commandExecutionError = try XCTUnwrap( + XCTAssertBuildFails( + fixturePath, + extraArgs: ["--target", "BasicMixedTarget"] + ) + ) + + XCTAssert( + commandExecutionError.stderr.contains( + "error: Targets with mixed language sources are only supported on Apple platforms." + ) + ) + } + } + + #endif + } From 5cb110fdc0317353b5400248991e26ab62316d25 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 21 Aug 2023 16:35:31 -0400 Subject: [PATCH 147/178] Refactor test suite for easier multiplatform support --- .../MixedTargets/DummyTargets/Package.swift | 12 +- .../Package.swift | 35 +++++ .../Sources/MixedTarget}/CxxCountdown.cpp | 9 +- .../Sources/MixedTarget}/SwiftCountdown.swift | 0 .../MixedTarget}/include/CxxCountdown.hpp | 0 .../Sources/Driver.m | 0 .../Sources/Engine.swift | 0 .../Sources/NewCar.swift | 0 .../Sources/OldCar.m | 0 .../Sources/Public/Driver.h | 0 .../Sources/Public/OldCar.h | 0 .../Sources/Transmission.h | 0 .../Sources/Transmission.m | 0 .../Package.swift | 22 +--- .../Sources/BasicMixedTarget/CarPart.m | 0 .../Sources/BasicMixedTarget/Driver.m | 0 .../Sources/BasicMixedTarget/Engine.swift | 0 .../Sources/BasicMixedTarget/FluxCapacitor.h | 0 .../Sources/BasicMixedTarget/FluxCapacitor.m | 0 .../Sources/BasicMixedTarget/NewCar.swift | 0 .../Sources/BasicMixedTarget/OldCar.m | 0 .../Sources/BasicMixedTarget/Transmission.h | 0 .../Sources/BasicMixedTarget/Transmission.m | 0 .../BasicMixedTarget/include/CarPart.h | 0 .../Sources/BasicMixedTarget/include/Driver.h | 0 .../Sources/BasicMixedTarget/include/OldCar.h | 0 .../Bakery.m | 0 .../Coffee.swift | 0 .../Cookie.swift | 0 .../Dessert.m | 0 .../include/Bakery.h | 0 .../BasicMixedTargetWithUmbrellaHeader.h | 0 .../include/Dessert.h | 0 .../Sources/ClangTarget/Vessel.m | 0 .../Sources/ClangTarget/include/Vessel.h | 0 .../JunkYard.m | 0 .../include/JunkYard.h | 0 .../NewBoat.swift | 0 .../MixedTargetDependsOnClangTarget/OldBoat.m | 0 .../include/OldBoat.h | 0 .../NewBoat.swift | 0 .../MixedTargetDependsOnMixedTarget/OldBoat.m | 0 .../include/OldBoat.h | 0 .../NewBoat.swift | 0 .../MixedTargetDependsOnSwiftTarget/OldBoat.m | 0 .../include/OldBoat.h | 0 .../Sources/MixedTargetWithC/factorial.swift | 0 .../Sources/MixedTargetWithC/find_factorial.c | 0 .../MixedTargetWithC/include/find_factorial.h | 0 .../Sources/MixedTargetWithCXX/Calculator.mm | 0 .../MixedTargetWithCXX/Factorial.swift | 0 .../MixedTargetWithCXX/FactorialFinder.cpp | 0 .../MixedTargetWithCXX/FactorialFinder.hpp | 0 .../MixedTargetWithCXX/include/Calculator.h | 0 .../Calculator.mm | 0 .../Factorial.swift | 0 .../FactorialFinder.cpp | 0 .../FactorialFinder.hpp | 0 .../include/Calculator.h | 0 .../include/module.modulemap | 0 .../CXXFactorialFinder.cpp | 0 .../CXXFactorialFinder.hpp | 0 .../CXXSumFinder.cpp | 0 .../Factorial.swift | 0 .../ObjcCalculator.mm | 0 .../MixedTargetWithCXXPublicAPI/Sum.swift | 0 .../include/CXXSumFinder.hpp | 0 .../include/ObjcCalculator.h | 0 .../Factorial.swift | 0 .../Sum.swift | 0 .../XYZCxxFactorialFinder.cpp | 0 .../XYZCxxFactorialFinder.hpp | 0 .../XYZCxxSumFinder.cpp | 0 .../XYZObjcCalculator.mm | 0 .../include/XYZCxxSumFinder.hpp | 0 .../include/XYZObjcCalculator.h | 0 .../include/module.modulemap | 0 .../MixedTargetWithCustomModuleMap/Driver.m | 0 .../Engine.swift | 0 .../MixedTargetWithCustomModuleMap/Machine.m | 0 .../NewCar.swift | 0 .../MixedTargetWithCustomModuleMap/OldCar.m | 0 .../include/Driver.h | 0 .../include/Machine.h | 0 .../include/MixedTarget.h | 0 .../MixedTargetWithCustomModuleMap.h | 0 .../include/OldCar.h | 0 .../include/module.modulemap | 0 .../Driver.m | 0 .../Engine.swift | 0 .../NewCar.swift | 0 .../OldCar.m | 0 .../Transmission.h | 0 .../Transmission.m | 0 .../foo.txt | 0 .../include/Driver.h | 0 ...xedTargetWithCustomModuleMapAndResources.h | 0 .../include/OldCar.h | 0 .../include/module.modulemap | 0 .../Foo.m | 0 .../Foo.swift | 0 .../include/Foo.h | 0 .../include/module.modulemap | 0 .../Blah/Public/Driver/Driver.h | 0 .../Blah/Public/OldCar.h | 0 .../Driver.m | 0 .../Engine.swift | 0 .../NewCar.swift | 0 .../OldCar.m | 0 .../Transmission.h | 0 .../Transmission.m | 0 .../Blah/Public/Driver/Driver.h | 0 .../Blah/Public/OldCar.h | 0 .../Blah/Public/module.modulemap | 0 .../Driver.m | 0 .../Engine.swift | 0 .../NewCar.swift | 0 .../OldCar.m | 0 .../Transmission.h | 0 .../Transmission.m | 0 .../Driver.m | 0 .../Engine.swift | 0 .../NewCar.swift | 0 .../OldCar.m | 0 .../Transmission.h | 0 .../Transmission.m | 0 .../include/Driver.h | 0 .../include/OldCar.h | 0 .../BarBaz.swift | 0 .../OnLoadHook.h | 0 .../OnLoadHook.m | 0 .../MixedTargetWithNonPublicHeaders/Bar.h | 0 .../MixedTargetWithNonPublicHeaders/Bar.m | 0 .../MixedTargetWithNonPublicHeaders/Bat.swift | 0 .../MixedTargetWithNonPublicHeaders/Baz.m | 0 .../Foo/Foo/Foo.h | 0 .../Foo/Foo/Foo.m | 0 .../include/Baz.h | 0 .../ObjcResourceReader.m | 0 .../SwiftResourceReader.swift | 0 .../Sources/MixedTargetWithResources/foo.txt | 0 .../include/ObjcResourceReader.h | 0 .../NewCar.swift | 0 .../OldCar.m | 0 .../include/OldCar.h | 0 .../Sources/SwiftTarget/Vessel.swift | 0 .../JunkYard.swift | 0 .../BasicMixedTargetTests.swift | 0 .../ObjcBasicMixedTargetTests.m | 0 ...icMixedTargetWithUmbrellaHeaderTests.swift | 0 ...cBasicMixedTargetWithUmbrellaHeaderTests.m | 0 ...getDoesNotSupportModuleAliasingTests.swift | 0 .../MixedTargetWithCTests.swift | 0 ...hCXXPublicAPIAndCustomModuleMapTests.swift | 0 ...WithCXXPublicAPIAndCustomModuleMapTests.mm | 0 .../MixedTargetWithCXXPublicAPITests.swift | 0 .../ObjcMixedTargetWithCXXPublicAPITests.mm | 0 .../MixedTargetWithCXXTests.swift | 0 ...WithCustomModuleMapAndResourcesTests.swift | 0 ...rgetWithCustomModuleMapAndResourcesTests.m | 0 .../MixedTargetWithCustomModuleMapTests.swift | 0 .../ObjcMixedTargetWithCustomModuleMapTests.m | 0 ...etWithNoPublicObjectiveCHeadersTests.swift | 0 ...TargetWithNoPublicObjectiveCHeadersTests.m | 0 .../MixedTargetWithResourcesTests.swift | 0 .../MixedTargetTests.swift | 0 .../ObjcMixedTargetTests.m | 0 .../ObjcTestHelper.h | 0 .../ObjcTestHelper.m | 0 .../OtherObjcTestHelper.m | 0 .../Subdirectory2/OtherObjcTestHelper.h | 0 .../SwiftTestHelper.swift | 0 ...dersCanBeTestedViaHeaderSearchPathsTests.m | 0 Tests/FunctionalTests/MixedTargetTests.swift | 124 +++++++----------- 174 files changed, 98 insertions(+), 104 deletions(-) create mode 100644 Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift rename Fixtures/MixedTargets/{BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled => MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget}/CxxCountdown.cpp (58%) rename Fixtures/MixedTargets/{BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled => MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget}/SwiftCountdown.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled => MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget}/include/CxxCountdown.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/Public/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/Public/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/Transmission.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/MixedTargetWithCustomPaths/Sources/Transmission.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Package.swift (92%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/CarPart.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/FluxCapacitor.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/FluxCapacitor.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/Transmission.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/Transmission.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/include/CarPart.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/include/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTarget/include/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/ClangTarget/Vessel.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/ClangTarget/include/Vessel.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnClangTarget/OldBoat.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithC/factorial.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithC/find_factorial.c (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithC/include/find_factorial.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXX/Calculator.mm (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXX/Factorial.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXX/FactorialFinder.cpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXX/FactorialFinder.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXX/include/Calculator.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/Sum.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/Machine.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/include/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/include/Machine.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/Bar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/Bar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/Bat.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/Baz.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithResources/ObjcResourceReader.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithResources/SwiftResourceReader.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithResources/foo.txt (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetWithResources/include/ObjcResourceReader.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/SwiftTarget/Vessel.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift (100%) rename Fixtures/MixedTargets/{BasicMixedTargets => MixedTargetsWithObjC}/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m (100%) diff --git a/Fixtures/MixedTargets/DummyTargets/Package.swift b/Fixtures/MixedTargets/DummyTargets/Package.swift index 99cee89d0e7..844b1b6a835 100644 --- a/Fixtures/MixedTargets/DummyTargets/Package.swift +++ b/Fixtures/MixedTargets/DummyTargets/Package.swift @@ -3,11 +3,11 @@ import PackageDescription -// This package vends targets to aid in testing the BasicMixedTargets package. +// This package vends targets to aid in testing the MixedTargetsWithObjC package. let package = Package( name: "DummyTargets", dependencies: [ - .package(path: "../BasicMixedTargets") + .package(path: "../MixedTargetsWithObjC") ], targets: [ .executableTarget( @@ -15,7 +15,7 @@ let package = Package( dependencies: [ .product( name: "DynamicallyLinkedBasicMixedTarget", - package: "BasicMixedTargets" + package: "MixedTargetsWithObjC" ) ] ), @@ -24,7 +24,7 @@ let package = Package( dependencies: [ .product( name: "StaticallyLinkedBasicMixedTarget", - package: "BasicMixedTargets" + package: "MixedTargetsWithObjC" ) ] ), @@ -33,7 +33,7 @@ let package = Package( dependencies: [ .product( name: "DynamicallyLinkedBasicMixedTarget", - package: "BasicMixedTargets" + package: "MixedTargetsWithObjC" ) ] ), @@ -42,7 +42,7 @@ let package = Package( dependencies: [ .product( name: "StaticallyLinkedBasicMixedTarget", - package: "BasicMixedTargets" + package: "MixedTargetsWithObjC" ) ] ) diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift new file mode 100644 index 00000000000..bed2518ae0e --- /dev/null +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version: 999.0 +// FIXME(ncooke3): Update above version with the next version of SwiftPM. + +import PackageDescription + +let package = Package( + name: "MixedTargetsWithCXX_InteropEnabled", + products: [ + .library( + name: "MixedTarget", + targets: ["MixedTarget"] + ), + .library( + name: "StaticallyLinkedMixedTarget", + type: .static, + targets: ["MixedTarget"] + ), + .library( + name: "DynamicallyLinkedMixedTarget", + type: .dynamic, + targets: ["MixedTarget"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "MixedTarget", + swiftSettings: [.interoperabilityMode(.Cxx)] + ) + ], + // TODO(ncooke3): Is the below note behavior that we want to be intended? + // This is needed for targets with that have + // `swiftSettings: [.interoperabilityMode(.Cxx)]` set. + cxxLanguageStandard: .cxx11 +) diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp similarity index 58% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp rename to Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp index daefacd6f5d..ce1b021d0f7 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/CxxCountdown.cpp +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp @@ -1,9 +1,10 @@ #include "CxxCountdown.hpp" -// #include "MixedTargetWithCXX_CXXInteropEnabled-Swift.h" -// #include -#include + #include +#include "MixedTarget-Swift.h" +#include + CxxCountdown::CxxCountdown(bool printCount) : printCount(printCount) {} void CxxCountdown::countdown(int x )const { @@ -16,6 +17,6 @@ void CxxCountdown::countdown(int x )const { if (x == 0) std::cout << "[c++] We have liftoff!"; - auto swiftCountdown = MixedTargetWithCXX_CXXInteropEnabled::SwiftCountdown::init(printCount); + auto swiftCountdown = MixedTarget::SwiftCountdown::init(printCount); swiftCountdown.countdown(x - 1); } diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/SwiftCountdown.swift b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/SwiftCountdown.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/SwiftCountdown.swift rename to Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/SwiftCountdown.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/include/CxxCountdown.hpp b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/include/CxxCountdown.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX_CXXInteropEnabled/include/CxxCountdown.hpp rename to Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/include/CxxCountdown.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Public/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Public/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Public/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Public/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Public/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Transmission.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Transmission.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Transmission.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/MixedTargetWithCustomPaths/Sources/Transmission.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/MixedTargetWithCustomPaths/Sources/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Package.swift similarity index 92% rename from Fixtures/MixedTargets/BasicMixedTargets/Package.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Package.swift index 1724d001a9a..304972434e4 100644 --- a/Fixtures/MixedTargets/BasicMixedTargets/Package.swift +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Package.swift @@ -247,25 +247,5 @@ let package = Package( // The below two targets are used for testing the above targets. .target(name: "SwiftTarget"), .target(name: "ClangTarget") - ] + targetsWithCxxInteropEnabled(), - // TODO(ncooke3): Is the below note behavior that we want to be intended? - // This is needed for targets with that have - // `swiftSettings: [.interoperabilityMode(.Cxx)]` set. - cxxLanguageStandard: .cxx11 + ] ) - -func targetsWithCxxInteropEnabled() -> [Target] { -// The below targets have C++ interoperability mode enabled, a feature that -// requires Swift 5.9 or greater. -#if swift(>=5.9) - // MARK: - MixedTargetWithCXX_CXXInteropEnabled - return [ - .target( - name: "MixedTargetWithCXX_CXXInteropEnabled", - swiftSettings: [.interoperabilityMode(.Cxx)] - ) - ] -#else - return [] -#endif // swift(>=5.9) -} diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/CarPart.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/CarPart.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/CarPart.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/FluxCapacitor.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/FluxCapacitor.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/FluxCapacitor.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/FluxCapacitor.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/FluxCapacitor.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Transmission.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Transmission.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Transmission.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/Transmission.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/CarPart.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/include/CarPart.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/CarPart.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/include/CarPart.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/include/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/include/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/include/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTarget/include/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTarget/include/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Bakery.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Coffee.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Cookie.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/Dessert.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/include/Bakery.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/include/BasicMixedTargetWithUmbrellaHeader.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/BasicMixedTargetWithUmbrellaHeader/include/Dessert.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/Vessel.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTarget/Vessel.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/Vessel.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTarget/Vessel.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTarget/include/Vessel.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTarget/include/Vessel.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTarget/include/Vessel.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTargetDependsOnMixedTarget/JunkYard.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/ClangTargetDependsOnMixedTarget/include/JunkYard.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnClangTarget/NewBoat.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/OldBoat.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnClangTarget/OldBoat.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/OldBoat.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnClangTarget/OldBoat.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnClangTarget/include/OldBoat.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnMixedTarget/NewBoat.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnMixedTarget/OldBoat.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnMixedTarget/include/OldBoat.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnSwiftTarget/NewBoat.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnSwiftTarget/OldBoat.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetDependsOnSwiftTarget/include/OldBoat.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/factorial.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithC/factorial.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/factorial.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithC/factorial.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/find_factorial.c b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithC/find_factorial.c similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/find_factorial.c rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithC/find_factorial.c diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/include/find_factorial.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithC/include/find_factorial.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithC/include/find_factorial.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithC/include/find_factorial.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Calculator.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/Calculator.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Calculator.mm rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/Calculator.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Factorial.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/Factorial.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/Factorial.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/Factorial.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/FactorialFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.cpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/FactorialFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/FactorialFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/FactorialFinder.hpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/FactorialFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/include/Calculator.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/include/Calculator.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXX/include/Calculator.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXX/include/Calculator.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/Calculator.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/Factorial.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/FactorialFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/include/Calculator.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXAndCustomModuleMap/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/Factorial.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Sum.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/Sum.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/Sum.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/Sum.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/include/CXXSumFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/include/ObjcCalculator.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Factorial.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/Sum.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxSumFinder.cpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZObjcCalculator.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZCxxSumFinder.hpp diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/XYZObjcCalculator.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Machine.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/Machine.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/Machine.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/Machine.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Machine.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/Machine.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/Machine.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/Machine.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTarget.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/foo.txt diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/MixedTargetWithCustomModuleMapAndResources.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMapAndResources/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/Foo.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/include/Foo.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithInvalidCustomModuleMap/include/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/Driver/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Blah/Public/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Transmission.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeaders/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/Driver/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Blah/Public/module.modulemap diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNestedPublicHeadersAndCustomModuleMap/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Driver.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Engine.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/Transmission.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/Driver.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoObjectiveCCompatibleSwiftAPI/include/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/OnLoadHook.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Bar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Bar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Bar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Bar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Bat.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Bat.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Bat.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Baz.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Baz.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Baz.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Baz.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/Foo/Foo/Foo.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNonPublicHeaders/include/Baz.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/ObjcResourceReader.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/ObjcResourceReader.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/ObjcResourceReader.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/ObjcResourceReader.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/SwiftResourceReader.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/SwiftResourceReader.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/SwiftResourceReader.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/SwiftResourceReader.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/foo.txt b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/foo.txt similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/foo.txt rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/foo.txt diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/include/ObjcResourceReader.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/include/ObjcResourceReader.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetWithResources/include/ObjcResourceReader.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithResources/include/ObjcResourceReader.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/NewCar.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/OldCar.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetsPublicHeadersAreIncludedInHeaderSearchPathsForObjcSource/include/OldCar.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTarget/Vessel.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/SwiftTarget/Vessel.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTarget/Vessel.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/SwiftTarget/Vessel.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/SwiftTargetDependsOnMixedTarget/JunkYard.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetTests/BasicMixedTargetTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetTests/ObjcBasicMixedTargetTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetWithUmbrellaHeaderTests/BasicMixedTargetWithUmbrellaHeaderTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/BasicMixedTargetWithUmbrellaHeaderTests/ObjcBasicMixedTargetWithUmbrellaHeaderTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetDoesNotSupportModuleAliasingTests/MixedTargetDoesNotSupportModuleAliasingTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCTests/MixedTargetWithCTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPIAndCustomModuleMapTests/ObjcMixedTargetWithCXXPublicAPIAndCustomModuleMapTests.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/MixedTargetWithCXXPublicAPITests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXTests/MixedTargetWithCXXTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/MixedTargetWithCustomModuleMapAndResourcesTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapAndResourcesTests/ObjcMixedTargetWithCustomModuleMapAndResourcesTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapTests/MixedTargetWithCustomModuleMapTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCustomModuleMapTests/ObjcMixedTargetWithCustomModuleMapTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/MixedTargetWithNoPublicObjectiveCHeadersTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithNoPublicObjectiveCHeadersTests/ObjcMixedTargetWithNoPublicObjectiveCHeadersTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithResourcesTests/MixedTargetWithResourcesTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/MixedTargetTests.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcMixedTargetTests.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/ObjcTestHelper.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/OtherObjcTestHelper.m diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/Subdirectory1/Subdirectory2/OtherObjcTestHelper.h diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTestTargetWithSharedUtilitiesTests/SwiftTestHelper.swift diff --git a/Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m similarity index 100% rename from Fixtures/MixedTargets/BasicMixedTargets/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m rename to Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests/PrivateHeadersCanBeTestedViaHeaderSearchPathsTests.m diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 3f4381a882c..9c8e7eafe1f 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -13,17 +13,31 @@ import XCTest import SPMTestSupport -// MARK: - MixedTargetTests - final class MixedTargetTests: XCTestCase { + // MARK: - All Platforms Tests + + // The below tests build targets with C++ interoperability mode enabled, a + // feature that requires Swift 5.9 or greater. + // FIXME(ncooke3): Update with next version of SPM. + #if swift(>=5.9) + func testMixedTargetWithCXX_InteropEnabled() throws { + try fixture(name: "MixedTargets/MixedTargetsWithCXX_InteropEnabled") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTarget"] + ) + } + } + #endif // swift(>=5.9) +} - // Mixed language targets are only supported on macOS. - #if os(macOS) - - // MARK: - Testing Mixed Targets +#if os(macOS) +extension MixedTargetTests { + // MARK: - macOS Tests + // The targets tested contain Objective-C, and thus require macOS to be tested. func testMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "BasicMixedTarget"] @@ -33,7 +47,7 @@ final class MixedTargetTests: XCTestCase { // FIXME(ncooke3): Re-enable with Swift compiler change (see proposal). // func testMixedTargetWithUmbrellaHeader() throws { -// try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in +// try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in // XCTAssertBuilds( // fixturePath, // extraArgs: ["--target", "BasicMixedTargetWithUmbrellaHeader"] @@ -46,7 +60,7 @@ final class MixedTargetTests: XCTestCase { // } func testMixedTargetWithResources() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: ["--filter", "MixedTargetWithResources"] @@ -55,7 +69,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithCustomModuleMap() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithCustomModuleMap"] @@ -72,7 +86,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithInvalidCustomModuleMap() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in // An invalid module map will cause the whole package to fail to // build. To work around this, the module map is made invalid // during the actual test. @@ -108,7 +122,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithCustomModuleMapAndResources() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: [ @@ -129,21 +143,8 @@ final class MixedTargetTests: XCTestCase { } } -// The below tests build targets with C++ interoperability mode enabled, a -// feature that requires Swift 5.9 or greater. -#if swift(>=5.9) - func testMixedTargetWithCXX_CXXInteropEnabled() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - XCTAssertBuilds( - fixturePath, - extraArgs: ["--target", "MixedTargetWithCXX_CXXInteropEnabled"] - ) - } - } -#endif // swift(>=5.9) - func testMixedTargetWithCXX() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: ["--filter", "MixedTargetWithCXXTests"] @@ -152,7 +153,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithCXXAndCustomModuleMap() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithCXXAndCustomModuleMap"] @@ -161,7 +162,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithCXXPublicAPI() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithCXXPublicAPI"] @@ -177,7 +178,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithCXXPublicAPIAndCustomModuleMap() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithCXXPublicAPIAndCustomModuleMap"] @@ -194,7 +195,7 @@ final class MixedTargetTests: XCTestCase { func testMixedTargetWithC() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: ["--filter", "MixedTargetWithC"] @@ -203,7 +204,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithNoPublicObjectiveCHeaders() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: ["--filter", "MixedTargetWithNoPublicObjectiveCHeadersTests"] @@ -218,7 +219,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithNoObjectiveCCompatibleSwiftAPI() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithNoObjectiveCCompatibleSwiftAPI"] @@ -233,7 +234,7 @@ final class MixedTargetTests: XCTestCase { } func testNonPublicHeadersAreVisibleFromSwiftPartOfMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuildFails( fixturePath, extraArgs: ["--target", "MixedTargetWithNonPublicHeaders"], @@ -249,7 +250,7 @@ final class MixedTargetTests: XCTestCase { // TODO(ncooke3): Use a different target to test this as the target below // has been deleted. func SKIP_testNonPublicHeadersAreNotVisibleFromOutsideOfTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in // The test target tries to access non-public headers so the build // should fail. XCTAssertBuildFails( @@ -265,7 +266,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithCustomPaths() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithCustomPaths"] @@ -275,7 +276,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetBuildsInReleaseMode() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: [ @@ -287,7 +288,7 @@ final class MixedTargetTests: XCTestCase { } func testStaticallyLinkedMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--product", "StaticallyLinkedBasicMixedTarget"] @@ -318,7 +319,7 @@ final class MixedTargetTests: XCTestCase { } func testDynamicallyLinkedMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--product", "DynamicallyLinkedBasicMixedTarget"] @@ -363,7 +364,7 @@ final class MixedTargetTests: XCTestCase { // - #import "include/OldCar.h" // // This aligns with the behavior of a Clang-only target. - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: [ @@ -375,7 +376,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithNestedPublicHeaders() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetWithNestedPublicHeaders"] @@ -384,7 +385,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetWithNestedPublicHeadersAndCustomModuleMap() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: [ @@ -398,7 +399,7 @@ final class MixedTargetTests: XCTestCase { // MARK: - Testing Mixed *Test* Targets func testMixedTestTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in for value in [0, 1] { XCTAssertSwiftTest( fixturePath, @@ -410,7 +411,7 @@ final class MixedTargetTests: XCTestCase { } func testTestUtilitiesCanBeSharedAcrossSwiftAndObjcTestFiles() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: ["--filter", "MixedTestTargetWithSharedUtilitiesTests"] @@ -419,7 +420,7 @@ final class MixedTargetTests: XCTestCase { } func testPrivateHeadersCanBeTestedViaHeaderSearchPaths() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertSwiftTest( fixturePath, extraArgs: ["--filter", "PrivateHeadersCanBeTestedViaHeaderSearchPathsTests"] @@ -430,7 +431,7 @@ final class MixedTargetTests: XCTestCase { // MARK: - Integrating Mixed Target with other Targets func testClangTargetDependsOnMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in for value in [0, 1] { XCTAssertBuilds( fixturePath, @@ -442,7 +443,7 @@ final class MixedTargetTests: XCTestCase { } func testSwiftTargetDependsOnMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "SwiftTargetDependsOnMixedTarget"] @@ -451,7 +452,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetDependsOnOtherMixedTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in for value in [0, 1] { XCTAssertBuilds( fixturePath, @@ -463,7 +464,7 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetDependsOnClangTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetDependsOnClangTarget"] @@ -472,35 +473,12 @@ final class MixedTargetTests: XCTestCase { } func testMixedTargetDependsOnSwiftTarget() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in + try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( fixturePath, extraArgs: ["--target", "MixedTargetDependsOnSwiftTarget"] ) } } - - #else - - // MARK: - Test Mixed Targets unsupported on non-macOS - - func testMixedTargetOnlySupportedOnMacOS() throws { - try fixture(name: "MixedTargets/BasicMixedTargets") { fixturePath in - let commandExecutionError = try XCTUnwrap( - XCTAssertBuildFails( - fixturePath, - extraArgs: ["--target", "BasicMixedTarget"] - ) - ) - - XCTAssert( - commandExecutionError.stderr.contains( - "error: Targets with mixed language sources are only supported on Apple platforms." - ) - ) - } - } - - #endif - } +#endif // os(macOS) From 778e65fd97494ccf84022b6e0182d0158eea5202 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Mon, 21 Aug 2023 17:33:40 -0400 Subject: [PATCH 148/178] Add multiplatform build test --- .../Package.swift | 32 +++++++++++++++++++ .../Sources/MixedTarget/CxxCountdown.cpp | 23 +++++++++++++ .../Sources/MixedTarget/SwiftCountdown.swift | 27 ++++++++++++++++ .../MixedTarget/include/CxxCountdown.hpp | 10 ++++++ .../Package.swift | 2 ++ .../Sources/MixedTarget/CxxCountdown.cpp | 2 ++ .../Sources/MixedTarget/SwiftCountdown.swift | 2 ++ .../MixedTarget/include/CxxCountdown.hpp | 1 - Tests/FunctionalTests/MixedTargetTests.swift | 9 ++++++ 9 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Package.swift create mode 100644 Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/CxxCountdown.cpp create mode 100644 Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/SwiftCountdown.swift create mode 100644 Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/include/CxxCountdown.hpp diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Package.swift b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Package.swift new file mode 100644 index 00000000000..8fd2edd1556 --- /dev/null +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version: 999.0 +// FIXME(ncooke3): Update above version with the next version of SwiftPM. + +// NOTE: This is package is intended to build on all platforms (macOS, Linux, and Windows). + +import PackageDescription + +let package = Package( + name: "MixedTargetsWithCXX_InteropDisabled", + products: [ + .library( + name: "MixedTarget", + targets: ["MixedTarget"] + ), + .library( + name: "StaticallyLinkedMixedTarget", + type: .static, + targets: ["MixedTarget"] + ), + .library( + name: "DynamicallyLinkedMixedTarget", + type: .dynamic, + targets: ["MixedTarget"] + ) + ], + dependencies: [], + targets: [ + .target( + name: "MixedTarget" + ) + ] +) diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/CxxCountdown.cpp b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/CxxCountdown.cpp new file mode 100644 index 00000000000..1458da19d01 --- /dev/null +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/CxxCountdown.cpp @@ -0,0 +1,23 @@ +#include "CxxCountdown.hpp" + +#include + +#include "MixedTarget-Swift.h" +#include + +CxxCountdown::CxxCountdown(bool printCount) : printCount(printCount) {} + +void CxxCountdown::countdown(int x )const { + if (x < 0) + std::cout << "[c++] Cannot count down from a negative number.\n"; + return; + + if (printCount) + std::cout << "[c++] T-minus " << x << "... \n"; + + if (x == 0) + std::cout << "[c++] We have liftoff!"; + return; + + CxxCountdown::countdown(x - 1); +} diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/SwiftCountdown.swift b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/SwiftCountdown.swift new file mode 100644 index 00000000000..8920e89a14c --- /dev/null +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/SwiftCountdown.swift @@ -0,0 +1,27 @@ +import Foundation + +public struct SwiftCountdown { + private let printCount: Bool + + public init(printCount: Bool) { + self.printCount = printCount + } + + public func countdown(x: Int) { + if x < 0 { + print("[swift] Cannot count down from a negative number.") + return + } + + if printCount { + print("[swift] T-minus \(x)...") + } + + if x == 0 { + print("[swift] We have liftoff!") + return + } + + countdown(x: x - 1) + } +} diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/include/CxxCountdown.hpp b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/include/CxxCountdown.hpp new file mode 100644 index 00000000000..9e0b988b010 --- /dev/null +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropDisabled/Sources/MixedTarget/include/CxxCountdown.hpp @@ -0,0 +1,10 @@ +#ifdef __cplusplus +class CxxCountdown +{ +public: + CxxCountdown(bool printCount); + void countdown(int x) const; +private: + bool printCount; +}; +#endif // __cplusplus diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift index bed2518ae0e..57aa762e33c 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Package.swift @@ -1,6 +1,8 @@ // swift-tools-version: 999.0 // FIXME(ncooke3): Update above version with the next version of SwiftPM. +// NOTE: This is package is intended to build on all platforms (macOS, Linux, and Windows). + import PackageDescription let package = Package( diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp index ce1b021d0f7..bca733ded47 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/CxxCountdown.cpp @@ -10,12 +10,14 @@ CxxCountdown::CxxCountdown(bool printCount) : printCount(printCount) {} void CxxCountdown::countdown(int x )const { if (x < 0) std::cout << "[c++] Cannot count down from a negative number.\n"; + return; if (printCount) std::cout << "[c++] T-minus " << x << "... \n"; if (x == 0) std::cout << "[c++] We have liftoff!"; + return; auto swiftCountdown = MixedTarget::SwiftCountdown::init(printCount); swiftCountdown.countdown(x - 1); diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/SwiftCountdown.swift b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/SwiftCountdown.swift index e2a3e80a236..1439c793d1e 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/SwiftCountdown.swift +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/SwiftCountdown.swift @@ -10,6 +10,7 @@ public struct SwiftCountdown { public func countdown(x: Int) { if x < 0 { print("[swift] Cannot count down from a negative number.") + return } if printCount { @@ -18,6 +19,7 @@ public struct SwiftCountdown { if x == 0 { print("[swift] We have liftoff!") + return } let cxxCountdown = CxxCountdown(printCount) diff --git a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/include/CxxCountdown.hpp b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/include/CxxCountdown.hpp index b179673130e..d42c728ad5c 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/include/CxxCountdown.hpp +++ b/Fixtures/MixedTargets/MixedTargetsWithCXX_InteropEnabled/Sources/MixedTarget/include/CxxCountdown.hpp @@ -1,4 +1,3 @@ -#pragma once class CxxCountdown { public: diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 9c8e7eafe1f..6a1b0770289 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -29,6 +29,15 @@ final class MixedTargetTests: XCTestCase { } } #endif // swift(>=5.9) + + func testMixedTargetWithCXX_InteropDisabled() throws { + try fixture(name: "MixedTargets/MixedTargetsWithCXX_InteropDisabled") { fixturePath in + XCTAssertBuilds( + fixturePath, + extraArgs: ["--target", "MixedTarget"] + ) + } + } } #if os(macOS) From 56ed7fb3b99a4fe76b8d902a21f222dfa8831886 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 22 Aug 2023 15:56:26 -0400 Subject: [PATCH 149/178] Fix some of the Linux CI failures - Some of the plugins tests failed on Linux because the plugins being tested had Objective-C. I think it should be fine to remove the Objective-C code and just have a "mixed" target with .swift, .h, and .m files. --- Tests/CommandsTests/PackageToolTests.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index a277078d791..e74054ce664 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -2972,21 +2972,17 @@ final class PackageToolTests: CommandsTestCase { ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Foo.swift"), string: """ - public struct Foo { } + a file with a filename suffix handled by the plugin """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "include", "Bar.h"), string: """ - #import - @interface Bar: NSObject - @end + a file with a filename suffix handled by the plugin """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Bar.m"), string: """ - #import "Bar.h" - @implementation Bar - @end + a file with a filename suffix handled by the plugin """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "library.foo"), string: From cffc22a149d8d189720b25722f15f58aff969901 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 22 Aug 2023 16:48:09 -0400 Subject: [PATCH 150/178] Fix mixed target related build tests --- Tests/BuildTests/BuildPlanTests.swift | 222 ++++++++++++++++++-------- 1 file changed, 157 insertions(+), 65 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index c95a8948603..042279e4ad2 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1533,14 +1533,25 @@ final class BuildPlanTests: XCTestCase { ]) let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) - XCTAssertMatch(clangPartOfLib, [ - "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", - "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", - "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", - "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", - "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-DHELLO_CLANG=1", - "-g" - ]) + #if os(macOS) + XCTAssertMatch(clangPartOfLib, [ + "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", + "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", + "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-DHELLO_CLANG=1", + "-g" + ]) + #elseif os(Linux) + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-O0", + "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", + "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-DHELLO_CLANG=1", + "-g" + ]) + #endif XCTAssertEqual( try result.target(for: "lib").mixedTarget().objects, @@ -1555,17 +1566,27 @@ final class BuildPlanTests: XCTestCase { buildPath.appending(components: "lib.build", "module.modulemap").pathString ) - XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, - "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, - "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", - "-Xlinker", "@loader_path", - "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", - "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", defaultTargetTriple, "-framework", "OtherFramework", "-Xlinker", "-add_ast_path", - "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, - "-g" - ]) + #if os(macOS) + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", + "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, "-framework", "OtherFramework", "-Xlinker", "-add_ast_path", + "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, + "-g" + ]) + #elseif os(Linux) + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", "-static-stdlib", "-emit-executable", "-Xlinker", "-rpath=$ORIGIN", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-target", defaultTargetTriple, "-g" + ]) + #endif testDiagnostics(observability.diagnostics) { result in result.check(diagnostic: .contains("can be downloaded"), severity: .warning) @@ -1637,13 +1658,23 @@ final class BuildPlanTests: XCTestCase { ]) let clangPartOfLib = try result.target(for: "lib").mixedTarget().clangTargetBuildDescription.basicArguments(isCXX: false) - XCTAssertMatch(clangPartOfLib, [ - "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", - "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", - "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", - "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", - "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" - ]) + #if os(macOS) + XCTAssertMatch(clangPartOfLib, [ + "-fobjc-arc", "-target", "\(defaultTargetTriple)", "-O0", + "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", + "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" + ]) + #elseif os(Linux) + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-O0", + "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", + "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", + "/Pkg/Sources/lib", "-I", "/path/to/build/debug/lib.build", + "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" + ]) + #endif XCTAssertEqual( try result.target(for: "lib").mixedTarget().objects, @@ -1658,6 +1689,28 @@ final class BuildPlanTests: XCTestCase { "/Pkg/Sources/lib/include/module.modulemap" ) + #if os(macOS) + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", + "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, "-framework", "OtherFramework", "-Xlinker", "-add_ast_path", + "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, + "-g" + ]) + #elseif os(Linux) + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", "-static-stdlib", "-emit-executable", "-Xlinker", "-rpath=$ORIGIN", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-target", defaultTargetTriple, "-g" + ]) + #endif + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, @@ -2640,33 +2693,59 @@ final class BuildPlanTests: XCTestCase { let fooLinkArgs = try result.buildProduct(for: "Foo").linkArguments() let barLinkArgs = try result.buildProduct(for: "Bar-Baz").linkArguments() - XCTAssertEqual(fooLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, - "-L", buildPath.pathString, - "-o", buildPath.appending(components: "Foo").pathString, - "-module-name", "Foo", - "-lBar-Baz", - "-emit-executable", - "-Xlinker", "-rpath", "-Xlinker", "@loader_path", - "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", - "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", defaultTargetTriple, - "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString, - "-g" - ]) + #if os(macOS) + XCTAssertEqual(fooLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "Foo").pathString, + "-module-name", "Foo", + "-lBar-Baz", + "-emit-executable", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString, + "-g" + ]) - XCTAssertEqual(barLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, - "-L", buildPath.pathString, - "-o", buildPath.appending(components: "libBar-Baz.dylib").pathString, - "-module-name", "Bar_Baz", - "-emit-library", - "-Xlinker", "-install_name", "-Xlinker", "@rpath/libBar-Baz.dylib", - "-Xlinker", "-rpath", "-Xlinker", "@loader_path", - "@\(buildPath.appending(components: "Bar-Baz.product", "Objects.LinkFileList"))", - "-runtime-compatibility-version", "none", "-target", defaultTargetTriple, - "-g" - ]) + XCTAssertEqual(barLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "libBar-Baz.dylib").pathString, + "-module-name", "Bar_Baz", + "-emit-library", + "-Xlinker", "-install_name", "-Xlinker", "@rpath/libBar-Baz.dylib", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "Bar-Baz.product", "Objects.LinkFileList"))", + "-runtime-compatibility-version", "none", "-target", defaultTargetTriple, + "-g" + ]) + #elseif os(Linux) + XCTAssertEqual(fooLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "Foo").pathString, + "-module-name", "Foo", + "-lBar-Baz", + "-emit-executable", + "-Xlinker", "-rpath=$ORIGIN", + "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", + "-target", defaultTargetTriple, "-g" + ]) + + XCTAssertEqual(barLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "libBar-Baz.so").pathString, + "-module-name", "Bar_Baz", + "-emit-library", + "-Xlinker", "-rpath=$ORIGIN", + "@\(buildPath.appending(components: "Bar-Baz.product", "Objects.LinkFileList"))", + "-runtime-compatibility-version", "none", "-target", defaultTargetTriple, + "-g" + ]) + #endif } func testStaticProductsForMixedTarget() throws { @@ -2732,19 +2811,32 @@ final class BuildPlanTests: XCTestCase { let fooLinkArgs = try result.buildProduct(for: "Foo").linkArguments() let barLinkArgs = try result.buildProduct(for: "Bar-Baz").linkArguments() - XCTAssertEqual(fooLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, - "-L", buildPath.pathString, - "-o", buildPath.appending(components: "Foo").pathString, - "-module-name", "Foo", - "-emit-executable", - "-Xlinker", "-rpath", "-Xlinker", "@loader_path", - "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", - "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", defaultTargetTriple, - "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString, - "-g" - ]) + #if os(macOS) + XCTAssertEqual(fooLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "Foo").pathString, + "-module-name", "Foo", + "-emit-executable", + "-Xlinker", "-rpath", "-Xlinker", "@loader_path", + "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.build", "Foo.swiftmodule").pathString, + "-g" + ]) + #elseif os(Linux) + XCTAssertEqual(fooLinkArgs, [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "Foo").pathString, + "-module-name", "Foo", + "-emit-executable", + "-Xlinker", "-rpath=$ORIGIN", + "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", + "-target", defaultTargetTriple, "-g" + ]) + #endif // No arguments for linking static libraries. XCTAssertEqual(barLinkArgs, []) From 1649cdd716cc10b0526417fcdd92d0ef5b4aa3bc Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 22 Aug 2023 17:45:45 -0400 Subject: [PATCH 151/178] Fix tests following 652fb7ea07c4480a0b116e2ddc485b4f4b0f4f66 (see more) - By removing the DawrinOS-only constraint on generating the Swift header, there were test cases that needed to get updated when running on a non-Darwin OS. --- Tests/BuildTests/BuildPlanTests.swift | 46 +++++-------------- .../BuildTests/ModuleAliasingBuildTests.swift | 31 ------------- 2 files changed, 11 insertions(+), 66 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 042279e4ad2..6c0d37bafa9 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -4360,18 +4360,10 @@ final class BuildPlanTests: XCTestCase { let buildPath = result.plan.buildParameters.dataPath.appending(components: "debug") let fooTarget = try result.target(for: "Foo").swiftTarget().compileArguments() - #if os(macOS) - XCTAssertMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - #else - XCTAssertNoMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - #endif + XCTAssertMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) let barTarget = try result.target(for: "Bar").clangTarget().basicArguments(isCXX: false) - #if os(macOS) - XCTAssertMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) - #else - XCTAssertNoMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) - #endif + XCTAssertMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) let yaml = try fs.tempDirectory.appending(components: UUID().uuidString, "debug.yaml") try fs.createDirectory(yaml.parentDirectory, recursive: true) @@ -4433,19 +4425,11 @@ final class BuildPlanTests: XCTestCase { let buildPath = result.plan.buildParameters.dataPath.appending(components: "debug") - let fooTarget = try result.target(for: "Foo").swiftTarget().compileArguments() - #if os(macOS) - XCTAssertMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - #else - XCTAssertNoMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - #endif + let fooTarget = try result.target(for: "Foo").swiftTarget().compileArguments() + XCTAssertMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - let barTarget = try result.target(for: "Bar").clangTarget().basicArguments(isCXX: false) - #if os(macOS) - XCTAssertMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) - #else - XCTAssertNoMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) - #endif + let barTarget = try result.target(for: "Bar").clangTarget().basicArguments(isCXX: false) + XCTAssertMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) let yaml = try fs.tempDirectory.appending(components: UUID().uuidString, "debug.yaml") try fs.createDirectory(yaml.parentDirectory, recursive: true) @@ -4511,19 +4495,11 @@ final class BuildPlanTests: XCTestCase { #endif let result = try BuildPlanResult(plan: plan) - let fooTarget = try result.target(for: "Foo").swiftTarget().compileArguments() - #if os(macOS) - XCTAssertMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - #else - XCTAssertNoMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) - #endif - - let barTarget = try result.target(for: "Bar").clangTarget().basicArguments(isCXX: false) - #if os(macOS) - XCTAssertMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) - #else - XCTAssertNoMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) - #endif + let fooTarget = try result.target(for: "Foo").swiftTarget().compileArguments() + XCTAssertMatch(fooTarget, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", .anySequence]) + + let barTarget = try result.target(for: "Bar").clangTarget().basicArguments(isCXX: false) + XCTAssertMatch(barTarget, [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence]) let buildPath = result.plan.buildParameters.dataPath.appending(components: "debug") diff --git a/Tests/BuildTests/ModuleAliasingBuildTests.swift b/Tests/BuildTests/ModuleAliasingBuildTests.swift index 98c49f30b90..b4a88bb03e4 100644 --- a/Tests/BuildTests/ModuleAliasingBuildTests.swift +++ b/Tests/BuildTests/ModuleAliasingBuildTests.swift @@ -708,7 +708,6 @@ final class ModuleAliasingBuildTests: XCTestCase { let fooLoggingArgs = try result.target(for: "FooLogging").swiftTarget().compileArguments() let barLoggingArgs = try result.target(for: "BarLogging").swiftTarget().compileArguments() let loggingArgs = try result.target(for: "Logging").swiftTarget().compileArguments() - #if os(macOS) XCTAssertMatch( fooLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", @@ -724,23 +723,6 @@ final class ModuleAliasingBuildTests: XCTestCase { [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] ) - #else - XCTAssertNoMatch( - fooLoggingArgs, - [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/FooLogging.build/FooLogging-Swift.h", .anySequence] - ) - XCTAssertNoMatch( - barLoggingArgs, - [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/BarLogging.build/BarLogging-Swift.h", .anySequence] - ) - XCTAssertNoMatch( - loggingArgs, - [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] - ) - #endif } func testModuleAliasingDuplicateTargetNameInUpstream() throws { @@ -826,7 +808,6 @@ final class ModuleAliasingBuildTests: XCTestCase { let otherLoggingArgs = try result.target(for: "OtherLogging").swiftTarget().compileArguments() let loggingArgs = try result.target(for: "Logging").swiftTarget().compileArguments() - #if os(macOS) XCTAssertMatch( otherLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", @@ -837,18 +818,6 @@ final class ModuleAliasingBuildTests: XCTestCase { [.anySequence, "-emit-objc-header", "-emit-objc-header-path", "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] ) - #else - XCTAssertNoMatch( - otherLoggingArgs, - [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/OtherLogging.build/OtherLogging-Swift.h", .anySequence] - ) - XCTAssertNoMatch( - loggingArgs, - [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] - ) - #endif } func testModuleAliasingMultipleAliasesInProduct() throws { From 163b78e8209c6548193bbe5c312c3650ae5309bc Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Aug 2023 10:48:08 -0400 Subject: [PATCH 152/178] Revert "Fix some of the Linux CI failures" This reverts commit 56ed7fb3b99a4fe76b8d902a21f222dfa8831886. --- Tests/CommandsTests/PackageToolTests.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index e74054ce664..a277078d791 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -2972,17 +2972,21 @@ final class PackageToolTests: CommandsTestCase { ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Foo.swift"), string: """ - a file with a filename suffix handled by the plugin + public struct Foo { } """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "include", "Bar.h"), string: """ - a file with a filename suffix handled by the plugin + #import + @interface Bar: NSObject + @end """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Bar.m"), string: """ - a file with a filename suffix handled by the plugin + #import "Bar.h" + @implementation Bar + @end """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "library.foo"), string: From 9b7413bce4d08057f1264f8c3145b7702eae4b55 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Aug 2023 10:53:46 -0400 Subject: [PATCH 153/178] Temp. disable test on non-macOS. Added TODO --- Tests/CommandsTests/PackageToolTests.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index a277078d791..e8d04d6baac 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -2941,6 +2941,13 @@ final class PackageToolTests: CommandsTestCase { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") + #if !os(macOS) + // TODO(ncooke3): Re-evaluate why this failed on Linux. Probably + // should make a mixed lang. plugin test with Swift + C/C++ instead. + // See https://ci.swift.org/job/swift-package-manager-self-hosted-Linux-smoke-test/2685/console + try XCTSkipIf(true, "skipping on non-macOS") + #endif + try testWithTemporaryDirectory { tmpPath in // Create a sample package with a library target and a plugin. let packageDir = tmpPath.appending(components: "MyPackage") From 4e540d376dab2bbbb2904c261d09ad5a3a7adaa6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Aug 2023 10:55:36 -0400 Subject: [PATCH 154/178] Resolve TODO; re-enable test on non-macOS --- Tests/CommandsTests/PackageToolTests.swift | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index e8d04d6baac..ab16594ca7f 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -2941,13 +2941,6 @@ final class PackageToolTests: CommandsTestCase { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") - #if !os(macOS) - // TODO(ncooke3): Re-evaluate why this failed on Linux. Probably - // should make a mixed lang. plugin test with Swift + C/C++ instead. - // See https://ci.swift.org/job/swift-package-manager-self-hosted-Linux-smoke-test/2685/console - try XCTSkipIf(true, "skipping on non-macOS") - #endif - try testWithTemporaryDirectory { tmpPath in // Create a sample package with a library target and a plugin. let packageDir = tmpPath.appending(components: "MyPackage") @@ -2979,21 +2972,17 @@ final class PackageToolTests: CommandsTestCase { ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Foo.swift"), string: """ - public struct Foo { } + // a file with a filename suffix handled by the plugin """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "include", "Bar.h"), string: """ - #import - @interface Bar: NSObject - @end + // a file with a filename suffix handled by the plugin """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "Bar.m"), string: """ - #import "Bar.h" - @implementation Bar - @end + // a file with a filename suffix handled by the plugin """ ) try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "MyLibrary", "library.foo"), string: From aca8f279544f5d7151b90ff088c3b382fba6f9c2 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Aug 2023 11:04:46 -0400 Subject: [PATCH 155/178] Fix remaining other BuildPlanTests on Linux --- Tests/BuildTests/BuildPlanTests.swift | 49 ++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6c0d37bafa9..f9d15b4761a 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1505,17 +1505,29 @@ final class BuildPlanTests: XCTestCase { let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") let exe = try result.target(for: "exe").swiftTarget().compileArguments() - - XCTAssertMatch(exe, [ - "-target", "\(defaultTargetTriple)", "-swift-version", "5", - "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), - "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", - "-fmodule-map-file=/path/to/build/debug/lib.build/module.modulemap", - "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", - "-module-cache-path", - "\(buildPath.appending(components: "ModuleCache"))", - .anySequence, "-parseable-output", "-g", "-Xcc", "-g", - ]) + #if os(macOS) + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "5", + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-fmodule-map-file=/path/to/build/debug/lib.build/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", + "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", + .anySequence, "-parseable-output", "-g", "-Xcc", "-g", + ]) + #elseif os(Linux) + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "5", + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-fmodule-map-file=/path/to/build/debug/lib.build/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", + "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", + .anySequence, "-static-stdlib", "-parseable-output", "-g", "-Xcc", "-g", + ]) + #endif let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() XCTAssertMatch(swiftPartOfLib, [ @@ -1633,6 +1645,18 @@ final class BuildPlanTests: XCTestCase { let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") let exe = try result.target(for: "exe").swiftTarget().compileArguments() + #if os(macOS) + XCTAssertMatch(exe, [ + "-target", "\(defaultTargetTriple)", "-swift-version", "5", + "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), + "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-fmodule-map-file=/Pkg/Sources/lib/include/module.modulemap", + "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", + "-module-cache-path", + "\(buildPath.appending(components: "ModuleCache"))", .anySequence, + "-parseable-output", "-g", "-Xcc", "-g" + ]) + #elseif os(Linux) XCTAssertMatch(exe, [ "-target", "\(defaultTargetTriple)", "-swift-version", "5", "-enable-batch-mode", "-Onone", "-enable-testing", .equal(j), @@ -1641,8 +1665,9 @@ final class BuildPlanTests: XCTestCase { "-Xcc", "-I", "-Xcc", "/Pkg/Sources/lib/include", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, - "-parseable-output", "-g", "-Xcc", "-g" + "-static-stdlib", "-parseable-output", "-g", "-Xcc", "-g" ]) + #endif let swiftPartOfLib = try result.target(for: "lib").mixedTarget().swiftTargetBuildDescription.compileArguments() XCTAssertMatch(swiftPartOfLib, [ From f415d897afc9816238e6ea547ec5c41d11f28082 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Aug 2023 18:12:14 -0400 Subject: [PATCH 156/178] Fix CI failures --- Tests/BuildTests/BuildPlanTests.swift | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index f9d15b4761a..dabe7d0e76e 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1692,7 +1692,7 @@ final class BuildPlanTests: XCTestCase { "-fmodules-cache-path=/path/to/build/debug/ModuleCache", "-g" ]) #elseif os(Linux) - XCTAssertMatch(exe, [ + XCTAssertMatch(clangPartOfLib, [ "-target", "\(defaultTargetTriple)", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", @@ -1736,18 +1736,6 @@ final class BuildPlanTests: XCTestCase { ]) #endif - XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, - "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, - "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", - "-Xlinker", "@loader_path", - "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", - "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", - "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, - "-g" - ]) - testDiagnostics(observability.diagnostics) { result in result.check(diagnostic: .contains("can be downloaded"), severity: .warning) } From a57bd0382a6c043280814d32aa69e167aaff6d31 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 23 Aug 2023 18:12:43 -0400 Subject: [PATCH 157/178] Remove no longer need property --- .../BuildDescription/ClangTargetBuildDescription.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index 3c22fcb4c07..9826e7469fb 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -100,11 +100,6 @@ public final class ClangTargetBuildDescription { /// The filesystem to operate on. private let fileSystem: FileSystem - /// Whether or not the target belongs to a mixed language target. - /// - /// Mixed language targets consist of an underlying Swift and Clang target. - private let isWithinMixedTarget: Bool - /// If this target is a test target. public var isTestTarget: Bool { target.type == .test @@ -135,7 +130,6 @@ public final class ClangTargetBuildDescription { self.buildParameters = buildParameters self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") self.derivedSources = Sources(paths: [], root: tempsPath.appending("DerivedSources")) - self.isWithinMixedTarget = isWithinMixedTarget // We did not use to apply package plugins to C-family targets in prior tools-versions, this preserves the behavior. if toolsVersion >= .v5_9 { From 151e5bd1d61d28ab26a902128afe405a7bfdeb1d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 24 Aug 2023 17:50:33 -0400 Subject: [PATCH 158/178] Fix macOS BuildPlan test --- Tests/BuildTests/BuildPlanTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index dabe7d0e76e..6c4aaae8fa6 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1722,7 +1722,7 @@ final class BuildPlanTests: XCTestCase { "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", defaultTargetTriple, "-framework", "OtherFramework", "-Xlinker", "-add_ast_path", + "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, "-g" ]) From 1c5d5028295876f86658170b0b163ecd3900171d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 24 Aug 2023 18:00:53 -0400 Subject: [PATCH 159/178] Fix linux test failure --- Tests/BuildTests/BuildPlanTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6c4aaae8fa6..3d359a9c6ff 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1555,7 +1555,7 @@ final class BuildPlanTests: XCTestCase { "-g" ]) #elseif os(Linux) - XCTAssertMatch(exe, [ + XCTAssertMatch(clangPartOfLib, [ "-target", "\(defaultTargetTriple)", "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", "/Pkg/Sources/lib/include", "-I", From b96629daa6036d51a1b66728dd78057fa1252052 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 25 Aug 2023 16:09:33 -0400 Subject: [PATCH 160/178] Fix Linux failure --- Tests/BuildTests/BuildPlanTests.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 3d359a9c6ff..cd7ae466dfe 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1736,9 +1736,13 @@ final class BuildPlanTests: XCTestCase { ]) #endif - testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: .contains("can be downloaded"), severity: .warning) - } + #if os(macOS) + testDiagnostics(observability.diagnostics) { result in + result.check(diagnostic: .contains("can be downloaded"), severity: .warning) + } + #else + XCTAssertNoDiagnostics(observability.diagnostics) + #endif } func testSwiftCMixed() throws { From e58fab9e7049af1d166a83ff4a247188ca25b983 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 25 Aug 2023 17:09:48 -0400 Subject: [PATCH 161/178] =?UTF-8?q?Review=20=E2=80=93=20Add=20ending=20new?= =?UTF-8?q?lines=20where=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp | 3 ++- .../Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp | 3 ++- .../Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm | 3 ++- .../XYZCxxFactorialFinder.hpp | 3 ++- .../MixedTargetWithCustomModuleMap.h | 3 ++- .../MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift | 3 ++- .../ObjcMixedTargetWithCXXPublicAPITests.mm | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp index e5f863d4d22..9e5ab1803ca 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXFactorialFinder.hpp @@ -6,4 +6,5 @@ class CXXFactorialFinder long factorial(int n); }; -#endif // __cplusplus \ No newline at end of file +#endif // __cplusplus + diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp index 2dca74255ec..24f3061cc6b 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/CXXSumFinder.cpp @@ -2,4 +2,5 @@ long CXXSumFinder::sum(int x, int y) { return x + y; -} \ No newline at end of file +} + diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm index fa79b95307a..51abb927965 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPI/ObjcCalculator.mm @@ -18,4 +18,5 @@ + (long)sumX:(int)x andY:(int)y { return sf.sum(x, y); } -@end \ No newline at end of file +@end + diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp index 71bec8bc8e4..4e615bfcab3 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCXXPublicAPIAndCustomModuleMap/XYZCxxFactorialFinder.hpp @@ -6,4 +6,5 @@ class XYZCxxFactorialFinder long factorial(int n); }; -#endif // __cplusplus \ No newline at end of file +#endif // __cplusplus + diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h index 09a635d7856..d2bd3472ff2 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithCustomModuleMap/include/MixedTargetWithCustomModuleMap/MixedTargetWithCustomModuleMap.h @@ -1 +1,2 @@ -#import "../MixedTarget.h" \ No newline at end of file +#import "../MixedTarget.h" + diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift index 4ad0a39918a..4288a78c2e6 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Sources/MixedTargetWithNoPublicObjectiveCHeaders/BarBaz.swift @@ -13,4 +13,5 @@ public struct Baz { public init(bar: Bar? = nil) { self.bar = bar } -} \ No newline at end of file +} + diff --git a/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm index c8113f2f951..13d02e36817 100644 --- a/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm +++ b/Fixtures/MixedTargets/MixedTargetsWithObjC/Tests/MixedTargetWithCXXPublicAPITests/ObjcMixedTargetWithCXXPublicAPITests.mm @@ -27,4 +27,5 @@ - (void)testPublicCXXAPI { XCTAssertEqual(sf.sum(1,2), 3); } -@end \ No newline at end of file +@end + From 2fb3e7a5838fe1d2512c3d31c3ef2784930460ba Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 29 Aug 2023 16:12:40 -0400 Subject: [PATCH 162/178] Fix Linux failure (2) --- Tests/BuildTests/BuildPlanTests.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index cd7ae466dfe..9a1f417e4ad 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1600,9 +1600,13 @@ final class BuildPlanTests: XCTestCase { ]) #endif - testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: .contains("can be downloaded"), severity: .warning) - } + #if os(macOS) + testDiagnostics(observability.diagnostics) { result in + result.check(diagnostic: .contains("can be downloaded"), severity: .warning) + } + #else + XCTAssertNoDiagnostics(observability.diagnostics) + #endif } func testBasicMixedLanguagesWithCustomModuleMap() throws { From 4ddb0aeb8e5582c459e5dd166e61ae0f1cb3f727 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 29 Aug 2023 16:21:27 -0400 Subject: [PATCH 163/178] Remove out-of-scope test --- Tests/FunctionalTests/MixedTargetTests.swift | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 6a1b0770289..f4fb4a144ac 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -256,24 +256,6 @@ extension MixedTargetTests { } } - // TODO(ncooke3): Use a different target to test this as the target below - // has been deleted. - func SKIP_testNonPublicHeadersAreNotVisibleFromOutsideOfTarget() throws { - try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in - // The test target tries to access non-public headers so the build - // should fail. - XCTAssertBuildFails( - fixturePath, - extraArgs: ["--target", "MixedTargetWithNonPublicHeadersTests"], - // Without selectively enabling the tests with the below macro, - // the intentional build failure will break other unit tests - // since all targets in the package are build when running - // `swift test`. - Xswiftc: ["EXPECT_FAILURE"] - ) - } - } - func testMixedTargetWithCustomPaths() throws { try fixture(name: "MixedTargets/MixedTargetsWithObjC") { fixturePath in XCTAssertBuilds( From 373c8ee2ac38844fbbbb83607d39fa0aed5f8331 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 29 Aug 2023 16:24:54 -0400 Subject: [PATCH 164/178] Resolve outstanding plugin TODO --- .../BuildDescription/MixedTargetBuildDescription.swift | 6 ++++++ Sources/Build/BuildDescription/TargetBuildDescription.swift | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index e08b9d315b0..c6439d65b94 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -60,6 +60,9 @@ public final class MixedTargetBuildDescription { self.swiftTargetBuildDescription.libraryBinaryPaths .union(self.clangTargetBuildDescription.libraryBinaryPaths) } + + /// The results of applying any build tool plugins to this target. + let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] /// The build description for the Clang sources. let clangTargetBuildDescription: ClangTargetBuildDescription @@ -83,6 +86,7 @@ public final class MixedTargetBuildDescription { } self.target = target + self.buildToolPluginInvocationResults = buildToolPluginInvocationResults let clangResolvedTarget = ResolvedTarget( target: mixedTarget.clangTarget, @@ -94,6 +98,8 @@ public final class MixedTargetBuildDescription { target: clangResolvedTarget, toolsVersion: toolsVersion, buildParameters: buildParameters, + buildToolPluginInvocationResults: buildToolPluginInvocationResults, + prebuildCommandResults: prebuildCommandResults, fileSystem: fileSystem, isWithinMixedTarget: true, observabilityScope: observabilityScope diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index 5f0d62f578e..781b803605e 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -104,9 +104,8 @@ public enum TargetBuildDescription { return target.buildToolPluginInvocationResults case .clang(let target): return target.buildToolPluginInvocationResults - // TODO(ncooke3): How should we handle this for mixed-lang targets? - case .mixed: - return [] + case .mixed(let target): + return target.buildToolPluginInvocationResults } } } From 8e74b5cba4cc38ac7d42b4f58d7ae82b37cff61f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 29 Aug 2023 16:47:32 -0400 Subject: [PATCH 165/178] Resolve infeasible TODO - Cannot do what the TODO suggested because the filename isn't customizable for the module map that is created by the ClangTargetBuildDescription. --- Sources/Build/BuildDescription/MixedTargetBuildDescription.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index c6439d65b94..097eac5f294 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -216,7 +216,6 @@ public final class MixedTargetBuildDescription { let generatedModuleMapType = mixedTarget.clangTarget.moduleMapType.generatedModuleMapType let unextendedModuleMapPath = tempsPath.appending(component: unextendedModuleMapFilename) - // TODO(ncooke3): We might be able to shift this to ClangTargetBuildDescription. try moduleMapGenerator.generateModuleMap( type: generatedModuleMapType, at: unextendedModuleMapPath From adc348d918f13dc42afecda04ddca9ccf35dce89 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 2 Dec 2023 16:54:27 -0500 Subject: [PATCH 166/178] Anticipate #6972's file deletions --- Sources/Build/{ => BuildPlan}/BuildPlan.swift | 0 Sources/PackageModel/{ => Target}/Target.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Sources/Build/{ => BuildPlan}/BuildPlan.swift (100%) rename Sources/PackageModel/{ => Target}/Target.swift (100%) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift similarity index 100% rename from Sources/Build/BuildPlan.swift rename to Sources/Build/BuildPlan/BuildPlan.swift diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target/Target.swift similarity index 100% rename from Sources/PackageModel/Target.swift rename to Sources/PackageModel/Target/Target.swift From 4e967abe0309b5cfc844d586c2bf5a39dabf2576 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sat, 2 Dec 2023 17:10:58 -0500 Subject: [PATCH 167/178] Anticipate #6972's file deletions (2) --- .../PackageModel/Target/BinaryTarget.swift | 138 ++++ Sources/PackageModel/Target/ClangTarget.swift | 110 +++ Sources/PackageModel/Target/MixedTarget.swift | 135 ++++ .../PackageModel/Target/PluginTarget.swift | 190 +++++ Sources/PackageModel/Target/SwiftTarget.swift | 133 ++++ .../Target/SystemLibraryTarget.swift | 70 ++ Sources/PackageModel/Target/Target.swift | 700 ------------------ 7 files changed, 776 insertions(+), 700 deletions(-) create mode 100644 Sources/PackageModel/Target/BinaryTarget.swift create mode 100644 Sources/PackageModel/Target/ClangTarget.swift create mode 100644 Sources/PackageModel/Target/MixedTarget.swift create mode 100644 Sources/PackageModel/Target/PluginTarget.swift create mode 100644 Sources/PackageModel/Target/SwiftTarget.swift create mode 100644 Sources/PackageModel/Target/SystemLibraryTarget.swift diff --git a/Sources/PackageModel/Target/BinaryTarget.swift b/Sources/PackageModel/Target/BinaryTarget.swift new file mode 100644 index 00000000000..cf61289be2b --- /dev/null +++ b/Sources/PackageModel/Target/BinaryTarget.swift @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath + +public final class BinaryTarget: Target { + /// The kind of binary artifact. + public let kind: Kind + + /// The original source of the binary artifact. + public let origin: Origin + + /// The binary artifact path. + public var artifactPath: AbsolutePath { + return self.sources.root + } + + public init( + name: String, + kind: Kind, + path: AbsolutePath, + origin: Origin + ) { + self.origin = origin + self.kind = kind + let sources = Sources(paths: [], root: path) + super.init( + name: name, + type: .binary, + path: .root, + sources: sources, + dependencies: [], + packageAccess: false, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case kind + case origin + case artifactSource // backwards compatibility 2/2021 + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.origin, forKey: .origin) + try container.encode(self.kind, forKey: .kind) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + // backwards compatibility 2/2021 + if !container.contains(.kind) { + self.kind = .xcframework + } else { + self.kind = try container.decode(Kind.self, forKey: .kind) + } + // backwards compatibility 2/2021 + if container.contains(.artifactSource) { + self.origin = try container.decode(Origin.self, forKey: .artifactSource) + } else { + self.origin = try container.decode(Origin.self, forKey: .origin) + } + try super.init(from: decoder) + } + + public enum Kind: String, RawRepresentable, Codable, CaseIterable { + case xcframework + case artifactsArchive + case unknown // for non-downloaded artifacts + + public var fileExtension: String { + switch self { + case .xcframework: + return "xcframework" + case .artifactsArchive: + return "artifactbundle" + case .unknown: + return "unknown" + } + } + } + + public var containsExecutable: Bool { + // FIXME: needs to be revisited once libraries are supported in artifact bundles + return self.kind == .artifactsArchive + } + + public enum Origin: Equatable, Codable { + + /// Represents an artifact that was downloaded from a remote URL. + case remote(url: String) + + /// Represents an artifact that was available locally. + case local + + private enum CodingKeys: String, CodingKey { + case remote, local + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .remote(let a1): + var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .remote) + try unkeyedContainer.encode(a1) + case .local: + try container.encodeNil(forKey: .local) + } + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + guard let key = values.allKeys.first(where: values.contains) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) + } + switch key { + case .remote: + var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) + let a1 = try unkeyedValues.decode(String.self) + self = .remote(url: a1) + case .local: + self = .local + } + } + } +} diff --git a/Sources/PackageModel/Target/ClangTarget.swift b/Sources/PackageModel/Target/ClangTarget.swift new file mode 100644 index 00000000000..44e1ebf438c --- /dev/null +++ b/Sources/PackageModel/Target/ClangTarget.swift @@ -0,0 +1,110 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.StringError + +public final class ClangTarget: Target { + + /// The default public include directory component. + public static let defaultPublicHeadersComponent = "include" + + /// The path to include directory. + public let includeDir: AbsolutePath + + /// The target's module map type, which determines whether this target vends a custom module map, a generated module map, or no module map at all. + public let moduleMapType: ModuleMapType + + /// The headers present in the target. + /// + /// Note that this contains both public and non-public headers. + public let headers: [AbsolutePath] + + /// True if this is a C++ target. + public let isCXX: Bool + + /// The C language standard flag. + public let cLanguageStandard: String? + + /// The C++ language standard flag. + public let cxxLanguageStandard: String? + + public init( + name: String, + potentialBundleName: String? = nil, + cLanguageStandard: String?, + cxxLanguageStandard: String?, + includeDir: AbsolutePath, + moduleMapType: ModuleMapType, + headers: [AbsolutePath] = [], + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency] = [], + buildSettings: BuildSettings.AssignmentTable = .init(), + usesUnsafeFlags: Bool + ) throws { + guard includeDir.isDescendantOfOrEqual(to: sources.root) else { + throw StringError("\(includeDir) should be contained in the source root \(sources.root)") + } + self.isCXX = sources.containsCXXFiles + self.cLanguageStandard = cLanguageStandard + self.cxxLanguageStandard = cxxLanguageStandard + self.includeDir = includeDir + self.moduleMapType = moduleMapType + self.headers = headers + super.init( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + packageAccess: false, + buildSettings: buildSettings, + pluginUsages: [], + usesUnsafeFlags: usesUnsafeFlags + ) + } + + private enum CodingKeys: String, CodingKey { + case includeDir, moduleMapType, headers, isCXX, cLanguageStandard, cxxLanguageStandard + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(includeDir, forKey: .includeDir) + try container.encode(moduleMapType, forKey: .moduleMapType) + try container.encode(headers, forKey: .headers) + try container.encode(isCXX, forKey: .isCXX) + try container.encode(cLanguageStandard, forKey: .cLanguageStandard) + try container.encode(cxxLanguageStandard, forKey: .cxxLanguageStandard) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.includeDir = try container.decode(AbsolutePath.self, forKey: .includeDir) + self.moduleMapType = try container.decode(ModuleMapType.self, forKey: .moduleMapType) + self.headers = try container.decode([AbsolutePath].self, forKey: .headers) + self.isCXX = try container.decode(Bool.self, forKey: .isCXX) + self.cLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cLanguageStandard) + self.cxxLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cxxLanguageStandard) + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/MixedTarget.swift b/Sources/PackageModel/Target/MixedTarget.swift new file mode 100644 index 00000000000..df5f6470d6a --- /dev/null +++ b/Sources/PackageModel/Target/MixedTarget.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 +// +//===----------------------------------------------------------------------===// + +public final class MixedTarget: Target { + + // The Clang target for the mixed target's Clang sources. + public let clangTarget: ClangTarget + + // The Swift target for the mixed target's Swift sources. + public let swiftTarget: SwiftTarget + + public init( + name: String, + potentialBundleName: String? = nil, + cLanguageStandard: String?, + cxxLanguageStandard: String?, + includeDir: AbsolutePath, + moduleMapType: ModuleMapType, + headers: [AbsolutePath] = [], + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency] = [], + packageAccess: Bool, + swiftVersion: SwiftLanguageVersion, + buildSettings: BuildSettings.AssignmentTable = .init(), + pluginUsages: [PluginUsage] = [], + usesUnsafeFlags: Bool + ) throws { + guard type == .library || type == .test else { + throw StringError( + "Target with mixed sources at '\(path)' is a \(type) " + + "target; targets with mixed language sources are only " + + "supported for library and test targets." + ) + } + + let swiftSources = Sources( + paths: sources.paths.filter { path in + guard let ext = path.extension else { return false } + return SupportedLanguageExtension.swiftExtensions.contains(ext) + }, + root: sources.root + ) + + self.swiftTarget = SwiftTarget( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: swiftSources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + packageAccess: packageAccess, + swiftVersion: swiftVersion, + buildSettings: buildSettings, + pluginUsages: pluginUsages, + usesUnsafeFlags: usesUnsafeFlags + ) + + let clangSources = Sources( + paths: sources.paths.filter { path in + guard let ext = path.extension else { return false } + return SupportedLanguageExtension.clangTargetExtensions(toolsVersion: .current).contains(ext) + }, + root: sources.root + ) + + self.clangTarget = try ClangTarget( + name: name, + potentialBundleName: potentialBundleName, + cLanguageStandard: cLanguageStandard, + cxxLanguageStandard: cxxLanguageStandard, + includeDir: includeDir, + moduleMapType: moduleMapType, + headers: headers, + type: type, + path: path, + sources: clangSources, + resources: resources, + ignored: ignored, + others: others, + buildSettings: buildSettings, + usesUnsafeFlags: usesUnsafeFlags + ) + + super.init( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: buildSettings, + pluginUsages: pluginUsages, + usesUnsafeFlags: usesUnsafeFlags + ) + } + + private enum CodingKeys: String, CodingKey { + case clangTarget, swiftTarget + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(clangTarget, forKey: .clangTarget) + try container.encode(swiftTarget, forKey: .swiftTarget) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.clangTarget = try container.decode(ClangTarget.self, forKey: .clangTarget) + self.swiftTarget = try container.decode(SwiftTarget.self, forKey: .swiftTarget) + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/PluginTarget.swift b/Sources/PackageModel/Target/PluginTarget.swift new file mode 100644 index 00000000000..1576c617f7d --- /dev/null +++ b/Sources/PackageModel/Target/PluginTarget.swift @@ -0,0 +1,190 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 +// +//===----------------------------------------------------------------------===// + +public final class PluginTarget: Target { + + /// Declared capability of the plugin. + public let capability: PluginCapability + + /// API version to use for PackagePlugin API availability. + public let apiVersion: ToolsVersion + + public init( + name: String, + sources: Sources, + apiVersion: ToolsVersion, + pluginCapability: PluginCapability, + dependencies: [Target.Dependency] = [], + packageAccess: Bool + ) { + self.capability = pluginCapability + self.apiVersion = apiVersion + super.init( + name: name, + type: .plugin, + path: .root, + sources: sources, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case capability + case apiVersion + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.capability, forKey: .capability) + try container.encode(self.apiVersion, forKey: .apiVersion) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.capability = try container.decode(PluginCapability.self, forKey: .capability) + self.apiVersion = try container.decode(ToolsVersion.self, forKey: .apiVersion) + try super.init(from: decoder) + } +} + +public enum PluginCapability: Hashable, Codable { + case buildTool + case command(intent: PluginCommandIntent, permissions: [PluginPermission]) + + private enum CodingKeys: String, CodingKey { + case buildTool, command + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .buildTool: + try container.encodeNil(forKey: .buildTool) + case .command(let a1, let a2): + var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command) + try unkeyedContainer.encode(a1) + try unkeyedContainer.encode(a2) + } + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + guard let key = values.allKeys.first(where: values.contains) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) + } + switch key { + case .buildTool: + self = .buildTool + case .command: + var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) + let a1 = try unkeyedValues.decode(PluginCommandIntent.self) + let a2 = try unkeyedValues.decode([PluginPermission].self) + self = .command(intent: a1, permissions: a2) + } + } + + public init(from desc: TargetDescription.PluginCapability) { + switch desc { + case .buildTool: + self = .buildTool + case .command(let intent, let permissions): + self = .command(intent: .init(from: intent), permissions: permissions.map{ .init(from: $0) }) + } + } +} + +public enum PluginCommandIntent: Hashable, Codable { + case documentationGeneration + case sourceCodeFormatting + case custom(verb: String, description: String) + + public init(from desc: TargetDescription.PluginCommandIntent) { + switch desc { + case .documentationGeneration: + self = .documentationGeneration + case .sourceCodeFormatting: + self = .sourceCodeFormatting + case .custom(let verb, let description): + self = .custom(verb: verb, description: description) + } + } +} + +public enum PluginNetworkPermissionScope: Hashable, Codable { + case none + case local(ports: [Int]) + case all(ports: [Int]) + case docker + case unixDomainSocket + + init(_ scope: TargetDescription.PluginNetworkPermissionScope) { + switch scope { + case .none: self = .none + case .local(let ports): self = .local(ports: ports) + case .all(let ports): self = .all(ports: ports) + case .docker: self = .docker + case .unixDomainSocket: self = .unixDomainSocket + } + } + + public var label: String { + switch self { + case .all: return "all" + case .local: return "local" + case .none: return "none" + case .docker: return "docker unix domain socket" + case .unixDomainSocket: return "unix domain socket" + } + } + + public var ports: [Int] { + switch self { + case .all(let ports): return ports + case .local(let ports): return ports + case .none, .docker, .unixDomainSocket: return [] + } + } +} + +public enum PluginPermission: Hashable, Codable { + case allowNetworkConnections(scope: PluginNetworkPermissionScope, reason: String) + case writeToPackageDirectory(reason: String) + + public init(from desc: TargetDescription.PluginPermission) { + switch desc { + case .allowNetworkConnections(let scope, let reason): + self = .allowNetworkConnections(scope: .init(scope), reason: reason) + case .writeToPackageDirectory(let reason): + self = .writeToPackageDirectory(reason: reason) + } + } +} + +public extension Sequence where Iterator.Element == Target { + var executables: [Target] { + return filter { + switch $0.type { + case .binary: + return ($0 as? BinaryTarget)?.containsExecutable == true + case .executable, .snippet, .macro: + return true + default: + return false + } + } + } +} diff --git a/Sources/PackageModel/Target/SwiftTarget.swift b/Sources/PackageModel/Target/SwiftTarget.swift new file mode 100644 index 00000000000..227d98b79c3 --- /dev/null +++ b/Sources/PackageModel/Target/SwiftTarget.swift @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.SwiftVersion + +public final class SwiftTarget: Target { + + /// The default name for the test entry point file located in a package. + public static let defaultTestEntryPointName = "XCTMain.swift" + + /// The list of all supported names for the test entry point file located in a package. + public static var testEntryPointNames: [String] { + [defaultTestEntryPointName, "LinuxMain.swift"] + } + + public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { + self.swiftVersion = .v5 + + super.init( + name: name, + type: .library, + path: .root, + sources: testDiscoverySrc, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + /// The swift version of this target. + public let swiftVersion: SwiftLanguageVersion + + public init( + name: String, + potentialBundleName: String? = nil, + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency] = [], + packageAccess: Bool, + swiftVersion: SwiftLanguageVersion, + buildSettings: BuildSettings.AssignmentTable = .init(), + pluginUsages: [PluginUsage] = [], + usesUnsafeFlags: Bool + ) { + self.swiftVersion = swiftVersion + super.init( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: buildSettings, + pluginUsages: pluginUsages, + usesUnsafeFlags: usesUnsafeFlags + ) + } + + /// Create an executable Swift target from test entry point file. + public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testEntryPointPath: AbsolutePath) { + // Look for the first swift test target and use the same swift version + // for linux main target. This will need to change if we move to a model + // where we allow per target swift language version build settings. + let swiftTestTarget = dependencies.first { + guard case .target(let target as SwiftTarget, _) = $0 else { return false } + return target.type == .test + }.flatMap { $0.target as? SwiftTarget } + + // FIXME: This is not very correct but doesn't matter much in practice. + // We need to select the latest Swift language version that can + // satisfy the current tools version but there is not a good way to + // do that currently. + self.swiftVersion = swiftTestTarget?.swiftVersion ?? SwiftLanguageVersion(string: String(SwiftVersion.current.major)) ?? .v4 + let sources = Sources(paths: [testEntryPointPath], root: testEntryPointPath.parentDirectory) + + super.init( + name: name, + type: .executable, + path: .root, + sources: sources, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case swiftVersion + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(swiftVersion, forKey: .swiftVersion) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.swiftVersion = try container.decode(SwiftLanguageVersion.self, forKey: .swiftVersion) + try super.init(from: decoder) + } + + public var supportsTestableExecutablesFeature: Bool { + // Exclude macros from testable executables if they are built as dylibs. + #if BUILD_MACROS_AS_DYLIBS + return type == .executable || type == .snippet + #else + return type == .executable || type == .macro || type == .snippet + #endif + } +} diff --git a/Sources/PackageModel/Target/SystemLibraryTarget.swift b/Sources/PackageModel/Target/SystemLibraryTarget.swift new file mode 100644 index 00000000000..beb99b670e5 --- /dev/null +++ b/Sources/PackageModel/Target/SystemLibraryTarget.swift @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath + +public final class SystemLibraryTarget: Target { + + /// The name of pkgConfig file, if any. + public let pkgConfig: String? + + /// List of system package providers, if any. + public let providers: [SystemPackageProviderDescription]? + + /// True if this system library should become implicit target + /// dependency of its dependent packages. + public let isImplicit: Bool + + public init( + name: String, + path: AbsolutePath, + isImplicit: Bool = true, + pkgConfig: String? = nil, + providers: [SystemPackageProviderDescription]? = nil + ) { + let sources = Sources(paths: [], root: path) + self.pkgConfig = pkgConfig + self.providers = providers + self.isImplicit = isImplicit + super.init( + name: name, + type: .systemModule, + path: sources.root, + sources: sources, + dependencies: [], + packageAccess: false, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case pkgConfig, providers, isImplicit + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(pkgConfig, forKey: .pkgConfig) + try container.encode(providers, forKey: .providers) + try container.encode(isImplicit, forKey: .isImplicit) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.pkgConfig = try container.decodeIfPresent(String.self, forKey: .pkgConfig) + self.providers = try container.decodeIfPresent([SystemPackageProviderDescription].self, forKey: .providers) + self.isImplicit = try container.decode(Bool.self, forKey: .isImplicit) + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift index 0f5400fd12b..83f1b66303f 100644 --- a/Sources/PackageModel/Target/Target.swift +++ b/Sources/PackageModel/Target/Target.swift @@ -334,703 +334,3 @@ extension Target: CustomStringConvertible { return "<\(Swift.type(of: self)): \(name)>" } } - -public final class SwiftTarget: Target { - - /// The default name for the test entry point file located in a package. - public static let defaultTestEntryPointName = "XCTMain.swift" - - /// The list of all supported names for the test entry point file located in a package. - public static var testEntryPointNames: [String] { - [defaultTestEntryPointName, "LinuxMain.swift"] - } - - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { - self.swiftVersion = .v5 - - super.init( - name: name, - type: .library, - path: .root, - sources: testDiscoverySrc, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - /// The swift version of this target. - public let swiftVersion: SwiftLanguageVersion - - public init( - name: String, - potentialBundleName: String? = nil, - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - packageAccess: Bool, - swiftVersion: SwiftLanguageVersion, - buildSettings: BuildSettings.AssignmentTable = .init(), - pluginUsages: [PluginUsage] = [], - usesUnsafeFlags: Bool - ) { - self.swiftVersion = swiftVersion - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: buildSettings, - pluginUsages: pluginUsages, - usesUnsafeFlags: usesUnsafeFlags - ) - } - - /// Create an executable Swift target from test entry point file. - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testEntryPointPath: AbsolutePath) { - // Look for the first swift test target and use the same swift version - // for linux main target. This will need to change if we move to a model - // where we allow per target swift language version build settings. - let swiftTestTarget = dependencies.first { - guard case .target(let target as SwiftTarget, _) = $0 else { return false } - return target.type == .test - }.flatMap { $0.target as? SwiftTarget } - - // FIXME: This is not very correct but doesn't matter much in practice. - // We need to select the latest Swift language version that can - // satisfy the current tools version but there is not a good way to - // do that currently. - self.swiftVersion = swiftTestTarget?.swiftVersion ?? SwiftLanguageVersion(string: String(SwiftVersion.current.major)) ?? .v4 - let sources = Sources(paths: [testEntryPointPath], root: testEntryPointPath.parentDirectory) - - super.init( - name: name, - type: .executable, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case swiftVersion - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(swiftVersion, forKey: .swiftVersion) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.swiftVersion = try container.decode(SwiftLanguageVersion.self, forKey: .swiftVersion) - try super.init(from: decoder) - } - - public var supportsTestableExecutablesFeature: Bool { - // Exclude macros from testable executables if they are built as dylibs. - #if BUILD_MACROS_AS_DYLIBS - return type == .executable || type == .snippet - #else - return type == .executable || type == .macro || type == .snippet - #endif - } -} - -public final class SystemLibraryTarget: Target { - - /// The name of pkgConfig file, if any. - public let pkgConfig: String? - - /// List of system package providers, if any. - public let providers: [SystemPackageProviderDescription]? - - /// True if this system library should become implicit target - /// dependency of its dependent packages. - public let isImplicit: Bool - - public init( - name: String, - path: AbsolutePath, - isImplicit: Bool = true, - pkgConfig: String? = nil, - providers: [SystemPackageProviderDescription]? = nil - ) { - let sources = Sources(paths: [], root: path) - self.pkgConfig = pkgConfig - self.providers = providers - self.isImplicit = isImplicit - super.init( - name: name, - type: .systemModule, - path: sources.root, - sources: sources, - dependencies: [], - packageAccess: false, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case pkgConfig, providers, isImplicit - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(pkgConfig, forKey: .pkgConfig) - try container.encode(providers, forKey: .providers) - try container.encode(isImplicit, forKey: .isImplicit) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.pkgConfig = try container.decodeIfPresent(String.self, forKey: .pkgConfig) - self.providers = try container.decodeIfPresent([SystemPackageProviderDescription].self, forKey: .providers) - self.isImplicit = try container.decode(Bool.self, forKey: .isImplicit) - try super.init(from: decoder) - } -} - -public final class ClangTarget: Target { - - /// The default public include directory component. - public static let defaultPublicHeadersComponent = "include" - - /// The path to include directory. - public let includeDir: AbsolutePath - - /// The target's module map type, which determines whether this target vends a custom module map, a generated module map, or no module map at all. - public let moduleMapType: ModuleMapType - - /// The headers present in the target. - /// - /// Note that this contains both public and non-public headers. - public let headers: [AbsolutePath] - - /// True if this is a C++ target. - public let isCXX: Bool - - /// The C language standard flag. - public let cLanguageStandard: String? - - /// The C++ language standard flag. - public let cxxLanguageStandard: String? - - public init( - name: String, - potentialBundleName: String? = nil, - cLanguageStandard: String?, - cxxLanguageStandard: String?, - includeDir: AbsolutePath, - moduleMapType: ModuleMapType, - headers: [AbsolutePath] = [], - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - buildSettings: BuildSettings.AssignmentTable = .init(), - usesUnsafeFlags: Bool - ) throws { - guard includeDir.isDescendantOfOrEqual(to: sources.root) else { - throw StringError("\(includeDir) should be contained in the source root \(sources.root)") - } - self.isCXX = sources.containsCXXFiles - self.cLanguageStandard = cLanguageStandard - self.cxxLanguageStandard = cxxLanguageStandard - self.includeDir = includeDir - self.moduleMapType = moduleMapType - self.headers = headers - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: false, - buildSettings: buildSettings, - pluginUsages: [], - usesUnsafeFlags: usesUnsafeFlags - ) - } - - private enum CodingKeys: String, CodingKey { - case includeDir, moduleMapType, headers, isCXX, cLanguageStandard, cxxLanguageStandard - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(includeDir, forKey: .includeDir) - try container.encode(moduleMapType, forKey: .moduleMapType) - try container.encode(headers, forKey: .headers) - try container.encode(isCXX, forKey: .isCXX) - try container.encode(cLanguageStandard, forKey: .cLanguageStandard) - try container.encode(cxxLanguageStandard, forKey: .cxxLanguageStandard) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.includeDir = try container.decode(AbsolutePath.self, forKey: .includeDir) - self.moduleMapType = try container.decode(ModuleMapType.self, forKey: .moduleMapType) - self.headers = try container.decode([AbsolutePath].self, forKey: .headers) - self.isCXX = try container.decode(Bool.self, forKey: .isCXX) - self.cLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cLanguageStandard) - self.cxxLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cxxLanguageStandard) - try super.init(from: decoder) - } -} - -public final class MixedTarget: Target { - - // The Clang target for the mixed target's Clang sources. - public let clangTarget: ClangTarget - - // The Swift target for the mixed target's Swift sources. - public let swiftTarget: SwiftTarget - - public init( - name: String, - potentialBundleName: String? = nil, - cLanguageStandard: String?, - cxxLanguageStandard: String?, - includeDir: AbsolutePath, - moduleMapType: ModuleMapType, - headers: [AbsolutePath] = [], - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - packageAccess: Bool, - swiftVersion: SwiftLanguageVersion, - buildSettings: BuildSettings.AssignmentTable = .init(), - pluginUsages: [PluginUsage] = [], - usesUnsafeFlags: Bool - ) throws { - guard type == .library || type == .test else { - throw StringError( - "Target with mixed sources at '\(path)' is a \(type) " + - "target; targets with mixed language sources are only " + - "supported for library and test targets." - ) - } - - let swiftSources = Sources( - paths: sources.paths.filter { path in - guard let ext = path.extension else { return false } - return SupportedLanguageExtension.swiftExtensions.contains(ext) - }, - root: sources.root - ) - - self.swiftTarget = SwiftTarget( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: swiftSources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: packageAccess, - swiftVersion: swiftVersion, - buildSettings: buildSettings, - pluginUsages: pluginUsages, - usesUnsafeFlags: usesUnsafeFlags - ) - - let clangSources = Sources( - paths: sources.paths.filter { path in - guard let ext = path.extension else { return false } - return SupportedLanguageExtension.clangTargetExtensions(toolsVersion: .current).contains(ext) - }, - root: sources.root - ) - - self.clangTarget = try ClangTarget( - name: name, - potentialBundleName: potentialBundleName, - cLanguageStandard: cLanguageStandard, - cxxLanguageStandard: cxxLanguageStandard, - includeDir: includeDir, - moduleMapType: moduleMapType, - headers: headers, - type: type, - path: path, - sources: clangSources, - resources: resources, - ignored: ignored, - others: others, - buildSettings: buildSettings, - usesUnsafeFlags: usesUnsafeFlags - ) - - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: buildSettings, - pluginUsages: pluginUsages, - usesUnsafeFlags: usesUnsafeFlags - ) - } - - private enum CodingKeys: String, CodingKey { - case clangTarget, swiftTarget - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(clangTarget, forKey: .clangTarget) - try container.encode(swiftTarget, forKey: .swiftTarget) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.clangTarget = try container.decode(ClangTarget.self, forKey: .clangTarget) - self.swiftTarget = try container.decode(SwiftTarget.self, forKey: .swiftTarget) - try super.init(from: decoder) - } -} - -public final class BinaryTarget: Target { - /// The kind of binary artifact. - public let kind: Kind - - /// The original source of the binary artifact. - public let origin: Origin - - /// The binary artifact path. - public var artifactPath: AbsolutePath { - return self.sources.root - } - - public init( - name: String, - kind: Kind, - path: AbsolutePath, - origin: Origin - ) { - self.origin = origin - self.kind = kind - let sources = Sources(paths: [], root: path) - super.init( - name: name, - type: .binary, - path: .root, - sources: sources, - dependencies: [], - packageAccess: false, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case kind - case origin - case artifactSource // backwards compatibility 2/2021 - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.origin, forKey: .origin) - try container.encode(self.kind, forKey: .kind) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - // backwards compatibility 2/2021 - if !container.contains(.kind) { - self.kind = .xcframework - } else { - self.kind = try container.decode(Kind.self, forKey: .kind) - } - // backwards compatibility 2/2021 - if container.contains(.artifactSource) { - self.origin = try container.decode(Origin.self, forKey: .artifactSource) - } else { - self.origin = try container.decode(Origin.self, forKey: .origin) - } - try super.init(from: decoder) - } - - public enum Kind: String, RawRepresentable, Codable, CaseIterable { - case xcframework - case artifactsArchive - case unknown // for non-downloaded artifacts - - public var fileExtension: String { - switch self { - case .xcframework: - return "xcframework" - case .artifactsArchive: - return "artifactbundle" - case .unknown: - return "unknown" - } - } - } - - public var containsExecutable: Bool { - // FIXME: needs to be revisited once libraries are supported in artifact bundles - return self.kind == .artifactsArchive - } - - public enum Origin: Equatable, Codable { - - /// Represents an artifact that was downloaded from a remote URL. - case remote(url: String) - - /// Represents an artifact that was available locally. - case local - - private enum CodingKeys: String, CodingKey { - case remote, local - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .remote(let a1): - var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .remote) - try unkeyedContainer.encode(a1) - case .local: - try container.encodeNil(forKey: .local) - } - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - guard let key = values.allKeys.first(where: values.contains) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) - } - switch key { - case .remote: - var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) - let a1 = try unkeyedValues.decode(String.self) - self = .remote(url: a1) - case .local: - self = .local - } - } - } -} - -public final class PluginTarget: Target { - - /// Declared capability of the plugin. - public let capability: PluginCapability - - /// API version to use for PackagePlugin API availability. - public let apiVersion: ToolsVersion - - public init( - name: String, - sources: Sources, - apiVersion: ToolsVersion, - pluginCapability: PluginCapability, - dependencies: [Target.Dependency] = [], - packageAccess: Bool - ) { - self.capability = pluginCapability - self.apiVersion = apiVersion - super.init( - name: name, - type: .plugin, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case capability - case apiVersion - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.capability, forKey: .capability) - try container.encode(self.apiVersion, forKey: .apiVersion) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.capability = try container.decode(PluginCapability.self, forKey: .capability) - self.apiVersion = try container.decode(ToolsVersion.self, forKey: .apiVersion) - try super.init(from: decoder) - } -} - -public enum PluginCapability: Hashable, Codable { - case buildTool - case command(intent: PluginCommandIntent, permissions: [PluginPermission]) - - private enum CodingKeys: String, CodingKey { - case buildTool, command - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .buildTool: - try container.encodeNil(forKey: .buildTool) - case .command(let a1, let a2): - var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command) - try unkeyedContainer.encode(a1) - try unkeyedContainer.encode(a2) - } - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - guard let key = values.allKeys.first(where: values.contains) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) - } - switch key { - case .buildTool: - self = .buildTool - case .command: - var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) - let a1 = try unkeyedValues.decode(PluginCommandIntent.self) - let a2 = try unkeyedValues.decode([PluginPermission].self) - self = .command(intent: a1, permissions: a2) - } - } - - public init(from desc: TargetDescription.PluginCapability) { - switch desc { - case .buildTool: - self = .buildTool - case .command(let intent, let permissions): - self = .command(intent: .init(from: intent), permissions: permissions.map{ .init(from: $0) }) - } - } -} - -public enum PluginCommandIntent: Hashable, Codable { - case documentationGeneration - case sourceCodeFormatting - case custom(verb: String, description: String) - - public init(from desc: TargetDescription.PluginCommandIntent) { - switch desc { - case .documentationGeneration: - self = .documentationGeneration - case .sourceCodeFormatting: - self = .sourceCodeFormatting - case .custom(let verb, let description): - self = .custom(verb: verb, description: description) - } - } -} - -public enum PluginNetworkPermissionScope: Hashable, Codable { - case none - case local(ports: [Int]) - case all(ports: [Int]) - case docker - case unixDomainSocket - - init(_ scope: TargetDescription.PluginNetworkPermissionScope) { - switch scope { - case .none: self = .none - case .local(let ports): self = .local(ports: ports) - case .all(let ports): self = .all(ports: ports) - case .docker: self = .docker - case .unixDomainSocket: self = .unixDomainSocket - } - } - - public var label: String { - switch self { - case .all: return "all" - case .local: return "local" - case .none: return "none" - case .docker: return "docker unix domain socket" - case .unixDomainSocket: return "unix domain socket" - } - } - - public var ports: [Int] { - switch self { - case .all(let ports): return ports - case .local(let ports): return ports - case .none, .docker, .unixDomainSocket: return [] - } - } -} - -public enum PluginPermission: Hashable, Codable { - case allowNetworkConnections(scope: PluginNetworkPermissionScope, reason: String) - case writeToPackageDirectory(reason: String) - - public init(from desc: TargetDescription.PluginPermission) { - switch desc { - case .allowNetworkConnections(let scope, let reason): - self = .allowNetworkConnections(scope: .init(scope), reason: reason) - case .writeToPackageDirectory(let reason): - self = .writeToPackageDirectory(reason: reason) - } - } -} - -public extension Sequence where Iterator.Element == Target { - var executables: [Target] { - return filter { - switch $0.type { - case .binary: - return ($0 as? BinaryTarget)?.containsExecutable == true - case .executable, .snippet, .macro: - return true - default: - return false - } - } - } -} From ccec9e7a5bad2ca41fd1b3932c9eaf7b2b8845e5 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 5 Dec 2023 22:54:47 -0500 Subject: [PATCH 168/178] Merge branch 'main' into nc/support-mixed-targets --- .gitignore | 1 + .mailmap | 4 +- .../xcschemes/SwiftPM-Package.xcscheme | 45 +- CHANGELOG.md | 72 +- CMakeLists.txt | 21 +- CODEOWNERS | 6 +- .../PackageRegistry/PackageRegistryUsage.md | 12 +- Documentation/PackageRegistry/Registry.md | 4 +- Documentation/README.md | 2 +- Documentation/ReleaseNotes/5.9.md | 31 + Documentation/Usage.md | 27 +- .../Miscellaneous/EmptyTestsPkg/.gitignore | 4 + .../Miscellaneous/EmptyTestsPkg/Package.swift | 14 + .../Sources/EmptyTestsPkg/EmptyTestsPkg.swift | 5 + .../EmptyTestsPkgTests/EmptyTestsTests.swift | 2 + .../InvalidRefs/InvalidBranch/Package.swift | 10 - .../InvalidRefs/InvalidRevision/Package.swift | 10 - .../Dependencies/A/Package.swift | 21 + .../Dependencies/A/Plugins/A.swift | 9 + .../Dependencies/B/Package.swift | 21 + .../Dependencies/B/Plugins/B.swift | 8 + .../Plugins/AmbiguousCommands/Package.swift | 15 + .../AmbiguousCommands/Sources/main.swift | 1 + .../TestDiscovery/SwiftTesting/Package.swift | 18 + .../Sources/SwiftTesting/SwiftTesting.swift | 1 + .../SwiftTestingTests/SwiftTestingTests.swift | 2 + .../TestableAsyncExe/Package.swift | 29 + .../Sources/TestableAsyncExe1/main.swift | 5 + .../Sources/TestableAsyncExe2/main.swift | 5 + .../Sources/TestableAsyncExe3/NonMain.swift | 10 + .../Sources/TestableAsyncExe4/NonMain.swift | 10 + .../TestableAsyncExeTests.swift | 25 + .../SwiftSDKs/test-sdk.artifactbundle.tar.gz | Bin 0 -> 412 bytes .../Tests/IntegrationTests/SwiftPMTests.swift | 2 +- Package.swift | 20 +- README.md | 2 +- Sources/Basics/Archiver/Archiver.swift | 24 +- Sources/Basics/AuthorizationProvider.swift | 38 +- .../Concurrency/ConcurrencyHelpers.swift | 31 + Sources/Basics/Dictionary+Extensions.swift | 14 - Sources/Basics/EnvironmentVariables.swift | 29 +- Sources/Basics/FileSystem/AbsolutePath.swift | 4 +- .../FileSystem/FileSystem+Extensions.swift | 2 +- Sources/Basics/FileSystem/TemporaryFile.swift | 1 + .../HTTPClient/HTTPClientResponse.swift | 6 +- Sources/Basics/ImportScanning.swift | 33 +- Sources/Basics/JSONDecoder+Extensions.swift | 6 +- Sources/Basics/SQLite.swift | 4 + Sources/Basics/SwiftVersion.swift | 6 +- Sources/Basics/Triple+Basics.swift | 38 +- .../ClangTargetBuildDescription.swift | 13 +- .../ProductBuildDescription.swift | 90 +- .../SwiftTargetBuildDescription.swift | 99 +- .../LLBuildManifestBuilder+Clang.swift | 136 + .../LLBuildManifestBuilder+Product.swift | 122 + .../LLBuildManifestBuilder+Resources.swift | 53 + .../LLBuildManifestBuilder+Swift.swift} | 615 +-- .../LLBuildManifestBuilder.swift | 367 ++ Sources/Build/BuildOperation.swift | 22 +- ...dOperationBuildSystemDelegateHandler.swift | 228 +- Sources/Build/BuildPlan/BuildPlan+Clang.swift | 74 + .../Build/BuildPlan/BuildPlan+Product.swift | 263 ++ Sources/Build/BuildPlan/BuildPlan+Swift.swift | 68 + Sources/Build/BuildPlan/BuildPlan+Test.swift | 219 + Sources/Build/BuildPlan/BuildPlan.swift | 596 +-- Sources/Build/CMakeLists.txt | 15 +- Sources/Build/TestObservation.swift | 509 +++ Sources/Commands/CMakeLists.txt | 3 +- .../Commands/PackageTools/ArchiveSource.swift | 3 +- .../PackageTools/ComputeChecksum.swift | 1 + Sources/Commands/PackageTools/Config.swift | 2 +- .../Commands/PackageTools/DumpCommands.swift | 16 +- Sources/Commands/PackageTools/Init.swift | 1 + .../Commands/PackageTools/PluginCommand.swift | 44 +- .../PackageTools/SwiftPackageTool.swift | 5 +- Sources/Commands/PackageTools/Update.swift | 8 - Sources/Commands/SwiftBuildTool.swift | 27 +- Sources/Commands/SwiftTestTool.swift | 667 +-- Sources/Commands/ToolWorkspaceDelegate.swift | 44 +- .../Utilities/DOTManifestSerializer.swift | 4 +- .../Commands/Utilities/PluginDelegate.swift | 11 +- .../Utilities/SymbolGraphExtract.swift | 15 +- .../Commands/Utilities/TestingSupport.swift | 60 +- Sources/Commands/Utilities/XCTEvents.swift | 310 ++ Sources/CoreCommands/BuildSystemSupport.swift | 94 +- Sources/CoreCommands/Options.swift | 14 +- Sources/CoreCommands/SwiftTool.swift | 133 +- .../DriverSupport/DriverSupportUtils.swift | 9 +- Sources/LLBuildManifest/CMakeLists.txt | 4 +- ...ldManifest.swift => LLBuildManifest.swift} | 113 +- .../LLBuildManifestWriter.swift | 292 ++ Sources/LLBuildManifest/ManifestWriter.swift | 177 - Sources/LLBuildManifest/Node.swift | 34 +- Sources/LLBuildManifest/Tools.swift | 36 +- Sources/PackageCollections/API.swift | 218 + .../PackageCollections.swift | 8 +- .../PackageIndexAndCollections.swift | 112 +- .../Providers/PackageCollectionProvider.swift | 12 + .../PackageCollectionsSourcesStorage.swift | 47 + .../Storage/PackageCollectionsStorage.swift | 40 + .../SQLitePackageCollectionsStorage.swift | 4 +- Sources/PackageCollectionsModel/Formats/v1.md | 4 +- .../PackageCollectionModel+v1.swift | 2 +- .../CertificatePolicy.swift | 42 +- .../PackageCollectionSigning.swift | 16 +- .../PackageCollectionsSigning/Signature.swift | 6 + .../X509Extensions.swift | 27 +- .../SwiftPackageCollectionsTool.swift | 2 +- .../PackageDependency.swift | 11 +- .../PackageDescription.swift | 2 +- .../PackageDescriptionSerialization.swift | 4 + Sources/PackageDescription/Product.swift | 3 +- .../FilePackageFingerprintStorage.swift | 26 +- Sources/PackageGraph/CMakeLists.txt | 28 +- Sources/PackageGraph/DependencyMirrors.swift | 31 +- Sources/PackageGraph/GraphLoadingNode.swift | 23 +- Sources/PackageGraph/ModuleAliasTracker.swift | 43 +- Sources/PackageGraph/PackageContainer.swift | 20 + .../PackageGraph/PackageGraph+Loading.swift | 203 +- Sources/PackageGraph/PackageGraph.swift | 20 +- Sources/PackageGraph/PackageGraphRoot.swift | 40 +- .../PackageModel+Extensions.swift | 4 +- .../DependencyResolutionNode.swift | 2 +- .../DependencyResolverBinding.swift | 20 + .../DependencyResolverDelegate.swift} | 31 +- .../Resolution/DependencyResolverError.swift | 27 + .../{ => Resolution}/PubGrub/Assignment.swift | 0 .../PubGrub/ContainerProvider.swift | 13 +- .../PubGrub/DiagnosticReportBuilder.swift | 0 .../PubGrub/Incompatibility.swift | 0 .../PubGrub/PartialSolution.swift | 0 .../PubGrub/PubGrubDependencyResolver.swift | 14 +- .../PubGrub/PubGrubPackageContainer.swift | 8 +- .../{ => Resolution}/PubGrub/Term.swift | 0 .../{ => Resolution}/ResolvedPackage.swift | 0 .../{ => Resolution}/ResolvedProduct.swift | 40 +- .../{ => Resolution}/ResolvedTarget.swift | 0 Sources/PackageLoading/ContextModel.swift | 10 +- .../PackageLoading/ManifestJSONParser.swift | 258 +- .../ManifestLoader+Validation.swift | 49 +- Sources/PackageLoading/ManifestLoader.swift | 410 +- .../ManifestSignatureParser.swift | 2 +- .../PackageLoading/ModuleMapGenerator.swift | 2 +- Sources/PackageLoading/PackageBuilder.swift | 2 +- Sources/PackageLoading/PkgConfig.swift | 2 +- Sources/PackageLoading/Platform.swift | 2 +- ...RegistryReleaseMetadataSerialization.swift | 2 +- Sources/PackageLoading/Target+PkgConfig.swift | 88 +- .../PackageLoading/TargetSourcesBuilder.swift | 4 +- .../PackageLoading/ToolsVersionParser.swift | 66 +- Sources/PackageMetadata/PackageMetadata.swift | 2 +- .../ArtifactsArchiveMetadata.swift | 2 + Sources/PackageModel/CMakeLists.txt | 17 +- Sources/PackageModel/DependencyMapper.swift | 346 ++ .../InstalledSwiftPMConfiguration.swift | 38 + Sources/PackageModel/Manifest/Manifest.swift | 98 +- .../MinimumDeploymentTarget.swift | 13 +- Sources/PackageModel/PackageIdentity.swift | 83 +- Sources/PackageModel/PackageReference.swift | 19 +- Sources/PackageModel/Platform.swift | 54 +- Sources/PackageModel/SwiftSDKBundle.swift | 487 --- .../{ => SwiftSDKs}/SwiftSDK.swift | 149 +- .../SwiftSDKs/SwiftSDKBundle.swift | 142 + .../SwiftSDKs/SwiftSDKBundleStore.swift | 371 ++ .../SwiftSDKConfigurationStore.swift | 19 +- Sources/PackageModel/Target/ClangTarget.swift | 1 - Sources/PackageModel/Target/MixedTarget.swift | 3 + .../PackageModel/Target/PluginTarget.swift | 15 - Sources/PackageModel/Target/SwiftTarget.swift | 1 - Sources/PackageModel/Target/Target.swift | 19 +- Sources/PackageModel/Toolchain.swift | 24 +- .../PackageModel/ToolchainConfiguration.swift | 8 +- Sources/PackageModel/ToolsVersion.swift | 46 +- Sources/PackageModel/Toolset.swift | 6 +- Sources/PackageModel/UserToolchain.swift | 70 +- Sources/PackagePlugin/Command.swift | 176 +- Sources/PackagePlugin/PackageModel.swift | 7 + Sources/PackagePlugin/Plugin.swift | 16 +- Sources/PackageRegistry/ChecksumTOFU.swift | 51 +- Sources/PackageRegistry/RegistryClient.swift | 214 +- .../RegistryDownloadsManager.swift | 20 + .../PackageRegistry/SignatureValidation.swift | 58 +- .../PackageRegistry/SigningEntityTOFU.swift | 22 + .../PackageRegistryTool+Publish.swift | 5 + .../PackageRegistryTool.swift | 7 +- .../PackageSigning/CertificateStores.swift | 4 + .../PackageSigning/SignatureProvider.swift | 12 +- .../PackageSigningEntityStorage.swift | 106 + .../SigningEntity/SigningEntity.swift | 5 + Sources/PackageSigning/SigningIdentity.swift | 12 +- Sources/PackageSigning/VerifierPolicies.swift | 44 +- Sources/PackageSigning/X509Extensions.swift | 36 +- .../BinaryTarget+Extensions.swift | 23 +- Sources/SPMBuildCore/BuildParameters.swift | 622 --- .../BuildParameters+Debugging.swift | 90 + .../BuildParameters+Driver.swift | 55 + .../BuildParameters+Linking.swift | 65 + .../BuildParameters+Output.swift | 28 + .../BuildParameters+Testing.swift | 142 + .../BuildParameters/BuildParameters.swift | 350 ++ .../{ => BuildSystem}/BuildSystem.swift | 40 +- .../BuildSystemCommand.swift | 0 .../BuildSystemDelegate.swift | 0 Sources/SPMBuildCore/BuiltTestProduct.swift | 7 +- Sources/SPMBuildCore/CMakeLists.txt | 21 +- Sources/SPMBuildCore/PluginMessages.swift | 1 - .../PluginContextSerializer.swift | 2 + .../{ => Plugins}/PluginInvocation.swift | 8 +- .../SPMBuildCore/Plugins/PluginMessages.swift | 1 + .../{ => Plugins}/PluginScriptRunner.swift | 2 +- Sources/SPMBuildCore/Triple+Extensions.swift | 8 +- .../SPMBuildCore/XCFrameworkMetadata.swift | 6 +- .../InMemoryGitRepository.swift | 12 +- .../SPMTestSupport/MockManifestLoader.swift | 5 + .../MockPackageSigningEntityStorage.swift | 16 + Sources/SPMTestSupport/MockWorkspace.swift | 28 +- Sources/SPMTestSupport/Observability.swift | 8 + .../SPMTestSupport/PackageGraphTester.swift | 24 +- Sources/SPMTestSupport/SwiftPMProduct.swift | 8 +- Sources/SPMTestSupport/Toolchain.swift | 44 + Sources/SPMTestSupport/XCTAssertHelpers.swift | 37 + Sources/SPMTestSupport/misc.swift | 178 +- Sources/SourceControl/GitRepository.swift | 97 +- Sources/SourceControl/Repository.swift | 8 +- Sources/SourceControl/RepositoryManager.swift | 44 +- .../ConfigurationSubcommand.swift | 22 +- .../Configuration/ConfigureSwiftSDK.swift | 2 +- .../Configuration/ResetConfiguration.swift | 26 +- .../Configuration/SetConfiguration.swift | 22 +- .../Configuration/ShowConfiguration.swift | 12 +- Sources/SwiftSDKTool/InstallSwiftSDK.swift | 15 +- Sources/SwiftSDKTool/ListSwiftSDKs.swift | 18 +- Sources/SwiftSDKTool/README.md | 6 +- Sources/SwiftSDKTool/RemoveSwiftSDK.swift | 16 +- Sources/SwiftSDKTool/SwiftSDKSubcommand.swift | 27 +- Sources/Workspace/CMakeLists.txt | 16 +- .../Workspace/DefaultPluginScriptRunner.swift | 6 +- Sources/Workspace/Diagnostics.swift | 67 +- Sources/Workspace/InitPackage.swift | 12 +- Sources/Workspace/LoadableResult.swift | 37 + Sources/Workspace/ManagedDependency.swift | 2 +- .../FileSystemPackageContainer.swift | 4 + .../RegistryPackageContainer.swift | 4 + .../SourceControlPackageContainer.swift | 4 + .../Workspace/Workspace+BinaryArtifacts.swift | 312 +- .../Workspace/Workspace+Configuration.swift | 41 +- Sources/Workspace/Workspace+Delegation.swift | 425 ++ .../Workspace/Workspace+Dependencies.swift | 1219 ++++++ Sources/Workspace/Workspace+Editing.swift | 241 ++ Sources/Workspace/Workspace+Manifests.swift | 896 +++++ .../Workspace+PackageContainer.swift | 103 + Sources/Workspace/Workspace+Pinning.swift | 197 + Sources/Workspace/Workspace+Registry.swift | 448 +++ Sources/Workspace/Workspace+Signing.swift | 102 + .../Workspace/Workspace+SourceControl.swift | 269 ++ Sources/Workspace/Workspace+State.swift | 86 +- Sources/Workspace/Workspace.swift | 3573 ++--------------- Sources/XCBuildSupport/PIFBuilder.swift | 37 +- Sources/XCBuildSupport/XCBuildDelegate.swift | 4 +- Sources/XCBuildSupport/XCBuildMessage.swift | 6 +- Sources/XCBuildSupport/XcodeBuildSystem.swift | 8 +- Sources/dummy-swiftc/main.swift | 27 + Sources/swift-bootstrap/main.swift | 33 +- .../Archiver/TarArchiverTests.swift | 46 +- .../Archiver/UniversalArchiverTests.swift | 54 +- .../Archiver/ZipArchiverTests.swift | 160 +- .../AuthorizationProviderTests.swift | 61 +- Tests/BasicsTests/HTTPClientTests.swift | 12 +- Tests/BasicsTests/LegacyHTTPClientTests.swift | 14 +- .../Serialization/SerializedJSONTests.swift | 12 +- Tests/BasicsTests/TripleTests.swift | 61 + .../URLSessionHTTPClientTests.swift | 37 +- Tests/BuildTests/BuildPlanTests.swift | 492 ++- Tests/BuildTests/IncrementalBuildTests.swift | 4 + .../LLBuildManifestBuilderTests.swift | 181 + Tests/BuildTests/LLBuildManifestTests.swift | 123 - Tests/BuildTests/MockBuildTestHelper.swift | 34 +- .../BuildTests/ModuleAliasingBuildTests.swift | 197 +- Tests/CommandsTests/APIDiffTests.swift | 6 +- Tests/CommandsTests/BuildToolTests.swift | 347 +- Tests/CommandsTests/PackageToolTests.swift | 76 +- Tests/CommandsTests/TestToolTests.swift | 34 + .../BuildPerfTests.swift | 4 +- .../FunctionalTests/CFamilyTargetTests.swift | 12 +- .../DependencyResolutionTests.swift | 8 +- .../FunctionalTests/MiscellaneousTests.swift | 63 +- .../ModuleAliasingFixtureTests.swift | 4 +- Tests/FunctionalTests/ModuleMapTests.swift | 6 +- Tests/FunctionalTests/PluginTests.swift | 4 + Tests/FunctionalTests/ResourcesTests.swift | 17 +- Tests/FunctionalTests/ToolsVersionTests.swift | 2 +- .../LLBuildManifestTests.swift | 222 + .../GitHubPackageMetadataProviderTests.swift | 68 +- .../JSONPackageCollectionProviderTests.swift | 102 +- ...PackageCollectionsSourcesStorageTest.swift | 78 +- .../PackageCollectionsStorageTests.swift | 108 +- .../PackageCollectionsTests.swift | 443 +- .../PackageIndexAndCollectionsTests.swift | 128 +- .../PackageIndexTests.swift | 56 +- .../FilePackageFingerprintStorageTests.swift | 12 +- .../DependencyResolverPerfTests.swift | 2 +- .../PackageGraphPerfTests.swift | 2 +- .../PackageGraphTests/PackageGraphTests.swift | 110 +- Tests/PackageGraphTests/PubgrubTests.swift | 46 +- Tests/PackageGraphTests/TargetTests.swift | 2 +- .../ManifestLoaderCacheTests.swift | 579 +++ .../ManifestLoaderSQLiteCacheTests.swift | 76 - .../PackageLoadingTests/PDLoadingTests.swift | 134 +- .../PD_4_0_LoadingTests.swift | 12 +- .../PD_4_2_LoadingTests.swift | 281 +- .../PD_5_2_LoadingTests.swift | 12 +- .../PD_5_6_LoadingTests.swift | 4 +- .../PD_5_7_LoadingTests.swift | 2 +- .../PkgConfigAllowlistTests.swift | 9 + .../ToolsVersionParserTests.swift | 15 + .../CanonicalPackageLocationTests.swift | 15 + .../InstalledSwiftPMConfigurationTests.swift | 32 + .../PackageModelTests/PackageModelTests.swift | 4 +- .../SwiftSDKBundleTests.swift | 279 +- Tests/PackageModelTests/SwiftSDKTests.swift | 22 +- .../PackageSigningEntityTOFUTests.swift | 455 +-- .../PackageVersionChecksumTOFUTests.swift | 221 +- .../RegistryClientTests.swift | 452 +-- .../RegistryDownloadsManagerTests.swift | 36 +- .../SignatureValidationTests.swift | 369 +- ...FilePackageSigningEntityStorageTests.swift | 174 +- Tests/PackageSigningTests/SigningTests.swift | 260 +- .../PluginInvocationTests.swift | 72 +- .../XCFrameworkMetadataTests.swift | 29 +- .../GitRepositoryTests.swift | 7 +- .../RepositoryManagerTests.swift | 221 +- Tests/WorkspaceTests/InitTests.swift | 7 +- .../ManifestSourceGenerationTests.swift | 121 +- .../MirrorsConfigurationTests.swift | 8 +- Tests/WorkspaceTests/PinsStoreTests.swift | 26 +- .../RegistryPackageContainerTests.swift | 60 +- .../SourceControlPackageContainerTests.swift | 63 +- Tests/WorkspaceTests/WorkspaceTests.swift | 1119 +++++- .../XCBuildSupportTests/PIFBuilderTests.swift | 15 +- Tests/XCBuildSupportTests/PIFTests.swift | 4 +- .../Package.swift | 12 + .../InstalledSwiftPMConfiguration.swift | 1 + .../Sources/exec.swift | 10 + Utilities/bootstrap | 12 +- .../build_ubuntu_cross_compilation_toolchain | 272 -- Utilities/config.json | 1 + Utilities/soundness.sh | 18 +- xcode/SwiftPM-Package.xctestplan | 8 + 348 files changed, 19277 insertions(+), 11231 deletions(-) create mode 100644 Documentation/ReleaseNotes/5.9.md create mode 100644 Fixtures/Miscellaneous/EmptyTestsPkg/.gitignore create mode 100644 Fixtures/Miscellaneous/EmptyTestsPkg/Package.swift create mode 100644 Fixtures/Miscellaneous/EmptyTestsPkg/Sources/EmptyTestsPkg/EmptyTestsPkg.swift create mode 100644 Fixtures/Miscellaneous/EmptyTestsPkg/Tests/EmptyTestsPkgTests/EmptyTestsTests.swift delete mode 100644 Fixtures/Miscellaneous/InvalidRefs/InvalidBranch/Package.swift delete mode 100644 Fixtures/Miscellaneous/InvalidRefs/InvalidRevision/Package.swift create mode 100644 Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Package.swift create mode 100644 Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Plugins/A.swift create mode 100644 Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Package.swift create mode 100644 Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Plugins/B.swift create mode 100644 Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Package.swift create mode 100644 Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Sources/main.swift create mode 100644 Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift create mode 100644 Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Sources/SwiftTesting/SwiftTesting.swift create mode 100644 Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Tests/SwiftTestingTests/SwiftTestingTests.swift create mode 100644 Fixtures/Miscellaneous/TestableAsyncExe/Package.swift create mode 100644 Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe1/main.swift create mode 100644 Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe2/main.swift create mode 100644 Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe3/NonMain.swift create mode 100644 Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe4/NonMain.swift create mode 100644 Fixtures/Miscellaneous/TestableAsyncExe/Tests/TestableAsyncExeTests/TestableAsyncExeTests.swift create mode 100644 Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz create mode 100644 Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift create mode 100644 Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift create mode 100644 Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift rename Sources/Build/{LLBuildManifestBuilder.swift => BuildManifest/LLBuildManifestBuilder+Swift.swift} (54%) create mode 100644 Sources/Build/BuildManifest/LLBuildManifestBuilder.swift create mode 100644 Sources/Build/BuildPlan/BuildPlan+Clang.swift create mode 100644 Sources/Build/BuildPlan/BuildPlan+Product.swift create mode 100644 Sources/Build/BuildPlan/BuildPlan+Swift.swift create mode 100644 Sources/Build/BuildPlan/BuildPlan+Test.swift create mode 100644 Sources/Build/TestObservation.swift create mode 100644 Sources/Commands/Utilities/XCTEvents.swift rename Sources/LLBuildManifest/{BuildManifest.swift => LLBuildManifest.swift} (68%) create mode 100644 Sources/LLBuildManifest/LLBuildManifestWriter.swift delete mode 100644 Sources/LLBuildManifest/ManifestWriter.swift rename Sources/PackageGraph/{ => Resolution}/DependencyResolutionNode.swift (99%) create mode 100644 Sources/PackageGraph/Resolution/DependencyResolverBinding.swift rename Sources/PackageGraph/{DependencyResolver.swift => Resolution/DependencyResolverDelegate.swift} (77%) create mode 100644 Sources/PackageGraph/Resolution/DependencyResolverError.swift rename Sources/PackageGraph/{ => Resolution}/PubGrub/Assignment.swift (100%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/ContainerProvider.swift (91%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/DiagnosticReportBuilder.swift (100%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/Incompatibility.swift (100%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/PartialSolution.swift (100%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/PubGrubDependencyResolver.swift (98%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/PubGrubPackageContainer.swift (96%) rename Sources/PackageGraph/{ => Resolution}/PubGrub/Term.swift (100%) rename Sources/PackageGraph/{ => Resolution}/ResolvedPackage.swift (100%) rename Sources/PackageGraph/{ => Resolution}/ResolvedProduct.swift (85%) rename Sources/PackageGraph/{ => Resolution}/ResolvedTarget.swift (100%) create mode 100644 Sources/PackageModel/DependencyMapper.swift create mode 100644 Sources/PackageModel/InstalledSwiftPMConfiguration.swift delete mode 100644 Sources/PackageModel/SwiftSDKBundle.swift rename Sources/PackageModel/{ => SwiftSDKs}/SwiftSDK.swift (87%) create mode 100644 Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift create mode 100644 Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift rename Sources/PackageModel/{ => SwiftSDKs}/SwiftSDKConfigurationStore.swift (89%) delete mode 100644 Sources/SPMBuildCore/BuildParameters.swift create mode 100644 Sources/SPMBuildCore/BuildParameters/BuildParameters+Debugging.swift create mode 100644 Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift create mode 100644 Sources/SPMBuildCore/BuildParameters/BuildParameters+Linking.swift create mode 100644 Sources/SPMBuildCore/BuildParameters/BuildParameters+Output.swift create mode 100644 Sources/SPMBuildCore/BuildParameters/BuildParameters+Testing.swift create mode 100644 Sources/SPMBuildCore/BuildParameters/BuildParameters.swift rename Sources/SPMBuildCore/{ => BuildSystem}/BuildSystem.swift (79%) rename Sources/SPMBuildCore/{ => BuildSystem}/BuildSystemCommand.swift (100%) rename Sources/SPMBuildCore/{ => BuildSystem}/BuildSystemDelegate.swift (100%) delete mode 120000 Sources/SPMBuildCore/PluginMessages.swift rename Sources/SPMBuildCore/{ => Plugins}/PluginContextSerializer.swift (99%) rename Sources/SPMBuildCore/{ => Plugins}/PluginInvocation.swift (99%) create mode 120000 Sources/SPMBuildCore/Plugins/PluginMessages.swift rename Sources/SPMBuildCore/{ => Plugins}/PluginScriptRunner.swift (94%) create mode 100644 Sources/Workspace/LoadableResult.swift rename Sources/Workspace/{ => PackageContainer}/FileSystemPackageContainer.swift (96%) rename Sources/Workspace/{ => PackageContainer}/RegistryPackageContainer.swift (98%) rename Sources/Workspace/{ => PackageContainer}/SourceControlPackageContainer.swift (99%) create mode 100644 Sources/Workspace/Workspace+Delegation.swift create mode 100644 Sources/Workspace/Workspace+Dependencies.swift create mode 100644 Sources/Workspace/Workspace+Editing.swift create mode 100644 Sources/Workspace/Workspace+Manifests.swift create mode 100644 Sources/Workspace/Workspace+PackageContainer.swift create mode 100644 Sources/Workspace/Workspace+Pinning.swift create mode 100644 Sources/Workspace/Workspace+Registry.swift create mode 100644 Sources/Workspace/Workspace+Signing.swift create mode 100644 Sources/Workspace/Workspace+SourceControl.swift create mode 100644 Sources/dummy-swiftc/main.swift create mode 100644 Tests/BuildTests/LLBuildManifestBuilderTests.swift delete mode 100644 Tests/BuildTests/LLBuildManifestTests.swift create mode 100644 Tests/LLBuildManifestTests/LLBuildManifestTests.swift create mode 100644 Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift delete mode 100644 Tests/PackageLoadingTests/ManifestLoaderSQLiteCacheTests.swift create mode 100644 Tests/PackageModelTests/InstalledSwiftPMConfigurationTests.swift create mode 100644 Utilities/InstalledSwiftPMConfiguration/Package.swift create mode 120000 Utilities/InstalledSwiftPMConfiguration/Sources/InstalledSwiftPMConfiguration.swift create mode 100644 Utilities/InstalledSwiftPMConfiguration/Sources/exec.swift delete mode 100755 Utilities/build_ubuntu_cross_compilation_toolchain create mode 100644 Utilities/config.json diff --git a/.gitignore b/.gitignore index 5d661847bcd..5be3c7b3009 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ Package.resolved *.pyc .docc-build .vscode +Utilities/InstalledSwiftPMConfiguration/config.json diff --git a/.mailmap b/.mailmap index 57bae093d4f..e63c4f1d675 100644 --- a/.mailmap +++ b/.mailmap @@ -101,5 +101,5 @@ Tim Gymnich Tom Doron Valeriy Van Valeriy Van -buttaface -buttaface +finagolfin +finagolfin diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme index 76df3c73435..780e7e3820f 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme @@ -580,6 +580,20 @@ ReferencedContainer = "container:"> + + + + + + + + + + + + - + - + diff --git a/CHANGELOG.md b/CHANGELOG.md index 68aeebd4b90..156587e37cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,51 +3,84 @@ Note: This is in reverse chronological order, so newer entries are added to the Swift Next ----------- -* [#6111] - - Package creation using `package init` now also supports the build tool plugin and command plugin types. +* [#7010] -* [#5728] + On macOS, `swift build` and `swift run` now produce binaries that allow backtraces in debug builds. Pass `SWIFT_BACKTRACE=enable=yes` environment variable to enable backtraces on such binaries when running them. - In packages that specify resources using a future tools version, the generated resource bundle accessor will import `Foundation.Bundle` for its own implementation only. _Clients_ of such packages therefore no longer silently import `Foundation`, preventing inadvertent use of Foundation extensions to standard library APIs, which helps to avoid unexpected code size increases. +* [7101] -* [#6185], [#6200] - - Add a new `CompilerPluginSupport` module which contains the definition for macro targets. Macro targets allow authoring and distribution of custom Swift macros such as [expression macros](https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md). + Binary artifacts are now cached along side repository checkouts so they do not need to be re-downloaded across projects. Swift 5.9 ----------- +* [SE-0386] + + SwiftPM packages can now use `package` as a new access modifier, allowing accessing symbols in another target / module within the same package without making it public. + +* [SE-0387] + + New `swift experimental-sdk` experimental command is now available for managing Swift SDK bundles that follow the format described in [SE-0387]: "Swift SDKs for Cross-Compilation". + +* [SE-0391] + + SwiftPM can now publish to a registry following the publishing spec as defined in [SE-0391]. SwiftPM also gains support for signed packages. Trust-on-first-use (TOFU) check which includes only fingerprints (e.g., checksums) previously has been extended to include signing identities, and it is enforced for source archives as well as package manifests. + * [#5966] Plugin compilation can be influenced by using `-Xbuild-tools-swiftc` arguments in the SwiftPM command line. This is similar to the existing mechanism for influencing the manifest compilation using `-Xmanifest` arguments. Manifest compilation will also be influenced by `-Xbuild-tools-swiftc`, but only if no other `-Xmanifest` arguments are provided. Using `-Xmanifest` will show a deprecation message. `-Xmanifest` will be removed in the future. +* [#6060] + + Support for building plugin dependencies for the host when cross-compiling. + * [#6067] Basic support for a new `.embedInCode` resource rule which allows embedding the contents of the resource into the executable code by generating a byte array, e.g. ``` struct PackageResources { - static let best_txt: [UInt8] = [104,101,108,108,111,32,119,111,114,108,100,10] + static let best_txt: [UInt8] = [104,101,108,108,111,32,119,111,114,108,100,10] } ``` -* [#6294] +* [#6111] - When a package contains a single target, sources may be distributed anywhere within the `./Sources` directory. If sources are placed in a subdirectory under `./Sources/`, or there is more than one target, the existing expectation for sources apply. + Package creation using `package init` now also supports the build tool plugin and command plugin types. * [#6114] Added a new `allowNetworkConnections(scope:reason:)` for giving a command plugin permissions to access the network. Permissions can be scoped to Unix domain sockets in general or specifically for Docker, as well as local or remote IP connections which can be limited by port. For non-interactive use cases, there is also a `--allow-network-connections` commandline flag to allow network connections for a particular scope. -* [#6060] - - Support for building plugin dependencies for the host when cross-compiling. - * [#6144] Remove the `system-module` and `manifest` templates and clean up the remaining `empty`, `library`, and `executable` templates so they include the minimum information needed to get started, with links to documentation in the generated library, executable, and test content. +* [#6185], [#6200] + + Add a new `CompilerPluginSupport` module which contains the definition for macro targets. Macro targets allow authoring and distribution of custom Swift macros such as [expression macros](https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md). + +* [#6276] + + Add new build setting in the package manifest that enables Swift/C++ Interoperability for a given Swift target. + + ``` + .interoperabilityMode(.Cxx, version: "swift-5.9") + ``` + +* [#6294] + + When a package contains a single target, sources may be distributed anywhere within the `./Sources` directory. If sources are placed in a subdirectory under `./Sources/`, or there is more than one target, the existing expectation for sources apply. + +* [#6540] + + Build tool plugins can be used with C-family targets + +* [#6663] + + Add `visionOS` as a platform alongside `iOS` and other platforms + + Swift 5.8 ----------- @@ -302,6 +335,9 @@ Swift 3.0 [SE-0339]: https://github.com/apple/swift-evolution/blob/main/proposals/0339-module-aliasing-for-disambiguation.md [SE-0362]: https://github.com/apple/swift-evolution/blob/main/proposals/0362-piecemeal-future-features.md [SE-0378]: https://github.com/apple/swift-evolution/blob/main/proposals/0378-package-registry-auth.md +[SE-0386]: https://github.com/apple/swift-evolution/blob/main/proposals/0386-package-access-modifier.md +[SE-0387]: https://github.com/apple/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md +[SE-0391]: https://github.com/apple/swift-evolution/blob/main/proposals/0391-package-registry-publish.md [SR-5918]: https://bugs.swift.org/browse/SR-5918 [SR-6978]: https://bugs.swift.org/browse/SR-6978 @@ -337,9 +373,13 @@ Swift 3.0 [#5966]: https://github.com/apple/swift-package-manager/pull/5966 [#6060]: https://github.com/apple/swift-package-manager/pull/6060 [#6067]: https://github.com/apple/swift-package-manager/pull/6067 +[#6111]: https://github.com/apple/swift-package-manager/pull/6111 [#6114]: https://github.com/apple/swift-package-manager/pull/6114 [#6144]: https://github.com/apple/swift-package-manager/pull/6144 [#6294]: https://github.com/apple/swift-package-manager/pull/6294 [#6185]: https://github.com/apple/swift-package-manager/pull/6185 [#6200]: https://github.com/apple/swift-package-manager/pull/6200 -[#6111]: https://github.com/apple/swift-package-manager/pull/6111 +[#6276]: https://github.com/apple/swift-package-manager/pull/6276 +[#6540]: https://github.com/apple/swift-package-manager/pull/6540 +[#6663]: https://github.com/apple/swift-package-manager/pull/6663 +[#7101]: https://github.com/apple/swift-package-manager/pull/7101 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2753b821b8b..46c94a9ea60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,28 +6,17 @@ # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for Swift project authors -cmake_minimum_required(VERSION 3.15.1) +cmake_minimum_required(VERSION 3.19) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) project(SwiftPM LANGUAGES C Swift) -set(SWIFT_VERSION 5) -set(CMAKE_Swift_LANGUAGE_VERSION ${SWIFT_VERSION}) -if(CMAKE_VERSION VERSION_LESS 3.16) - add_compile_options($<$:-swift-version$${SWIFT_VERSION}>) - set(CMAKE_LINK_LIBRARY_FLAG "-l") -endif() - +set(CMAKE_Swift_LANGUAGE_VERSION 5) set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) -if(CMAKE_VERSION VERSION_LESS 3.16 AND CMAKE_SYSTEM_NAME STREQUAL Windows) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -else() - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -endif() +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) option(BUILD_SHARED_LIBS "Build shared libraries by default" YES) @@ -42,6 +31,8 @@ if(BUILD_SHARED_LIBS) set(CMAKE_POSITION_INDEPENDENT_CODE YES) endif() +add_compile_options(-DUSE_IMPL_ONLY_IMPORTS) + if(FIND_PM_DEPS) find_package(SwiftSystem CONFIG REQUIRED) find_package(TSC CONFIG REQUIRED) diff --git a/CODEOWNERS b/CODEOWNERS index 39b91529d50..a45a3b347e9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,10 +8,6 @@ # (W), PGP key ID and fingerprint (P), description (D), and snail-mail address # (S). -# N: Anders Bertelrud -# E: anders@apple.com -# D: Package Manager - # N: Boris Buegling # E: bbuegling@apple.com # D: Package Manager @@ -28,4 +24,4 @@ # The following lines are used by GitHub to automatically recommend reviewers. -* @abertelrud @neonichu @tomerd @MaxDesiatov +* @neonichu @tomerd @MaxDesiatov diff --git a/Documentation/PackageRegistry/PackageRegistryUsage.md b/Documentation/PackageRegistry/PackageRegistryUsage.md index 0eff284caf8..791ab103047 100644 --- a/Documentation/PackageRegistry/PackageRegistryUsage.md +++ b/Documentation/PackageRegistry/PackageRegistryUsage.md @@ -127,8 +127,8 @@ and apply them automatically when making registry API requests. ## Dependency Resolution Using Registry Resolving a registry dependency involves these steps: -1. Fetch a package's available versions by calling the [list package releases](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#41-list-package-releases) API. -2. Compute the dependency graph by [fetching manifest(s) for a package release](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#43-fetch-manifest-for-a-package-release). +1. Fetch a package's available versions by calling the [list package releases](Registry.md#41-list-package-releases) API. +2. Compute the dependency graph by [fetching manifest(s) for a package release](Registry.md#43-fetch-manifest-for-a-package-release). 3. Pinpoint the package version to use. ### Using registry for source control dependencies @@ -151,7 +151,7 @@ they are considered different even though they are the same package, and would result in symbol clashes. SwiftPM can deduplicate packages by performing a -[lookup on the source control URL](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-5) +[lookup on the source control URL](Registry.md#endpoint-5) (e.g., `https://github.com/mona/LinkedList`) to see if it is associated with any package identifier (e.g., `mona.LinkedList`). @@ -164,7 +164,7 @@ source control dependencies by setting one of these flags: ## Dependency Download From Registry After a registry dependency is resolved, SwiftPM can -[download source archive](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-4) +[download source archive](Registry.md#endpoint-4) of the computed package version from the registry. ### Checksum TOFU @@ -173,7 +173,7 @@ SwiftPM performs checksum TOFU ([trust-on-first-use](https://en.wikipedia.org/wiki/Trust_on_first_use)) on the downloaded source archive. If the archive is downloaded for the first time, SwiftPM -[fetches metadata of the package release](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-2) +[fetches metadata of the package release](Registry.md#endpoint-2) to obtain the expected checksum. Otherwise, SwiftPM compares the checksum with that in local storage (`~/.swiftpm/security/fingerprints/`) saved from previous download. @@ -273,7 +273,7 @@ OPTIONS: The command creates source archive for the package release, optionally signs the package release, and -[publishes the package release](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#endpoint-6) +[publishes the package release](Registry.md#endpoint-6) to the registry. If authentication is required for package publication, diff --git a/Documentation/PackageRegistry/Registry.md b/Documentation/PackageRegistry/Registry.md index f6283fe1217..c4667416aa4 100644 --- a/Documentation/PackageRegistry/Registry.md +++ b/Documentation/PackageRegistry/Registry.md @@ -1273,7 +1273,7 @@ JSON schema below. ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md", + "$id": "https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md", "title": "Package Release Metadata", "description": "Metadata of a package release.", "type": "object", @@ -1368,7 +1368,7 @@ JSON schema below. | `licenseURL` | String | URL of the package release's license document. | | | `originalPublicationTime` | String | Original publication time of the package release in [ISO 8601] format. This can be set if the package release was previously published elsewhere.
A registry should record the publication time independently and include it as `publishedAt` in the [package release metadata response](#42-fetch-information-about-a-package-release).
In case both `originalPublicationTime` and `publishedAt` are set, `originalPublicationTime` should be used. | | | `readmeURL` | String | URL of the README specifically for the package release or broadly for the package. | | -| `repositoryURLs` | Array | Code repository URL(s) of the package. It is recommended to include all URL variations (e.g., SSH, HTTPS) for the same repository. This can be an empty array if the package does not have source control representation.
Setting this property is one way through which a registry can obtain repository URL to package identifier mappings for the ["lookup package identifiers registered for a URL" API](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#45-lookup-package-identifiers-registered-for-a-url). A registry may choose other mechanism(s) for package authors to specify such mappings. | | +| `repositoryURLs` | Array | Code repository URL(s) of the package. It is recommended to include all URL variations (e.g., SSH, HTTPS) for the same repository. This can be an empty array if the package does not have source control representation.
Setting this property is one way through which a registry can obtain repository URL to package identifier mappings for the ["lookup package identifiers registered for a URL" API](Registry.md#45-lookup-package-identifiers-registered-for-a-url). A registry may choose other mechanism(s) for package authors to specify such mappings. | | ##### `Author` type diff --git a/Documentation/README.md b/Documentation/README.md index 8bdbbd2e952..b9533baacc6 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -19,7 +19,7 @@ We’ve designed the system to make it really easy to share packages on services * [Package manifest specification](PackageDescription.md) * [Getting Started with Plugins](Plugins.md) * [Package discovery with Package Collections](PackageCollections.md) -* [Package Registry service specification](Registry.md) +* [Package Registry service specification](PackageRegistry/Registry.md) * [Using SwiftPM as a library](libSwiftPM.md) * [Using Module Aliasing](ModuleAliasing.md) diff --git a/Documentation/ReleaseNotes/5.9.md b/Documentation/ReleaseNotes/5.9.md new file mode 100644 index 00000000000..e1bb7f1397e --- /dev/null +++ b/Documentation/ReleaseNotes/5.9.md @@ -0,0 +1,31 @@ +# SwiftPM 5.9 Release Notes + +## Cross compilation + +SwiftPM now supports cross compilation based on the Swift SDK bundle format. While the feature is still considered experimental, we invite users to try it out and provide feedback. + +## Package registry + +SwiftPM can now publish to a registry following the specification defined in SE-0391, as well as support signed packages, which may be required by a registry. Trust-on-first-use (TOFU) validation checks can now use signing identities in addition to fingerprints, and are enforced for source archives as well as package manifests. + +## Embedded resources + +Basic support for a new `.embedInCode` resource rule which allows embedding the contents of the resource into the executable code by generating a byte array + +``` +struct PackageResources { + static let best_txt: [UInt8] = [104,101,108,108,111,32,119,111,114,108,100,10] +} +``` + +## Other improvements + +The `CompilerPluginSupport` module enables defining macro targets. Macro targets allow authoring and distributing custom Swift macros as APIs in a library. + +Packages can use the new `package` access modifier, allowing access of symbols in another target / module within the same package without making them public. SwiftPM automatically sets the new compiler configuration to ensure this feature works out-of-the-box for packages. + +The `allowNetworkConnections(scope:reason:)` setting gives a command plugin permissions to access the network. Permissions can be scoped to Unix domain sockets as well as local or remote IP connections, with an option to limit by port. For non-interactive use cases, the `--allow-network-connections` command-line flag allows network connections for a particular scope. + +When a package contains a single target, sources may be distributed anywhere within the `./Sources` directory. If sources are placed in a subdirectory under `./Sources/`, or there is more than one target, the existing expectation for sources apply + +Build tool plugins can be used with C-family targets diff --git a/Documentation/Usage.md b/Documentation/Usage.md index 73dbc50410d..24eeee9057c 100644 --- a/Documentation/Usage.md +++ b/Documentation/Usage.md @@ -7,6 +7,7 @@ * [Creating a Package](#creating-a-package) * [Creating a Library Package](#creating-a-library-package) * [Creating an Executable Package](#creating-an-executable-package) + * [Creating a Macro Package](#creating-a-macro-package) * [Defining Dependencies](#defining-dependencies) * [Publishing a Package](#publishing-a-package) * [Requiring System Libraries](#requiring-system-libraries) @@ -68,6 +69,25 @@ can be turned into a executable target if there is a `main.swift` file present i its sources. The complete reference for layout is [here](PackageDescription.md#target). +### Creating a Macro Package + +SwiftPM can generate boilerplate for custom macros: + + $ mkdir MyMacro + $ cd MyMacro + $ swift package init --type macro + $ swift build + $ swift run + The value 42 was produced by the code "a + b" + +This creates a package with a `.macro` type target with its required dependencies +on [swift-syntax](https://github.com/apple/swift-syntax), a library `.target` +containing the macro's code, and an `.executableTarget` and `.testTarget` for +running the macro. The sample macro, `StringifyMacro`, is documented in the Swift +Evolution proposal for [Expression Macros](https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md) +and the WWDC [Write Swift macros](https://developer.apple.com/videos/play/wwdc2023/10166) +video. See further documentation on macros in [The Swift Programming Language](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) book. + ## Defining Dependencies To depend on a package, define the dependency and the version in the manifest of @@ -206,9 +226,10 @@ The header file should look like this: #include ``` -**Note:** Alternatively, you can provide an absolute path to `git2.h` provided -by the library in the `modile.modulemap`. However, doing so might break -cross-platform compatibility of your project. +**Note:** Avoiding specifying an absolute path to `git2.h` provided +by the library in the `module.modulemap`. Doing so will break compatibility of +your project between machines that may use a different file system layout or +install libraries to different paths. > The convention we hope the community will adopt is to prefix such modules > with `C` and to camelcase the modules as per Swift module name conventions. diff --git a/Fixtures/Miscellaneous/EmptyTestsPkg/.gitignore b/Fixtures/Miscellaneous/EmptyTestsPkg/.gitignore new file mode 100644 index 00000000000..02c087533d1 --- /dev/null +++ b/Fixtures/Miscellaneous/EmptyTestsPkg/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/Fixtures/Miscellaneous/EmptyTestsPkg/Package.swift b/Fixtures/Miscellaneous/EmptyTestsPkg/Package.swift new file mode 100644 index 00000000000..6643cad540f --- /dev/null +++ b/Fixtures/Miscellaneous/EmptyTestsPkg/Package.swift @@ -0,0 +1,14 @@ +// swift-tools-version:4.2 +import PackageDescription + +let package = Package( + name: "EmptyTestsPkg", + targets: [ + .target( + name: "EmptyTestsPkg", + dependencies: []), + .testTarget( + name: "EmptyTestsPkgTests", + dependencies: ["EmptyTestsPkg"]), + ] +) diff --git a/Fixtures/Miscellaneous/EmptyTestsPkg/Sources/EmptyTestsPkg/EmptyTestsPkg.swift b/Fixtures/Miscellaneous/EmptyTestsPkg/Sources/EmptyTestsPkg/EmptyTestsPkg.swift new file mode 100644 index 00000000000..d5fe06397ea --- /dev/null +++ b/Fixtures/Miscellaneous/EmptyTestsPkg/Sources/EmptyTestsPkg/EmptyTestsPkg.swift @@ -0,0 +1,5 @@ +struct EmptyTests { + + var text = "Hello, World!" + var bool = false +} diff --git a/Fixtures/Miscellaneous/EmptyTestsPkg/Tests/EmptyTestsPkgTests/EmptyTestsTests.swift b/Fixtures/Miscellaneous/EmptyTestsPkg/Tests/EmptyTestsPkgTests/EmptyTestsTests.swift new file mode 100644 index 00000000000..e1e4e155c57 --- /dev/null +++ b/Fixtures/Miscellaneous/EmptyTestsPkg/Tests/EmptyTestsPkgTests/EmptyTestsTests.swift @@ -0,0 +1,2 @@ +import XCTest +@testable import EmptyTestsPkg diff --git a/Fixtures/Miscellaneous/InvalidRefs/InvalidBranch/Package.swift b/Fixtures/Miscellaneous/InvalidRefs/InvalidBranch/Package.swift deleted file mode 100644 index 817412629a0..00000000000 --- a/Fixtures/Miscellaneous/InvalidRefs/InvalidBranch/Package.swift +++ /dev/null @@ -1,10 +0,0 @@ -// swift-tools-version:5.5 -import PackageDescription - -let package = Package( - name: "Foo", - dependencies: [ - .package(url: "https://localhost/foo/bar", branch: "#!~") - ], - targets: [] -) diff --git a/Fixtures/Miscellaneous/InvalidRefs/InvalidRevision/Package.swift b/Fixtures/Miscellaneous/InvalidRefs/InvalidRevision/Package.swift deleted file mode 100644 index 76dc0107923..00000000000 --- a/Fixtures/Miscellaneous/InvalidRefs/InvalidRevision/Package.swift +++ /dev/null @@ -1,10 +0,0 @@ -// swift-tools-version:5.5 -import PackageDescription - -let package = Package( - name: "Foo", - dependencies: [ - .package(url: "https://localhost/foo/bar", revision: "#!~") - ], - targets: [] -) diff --git a/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Package.swift b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Package.swift new file mode 100644 index 00000000000..a63b8f5467e --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "A", + products: [ + .plugin( + name: "A", + targets: ["A"]), + ], + targets: [ + .plugin( + name: "A", + capability: .command(intent: .custom( + verb: "A", + description: "prints hello" + )) + ), + ] +) diff --git a/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Plugins/A.swift b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Plugins/A.swift new file mode 100644 index 00000000000..23c615c03f6 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/A/Plugins/A.swift @@ -0,0 +1,9 @@ +import PackagePlugin + +@main +struct A: CommandPlugin { + func performCommand(context: PluginContext, arguments: [String]) async throws { + print("Hello A!") + } +} + diff --git a/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Package.swift b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Package.swift new file mode 100644 index 00000000000..d35f4946053 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "B", + products: [ + .plugin( + name: "B", + targets: ["B"]), + ], + targets: [ + .plugin( + name: "B", + capability: .command(intent: .custom( + verb: "A", + description: "prints hello" + )) + ), + ] +) diff --git a/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Plugins/B.swift b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Plugins/B.swift new file mode 100644 index 00000000000..7d8a099472d --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Dependencies/B/Plugins/B.swift @@ -0,0 +1,8 @@ +import PackagePlugin + +@main +struct B: CommandPlugin { + func performCommand(context: PluginContext, arguments: [String]) async throws { + print("Hello B!") + } +} diff --git a/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Package.swift b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Package.swift new file mode 100644 index 00000000000..8cc97be5c8b --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "AmbiguousCommands", + dependencies: [ + .package(path: "Dependencies/A"), + .package(path: "Dependencies/B"), + ], + targets: [ + .executableTarget( + name: "AmbiguousCommands"), + ] +) diff --git a/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Sources/main.swift b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Sources/main.swift new file mode 100644 index 00000000000..f7cf60e14f9 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/AmbiguousCommands/Sources/main.swift @@ -0,0 +1 @@ +print("Hello, world!") diff --git a/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift new file mode 100644 index 00000000000..2588f440a55 --- /dev/null +++ b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift @@ -0,0 +1,18 @@ +// swift-tools-version: 5.10 +import PackageDescription + +let package = Package( + name: "SwiftTesting", + platforms: [ + .macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16), .visionOS(.v1) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), + ], + targets: [ + .testTarget( + name: "SwiftTestingTests", + dependencies: [.product(name: "Testing", package: "swift-testing"),] + ), + ] +) diff --git a/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Sources/SwiftTesting/SwiftTesting.swift b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Sources/SwiftTesting/SwiftTesting.swift new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Sources/SwiftTesting/SwiftTesting.swift @@ -0,0 +1 @@ + diff --git a/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Tests/SwiftTestingTests/SwiftTestingTests.swift b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Tests/SwiftTestingTests/SwiftTestingTests.swift new file mode 100644 index 00000000000..44e3d56cfdb --- /dev/null +++ b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Tests/SwiftTestingTests/SwiftTestingTests.swift @@ -0,0 +1,2 @@ +import Testing +@Test("SOME TEST FUNCTION") func someTestFunction() {} diff --git a/Fixtures/Miscellaneous/TestableAsyncExe/Package.swift b/Fixtures/Miscellaneous/TestableAsyncExe/Package.swift new file mode 100644 index 00000000000..289632dccda --- /dev/null +++ b/Fixtures/Miscellaneous/TestableAsyncExe/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.5 +import PackageDescription + +let package = Package( + name: "TestableAsyncExe", + targets: [ + .executableTarget( + name: "TestableAsyncExe1" + ), + .executableTarget( + name: "TestableAsyncExe2" + ), + .executableTarget( + name: "TestableAsyncExe3" + ), + .executableTarget( + name: "TestableAsyncExe4" + ), + .testTarget( + name: "TestableAsyncExeTests", + dependencies: [ + "TestableAsyncExe1", + "TestableAsyncExe2", + "TestableAsyncExe3", + "TestableAsyncExe4", + ] + ), + ] +) diff --git a/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe1/main.swift b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe1/main.swift new file mode 100644 index 00000000000..69909618ffc --- /dev/null +++ b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe1/main.swift @@ -0,0 +1,5 @@ +public func GetAsyncGreeting1() async -> String { + return "Hello, async world" +} + +await print("\(GetAsyncGreeting1())!") diff --git a/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe2/main.swift b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe2/main.swift new file mode 100644 index 00000000000..7f1e347c493 --- /dev/null +++ b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe2/main.swift @@ -0,0 +1,5 @@ +public func GetAsyncGreeting2() async -> String { + return "Hello, async planet" +} + +await print("\(GetAsyncGreeting2())!") diff --git a/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe3/NonMain.swift b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe3/NonMain.swift new file mode 100644 index 00000000000..99c5794730d --- /dev/null +++ b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe3/NonMain.swift @@ -0,0 +1,10 @@ +@main +struct AsyncMain3 { + static func main() async { + print(await getGreeting3()) + } + + static func getGreeting3() async -> String { + return "Hello, async galaxy" + } +} diff --git a/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe4/NonMain.swift b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe4/NonMain.swift new file mode 100644 index 00000000000..b8739dc43ab --- /dev/null +++ b/Fixtures/Miscellaneous/TestableAsyncExe/Sources/TestableAsyncExe4/NonMain.swift @@ -0,0 +1,10 @@ +@main +struct AsyncMain4 { + static func main() async { + print(await getGreeting4()) + } + + static func getGreeting4() async -> String { + return "Hello, async universe" + } +} diff --git a/Fixtures/Miscellaneous/TestableAsyncExe/Tests/TestableAsyncExeTests/TestableAsyncExeTests.swift b/Fixtures/Miscellaneous/TestableAsyncExe/Tests/TestableAsyncExeTests/TestableAsyncExeTests.swift new file mode 100644 index 00000000000..31d21d97f07 --- /dev/null +++ b/Fixtures/Miscellaneous/TestableAsyncExe/Tests/TestableAsyncExeTests/TestableAsyncExeTests.swift @@ -0,0 +1,25 @@ +import XCTest +@testable import TestableAsyncExe1 +@testable import TestableAsyncExe2 +@testable import TestableAsyncExe3 +@testable import TestableAsyncExe4 + +final class TestableAsyncExeTests: XCTestCase { + func testExample() async throws { + let greeting1 = await GetAsyncGreeting1() + print(greeting1) + XCTAssertEqual(greeting1, "Hello, async world") + + let greeting2 = await GetAsyncGreeting2() + print(greeting2) + XCTAssertEqual(greeting2, "Hello, async planet") + + let greeting3 = await AsyncMain3.getGreeting3() + print(greeting3) + XCTAssertEqual(greeting3, "Hello, async galaxy") + + let greeting4 = await AsyncMain4.getGreeting4() + print(greeting4) + XCTAssertEqual(greeting4, "Hello, async universe") + } +} diff --git a/Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz b/Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..04f705d0b65ecc94c5becc71e923cf62d8a1c2c3 GIT binary patch literal 412 zcmV;N0b~9jiwFQ|!X{+^1MO5jPs1<}?VMj>c_zV5ZAW5eV?lse5QAx)Hner3*r7#L z{yT1*eulIlqE@JR%USQvKHKl^v{2er;n?Gujw3$MZ@CO7;=0!&5JCZ^ptQ_du=uWu zO%W4Q#8rix{}~Fs>7uRi(A-Y7Nny(9AxZT8vU97(-R~%;IQMUK=+L3V<=_j~IzBo8 G5C8xL`q1S7 literal 0 HcmV?d00001 diff --git a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift index 70c4202bab2..d9fcdccc1bf 100644 --- a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift @@ -63,7 +63,7 @@ final class SwiftPMTests: XCTestCase { try localFileSystem.removeFileTree(packagePath.appending(components: "Sources", entry)) } try localFileSystem.writeFileContents(AbsolutePath(validating: "Sources/main.m", relativeTo: packagePath)) { - $0 <<< "int main() {}" + $0.send("int main() {}") } let archs = ["x86_64", "arm64"] diff --git a/Package.swift b/Package.swift index 36e264623c1..7c711b0af32 100644 --- a/Package.swift +++ b/Package.swift @@ -128,6 +128,7 @@ let package = Package( name: "PackageDescription", exclude: ["CMakeLists.txt"], swiftSettings: [ + .define("USE_IMPL_ONLY_IMPORTS"), .unsafeFlags(["-package-description-version", "999.0"]), .unsafeFlags(["-enable-library-evolution"]), ], @@ -548,7 +549,11 @@ let package = Package( ), .testTarget( name: "BuildTests", - dependencies: ["Build", "SPMTestSupport"] + dependencies: ["Build", "PackageModel", "SPMTestSupport"] + ), + .testTarget( + name: "LLBuildManifestTests", + dependencies: ["Basics", "LLBuildManifest", "SPMTestSupport"] ), .testTarget( name: "WorkspaceTests", @@ -666,6 +671,13 @@ if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] == ] ), + .executableTarget( + name: "dummy-swiftc", + dependencies: [ + "Basics", + ] + ), + .testTarget( name: "CommandsTests", dependencies: [ @@ -681,6 +693,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] == "SourceControl", "SPMTestSupport", "Workspace", + "dummy-swiftc", ] ), ]) @@ -713,7 +726,6 @@ if ProcessInfo.processInfo.environment["SWIFTPM_LLBUILD_FWK"] == nil { } package.targets.first(where: { $0.name == "SPMLLBuild" })!.dependencies += [ .product(name: "llbuildSwift", package: "swift-llbuild"), - .product(name: "llbuild", package: "swift-llbuild"), ] } @@ -725,10 +737,10 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { // dependency version changes here with those projects. .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.2")), .package(url: "https://github.com/apple/swift-driver.git", branch: relatedDependenciesBranch), - .package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "2.5.0")), + .package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "3.0.0")), .package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "1.1.1")), .package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.1")), - .package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "0.6.0")), + .package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "1.0.1")), ] } else { package.dependencies += [ diff --git a/README.md b/README.md index 9e0df771c69..4525450fea0 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ If you have any trouble with the package manager, help is available. We recommen When reporting an issue please follow the bug reporting guidelines, they can be found in [contribution guide](./CONTRIBUTING.md#reporting-issues). -If you’re not comfortable sharing your question with the list, contact details for the code owners can be found in [CODEOWNERS](CODEOWNERS); however, the mailing list is usually the best place to go for help. +If you’re not comfortable sharing your question with the list, contact details for the code owners can be found in [CODEOWNERS](CODEOWNERS); however, Swift Forums is usually the best place to go for help. --- diff --git a/Sources/Basics/Archiver/Archiver.swift b/Sources/Basics/Archiver/Archiver.swift index 64d6edc9717..9763ec60ce4 100644 --- a/Sources/Basics/Archiver/Archiver.swift +++ b/Sources/Basics/Archiver/Archiver.swift @@ -23,6 +23,7 @@ public protocol Archiver { /// - archivePath: The `AbsolutePath` to the archive to extract. /// - destinationPath: The `AbsolutePath` to the directory to extract to. /// - completion: The completion handler that will be called when the operation finishes to notify of its success. + @available(*, noasync, message: "Use the async alternative") func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, @@ -35,6 +36,7 @@ public protocol Archiver { /// - directory: The `AbsolutePath` to the archive to extract. /// - destinationPath: The `AbsolutePath` to the directory to extract to. /// - completion: The completion handler that will be called when the operation finishes to notify of its success. + @available(*, noasync, message: "Use the async alternative") func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, @@ -46,6 +48,7 @@ public protocol Archiver { /// - Parameters: /// - path: The `AbsolutePath` to the archive to validate. /// - completion: The completion handler that will be called when the operation finishes to notify of its success. + @available(*, noasync, message: "Use the async alternative") func validate( path: AbsolutePath, completion: @escaping (Result) -> Void @@ -57,8 +60,25 @@ extension Archiver { from archivePath: AbsolutePath, to destinationPath: AbsolutePath ) async throws { - try await withCheckedThrowingContinuation { - self.extract(from: archivePath, to: destinationPath, completion: $0.resume(with:)) + try await safe_async { + self.extract(from: archivePath, to: destinationPath, completion: $0) + } + } + + public func compress( + directory: AbsolutePath, + to: AbsolutePath + ) async throws { + try await safe_async { + self.compress(directory: directory, to: to, completion: $0) + } + } + + public func validate( + path: AbsolutePath + ) async throws -> Bool { + try await safe_async { + self.validate(path: path, completion: $0) } } } diff --git a/Sources/Basics/AuthorizationProvider.swift b/Sources/Basics/AuthorizationProvider.swift index 4335b32146b..11e80cb79ed 100644 --- a/Sources/Basics/AuthorizationProvider.swift +++ b/Sources/Basics/AuthorizationProvider.swift @@ -23,6 +23,7 @@ public protocol AuthorizationProvider { } public protocol AuthorizationWriter { + @available(*, noasync, message: "Use the async alternative") func addOrUpdate( for url: URL, user: String, @@ -31,13 +32,37 @@ public protocol AuthorizationWriter { callback: @escaping (Result) -> Void ) + @available(*, noasync, message: "Use the async alternative") func remove(for url: URL, callback: @escaping (Result) -> Void) } +public extension AuthorizationWriter { + func addOrUpdate( + for url: URL, + user: String, + password: String, + persist: Bool = true + ) async throws { + try await safe_async { + self.addOrUpdate( + for: url, + user: user, + password: password, + persist: persist, + callback: $0) + } + } + + func remove(for url: URL) async throws { + try await safe_async { + self.remove(for: url, callback: $0) + } + } +} + public enum AuthorizationProviderError: Error { case invalidURLHost case notFound - case cannotEncodePassword case other(String) } @@ -48,9 +73,7 @@ extension AuthorizationProvider { return nil } let authString = "\(user):\(password)" - guard let authData = authString.data(using: .utf8) else { - return nil - } + let authData = Data(authString.utf8) return "Basic \(authData.base64EncodedString())" } } @@ -207,9 +230,7 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization return callback(.success(())) } - guard let passwordData = password.data(using: .utf8) else { - return callback(.failure(AuthorizationProviderError.cannotEncodePassword)) - } + let passwordData = Data(password.utf8) do { if !(try self.update(protocolHostPort: protocolHostPort, account: user, password: passwordData)) { @@ -290,12 +311,13 @@ public class KeychainAuthorizationProvider: AuthorizationProvider, Authorization modified: mostRecent[kSecAttrModificationDate as String] as? Date ) as? [String: Any], let passwordData = existingItem[kSecValueData as String] as? Data, - let password = String(data: passwordData, encoding: String.Encoding.utf8), let account = existingItem[kSecAttrAccount as String] as? String else { throw AuthorizationProviderError .other("Failed to extract credentials for '\(protocolHostPort)' from keychain") } + + let password = String(decoding: passwordData, as: UTF8.self) return (user: account, password: password) } catch { diff --git a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift index 8c39a89f26a..effbaa7c4ab 100644 --- a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift +++ b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import _Concurrency import Dispatch import class Foundation.NSLock import class Foundation.ProcessInfo @@ -46,6 +47,36 @@ extension DispatchQueue { ) } +/// Bridges between potentially blocking methods that take a result completion closure and async/await +public func safe_async( + _ body: @Sendable @escaping (@Sendable @escaping (Result) -> Void) -> Void +) async throws -> T { + try await withCheckedThrowingContinuation { continuation in + // It is possible that body make block indefinitely on a lock, semaphore, + // or similar then synchronously call the completion handler. For full safety + // it is essential to move the execution off the swift concurrency pool + DispatchQueue.sharedConcurrent.async { + body { + continuation.resume(with: $0) + } + } + } +} + +/// Bridges between potentially blocking methods that take a result completion closure and async/await +public func safe_async(_ body: @escaping (@escaping (Result) -> Void) -> Void) async -> T { + await withCheckedContinuation { continuation in + // It is possible that body make block indefinitely on a lock, sempahore, + // or similar then synchrously call the completion handler. For full safety + // it is essential to move the execution off the swift concurrency pool + DispatchQueue.sharedConcurrent.async { + body { + continuation.resume(with: $0) + } + } + } +} + #if !canImport(Darwin) // As of Swift 5.7 and 5.8 swift-corelibs-foundation doesn't have `Sendable` annotations yet. extension URL: @unchecked Sendable {} diff --git a/Sources/Basics/Dictionary+Extensions.swift b/Sources/Basics/Dictionary+Extensions.swift index fdc6046999b..f31e0b57104 100644 --- a/Sources/Basics/Dictionary+Extensions.swift +++ b/Sources/Basics/Dictionary+Extensions.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import OrderedCollections - extension Dictionary { @inlinable @discardableResult @@ -36,15 +34,3 @@ extension Dictionary { } } } - -/* - extension OrderedDictionary { - public subscript(key: Key, `default` `default`: Value) -> Value { - set { - self[key] = newValue - } get { - self[key] ?? `default` - } - } - } - */ diff --git a/Sources/Basics/EnvironmentVariables.swift b/Sources/Basics/EnvironmentVariables.swift index bb24179f4b8..25e89781a0f 100644 --- a/Sources/Basics/EnvironmentVariables.swift +++ b/Sources/Basics/EnvironmentVariables.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import Foundation +import class Foundation.ProcessInfo public typealias EnvironmentVariables = [String: String] @@ -58,3 +58,30 @@ extension EnvironmentVariables { return self[pathArg] } } + +// filter env variable that should not be included in a cache as they change +// often and should not be considered in business logic +// rdar://107029374 +extension EnvironmentVariables { + // internal for testing + internal static let nonCachableKeys: Set = [ + "TERM", + "TERM_PROGRAM", + "TERM_PROGRAM_VERSION", + "TERM_SESSION_ID", + "ITERM_PROFILE", + "ITERM_SESSION_ID", + "SECURITYSESSIONID", + "LaunchInstanceID", + "LC_TERMINAL", + "LC_TERMINAL_VERSION", + "CLICOLOR", + "LS_COLORS", + "VSCODE_IPC_HOOK_CLI", + "HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET", + ] + + public var cachable: EnvironmentVariables { + return self.filter { !Self.nonCachableKeys.contains($0.key) } + } +} diff --git a/Sources/Basics/FileSystem/AbsolutePath.swift b/Sources/Basics/FileSystem/AbsolutePath.swift index 32e8ba1eb01..4f4034d08f3 100644 --- a/Sources/Basics/FileSystem/AbsolutePath.swift +++ b/Sources/Basics/FileSystem/AbsolutePath.swift @@ -268,7 +268,7 @@ extension AbsolutePath { } /// Returns the basename dropping any possible extension. - public func basenameWithoutAnyExtension() -> String { + public var basenameWithoutAnyExtension: String { var basename = self.basename if let index = basename.firstIndex(of: ".") { basename.removeSubrange(index ..< basename.endIndex) @@ -320,7 +320,7 @@ extension AbsolutePath { } extension AbsolutePath { - public func escapedPathString() -> String { + public var escapedPathString: String { self.pathString.replacingOccurrences(of: "\\", with: "\\\\") } } diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index 5cfed5aae7b..e4e18dc0ecb 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -496,7 +496,7 @@ extension FileSystem { } } - public func getSharedSwiftSDKsDirectory(explicitDirectory: AbsolutePath?) throws -> AbsolutePath? { + public func getSharedSwiftSDKsDirectory(explicitDirectory: AbsolutePath?) throws -> AbsolutePath { if let explicitDirectory { // Create the explicit SDKs path if necessary if !exists(explicitDirectory) { diff --git a/Sources/Basics/FileSystem/TemporaryFile.swift b/Sources/Basics/FileSystem/TemporaryFile.swift index cb7ce4eaae8..a8e31126448 100644 --- a/Sources/Basics/FileSystem/TemporaryFile.swift +++ b/Sources/Basics/FileSystem/TemporaryFile.swift @@ -66,6 +66,7 @@ public func withTemporaryDirectory( /// return value for the `withTemporaryDirectory` function. /// /// - Throws: An error when creating directory and rethrows all errors from `body`. +@discardableResult public func withTemporaryDirectory( fileSystem: FileSystem = localFileSystem, dir: AbsolutePath? = nil, diff --git a/Sources/Basics/HTTPClient/HTTPClientResponse.swift b/Sources/Basics/HTTPClient/HTTPClientResponse.swift index 7c7fd1b4c79..201d12530b5 100644 --- a/Sources/Basics/HTTPClient/HTTPClientResponse.swift +++ b/Sources/Basics/HTTPClient/HTTPClientResponse.swift @@ -37,7 +37,7 @@ public struct HTTPClientResponse: Sendable { extension HTTPClientResponse { public static func okay(body: String? = nil) -> HTTPClientResponse { - .okay(body: body?.data(using: .utf8)) + .okay(body: body.map { Data($0.utf8) }) } public static func okay(body: Data?) -> HTTPClientResponse { @@ -45,10 +45,10 @@ extension HTTPClientResponse { } public static func notFound(reason: String? = nil) -> HTTPClientResponse { - HTTPClientResponse(statusCode: 404, body: (reason ?? "Not Found").data(using: .utf8)) + HTTPClientResponse(statusCode: 404, body: Data((reason ?? "Not Found").utf8)) } public static func serverError(reason: String? = nil) -> HTTPClientResponse { - HTTPClientResponse(statusCode: 500, body: (reason ?? "Internal Server Error").data(using: .utf8)) + HTTPClientResponse(statusCode: 500, body: Data((reason ?? "Internal Server Error").utf8)) } } diff --git a/Sources/Basics/ImportScanning.swift b/Sources/Basics/ImportScanning.swift index 9f931db3d50..b517655d629 100644 --- a/Sources/Basics/ImportScanning.swift +++ b/Sources/Basics/ImportScanning.swift @@ -23,11 +23,7 @@ private struct Imports: Decodable { } public protocol ImportScanner { - func scanImports( - _ filePathToScan: AbsolutePath, - callbackQueue: DispatchQueue, - completion: @escaping (Result<[String], Error>) -> Void - ) + func scanImports(_ filePathToScan: AbsolutePath) async throws -> [String] } public struct SwiftcImportScanner: ImportScanner { @@ -45,32 +41,15 @@ public struct SwiftcImportScanner: ImportScanner { self.swiftCompilerPath = swiftCompilerPath } - public func scanImports( - _ filePathToScan: AbsolutePath, - callbackQueue: DispatchQueue, - completion: @escaping (Result<[String], Error>) -> Void - ) { + public func scanImports(_ filePathToScan: AbsolutePath) async throws -> [String] { let cmd = [swiftCompilerPath.pathString, filePathToScan.pathString, "-scan-dependencies", "-Xfrontend", "-import-prescan"] + self.swiftCompilerFlags - TSCBasic.Process - .popen(arguments: cmd, environment: self.swiftCompilerEnvironment, queue: callbackQueue) { result in - dispatchPrecondition(condition: .onQueue(callbackQueue)) - - do { - let stdout = try result.get().utf8Output() - let imports = try JSONDecoder.makeWithDefaults().decode(Imports.self, from: stdout).imports - .filter { !defaultImports.contains($0) } + let result = try await TSCBasic.Process.popen(arguments: cmd, environment: self.swiftCompilerEnvironment) - callbackQueue.async { - completion(.success(imports)) - } - } catch { - callbackQueue.async { - completion(.failure(error)) - } - } - } + let stdout = try result.utf8Output() + return try JSONDecoder.makeWithDefaults().decode(Imports.self, from: stdout).imports + .filter { !defaultImports.contains($0) } } } diff --git a/Sources/Basics/JSONDecoder+Extensions.swift b/Sources/Basics/JSONDecoder+Extensions.swift index e3051857211..7a5e9cd07b6 100644 --- a/Sources/Basics/JSONDecoder+Extensions.swift +++ b/Sources/Basics/JSONDecoder+Extensions.swift @@ -14,11 +14,7 @@ import Foundation extension JSONDecoder { public func decode(_ type: T.Type, from string: String) throws -> T where T: Decodable { - guard let data = string.data(using: .utf8) else { - let context = DecodingError.Context(codingPath: [], debugDescription: "invalid UTF-8 string") - throw DecodingError.dataCorrupted(context) - } - + let data = Data(string.utf8) return try self.decode(type, from: data) } } diff --git a/Sources/Basics/SQLite.swift b/Sources/Basics/SQLite.swift index 785d180ae34..31fbe3ab2fa 100644 --- a/Sources/Basics/SQLite.swift +++ b/Sources/Basics/SQLite.swift @@ -12,7 +12,11 @@ import Foundation +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SPMSQLite3 +#else +import SPMSQLite3 +#endif /// A minimal SQLite wrapper. public final class SQLite { diff --git a/Sources/Basics/SwiftVersion.swift b/Sources/Basics/SwiftVersion.swift index 10be26ef8ba..c68a4cb0264 100644 --- a/Sources/Basics/SwiftVersion.swift +++ b/Sources/Basics/SwiftVersion.swift @@ -10,7 +10,11 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import TSCclibc +#else +import TSCclibc +#endif public struct SwiftVersion { /// The version number. @@ -54,7 +58,7 @@ public struct SwiftVersion { extension SwiftVersion { /// The current version of the package manager. public static let current = SwiftVersion( - version: (5, 9, 0), + version: (5, 10, 0), isDevelopment: true, buildIdentifier: getBuildIdentifier() ) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index afb56498769..a7664bd39b9 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -64,13 +64,11 @@ extension Triple { /// This is currently meant for Apple platforms only. public func tripleString(forPlatformVersion version: String) -> String { precondition(isDarwin()) - // This function did not handle triples with a specific environments and - // object formats previously to using SwiftDriver.Triple and still does - // not. return """ \(self.archName)-\ \(self.vendorName)-\ - \(self.osNameUnversioned)\(version) + \(self.osNameUnversioned)\(version)\ + \(self.environmentName.isEmpty ? "" : "-\(self.environmentName)") """ } @@ -136,7 +134,7 @@ extension Triple { } switch os { - case .darwin, .macosx: + case _ where isDarwin(): return ".dylib" case .linux, .openbsd: return ".so" @@ -155,7 +153,7 @@ extension Triple { } switch os { - case .darwin, .macosx: + case _ where isDarwin(): return "" case .linux, .openbsd: return "" @@ -178,13 +176,31 @@ extension Triple { /// The file extension for Foundation-style bundle. public var nsbundleExtension: String { switch os { - case .darwin, .macosx: + case _ where isDarwin(): return ".bundle" default: // See: https://github.com/apple/swift-corelibs-foundation/blob/master/Docs/FHS%20Bundles.md return ".resources" } } + + /// Returns `true` if code compiled for `triple` can run on `self` value of ``Triple``. + public func isRuntimeCompatible(with triple: Triple) -> Bool { + guard self != triple else { + return true + } + + if + self.arch == triple.arch && + self.vendor == triple.vendor && + self.os == triple.os && + self.environment == triple.environment + { + return self.osVersion >= triple.osVersion + } else { + return false + } + } } extension Triple: CustomStringConvertible { @@ -192,7 +208,11 @@ extension Triple: CustomStringConvertible { } extension Triple: Equatable { - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.triple == rhs.triple + public static func ==(lhs: Triple, rhs: Triple) -> Bool { + lhs.arch == rhs.arch + && lhs.vendor == rhs.vendor + && lhs.os == rhs.os + && lhs.environment == rhs.environment + && lhs.osVersion == rhs.osVersion } } diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index 9826e7469fb..bc4e9a03afa 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -295,7 +295,7 @@ public final class ClangTargetBuildDescription { } // Enable the correct lto mode if requested. - switch self.buildParameters.linkTimeOptimizationMode { + switch self.buildParameters.linkingParameters.linkTimeOptimizationMode { case nil: break case .full: @@ -304,6 +304,17 @@ public final class ClangTargetBuildDescription { args += ["-flto=thin"] } + // rdar://117578677 + // Pass -fno-omit-frame-pointer to support backtraces + // this can be removed once the backtracer uses DWARF instead of frame pointers + if let omitFramePointers = self.buildParameters.debuggingParameters.omitFramePointers { + if omitFramePointers { + args += ["-fomit-frame-pointer"] + } else { + args += ["-fno-omit-frame-pointer"] + } + } + // Pass default include paths from the toolchain. for includeSearchPath in self.buildParameters.toolchain.includeSearchPaths { args += ["-I", includeSearchPath.pathString] diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index 8ef750dd88f..bd63fa749c4 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import DriverSupport import PackageGraph import PackageModel import OrderedCollections @@ -107,7 +106,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription } private var deadStripArguments: [String] { - if !self.buildParameters.linkerDeadStrip { + if !self.buildParameters.linkingParameters.linkerDeadStrip { return [] } @@ -124,7 +123,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // during GC, and it removes Swift metadata sections like swift5_protocols // We should add support of SHF_GNU_RETAIN-like flag for __attribute__((retain)) // to LLVM and wasm-ld - // This workaround is required for not only WASI but also all WebAssembly archs + // This workaround is required for not only WASI but also all WebAssembly triples // using wasm-ld (e.g. wasm32-unknown-unknown). So this branch is conditioned by // arch == .wasm32 return [] @@ -154,7 +153,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += self.additionalFlags // pass `-v` during verbose builds. - if self.buildParameters.verboseOutput { + if self.buildParameters.outputParameters.isVerbose { args += ["-v"] } @@ -169,7 +168,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += self.dylibs.map { "-l" + $0.product.name } // Add arguments needed for code coverage if it is enabled. - if self.buildParameters.enableCodeCoverage { + if self.buildParameters.testingParameters.enableCodeCoverage { args += ["-profile-coverage-mapping", "-profile-generate"] } @@ -187,6 +186,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription derivedProductType = self.product.type } + var isLinkingStaticStdlib = false switch derivedProductType { case .macro: throw InternalError("macro not supported") // should never be reached @@ -197,7 +197,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription return [] case .test: // Test products are bundle when using objectiveC, executable when using test entry point. - switch self.buildParameters.testProductStyle { + switch self.buildParameters.testingParameters.testProductStyle { case .loadableBundle: args += ["-Xlinker", "-bundle"] case .entryPointExecutable: @@ -213,11 +213,13 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += self.deadStripArguments case .executable, .snippet: // Link the Swift stdlib statically, if requested. - if self.buildParameters.shouldLinkStaticSwiftStdlib { + // TODO: unify this logic with SwiftTargetBuildDescription.stdlibArguments + if self.buildParameters.linkingParameters.shouldLinkStaticSwiftStdlib { if self.buildParameters.targetTriple.isDarwin() { self.observabilityScope.emit(.swiftBackDeployError) } else if self.buildParameters.targetTriple.isSupportingStaticStdlib { args += ["-static-stdlib"] + isLinkingStaticStdlib = true } } args += ["-emit-executable"] @@ -232,8 +234,10 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // Support for linking tests against executables is conditional on the tools // version of the package that defines the executable product. let executableTarget = try product.executableTarget - if let target = executableTarget.underlyingTarget as? SwiftTarget, self.toolsVersion >= .v5_5, - self.buildParameters.canRenameEntrypointFunctionName, target.supportsTestableExecutablesFeature + if let target = executableTarget.underlyingTarget as? SwiftTarget, + self.toolsVersion >= .v5_5, + self.buildParameters.driverParameters.canRenameEntrypointFunctionName, + target.supportsTestableExecutablesFeature { if let flags = buildParameters.linkerFlagsForRenamingMainFunction(of: executableTarget) { args += flags @@ -243,13 +247,25 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription throw InternalError("unexpectedly asked to generate linker arguments for a plugin product") } + if let resourcesPath = self.buildParameters.toolchain.swiftResourcesPath(isStatic: isLinkingStaticStdlib) { + args += ["-resource-dir", "\(resourcesPath)"] + } + + // clang resources are always in lib/swift/ + if let dynamicResourcesPath = self.buildParameters.toolchain.swiftResourcesPath { + let clangResourcesPath = dynamicResourcesPath.appending("clang") + args += ["-Xclang-linker", "-resource-dir", "-Xclang-linker", "\(clangResourcesPath)"] + } + // Set rpath such that dynamic libraries are looked up - // adjacent to the product. - if self.buildParameters.targetTriple.isLinux() { - args += ["-Xlinker", "-rpath=$ORIGIN"] - } else if self.buildParameters.targetTriple.isDarwin() { - let rpath = self.product.type == .test ? "@loader_path/../../../" : "@loader_path" - args += ["-Xlinker", "-rpath", "-Xlinker", rpath] + // adjacent to the product, unless overridden. + if !self.buildParameters.linkingParameters.shouldDisableLocalRpath { + if self.buildParameters.targetTriple.isLinux() { + args += ["-Xlinker", "-rpath=$ORIGIN"] + } else if self.buildParameters.targetTriple.isDarwin() { + let rpath = self.product.type == .test ? "@loader_path/../../../" : "@loader_path" + args += ["-Xlinker", "-rpath", "-Xlinker", rpath] + } } args += ["@\(self.linkFileListPath.pathString)"] @@ -267,16 +283,16 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // When deploying to macOS prior to macOS 12, add an rpath to the // back-deployed concurrency libraries. - if useStdlibRpath, self.buildParameters.targetTriple.isDarwin(), - let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS), - macOSSupportedPlatform.version.major < 12 - { - let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib - .parentDirectory - .parentDirectory - .appending("swift-5.5") - .appending("macosx") - args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString] + if useStdlibRpath, self.buildParameters.targetTriple.isMacOSX { + let macOSSupportedPlatform = self.package.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest) + if macOSSupportedPlatform.version.major < 12 { + let backDeployedStdlib = try buildParameters.toolchain.macosSwiftStdlib + .parentDirectory + .parentDirectory + .appending("swift-5.5") + .appending("macosx") + args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString] + } } } @@ -295,7 +311,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += try self.buildParameters.targetTripleArgs(for: self.product.targets[0]) // Add arguments from declared build settings. - args += self.buildSettingsFlags() + args += self.buildSettingsFlags // Add AST paths to make the product debuggable. This array is only populated when we're // building for Darwin in debug configuration. @@ -314,14 +330,6 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += ["-L", librarySearchPath.pathString] } - // Add toolchain's libdir at the very end (even after the user -Xlinker arguments). - // - // This will allow linking to libraries shipped in the toolchain. - let toolchainLibDir = try buildParameters.toolchain.toolchainLibDir - if self.fileSystem.isDirectory(toolchainLibDir) { - args += ["-L", toolchainLibDir.pathString] - } - // Library search path for the toolchain's copy of SwiftSyntax. #if BUILD_MACROS_AS_DYLIBS if product.type == .macro { @@ -333,19 +341,19 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription } /// Returns the build flags from the declared build settings. - private func buildSettingsFlags() -> [String] { + private var buildSettingsFlags: [String] { var flags: [String] = [] // Linked libraries. - let libraries = OrderedSet(staticTargets.reduce([]) { - $0 + buildParameters.createScope(for: $1).evaluate(.LINK_LIBRARIES) + let libraries = OrderedSet(self.staticTargets.reduce([]) { + $0 + self.buildParameters.createScope(for: $1).evaluate(.LINK_LIBRARIES) }) flags += libraries.map { "-l" + $0 } // Linked frameworks. if self.buildParameters.targetTriple.supportsFrameworks { - let frameworks = OrderedSet(staticTargets.reduce([]) { - $0 + buildParameters.createScope(for: $1).evaluate(.LINK_FRAMEWORKS) + let frameworks = OrderedSet(self.staticTargets.reduce([]) { + $0 + self.buildParameters.createScope(for: $1).evaluate(.LINK_FRAMEWORKS) }) flags += frameworks.flatMap { ["-framework", $0] } } @@ -358,6 +366,10 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription return flags } + + func codeSigningArguments(plistPath: AbsolutePath, binaryPath: AbsolutePath) -> [String] { + ["codesign", "--force", "--sign", "-", "--entitlements", plistPath.pathString, binaryPath.pathString] + } } extension SortedArray where Element == AbsolutePath { diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index de3f9bd9d03..ed342c6b303 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -16,7 +16,12 @@ import PackageGraph import PackageLoading import PackageModel import SPMBuildCore + +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import DriverSupport +#else +import DriverSupport +#endif import struct TSCBasic.ByteString @@ -50,8 +55,6 @@ public final class SwiftTargetBuildDescription { /// These are the resource files derived from plugins. private var pluginDerivedResources: [Resource] - private let driverSupport = DriverSupport() - /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { if let bundleName = target.underlyingTarget.potentialBundleName, needsResourceBundle { @@ -90,7 +93,7 @@ public final class SwiftTargetBuildDescription { let relativeSources = self.target.sources.relativePaths + self.derivedSources.relativePaths + self.pluginDerivedSources.relativePaths - let ltoEnabled = self.buildParameters.linkTimeOptimizationMode != nil + let ltoEnabled = self.buildParameters.linkingParameters.linkTimeOptimizationMode != nil let objectFileExtension = ltoEnabled ? "bc" : "o" return try relativeSources.map { try AbsolutePath( @@ -234,6 +237,9 @@ public final class SwiftTargetBuildDescription { /// ObservabilityScope with which to emit diagnostics private let observabilityScope: ObservabilityScope + /// Whether or not to generate code for test observation. + private let shouldGenerateTestObservation: Bool + /// Create a new target description with target and build parameters. init( package: ResolvedPackage, @@ -245,6 +251,7 @@ public final class SwiftTargetBuildDescription { prebuildCommandResults: [PrebuildCommandResult] = [], requiredMacroProducts: [ResolvedProduct] = [], testTargetRole: TestTargetRole? = nil, + shouldGenerateTestObservation: Bool = false, fileSystem: FileSystem, observabilityScope: ObservabilityScope, isWithinMixedTarget: Bool = false @@ -252,6 +259,7 @@ public final class SwiftTargetBuildDescription { guard target.underlyingTarget is SwiftTarget else { throw InternalError("underlying target type mismatch \(target)") } + self.package = package self.target = target self.toolsVersion = toolsVersion @@ -264,12 +272,14 @@ public final class SwiftTargetBuildDescription { } else { self.testTargetRole = nil } - self.fileSystem = fileSystem + self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") self.derivedSources = Sources(paths: [], root: self.tempsPath.appending("DerivedSources")) self.buildToolPluginInvocationResults = buildToolPluginInvocationResults self.prebuildCommandResults = prebuildCommandResults self.requiredMacroProducts = requiredMacroProducts + self.shouldGenerateTestObservation = shouldGenerateTestObservation + self.fileSystem = fileSystem self.observabilityScope = observabilityScope self.isWithinMixedTarget = isWithinMixedTarget @@ -301,6 +311,31 @@ public final class SwiftTargetBuildDescription { } try self.generateResourceEmbeddingCode() + try self.generateTestObservation() + } + + private func generateTestObservation() throws { + guard target.type == .test else { + return + } + + let subpath = try RelativePath(validating: "test_observation.swift") + let path = self.derivedSources.root.appending(subpath) + + guard shouldGenerateTestObservation else { + _ = try? fileSystem.removeFileTree(path) + return + } + + guard buildParameters.targetTriple.isDarwin(), buildParameters.testingParameters.experimentalTestOutput else { + return + } + + let content = generateTestObservationCode(buildParameters: self.buildParameters) + + // FIXME: We should generate this file during the actual build. + self.derivedSources.relativePaths.append(subpath) + try self.fileSystem.writeIfChanged(path: path, string: content) } // FIXME: This will not work well for large files, as we will store the entire contents, plus its byte array representation in memory and also `writeIfChanged()` will read the entire generated file again. @@ -353,7 +388,7 @@ public final class SwiftTargetBuildDescription { let content = """ - \(self.toolsVersion < .vNext ? "import" : "@_implementationOnly import") Foundation + import Foundation extension Foundation.Bundle { static let module: Bundle = { @@ -363,7 +398,8 @@ public final class SwiftTargetBuildDescription { let preferredBundle = Bundle(path: mainPath) guard let bundle = preferredBundle ?? Bundle(path: buildPath) else { - fatalError("could not load resource bundle: from \\(mainPath) or \\(buildPath)") + // Users can write a function called fatalError themselves, we should be resilient against that. + Swift.fatalError("could not load resource bundle: from \\(mainPath) or \\(buildPath)") } return bundle @@ -385,7 +421,7 @@ public final class SwiftTargetBuildDescription { private func packageNameArgumentIfSupported(with pkg: ResolvedPackage, packageAccess: Bool) -> [String] { let flag = "-package-name" if pkg.manifest.usePackageNameFlag, - driverSupport.checkToolchainDriverFlags(flags: [flag], toolchain: self.buildParameters.toolchain, fileSystem: self.fileSystem) { + DriverSupport.checkToolchainDriverFlags(flags: [flag], toolchain: self.buildParameters.toolchain, fileSystem: self.fileSystem) { if packageAccess { let pkgID = pkg.identity.description.spm_mangledToC99ExtendedIdentifier() return [flag, pkgID] @@ -413,7 +449,7 @@ public final class SwiftTargetBuildDescription { #endif // If we're using an OSS toolchain, add the required arguments bringing in the plugin server from the default toolchain if available. - if self.buildParameters.toolchain.isSwiftDevelopmentToolchain, driverSupport.checkSupportedFrontendFlags(flags: ["-external-plugin-path"], toolchain: self.buildParameters.toolchain, fileSystem: self.fileSystem), let pluginServer = self.buildParameters.toolchain.swiftPluginServerPath { + if self.buildParameters.toolchain.isSwiftDevelopmentToolchain, DriverSupport.checkSupportedFrontendFlags(flags: ["-external-plugin-path"], toolchain: self.buildParameters.toolchain, fileSystem: self.fileSystem), let pluginServer = try self.buildParameters.toolchain.swiftPluginServerPath { let toolchainUsrPath = pluginServer.parentDirectory.parentDirectory let pluginPathComponents = ["lib", "swift", "host", "plugins"] @@ -434,7 +470,7 @@ public final class SwiftTargetBuildDescription { args += ["-swift-version", self.swiftVersion.rawValue] // pass `-v` during verbose builds. - if self.buildParameters.verboseOutput { + if self.buildParameters.outputParameters.isVerbose { args += ["-v"] } @@ -477,7 +513,7 @@ public final class SwiftTargetBuildDescription { // we can rename the symbol unconditionally. // No `-` for these flags because the set of Strings in driver.supportedFrontendFlags do // not have a leading `-` - if self.buildParameters.canRenameEntrypointFunctionName, + if self.buildParameters.driverParameters.canRenameEntrypointFunctionName, self.buildParameters.linkerFlagsForRenamingMainFunction(of: self.target) != nil { args += ["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "\(self.target.c99name)_main"] @@ -500,12 +536,12 @@ public final class SwiftTargetBuildDescription { } // Add arguments needed for code coverage if it is enabled. - if self.buildParameters.enableCodeCoverage { + if self.buildParameters.testingParameters.enableCodeCoverage { args += ["-profile-coverage-mapping", "-profile-generate"] } // Add arguments to colorize output if stdout is tty - if self.buildParameters.colorizedOutput { + if self.buildParameters.outputParameters.isColorized { args += ["-color-diagnostics"] } @@ -514,7 +550,7 @@ public final class SwiftTargetBuildDescription { // Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other // way. - if self.buildParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { + if self.buildParameters.driverParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString] } @@ -532,8 +568,8 @@ public final class SwiftTargetBuildDescription { // // User arguments (from -Xcxx) should follow generated arguments to allow user overrides // args += self.buildParameters.flags.cxxCompilerFlags.asSwiftcCXXCompilerFlags() - // Enable the correct lto mode if requested. - switch self.buildParameters.linkTimeOptimizationMode { + // Enable the correct LTO mode if requested. + switch self.buildParameters.linkingParameters.linkTimeOptimizationMode { case nil: break case .full: @@ -563,6 +599,17 @@ public final class SwiftTargetBuildDescription { args += self.packageNameArgumentIfSupported(with: self.package, packageAccess: self.target.packageAccess) args += try self.macroArguments() + + // rdar://117578677 + // Pass -fno-omit-frame-pointer to support backtraces + // this can be removed once the backtracer uses DWARF instead of frame pointers + if let omitFramePointers = self.buildParameters.debuggingParameters.omitFramePointers { + if omitFramePointers { + args += ["-Xcc", "-fomit-frame-pointer"] + } else { + args += ["-Xcc", "-fno-omit-frame-pointer"] + } + } return args } @@ -664,7 +711,7 @@ public final class SwiftTargetBuildDescription { // Write out the entries for each source file. let sources = self.sources let objects = try self.objects - let ltoEnabled = self.buildParameters.linkTimeOptimizationMode != nil + let ltoEnabled = self.buildParameters.linkingParameters.linkTimeOptimizationMode != nil let objectKey = ltoEnabled ? "llvm-bc" : "object" for idx in 0.. [Node] { + let standards = [ + (target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), + (target.clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions), + ] + + var inputs: [Node] = inputs + + if createResourceBundle { + // Add resources node as the input to the target. This isn't great because we + // don't need to block building of a module until its resources are assembled but + // we don't currently have a good way to express that resources should be built + // whenever a module is being built. + if let resourcesNode = try self.createResourcesBundle(for: .clang(target)) { + inputs.append(resourcesNode) + } + } + + func addStaticTargetInputs(_ target: ResolvedTarget) { + if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { + inputs.append(file: desc.moduleOutputPath) + } else if case .mixed(let desc)? = plan.targetMap[target], target.type == .library { + inputs.append(file: desc.swiftTargetBuildDescription.moduleOutputPath) + } + } + + for dependency in target.target.dependencies(satisfying: self.buildEnvironment) { + switch dependency { + case .target(let target, _): + addStaticTargetInputs(target) + + case .product(let product, _): + switch product.type { + case .executable, .snippet, .library(.dynamic), .macro: + guard let planProduct = plan.productMap[product] else { + throw InternalError("unknown product \(product)") + } + // Establish a dependency on binary of the product. + let binary = try planProduct.binaryPath + inputs.append(file: binary) + + case .library(.automatic), .library(.static), .plugin: + for target in product.targets { + addStaticTargetInputs(target) + } + case .test: + break + } + } + } + + for binaryPath in target.libraryBinaryPaths { + let path = destinationPath(forBinaryAt: binaryPath) + if self.fileSystem.isDirectory(binaryPath) { + inputs.append(directory: path) + } else { + inputs.append(file: path) + } + } + + var objectFileNodes: [Node] = [] + + for path in try target.compilePaths() { + let isCXX = path.source.extension.map { SupportedLanguageExtension.cppExtensions.contains($0) } ?? false + let isC = path.source.extension.map { $0 == SupportedLanguageExtension.c.rawValue } ?? false + + var args = try target.basicArguments(isCXX: isCXX, isC: isC) + + args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString] + + // Add language standard flag if needed. + if let ext = path.source.extension { + for (standard, validExtensions) in standards { + if let standard, validExtensions.contains(ext) { + args += ["-std=\(standard)"] + } + } + } + + args += ["-c", path.source.pathString, "-o", path.object.pathString] + + let clangCompiler = try buildParameters.toolchain.getClangCompiler().pathString + args.insert(clangCompiler, at: 0) + + let objectFileNode: Node = .file(path.object) + objectFileNodes.append(objectFileNode) + + self.manifest.addClangCmd( + name: path.object.pathString, + description: "Compiling \(target.target.name) \(path.filename)", + inputs: inputs + [.file(path.source)], + outputs: [objectFileNode], + arguments: args, + dependencies: path.deps.pathString + ) + } + + try addBuildToolPlugins(.clang(target)) + + if addTargetCmd { + self.addTargetCmd( + target: target.target, + isTestTarget: target.isTestTarget, + inputs: objectFileNodes + ) + } + + return objectFileNodes + } +} diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift new file mode 100644 index 00000000000..5dc406bb264 --- /dev/null +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift @@ -0,0 +1,122 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 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 struct Basics.AbsolutePath +import struct LLBuildManifest.Node + +extension LLBuildManifestBuilder { + func createProductCommand(_ buildProduct: ProductBuildDescription) throws { + let cmdName = try buildProduct.product.getCommandName(config: self.buildConfig) + + // Add dependency on Info.plist generation on Darwin platforms. + let testInputs: [AbsolutePath] + if buildProduct.product.type == .test + && buildProduct.buildParameters.targetTriple.isDarwin() + && buildProduct.buildParameters.testingParameters.experimentalTestOutput { + let testBundleInfoPlistPath = try buildProduct.binaryPath.parentDirectory.parentDirectory.appending(component: "Info.plist") + testInputs = [testBundleInfoPlistPath] + + self.manifest.addWriteInfoPlistCommand( + principalClass: "\(buildProduct.product.targets[0].c99name).SwiftPMXCTestObserver", + outputPath: testBundleInfoPlistPath + ) + } else { + testInputs = [] + } + + // Create a phony node to represent the entire target. + let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig) + let output: Node = .virtual(targetName) + + let finalProductNode: Node + switch buildProduct.product.type { + case .library(.static): + finalProductNode = try .file(buildProduct.binaryPath) + try self.manifest.addShellCmd( + name: cmdName, + description: "Archiving \(buildProduct.binaryPath.prettyPath())", + inputs: (buildProduct.objects + [buildProduct.linkFileListPath]).map(Node.file), + outputs: [finalProductNode], + arguments: try buildProduct.archiveArguments() + ) + + default: + let inputs = try buildProduct.objects + + buildProduct.dylibs.map { try $0.binaryPath } + + [buildProduct.linkFileListPath] + + testInputs + + let shouldCodeSign: Bool + let linkedBinaryNode: Node + let linkedBinaryPath = try buildProduct.binaryPath + if case .executable = buildProduct.product.type, + buildParameters.targetTriple.isMacOSX, + buildParameters.debuggingParameters.shouldEnableDebuggingEntitlement { + shouldCodeSign = true + linkedBinaryNode = try .file(buildProduct.binaryPath, isMutated: true) + } else { + shouldCodeSign = false + linkedBinaryNode = try .file(buildProduct.binaryPath) + } + + try self.manifest.addShellCmd( + name: cmdName, + description: "Linking \(buildProduct.binaryPath.prettyPath())", + inputs: inputs.map(Node.file), + outputs: [linkedBinaryNode], + arguments: try buildProduct.linkArguments() + ) + + if shouldCodeSign { + let basename = try buildProduct.binaryPath.basename + let plistPath = try buildProduct.binaryPath.parentDirectory + .appending(component: "\(basename)-entitlement.plist") + self.manifest.addEntitlementPlistCommand( + entitlement: "com.apple.security.get-task-allow", + outputPath: plistPath + ) + + let cmdName = try buildProduct.product.getCommandName(config: self.buildConfig) + let codeSigningOutput = Node.virtual(targetName + "-CodeSigning") + try self.manifest.addShellCmd( + name: "\(cmdName)-entitlements", + description: "Applying debug entitlements to \(buildProduct.binaryPath.prettyPath())", + inputs: [linkedBinaryNode, .file(plistPath)], + outputs: [codeSigningOutput], + arguments: buildProduct.codeSigningArguments(plistPath: plistPath, binaryPath: linkedBinaryPath) + ) + finalProductNode = codeSigningOutput + } else { + finalProductNode = linkedBinaryNode + } + } + + self.manifest.addNode(output, toTarget: targetName) + self.manifest.addPhonyCmd( + name: output.name, + inputs: [finalProductNode], + outputs: [output] + ) + + if self.plan.graph.reachableProducts.contains(buildProduct.product) { + if buildProduct.product.type != .test { + self.addNode(output, toTarget: .main) + } + self.addNode(output, toTarget: .test) + } + + self.manifest.addWriteLinkFileListCommand( + objects: Array(buildProduct.objects), + linkFileListPath: buildProduct.linkFileListPath + ) + } +} diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift new file mode 100644 index 00000000000..308618c3197 --- /dev/null +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 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 struct LLBuildManifest.Node +import struct Basics.RelativePath + +extension LLBuildManifestBuilder { + /// Adds command for creating the resources bundle of the given target. + /// + /// Returns the virtual node that will build the entire bundle. + func createResourcesBundle( + for target: TargetBuildDescription + ) throws -> Node? { + guard let bundlePath = target.bundlePath else { return nil } + + var outputs: [Node] = [] + + let infoPlistDestination = try RelativePath(validating: "Info.plist") + + // Create a copy command for each resource file. + for resource in target.resources { + switch resource.rule { + case .copy, .process: + let destination = try bundlePath.appending(resource.destination) + let (_, output) = addCopyCommand(from: resource.path, to: destination) + outputs.append(output) + case .embedInCode: + break + } + } + + // Create a copy command for the Info.plist if a resource with the same name doesn't exist yet. + if let infoPlistPath = target.resourceBundleInfoPlistPath { + let destination = bundlePath.appending(infoPlistDestination) + let (_, output) = addCopyCommand(from: infoPlistPath, to: destination) + outputs.append(output) + } + + let cmdName = target.target.getLLBuildResourcesCmdName(config: self.buildConfig) + self.manifest.addPhonyCmd(name: cmdName, inputs: outputs, outputs: [.virtual(cmdName)]) + + return .virtual(cmdName) + } +} diff --git a/Sources/Build/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift similarity index 54% rename from Sources/Build/LLBuildManifestBuilder.swift rename to Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index fd65ac2a7a1..7f077bbb863 100644 --- a/Sources/Build/LLBuildManifestBuilder.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2015-2016 Apple Inc. and the Swift project authors +// Copyright (c) 2015-2023 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 @@ -10,247 +10,32 @@ // //===----------------------------------------------------------------------===// -import Basics -@_implementationOnly import DriverSupport -import LLBuildManifest -import PackageGraph -import PackageModel -import SPMBuildCore -@_implementationOnly import SwiftDriver - -import struct TSCBasic.ByteString +import struct Basics.InternalError +import struct Basics.AbsolutePath +import struct Basics.RelativePath +import struct Basics.TSCAbsolutePath +import struct LLBuildManifest.Node +import struct LLBuildManifest.LLBuildManifest +import struct SPMBuildCore.BuildParameters +import class PackageGraph.ResolvedTarget +import protocol TSCBasic.FileSystem import enum TSCBasic.ProcessEnv import func TSCBasic.topologicalSort -public class LLBuildManifestBuilder { - public enum TargetKind { - case main - case test - - public var targetName: String { - switch self { - case .main: return "main" - case .test: return "test" - } - } - } - - /// The build plan to work on. - public let plan: BuildPlan - - /// Whether to sandbox commands from build tool plugins. - public let disableSandboxForPluginCommands: Bool - - /// File system reference. - private let fileSystem: FileSystem - - /// ObservabilityScope with which to emit diagnostics - public let observabilityScope: ObservabilityScope - - public private(set) var manifest: BuildManifest = .init() - - var buildConfig: String { self.buildParameters.configuration.dirname } - var buildParameters: BuildParameters { self.plan.buildParameters } - var buildEnvironment: BuildEnvironment { self.buildParameters.buildEnvironment } - - /// Create a new builder with a build plan. - public init( - _ plan: BuildPlan, - disableSandboxForPluginCommands: Bool = false, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) { - self.plan = plan - self.disableSandboxForPluginCommands = disableSandboxForPluginCommands - self.fileSystem = fileSystem - self.observabilityScope = observabilityScope - } - - // MARK: - Generate Manifest - - /// Generate manifest at the given path. - @discardableResult - public func generateManifest(at path: AbsolutePath) throws -> BuildManifest { - self.manifest.createTarget(TargetKind.main.targetName) - self.manifest.createTarget(TargetKind.test.targetName) - self.manifest.defaultTarget = TargetKind.main.targetName - - addPackageStructureCommand() - addBinaryDependencyCommands() - if self.buildParameters.useExplicitModuleBuild { - // Explicit module builds use the integrated driver directly and - // require that every target's build jobs specify its dependencies explicitly to plan - // its build. - // Currently behind: - // --experimental-explicit-module-build - try addTargetsToExplicitBuildManifest() - } else { - // Create commands for all target descriptions in the plan. - for (_, description) in self.plan.targetMap { - switch description { - case .swift(let desc): - try self.createSwiftCompileCommand(desc) - case .clang(let desc): - try self.createClangCompileCommand(desc) - case .mixed(let desc): - try self.createMixedCompileCommand(desc) - } - } - } - - try self.addTestDiscoveryGenerationCommand() - try self.addTestEntryPointGenerationCommand() - - // Create command for all products in the plan. - for (_, description) in self.plan.productMap { - try self.createProductCommand(description) - } - - try ManifestWriter(fileSystem: self.fileSystem).write(self.manifest, at: path) - return self.manifest - } - - func addNode(_ node: Node, toTarget targetKind: TargetKind) { - self.manifest.addNode(node, toTarget: targetKind.targetName) - } -} - -// MARK: - Package Structure - -extension LLBuildManifestBuilder { - private func addPackageStructureCommand() { - let inputs = self.plan.graph.rootPackages.flatMap { package -> [Node] in - var inputs = package.targets - .map(\.sources.root) - .sorted() - .map { Node.directoryStructure($0) } - - // Add the output paths of any prebuilds that were run, so that we redo the plan if they change. - var derivedSourceDirPaths: [AbsolutePath] = [] - for result in plan.prebuildCommandResults.values.flatMap({ $0 }) { - derivedSourceDirPaths.append(contentsOf: result.outputDirectories) - } - inputs.append(contentsOf: derivedSourceDirPaths.sorted().map { Node.directoryStructure($0) }) - - // FIXME: Need to handle version-specific manifests. - inputs.append(file: package.manifest.path) - - // FIXME: This won't be the location of Package.resolved for multiroot packages. - inputs.append(file: package.path.appending("Package.resolved")) - - // FIXME: Add config file as an input - - return inputs - } - - let name = "PackageStructure" - let output: Node = .virtual(name) - - self.manifest.addPkgStructureCmd( - name: name, - inputs: inputs, - outputs: [output] - ) - self.manifest.addNode(output, toTarget: name) - } -} - -// MARK: - Binary Dependencies - -extension LLBuildManifestBuilder { - // Creates commands for copying all binary artifacts depended on in the plan. - private func addBinaryDependencyCommands() { - let binaryPaths = Set(plan.targetMap.values.flatMap(\.libraryBinaryPaths)) - for binaryPath in binaryPaths { - let destination = destinationPath(forBinaryAt: binaryPath) - addCopyCommand(from: binaryPath, to: destination) - } - } -} - -// MARK: - Resources Bundle - -extension LLBuildManifestBuilder { - /// Adds command for creating the resources bundle of the given target. - /// - /// Returns the virtual node that will build the entire bundle. - private func createResourcesBundle( - for target: TargetBuildDescription - ) throws -> Node? { - guard let bundlePath = target.bundlePath else { return nil } - - var outputs: [Node] = [] - - let infoPlistDestination = try RelativePath(validating: "Info.plist") - - // Create a copy command for each resource file. - for resource in target.resources { - switch resource.rule { - case .copy, .process: - let destination = try bundlePath.appending(resource.destination) - let (_, output) = addCopyCommand(from: resource.path, to: destination) - outputs.append(output) - case .embedInCode: - break - } - } - - // Create a copy command for the Info.plist if a resource with the same name doesn't exist yet. - if let infoPlistPath = target.resourceBundleInfoPlistPath { - let destination = bundlePath.appending(infoPlistDestination) - let (_, output) = addCopyCommand(from: infoPlistPath, to: destination) - outputs.append(output) - } - - let cmdName = target.target.getLLBuildResourcesCmdName(config: self.buildConfig) - self.manifest.addPhonyCmd(name: cmdName, inputs: outputs, outputs: [.virtual(cmdName)]) - - return .virtual(cmdName) - } -} - -// MARK: - Compilation - -extension LLBuildManifestBuilder { - private func addBuildToolPlugins(_ target: TargetBuildDescription) throws { - // Add any regular build commands created by plugins for the target. - for result in target.buildToolPluginInvocationResults { - // Only go through the regular build commands — prebuild commands are handled separately. - for command in result.buildCommands { - // Create a shell command to invoke the executable. We include the path of the executable as a - // dependency, and make sure the name is unique. - let execPath = command.configuration.executable - let uniquedName = ([execPath.pathString] + command.configuration.arguments).joined(separator: "|") - let displayName = command.configuration.displayName ?? execPath.basename - var commandLine = [execPath.pathString] + command.configuration.arguments - if !self.disableSandboxForPluginCommands { - commandLine = try Sandbox.apply( - command: commandLine, - fileSystem: self.fileSystem, - strictness: .writableTemporaryDirectory, - writableDirectories: [result.pluginOutputDirectory] - ) - } - self.manifest.addShellCmd( - name: displayName + "-" + ByteString(encodingAsUTF8: uniquedName).sha256Checksum, - description: displayName, - inputs: command.inputFiles.map { .file($0) }, - outputs: command.outputFiles.map { .file($0) }, - arguments: commandLine, - environment: command.configuration.environment, - workingDirectory: command.configuration.workingDirectory?.pathString - ) - } - } - } -} +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import class DriverSupport.SPMSwiftDriverExecutor +@_implementationOnly import SwiftDriver +#else +import class DriverSupport.SPMSwiftDriverExecutor +import SwiftDriver +#endif -// MARK: - Compile Swift +import PackageModel extension LLBuildManifestBuilder { /// Create a llbuild target for a Swift target description and returns the Swift target's outputs. @discardableResult - private func createSwiftCompileCommand( + func createSwiftCompileCommand( _ target: SwiftTargetBuildDescription, addTargetCmd: Bool = true ) throws -> [Node] { @@ -262,11 +47,10 @@ extension LLBuildManifestBuilder { let moduleNode = Node.file(target.moduleOutputPath) let cmdOutputs = objectNodes + [moduleNode] - if buildParameters.useIntegratedSwiftDriver { + if self.buildParameters.driverParameters.useIntegratedSwiftDriver { try self.addSwiftCmdsViaIntegratedDriver( target, inputs: inputs, - objectNodes: objectNodes, moduleNode: moduleNode ) } else { @@ -288,7 +72,6 @@ extension LLBuildManifestBuilder { private func addSwiftCmdsViaIntegratedDriver( _ target: SwiftTargetBuildDescription, inputs: [Node], - objectNodes: [Node], moduleNode: Node ) throws { // Use the integrated Swift driver to compute the set of frontend @@ -310,6 +93,8 @@ extension LLBuildManifestBuilder { fileSystem: self.fileSystem, executor: executor ) + try driver.checkLDPathOption(commandLine: commandLine) + let jobs = try driver.planBuild() try self.addSwiftDriverJobs( for: target, @@ -357,7 +142,7 @@ extension LLBuildManifestBuilder { // common intermediate dependency modules, such dependencies can lead // to cycles in the resulting manifest. var manifestNodeInputs: [Node] = [] - if self.buildParameters.useExplicitModuleBuild && !isMainModule(job) { + if self.buildParameters.driverParameters.useExplicitModuleBuild && !isMainModule(job) { manifestNodeInputs = jobInputs } else { manifestNodeInputs = (inputs + jobInputs).uniqued() @@ -394,7 +179,7 @@ extension LLBuildManifestBuilder { // Building a Swift module in Explicit Module Build mode requires passing all of its module // dependencies as explicit arguments to the build command. Thus, building a SwiftPM package - // with multiple inter-dependent targets thus requires that each target’s build job must + // with multiple inter-dependent targets requires that each target’s build job must // have its target dependencies’ modules passed into it as explicit module dependencies. // Because none of the targets have been built yet, a given target's dependency scanning // action will not be able to discover its target dependencies' modules. Instead, it is @@ -530,6 +315,8 @@ extension LLBuildManifestBuilder { externalTargetModuleDetailsMap: dependencyModuleDetailsMap, interModuleDependencyOracle: dependencyOracle ) + try driver.checkLDPathOption(commandLine: commandLine) + let jobs = try driver.planBuild() try self.addSwiftDriverJobs( for: targetDescription, @@ -626,6 +413,9 @@ extension LLBuildManifestBuilder { ) throws -> [Node] { var inputs = target.sources.map(Node.file) + let swiftVersionFilePath = addSwiftGetVersionCommand(buildParameters: target.buildParameters) + inputs.append(.file(swiftVersionFilePath)) + // Add resources node as the input to the target. This isn't great because we // don't need to block building of a module until its resources are assembled but // we don't currently have a good way to express that resources should be built @@ -702,7 +492,7 @@ extension LLBuildManifestBuilder { } for binaryPath in target.libraryBinaryPaths { - let path = destinationPath(forBinaryAt: binaryPath) + let path = self.destinationPath(forBinaryAt: binaryPath) if self.fileSystem.isDirectory(binaryPath) { inputs.append(directory: path) } else { @@ -710,7 +500,7 @@ extension LLBuildManifestBuilder { } } - try addBuildToolPlugins(.swift(target)) + try self.addBuildToolPlugins(.swift(target)) // Depend on any required macro product's output. try target.requiredMacroProducts.forEach { macro in @@ -721,13 +511,13 @@ extension LLBuildManifestBuilder { } /// Adds a top-level phony command that builds the entire target. - private func addTargetCmd( + func addTargetCmd( target: ResolvedTarget, isTestTarget: Bool, inputs: [Node] ) { // Create a phony node to represent the entire target. - let targetName = target.getLLBuildTargetName(config: buildConfig) + let targetName = target.getLLBuildTargetName(config: self.buildConfig) let targetOutput: Node = .virtual(targetName) self.manifest.addNode(targetOutput, toTarget: targetName) @@ -761,6 +551,22 @@ extension LLBuildManifestBuilder { arguments: moduleWrapArgs ) } + + private func addSwiftGetVersionCommand(buildParameters: BuildParameters) -> AbsolutePath { + let swiftCompilerPath = buildParameters.toolchain.swiftCompilerPath + + // If we are already tracking this compiler, we can re-use the existing command by just returning the tracking file. + if let swiftVersionFilePath = swiftGetVersionFiles[swiftCompilerPath] { + return swiftVersionFilePath + } + + // Otherwise, come up with a path for the new file and generate a command to populate it. + let swiftCompilerPathHash = String(swiftCompilerPath.pathString.hash, radix: 16, uppercase: true) + let swiftVersionFilePath = buildParameters.buildPath.appending(component: "swift-version-\(swiftCompilerPathHash).txt") + self.manifest.addSwiftGetVersionCommand(swiftCompilerPath: swiftCompilerPath, swiftVersionFilePath: swiftVersionFilePath) + swiftGetVersionFiles[swiftCompilerPath] = swiftVersionFilePath + return swiftVersionFilePath + } } extension SwiftDriver.Job { @@ -789,130 +595,12 @@ private class UniqueExplicitDependencyJobTracker { } } -// MARK: - Compile C-family - -extension LLBuildManifestBuilder { - /// Create a llbuild target for a Clang target description and returns the Clang target's outputs. - @discardableResult - private func createClangCompileCommand( - _ target: ClangTargetBuildDescription, - addTargetCmd: Bool = true, - inputs: [Node] = [], - createResourceBundle: Bool = true - ) throws -> [Node] { - let standards = [ - (target.clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), - (target.clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions), - ] - - var inputs: [Node] = inputs - - if createResourceBundle { - // Add resources node as the input to the target. This isn't great because we - // don't need to block building of a module until its resources are assembled but - // we don't currently have a good way to express that resources should be built - // whenever a module is being built. - if let resourcesNode = try createResourcesBundle(for: .clang(target)) { - inputs.append(resourcesNode) - } - } - - func addStaticTargetInputs(_ target: ResolvedTarget) { - if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { - inputs.append(file: desc.moduleOutputPath) - } else if case .mixed(let desc)? = plan.targetMap[target], target.type == .library { - inputs.append(file: desc.swiftTargetBuildDescription.moduleOutputPath) - } - } - - for dependency in target.target.dependencies(satisfying: self.buildEnvironment) { - switch dependency { - case .target(let target, _): - addStaticTargetInputs(target) - - case .product(let product, _): - switch product.type { - case .executable, .snippet, .library(.dynamic), .macro: - guard let planProduct = plan.productMap[product] else { - throw InternalError("unknown product \(product)") - } - // Establish a dependency on binary of the product. - let binary = try planProduct.binaryPath - inputs.append(file: binary) - - case .library(.automatic), .library(.static), .plugin: - for target in product.targets { - addStaticTargetInputs(target) - } - case .test: - break - } - } - } - - for binaryPath in target.libraryBinaryPaths { - let path = destinationPath(forBinaryAt: binaryPath) - if self.fileSystem.isDirectory(binaryPath) { - inputs.append(directory: path) - } else { - inputs.append(file: path) - } - } - - var objectFileNodes: [Node] = [] - - for path in try target.compilePaths() { - let isCXX = path.source.extension.map { SupportedLanguageExtension.cppExtensions.contains($0) } ?? false - let isC = path.source.extension.map { $0 == SupportedLanguageExtension.c.rawValue } ?? false - - var args = try target.basicArguments(isCXX: isCXX, isC: isC) - - args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString] - - // Add language standard flag if needed. - if let ext = path.source.extension { - for (standard, validExtensions) in standards { - if let standard, validExtensions.contains(ext) { - args += ["-std=\(standard)"] - } - } - } - - args += ["-c", path.source.pathString, "-o", path.object.pathString] - - let clangCompiler = try buildParameters.toolchain.getClangCompiler().pathString - args.insert(clangCompiler, at: 0) - - let objectFileNode: Node = .file(path.object) - objectFileNodes.append(objectFileNode) - - self.manifest.addClangCmd( - name: path.object.pathString, - description: "Compiling \(target.target.name) \(path.filename)", - inputs: inputs + [.file(path.source)], - outputs: [objectFileNode], - arguments: args, - dependencies: path.deps.pathString - ) - } - - if addTargetCmd { - self.addTargetCmd( - target: target.target, - isTestTarget: target.isTestTarget, - inputs: objectFileNodes - ) - } - - return objectFileNodes - } -} - // MARK: - Compile Mixed Languages +// TODO(ncooke3): Post-merge move to other file? extension LLBuildManifestBuilder { /// Create a llbuild target for a mixed target description. - private func createMixedCompileCommand( + func createMixedCompileCommand( _ target: MixedTargetBuildDescription ) throws { let swiftOutputs = try createSwiftCompileCommand(target.swiftTargetBuildDescription, addTargetCmd: false) @@ -930,196 +618,13 @@ extension LLBuildManifestBuilder { } } -// MARK: - Test File Generation - -extension LLBuildManifestBuilder { - private func addTestDiscoveryGenerationCommand() throws { - for testDiscoveryTarget in self.plan.targets.compactMap(\.testDiscoveryTargetBuildDescription) { - let testTargets = testDiscoveryTarget.target.dependencies - .compactMap(\.target).compactMap { plan.targetMap[$0] } - let objectFiles = try testTargets.flatMap { try $0.objects }.sorted().map(Node.file) - let outputs = testDiscoveryTarget.target.sources.paths - - guard let mainOutput = (outputs.first { $0.basename == TestDiscoveryTool.mainFileName }) else { - throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found") - } - let cmdName = mainOutput.pathString - self.manifest.addTestDiscoveryCmd( - name: cmdName, - inputs: objectFiles, - outputs: outputs.map(Node.file) - ) - } - } - - private func addTestEntryPointGenerationCommand() throws { - for target in self.plan.targets { - guard case .swift(let target) = target, - case .entryPoint(let isSynthesized) = target.testTargetRole, - isSynthesized else { continue } - - let testEntryPointTarget = target - - // Get the Swift target build descriptions of all discovery targets this synthesized entry point target - // depends on. - let discoveredTargetDependencyBuildDescriptions = testEntryPointTarget.target.dependencies - .compactMap(\.target) - .compactMap { plan.targetMap[$0] } - .compactMap(\.testDiscoveryTargetBuildDescription) - - // The module outputs of the discovery targets this synthesized entry point target depends on are - // considered the inputs to the entry point command. - let inputs = discoveredTargetDependencyBuildDescriptions.map(\.moduleOutputPath) - - let outputs = testEntryPointTarget.target.sources.paths - - guard let mainOutput = (outputs.first { $0.basename == TestEntryPointTool.mainFileName }) else { - throw InternalError("main output (\(TestEntryPointTool.mainFileName)) not found") - } - let cmdName = mainOutput.pathString - self.manifest.addTestEntryPointCmd( - name: cmdName, - inputs: inputs.map(Node.file), - outputs: outputs.map(Node.file) - ) - } - } -} - -extension TargetBuildDescription { - /// If receiver represents a Swift target build description whose test target role is Discovery, - /// then this returns that Swift target build description, else returns nil. - fileprivate var testDiscoveryTargetBuildDescription: SwiftTargetBuildDescription? { - guard case .swift(let targetBuildDescription) = self, - case .discovery = targetBuildDescription.testTargetRole else { return nil } - return targetBuildDescription - } -} - -// MARK: - Product Command - -extension LLBuildManifestBuilder { - private func createProductCommand(_ buildProduct: ProductBuildDescription) throws { - let cmdName = try buildProduct.product.getCommandName(config: self.buildConfig) - - switch buildProduct.product.type { - case .library(.static): - try self.manifest.addShellCmd( - name: cmdName, - description: "Archiving \(buildProduct.binaryPath.prettyPath())", - inputs: (buildProduct.objects + [buildProduct.linkFileListPath]).map(Node.file), - outputs: [.file(buildProduct.binaryPath)], - arguments: try buildProduct.archiveArguments() - ) - - default: - let inputs = try buildProduct.objects + buildProduct.dylibs.map{ try $0.binaryPath } + [buildProduct.linkFileListPath] - - try self.manifest.addShellCmd( - name: cmdName, - description: "Linking \(buildProduct.binaryPath.prettyPath())", - inputs: inputs.map(Node.file), - outputs: [.file(buildProduct.binaryPath)], - arguments: try buildProduct.linkArguments() - ) - } - - // Create a phony node to represent the entire target. - let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig) - let output: Node = .virtual(targetName) - - self.manifest.addNode(output, toTarget: targetName) - try self.manifest.addPhonyCmd( - name: output.name, - inputs: [.file(buildProduct.binaryPath)], - outputs: [output] - ) - - if self.plan.graph.reachableProducts.contains(buildProduct.product) { - if buildProduct.product.type != .test { - self.addNode(output, toTarget: .main) - } - self.addNode(output, toTarget: .test) - } - - self.manifest.addWriteLinkFileListCommand(objects: Array(buildProduct.objects), linkFileListPath: buildProduct.linkFileListPath) - } -} - -extension ResolvedTarget { - public func getCommandName(config: String) -> String { - "C." + self.getLLBuildTargetName(config: config) - } - - public func getLLBuildTargetName(config: String) -> String { - "\(name)-\(config).module" - } - - public func getLLBuildResourcesCmdName(config: String) -> String { - "\(name)-\(config).module-resources" - } -} - -extension ResolvedProduct { - public func getLLBuildTargetName(config: String) throws -> String { - let potentialExecutableTargetName = "\(name)-\(config).exe" - let potentialLibraryTargetName = "\(name)-\(config).dylib" - - switch type { - case .library(.dynamic): - return potentialLibraryTargetName - case .test: - return "\(name)-\(config).test" - case .library(.static): - return "\(name)-\(config).a" - case .library(.automatic): - throw InternalError("automatic library not supported") - case .executable, .snippet: - return potentialExecutableTargetName - case .macro: - #if BUILD_MACROS_AS_DYLIBS - return potentialLibraryTargetName - #else - return potentialExecutableTargetName - #endif - case .plugin: - throw InternalError("unexpectedly asked for the llbuild target name of a plugin product") - } - } - - public func getCommandName(config: String) throws -> String { - try "C." + self.getLLBuildTargetName(config: config) - } -} - -// MARK: - Helper - -extension LLBuildManifestBuilder { - @discardableResult - private func addCopyCommand( - from source: AbsolutePath, - to destination: AbsolutePath - ) -> (inputNode: Node, outputNode: Node) { - let isDirectory = self.fileSystem.isDirectory(source) - let nodeType = isDirectory ? Node.directory : Node.file - let inputNode = nodeType(source) - let outputNode = nodeType(destination) - self.manifest.addCopyCmd(name: destination.pathString, inputs: [inputNode], outputs: [outputNode]) - return (inputNode, outputNode) - } - - private func destinationPath(forBinaryAt path: AbsolutePath) -> AbsolutePath { - self.plan.buildParameters.buildPath.appending(component: path.basename) - } -} - extension TypedVirtualPath { /// Resolve a typed virtual path provided by the Swift driver to /// a node in the build graph. - func resolveToNode(fileSystem: FileSystem) throws -> Node { - if let absolutePath = (file.absolutePath.flatMap{ AbsolutePath($0) }) { + fileprivate func resolveToNode(fileSystem: some FileSystem) throws -> Node { + if let absolutePath = (file.absolutePath.flatMap { AbsolutePath($0) }) { return Node.file(absolutePath) - } else if let relativePath = (file.relativePath.flatMap{ RelativePath($0) }) { + } else if let relativePath = (file.relativePath.flatMap { RelativePath($0) }) { guard let workingDirectory: AbsolutePath = fileSystem.currentWorkingDirectory else { throw InternalError("unknown working directory") } @@ -1132,10 +637,12 @@ extension TypedVirtualPath { } } -extension Sequence where Element: Hashable { - /// Unique the elements in a sequence. - func uniqued() -> [Element] { - var seen: Set = [] - return filter { seen.insert($0).inserted } +extension Driver { + func checkLDPathOption(commandLine: [String]) throws { + // `-ld-path` option is only available in recent versions of the compiler: rdar://117049947 + if let option = commandLine.first(where: { $0.hasPrefix("-ld-path") }), + !self.supportedFrontendFeatures.contains("ld-path-driver-option") { + throw LLBuildManifestBuilder.Error.ldPathDriverOptionUnavailable(option: option) + } } } diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift new file mode 100644 index 00000000000..878a0f1670c --- /dev/null +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift @@ -0,0 +1,367 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 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 Basics +import LLBuildManifest +import PackageGraph +import PackageModel +import SPMBuildCore + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import SwiftDriver +#else +import SwiftDriver +#endif + +import struct TSCBasic.ByteString +import enum TSCBasic.ProcessEnv +import func TSCBasic.topologicalSort + +/// High-level interface to ``LLBuildManifest`` and ``LLBuildManifestWriter``. +public class LLBuildManifestBuilder { + enum Error: Swift.Error { + case ldPathDriverOptionUnavailable(option: String) + + var description: String { + switch self { + case .ldPathDriverOptionUnavailable(let option): + return "Unable to pass \(option), currently used version of `swiftc` doesn't support it." + } + } + } + + public enum TargetKind { + case main + case test + + public var targetName: String { + switch self { + case .main: return "main" + case .test: return "test" + } + } + } + + /// The build plan to work on. + public let plan: BuildPlan + + /// Whether to sandbox commands from build tool plugins. + public let disableSandboxForPluginCommands: Bool + + /// File system reference. + let fileSystem: any FileSystem + + /// ObservabilityScope with which to emit diagnostics + public let observabilityScope: ObservabilityScope + + public internal(set) var manifest: LLBuildManifest = .init() + + var buildConfig: String { self.buildParameters.configuration.dirname } + var buildParameters: BuildParameters { self.plan.buildParameters } + var buildEnvironment: BuildEnvironment { self.buildParameters.buildEnvironment } + + /// Mapping from Swift compiler path to Swift get version files. + var swiftGetVersionFiles = [AbsolutePath: AbsolutePath]() + + /// Create a new builder with a build plan. + public init( + _ plan: BuildPlan, + disableSandboxForPluginCommands: Bool = false, + fileSystem: any FileSystem, + observabilityScope: ObservabilityScope + ) { + self.plan = plan + self.disableSandboxForPluginCommands = disableSandboxForPluginCommands + self.fileSystem = fileSystem + self.observabilityScope = observabilityScope + } + + // MARK: - Generate Manifest + + /// Generate manifest at the given path. + @discardableResult + public func generateManifest(at path: AbsolutePath) throws -> LLBuildManifest { + self.swiftGetVersionFiles.removeAll() + + self.manifest.createTarget(TargetKind.main.targetName) + self.manifest.createTarget(TargetKind.test.targetName) + self.manifest.defaultTarget = TargetKind.main.targetName + + addPackageStructureCommand() + addBinaryDependencyCommands() + if self.buildParameters.driverParameters.useExplicitModuleBuild { + // Explicit module builds use the integrated driver directly and + // require that every target's build jobs specify its dependencies explicitly to plan + // its build. + // Currently behind: + // --experimental-explicit-module-build + try addTargetsToExplicitBuildManifest() + } else { + // Create commands for all target descriptions in the plan. + for (_, description) in self.plan.targetMap { + switch description { + case .swift(let desc): + try self.createSwiftCompileCommand(desc) + case .clang(let desc): + try self.createClangCompileCommand(desc) + case .mixed(let desc): + try self.createMixedCompileCommand(desc) + } + } + } + + try self.addTestDiscoveryGenerationCommand() + try self.addTestEntryPointGenerationCommand() + + // Create command for all products in the plan. + for (_, description) in self.plan.productMap { + try self.createProductCommand(description) + } + + try LLBuildManifestWriter.write(self.manifest, at: path, fileSystem: self.fileSystem) + return self.manifest + } + + func addNode(_ node: Node, toTarget targetKind: TargetKind) { + self.manifest.addNode(node, toTarget: targetKind.targetName) + } +} + +// MARK: - Package Structure + +extension LLBuildManifestBuilder { + private func addPackageStructureCommand() { + let inputs = self.plan.graph.rootPackages.flatMap { package -> [Node] in + var inputs = package.targets + .map(\.sources.root) + .sorted() + .map { Node.directoryStructure($0) } + + // Add the output paths of any prebuilds that were run, so that we redo the plan if they change. + var derivedSourceDirPaths: [AbsolutePath] = [] + for result in self.plan.prebuildCommandResults.values.flatMap({ $0 }) { + derivedSourceDirPaths.append(contentsOf: result.outputDirectories) + } + inputs.append(contentsOf: derivedSourceDirPaths.sorted().map { Node.directoryStructure($0) }) + + // FIXME: Need to handle version-specific manifests. + inputs.append(file: package.manifest.path) + + // FIXME: This won't be the location of Package.resolved for multiroot packages. + inputs.append(file: package.path.appending("Package.resolved")) + + // FIXME: Add config file as an input + + return inputs + } + + let name = "PackageStructure" + let output: Node = .virtual(name) + + self.manifest.addPkgStructureCmd( + name: name, + inputs: inputs, + outputs: [output] + ) + self.manifest.addNode(output, toTarget: name) + } +} + +// MARK: - Binary Dependencies + +extension LLBuildManifestBuilder { + // Creates commands for copying all binary artifacts depended on in the plan. + private func addBinaryDependencyCommands() { + let binaryPaths = Set(plan.targetMap.values.flatMap(\.libraryBinaryPaths)) + for binaryPath in binaryPaths { + let destination = destinationPath(forBinaryAt: binaryPath) + addCopyCommand(from: binaryPath, to: destination) + } + } +} + +// MARK: - Compilation + +extension LLBuildManifestBuilder { + func addBuildToolPlugins(_ target: TargetBuildDescription) throws { + // Add any regular build commands created by plugins for the target. + for result in target.buildToolPluginInvocationResults { + // Only go through the regular build commands — prebuild commands are handled separately. + for command in result.buildCommands { + // Create a shell command to invoke the executable. We include the path of the executable as a + // dependency, and make sure the name is unique. + let execPath = command.configuration.executable + let uniquedName = ([execPath.pathString] + command.configuration.arguments).joined(separator: "|") + let displayName = command.configuration.displayName ?? execPath.basename + var commandLine = [execPath.pathString] + command.configuration.arguments + if !self.disableSandboxForPluginCommands { + commandLine = try Sandbox.apply( + command: commandLine, + fileSystem: self.fileSystem, + strictness: .writableTemporaryDirectory, + writableDirectories: [result.pluginOutputDirectory] + ) + } + self.manifest.addShellCmd( + name: displayName + "-" + ByteString(encodingAsUTF8: uniquedName).sha256Checksum, + description: displayName, + inputs: command.inputFiles.map { .file($0) }, + outputs: command.outputFiles.map { .file($0) }, + arguments: commandLine, + environment: command.configuration.environment, + workingDirectory: command.configuration.workingDirectory?.pathString + ) + } + } + } +} + +// MARK: - Test File Generation + +extension LLBuildManifestBuilder { + private func addTestDiscoveryGenerationCommand() throws { + for testDiscoveryTarget in self.plan.targets.compactMap(\.testDiscoveryTargetBuildDescription) { + let testTargets = testDiscoveryTarget.target.dependencies + .compactMap(\.target).compactMap { self.plan.targetMap[$0] } + let objectFiles = try testTargets.flatMap { try $0.objects }.sorted().map(Node.file) + let outputs = testDiscoveryTarget.target.sources.paths + + guard let mainOutput = (outputs.first { $0.basename == TestDiscoveryTool.mainFileName }) else { + throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found") + } + let cmdName = mainOutput.pathString + self.manifest.addTestDiscoveryCmd( + name: cmdName, + inputs: objectFiles, + outputs: outputs.map(Node.file) + ) + } + } + + private func addTestEntryPointGenerationCommand() throws { + for target in self.plan.targets { + guard case .swift(let target) = target, + case .entryPoint(let isSynthesized) = target.testTargetRole, + isSynthesized else { continue } + + let testEntryPointTarget = target + + // Get the Swift target build descriptions of all discovery targets this synthesized entry point target + // depends on. + let discoveredTargetDependencyBuildDescriptions = testEntryPointTarget.target.dependencies + .compactMap(\.target) + .compactMap { self.plan.targetMap[$0] } + .compactMap(\.testDiscoveryTargetBuildDescription) + + // The module outputs of the discovery targets this synthesized entry point target depends on are + // considered the inputs to the entry point command. + let inputs = discoveredTargetDependencyBuildDescriptions.map(\.moduleOutputPath) + + let outputs = testEntryPointTarget.target.sources.paths + + guard let mainOutput = (outputs.first { $0.basename == TestEntryPointTool.mainFileName }) else { + throw InternalError("main output (\(TestEntryPointTool.mainFileName)) not found") + } + let cmdName = mainOutput.pathString + self.manifest.addTestEntryPointCmd( + name: cmdName, + inputs: inputs.map(Node.file), + outputs: outputs.map(Node.file) + ) + } + } +} + +extension TargetBuildDescription { + /// If receiver represents a Swift target build description whose test target role is Discovery, + /// then this returns that Swift target build description, else returns nil. + fileprivate var testDiscoveryTargetBuildDescription: SwiftTargetBuildDescription? { + guard case .swift(let targetBuildDescription) = self, + case .discovery = targetBuildDescription.testTargetRole else { return nil } + return targetBuildDescription + } +} + +extension ResolvedTarget { + public func getCommandName(config: String) -> String { + "C." + self.getLLBuildTargetName(config: config) + } + + public func getLLBuildTargetName(config: String) -> String { + "\(name)-\(config).module" + } + + public func getLLBuildResourcesCmdName(config: String) -> String { + "\(name)-\(config).module-resources" + } +} + +extension ResolvedProduct { + public func getLLBuildTargetName(config: String) throws -> String { + let potentialExecutableTargetName = "\(name)-\(config).exe" + let potentialLibraryTargetName = "\(name)-\(config).dylib" + + switch type { + case .library(.dynamic): + return potentialLibraryTargetName + case .test: + return "\(name)-\(config).test" + case .library(.static): + return "\(name)-\(config).a" + case .library(.automatic): + throw InternalError("automatic library not supported") + case .executable, .snippet: + return potentialExecutableTargetName + case .macro: + #if BUILD_MACROS_AS_DYLIBS + return potentialLibraryTargetName + #else + return potentialExecutableTargetName + #endif + case .plugin: + throw InternalError("unexpectedly asked for the llbuild target name of a plugin product") + } + } + + public func getCommandName(config: String) throws -> String { + try "C." + self.getLLBuildTargetName(config: config) + } +} + +// MARK: - Helper + +extension LLBuildManifestBuilder { + @discardableResult + func addCopyCommand( + from source: AbsolutePath, + to destination: AbsolutePath + ) -> (inputNode: Node, outputNode: Node) { + let isDirectory = self.fileSystem.isDirectory(source) + let nodeType = isDirectory ? Node.directory : Node.file + let inputNode = nodeType(source) + let outputNode = nodeType(destination) + self.manifest.addCopyCmd(name: destination.pathString, inputs: [inputNode], outputs: [outputNode]) + return (inputNode, outputNode) + } + + func destinationPath(forBinaryAt path: AbsolutePath) -> AbsolutePath { + self.plan.buildParameters.buildPath.appending(component: path.basename) + } +} + +extension Sequence where Element: Hashable { + /// Unique the elements in a sequence. + func uniqued() -> [Element] { + var seen: Set = [] + return filter { seen.insert($0).inserted } + } +} diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index b8249916ade..94b8328502a 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import DriverSupport import LLBuildManifest import PackageGraph import PackageLoading @@ -31,7 +30,13 @@ import class TSCUtility.MultiLineNinjaProgressAnimation import class TSCUtility.NinjaProgressAnimation import protocol TSCUtility.ProgressAnimationProtocol +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import DriverSupport @_implementationOnly import SwiftDriver +#else +import DriverSupport +import SwiftDriver +#endif public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildSystem, BuildErrorAdviceProvider { /// The delegate used by the build system. @@ -96,8 +101,6 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS /// Alternative path to search for pkg-config `.pc` files. private let pkgConfigDirectories: [AbsolutePath] - private let driverSupport = DriverSupport() - public init( buildParameters: BuildParameters, cacheBuildManifest: Bool, @@ -112,7 +115,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS ) { /// Checks if stdout stream is tty. var buildParameters = buildParameters - buildParameters.colorizedOutput = outputStream.isTTY + buildParameters.outputParameters.isColorized = outputStream.isTTY self.buildParameters = buildParameters self.cacheBuildManifest = cacheBuildManifest @@ -164,7 +167,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } } - public func getBuildManifest() throws -> LLBuildManifest.BuildManifest { + public func getBuildManifest() throws -> LLBuildManifest { return try self.plan().manifest } @@ -181,7 +184,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS return } // Ensure the compiler supports the import-scan operation - guard driverSupport.checkSupportedFrontendFlags(flags: ["import-prescan"], toolchain: self.buildParameters.toolchain, fileSystem: localFileSystem) else { + guard DriverSupport.checkSupportedFrontendFlags(flags: ["import-prescan"], toolchain: self.buildParameters.toolchain, fileSystem: localFileSystem) else { return } @@ -418,7 +421,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } /// Create the build plan and return the build description. - private func plan() throws -> (description: BuildDescription, manifest: LLBuildManifest.BuildManifest) { + private func plan() throws -> (description: BuildDescription, manifest: LLBuildManifest) { // Load the package graph. let graph = try getPackageGraph() @@ -443,6 +446,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS buildEnvironment: self.buildParameters.buildEnvironment, toolSearchDirectories: [self.buildParameters.toolchain.swiftCompilerPath.parentDirectory], pkgConfigDirectories: self.pkgConfigDirectories, + sdkRootPath: self.buildParameters.toolchain.sdkRootPath, pluginScriptRunner: pluginConfiguration.scriptRunner, observabilityScope: self.observabilityScope, fileSystem: self.fileSystem @@ -669,7 +673,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS public func packageStructureChanged() -> Bool { do { - _ = try plan() + _ = try self.plan() } catch Diagnostics.fatalError { return false @@ -702,7 +706,7 @@ extension BuildOperation { } extension BuildDescription { - static func create(with plan: BuildPlan, disableSandboxForPluginCommands: Bool, fileSystem: Basics.FileSystem, observabilityScope: ObservabilityScope) throws -> (BuildDescription, LLBuildManifest.BuildManifest) { + static func create(with plan: BuildPlan, disableSandboxForPluginCommands: Bool, fileSystem: Basics.FileSystem, observabilityScope: ObservabilityScope) throws -> (BuildDescription, LLBuildManifest) { // Generate the llbuild manifest. let llbuild = LLBuildManifestBuilder(plan, disableSandboxForPluginCommands: disableSandboxForPluginCommands, fileSystem: fileSystem, observabilityScope: observabilityScope) let buildManifest = try llbuild.generateManifest(at: plan.buildParameters.llbuildManifest) diff --git a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift index 386fb27194f..699309fdd0b 100644 --- a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift +++ b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift @@ -19,7 +19,6 @@ import SPMBuildCore import SPMLLBuild import struct TSCBasic.ByteString -import protocol TSCBasic.ByteStreamable import struct TSCBasic.Format import class TSCBasic.LocalFileOutputByteStream import protocol TSCBasic.OutputByteStream @@ -109,74 +108,75 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand { try fileSystem.writeFileContents(path, string: content) } - private func execute(fileSystem: Basics.FileSystem, tool: LLBuildManifest.TestDiscoveryTool) throws { - let index = self.context.buildParameters.indexStore - let api = try self.context.indexStoreAPI.get() - let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api) - - // FIXME: We can speed this up by having one llbuild command per object file. - let tests = try store.listTests(in: tool.inputs.map { try TSCAbsolutePath(AbsolutePath(validating: $0.name)) }) - + private func execute(fileSystem: Basics.FileSystem, tool: TestDiscoveryTool) throws { let outputs = tool.outputs.compactMap { try? AbsolutePath(validating: $0.name) } - let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() }) - - func isMainFile(_ path: AbsolutePath) -> Bool { - path.basename == LLBuildManifest.TestDiscoveryTool.mainFileName - } - var maybeMainFile: AbsolutePath? - // Write one file for each test module. - // - // We could write everything in one file but that can easily run into type conflicts due - // in complex packages with large number of test targets. - for file in outputs { - if maybeMainFile == nil && isMainFile(file) { - maybeMainFile = file - continue + switch self.context.buildParameters.testingParameters.library { + case .swiftTesting: + for file in outputs { + try fileSystem.writeIfChanged(path: file, string: "") } + case .xctest: + let index = self.context.buildParameters.indexStore + let api = try self.context.indexStoreAPI.get() + let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api) - // FIXME: This is relying on implementation detail of the output but passing the - // the context all the way through is not worth it right now. - let module = file.basenameWithoutExt.spm_mangledToC99ExtendedIdentifier() + // FIXME: We can speed this up by having one llbuild command per object file. + let tests = try store.listTests(in: tool.inputs.map { try TSCAbsolutePath(AbsolutePath(validating: $0.name)) }) - guard let tests = testsByModule[module] else { - // This module has no tests so just write an empty file for it. - try fileSystem.writeFileContents(file, bytes: "") - continue + let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() }) + + // Find the main file path. + guard let mainFile = outputs.first(where: { path in + path.basename == TestDiscoveryTool.mainFileName + }) else { + throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found") } - try write( - tests: tests, - forModule: module, - fileSystem: fileSystem, - path: file - ) - } - guard let mainFile = maybeMainFile else { - throw InternalError("main output (\(LLBuildManifest.TestDiscoveryTool.mainFileName)) not found") - } + // Write one file for each test module. + // + // We could write everything in one file but that can easily run into type conflicts due + // in complex packages with large number of test targets. + for file in outputs where file != mainFile { + // FIXME: This is relying on implementation detail of the output but passing the + // the context all the way through is not worth it right now. + let module = file.basenameWithoutExt.spm_mangledToC99ExtendedIdentifier() + + guard let tests = testsByModule[module] else { + // This module has no tests so just write an empty file for it. + try fileSystem.writeFileContents(file, bytes: "") + continue + } + try write( + tests: tests, + forModule: module, + fileSystem: fileSystem, + path: file + ) + } - let testsKeyword = tests.isEmpty ? "let" : "var" + let testsKeyword = tests.isEmpty ? "let" : "var" - // Write the main file. - let stream = try LocalFileOutputByteStream(mainFile) + // Write the main file. + let stream = try LocalFileOutputByteStream(mainFile) - stream.send( - #""" - import XCTest + stream.send( + #""" + import XCTest - @available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings") - public func __allDiscoveredTests() -> [XCTestCaseEntry] { - \#(testsKeyword) tests = [XCTestCaseEntry]() + @available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings") + public func __allDiscoveredTests() -> [XCTestCaseEntry] { + \#(testsKeyword) tests = [XCTestCaseEntry]() - \#(testsByModule.keys.map { "tests += __\($0)__allTests()" }.joined(separator: "\n ")) + \#(testsByModule.keys.map { "tests += __\($0)__allTests()" }.joined(separator: "\n ")) - return tests - } - """# - ) + return tests + } + """# + ) - stream.flush() + stream.flush() + } } override func execute( @@ -201,37 +201,67 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand { } final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand { - private func execute(fileSystem: Basics.FileSystem, tool: LLBuildManifest.TestEntryPointTool) throws { - // Find the inputs, which are the names of the test discovery module(s) - let inputs = tool.inputs.compactMap { try? AbsolutePath(validating: $0.name) } - let discoveryModuleNames = inputs.map(\.basenameWithoutExt) - + private func execute(fileSystem: Basics.FileSystem, tool: TestEntryPointTool) throws { let outputs = tool.outputs.compactMap { try? AbsolutePath(validating: $0.name) } // Find the main output file guard let mainFile = outputs.first(where: { path in - path.basename == LLBuildManifest.TestEntryPointTool.mainFileName + path.basename == TestEntryPointTool.mainFileName }) else { - throw InternalError("main file output (\(LLBuildManifest.TestEntryPointTool.mainFileName)) not found") + throw InternalError("main file output (\(TestEntryPointTool.mainFileName)) not found") } // Write the main file. let stream = try LocalFileOutputByteStream(mainFile) - stream.send( - #""" - import XCTest - \#(discoveryModuleNames.map { "import \($0)" }.joined(separator: "\n")) + switch self.context.buildParameters.testingParameters.library { + case .swiftTesting: + stream.send( + #""" + #if canImport(Testing) + import Testing + #endif - @main - @available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings") - struct Runner { - static func main() { - XCTMain(__allDiscoveredTests()) + @main struct Runner { + static func main() async { + #if canImport(Testing) + await Testing.__swiftPMEntryPoint() as Never + #endif + } } + """# + ) + case .xctest: + // Find the inputs, which are the names of the test discovery module(s) + let inputs = tool.inputs.compactMap { try? AbsolutePath(validating: $0.name) } + let discoveryModuleNames = inputs.map(\.basenameWithoutExt) + + let testObservabilitySetup: String + if self.context.buildParameters.testingParameters.experimentalTestOutput + && self.context.buildParameters.targetTriple.supportsTestSummary { + testObservabilitySetup = "_ = SwiftPMXCTestObserver()\n" + } else { + testObservabilitySetup = "" } - """# - ) + + stream.send( + #""" + \#(generateTestObservationCode(buildParameters: self.context.buildParameters)) + + import XCTest + \#(discoveryModuleNames.map { "import \($0)" }.joined(separator: "\n")) + + @main + @available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings") + struct Runner { + static func main() { + \#(testObservabilitySetup) + XCTMain(__allDiscoveredTests()) as Never + } + } + """# + ) + } stream.flush() } @@ -259,15 +289,6 @@ final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand { private protocol TestBuildCommand {} -/// Functionality common to all build commands related to test targets. -extension TestBuildCommand { - /// Returns a value containing `spaces` number of space characters. - /// Intended to facilitate indenting generated code a specified number of levels. - fileprivate func indent(_ spaces: Int) -> ByteStreamable { - Format.asRepeating(string: " ", count: spaces) - } -} - private final class InProcessTool: Tool { let context: BuildExecutionContext let type: CustomLLBuildCommand.Type @@ -289,22 +310,22 @@ public struct BuildDescription: Codable { public typealias CommandLineFlag = String /// The Swift compiler invocation targets. - let swiftCommands: [BuildManifest.CmdName: SwiftCompilerTool] + let swiftCommands: [LLBuildManifest.CmdName: SwiftCompilerTool] /// The Swift compiler frontend invocation targets. - let swiftFrontendCommands: [BuildManifest.CmdName: SwiftFrontendTool] + let swiftFrontendCommands: [LLBuildManifest.CmdName: SwiftFrontendTool] /// The map of test discovery commands. - let testDiscoveryCommands: [BuildManifest.CmdName: LLBuildManifest.TestDiscoveryTool] + let testDiscoveryCommands: [LLBuildManifest.CmdName: TestDiscoveryTool] /// The map of test entry point commands. - let testEntryPointCommands: [BuildManifest.CmdName: LLBuildManifest.TestEntryPointTool] + let testEntryPointCommands: [LLBuildManifest.CmdName: TestEntryPointTool] /// The map of copy commands. - let copyCommands: [BuildManifest.CmdName: LLBuildManifest.CopyTool] + let copyCommands: [LLBuildManifest.CmdName: CopyTool] /// The map of write commands. - let writeCommands: [BuildManifest.CmdName: LLBuildManifest.WriteAuxiliaryFile] + let writeCommands: [LLBuildManifest.CmdName: WriteAuxiliaryFile] /// A flag that indicates this build should perform a check for whether targets only import /// their explicitly-declared dependencies @@ -327,12 +348,12 @@ public struct BuildDescription: Codable { public init( plan: BuildPlan, - swiftCommands: [BuildManifest.CmdName: SwiftCompilerTool], - swiftFrontendCommands: [BuildManifest.CmdName: SwiftFrontendTool], - testDiscoveryCommands: [BuildManifest.CmdName: LLBuildManifest.TestDiscoveryTool], - testEntryPointCommands: [BuildManifest.CmdName: LLBuildManifest.TestEntryPointTool], - copyCommands: [BuildManifest.CmdName: LLBuildManifest.CopyTool], - writeCommands: [BuildManifest.CmdName: LLBuildManifest.WriteAuxiliaryFile], + swiftCommands: [LLBuildManifest.CmdName: SwiftCompilerTool], + swiftFrontendCommands: [LLBuildManifest.CmdName: SwiftFrontendTool], + testDiscoveryCommands: [LLBuildManifest.CmdName: TestDiscoveryTool], + testEntryPointCommands: [LLBuildManifest.CmdName: TestEntryPointTool], + copyCommands: [LLBuildManifest.CmdName: CopyTool], + writeCommands: [LLBuildManifest.CmdName: WriteAuxiliaryFile], pluginDescriptions: [PluginDescription] ) throws { self.swiftCommands = swiftCommands @@ -341,7 +362,7 @@ public struct BuildDescription: Codable { self.testEntryPointCommands = testEntryPointCommands self.copyCommands = copyCommands self.writeCommands = writeCommands - self.explicitTargetDependencyImportCheckingMode = plan.buildParameters + self.explicitTargetDependencyImportCheckingMode = plan.buildParameters.driverParameters .explicitTargetDependencyImportCheckingMode self.targetDependencyMap = try plan.targets.reduce(into: [TargetName: [TargetName]]()) { let deps = try $1.target.recursiveDependencies(satisfying: plan.buildParameters.buildEnvironment) @@ -369,9 +390,10 @@ public struct BuildDescription: Codable { self.swiftTargetScanArgs = targetCommandLines self.generatedSourceTargetSet = Set(generatedSourceTargets) self.builtTestProducts = try plan.buildProducts.filter { $0.product.type == .test }.map { desc in - try BuiltTestProduct( + return try BuiltTestProduct( productName: desc.product.name, - binaryPath: desc.binaryPath + binaryPath: desc.binaryPath, + packagePath: desc.package.path ) } self.pluginDescriptions = pluginDescriptions @@ -473,9 +495,11 @@ public final class BuildExecutionContext { final class WriteAuxiliaryFileCommand: CustomLLBuildCommand { override func getSignature(_ command: SPMLLBuild.Command) -> [UInt8] { guard let buildDescription = self.context.buildDescription else { + self.context.observabilityScope.emit(error: "unknown build description") return [] } - guard let tool = buildDescription.copyCommands[command.name] else { + guard let tool = buildDescription.writeCommands[command.name] else { + self.context.observabilityScope.emit(error: "command \(command.name) not registered") return [] } @@ -890,11 +914,9 @@ final class BuildOperationBuildSystemDelegateHandler: LLBuildBuildSystemDelegate if output.utf8.count < 1024 * 10 { let regex = try! RegEx(pattern: #".*(error:[^\n]*)\n.*"#, options: .dotMatchesLineSeparators) for match in regex.matchGroups(in: output) { - self - .errorMessagesByTarget[parser.targetName] = ( - self - .errorMessagesByTarget[parser.targetName] ?? [] - ) + [match[0]] + self.errorMessagesByTarget[parser.targetName] = ( + self.errorMessagesByTarget[parser.targetName] ?? [] + ) + [match[0]] } } } diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift new file mode 100644 index 00000000000..cb3e2bedd7e --- /dev/null +++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 class PackageModel.BinaryTarget +import class PackageModel.ClangTarget +import class PackageModel.MixedTarget +import class PackageModel.SwiftTarget +import class PackageModel.SystemLibraryTarget + +extension BuildPlan { + /// Plan a Clang target. + func plan(clangTarget: ClangTargetBuildDescription) throws { + for case .target(let dependency, _) in try clangTarget.target.recursiveDependencies(satisfying: buildEnvironment) { + switch dependency.underlyingTarget { + case is SwiftTarget: + if case let .swift(dependencyTargetDescription)? = targetMap[dependency] { + if let moduleMap = dependencyTargetDescription.moduleMap { + clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] + } + } + + case let target as ClangTarget where target.type == .library: + // Setup search paths for C dependencies: + clangTarget.additionalFlags += ["-I", target.includeDir.pathString] + + // Add the modulemap of the dependency if it has one. + if case let .clang(dependencyTargetDescription)? = targetMap[dependency] { + if let moduleMap = dependencyTargetDescription.moduleMap { + clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] + } + } + case let target as MixedTarget where target.type == .library: + // Add the modulemap of the dependency. + if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { + // Add the dependency's modulemap. + clangTarget.additionalFlags.append( + "-fmodule-map-file=\(dependencyTargetDescription.moduleMap.pathString)" + ) + + // Add the dependency's public headers. + clangTarget.additionalFlags += [ "-I", dependencyTargetDescription.publicHeadersDir.pathString ] + + // Add the dependency's public VFS overlay. + clangTarget.additionalFlags += [ + "-ivfsoverlay", dependencyTargetDescription.allProductHeadersOverlay.pathString + ] + } + case let target as SystemLibraryTarget: + clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"] + clangTarget.additionalFlags += try pkgConfig(for: target).cFlags + case let target as BinaryTarget: + if case .xcframework = target.kind { + let libraries = try self.parseXCFramework(for: target) + for library in libraries { + library.headersPaths.forEach { + clangTarget.additionalFlags += ["-I", $0.pathString] + } + clangTarget.libraryBinaryPaths.insert(library.libraryPath) + } + } + default: continue + } + } + } +} diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift new file mode 100644 index 00000000000..78af6a044a5 --- /dev/null +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -0,0 +1,263 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.InternalError +import class PackageGraph.ResolvedProduct +import class PackageGraph.ResolvedTarget +import class PackageModel.BinaryTarget +import class PackageModel.ClangTarget +import class PackageModel.Target +import class PackageModel.SwiftTarget +import class PackageModel.SystemLibraryTarget +import struct SPMBuildCore.ExecutableInfo +import func TSCBasic.topologicalSort + +extension BuildPlan { + /// Plan a product. + func plan(buildProduct: ProductBuildDescription) throws { + // Compute the product's dependency. + let dependencies = try computeDependencies(of: buildProduct.product) + + // Add flags for system targets. + for systemModule in dependencies.systemModules { + guard case let target as SystemLibraryTarget = systemModule.underlyingTarget else { + throw InternalError("This should not be possible.") + } + // Add pkgConfig libs arguments. + buildProduct.additionalFlags += try pkgConfig(for: target).libs + } + + // Add flags for binary dependencies. + for binaryPath in dependencies.libraryBinaryPaths { + if binaryPath.extension == "framework" { + buildProduct.additionalFlags += ["-framework", binaryPath.basenameWithoutExt] + } else if binaryPath.basename.starts(with: "lib") { + buildProduct.additionalFlags += ["-l\(binaryPath.basenameWithoutExt.dropFirst(3))"] + } else { + self.observabilityScope.emit(error: "unexpected binary framework") + } + } + + // Link C++ if needed. + // Note: This will come from build settings in future. + for target in dependencies.staticTargets { + if case let target as ClangTarget = target.underlyingTarget, target.isCXX { + if buildParameters.targetTriple.isDarwin() { + buildProduct.additionalFlags += ["-lc++"] + } else if buildParameters.targetTriple.isWindows() { + // Don't link any C++ library. + } else { + buildProduct.additionalFlags += ["-lstdc++"] + } + break + } + } + + for target in dependencies.staticTargets { + switch target.underlyingTarget { + case is SwiftTarget: + // Swift targets are guaranteed to have a corresponding Swift description. + guard case .swift(let description) = targetMap[target] else { + throw InternalError("unknown target \(target)") + } + + // Based on the debugging strategy, we either need to pass swiftmodule paths to the + // product or link in the wrapped module object. This is required for properly debugging + // Swift products. Debugging strategy is computed based on the current platform we're + // building for and is nil for the release configuration. + switch buildParameters.debuggingStrategy { + case .swiftAST: + buildProduct.swiftASTs.insert(description.moduleOutputPath) + case .modulewrap: + buildProduct.objects += [description.wrappedModuleOutputPath] + case nil: + break + } + default: break + } + } + + buildProduct.staticTargets = dependencies.staticTargets + buildProduct.dylibs = try dependencies.dylibs.map{ + guard let product = productMap[$0] else { + throw InternalError("unknown product \($0)") + } + return product + } + buildProduct.objects += try dependencies.staticTargets.flatMap { targetName -> [AbsolutePath] in + guard let target = targetMap[targetName] else { + throw InternalError("unknown target \(targetName)") + } + return try target.objects + } + buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths + + buildProduct.availableTools = dependencies.availableTools + } + + /// Computes the dependencies of a product. + private func computeDependencies( + of product: ResolvedProduct + ) throws -> ( + dylibs: [ResolvedProduct], + staticTargets: [ResolvedTarget], + systemModules: [ResolvedTarget], + libraryBinaryPaths: Set, + availableTools: [String: AbsolutePath] + ) { + /* Prior to tools-version 5.9, we used to erroneously recursively traverse executable/plugin dependencies and statically include their + targets. For compatibility reasons, we preserve that behavior for older tools-versions. */ + let shouldExcludePlugins: Bool + if let toolsVersion = self.graph.package(for: product)?.manifest.toolsVersion { + shouldExcludePlugins = toolsVersion >= .v5_9 + } else { + shouldExcludePlugins = false + } + + // For test targets, we need to consider the first level of transitive dependencies since the first level is always test targets. + let topLevelDependencies: [PackageModel.Target] + if product.type == .test { + topLevelDependencies = product.targets.flatMap { $0.underlyingTarget.dependencies }.compactMap { + switch $0 { + case .product: + return nil + case .target(let target, _): + return target + } + } + } else { + topLevelDependencies = [] + } + + // Sort the product targets in topological order. + let nodes: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) } + let allTargets = try topologicalSort(nodes, successors: { dependency in + switch dependency { + // Include all the dependencies of a target. + case .target(let target, _): + let isTopLevel = topLevelDependencies.contains(target.underlyingTarget) || product.targets.contains(target) + let topLevelIsMacro = isTopLevel && product.type == .macro + let topLevelIsPlugin = isTopLevel && product.type == .plugin + let topLevelIsTest = isTopLevel && product.type == .test + + if !topLevelIsMacro && !topLevelIsTest && target.type == .macro { + return [] + } + if shouldExcludePlugins, !topLevelIsPlugin && !topLevelIsTest && target.type == .plugin { + return [] + } + return target.dependencies.filter { $0.satisfies(self.buildEnvironment) } + + // For a product dependency, we only include its content only if we + // need to statically link it. + case .product(let product, _): + guard dependency.satisfies(self.buildEnvironment) else { + return [] + } + + let productDependencies: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) } + switch product.type { + case .library(.automatic), .library(.static): + return productDependencies + case .plugin: + return shouldExcludePlugins ? [] : productDependencies + case .library(.dynamic), .test, .executable, .snippet, .macro: + return [] + } + } + }) + + // Create empty arrays to collect our results. + var linkLibraries = [ResolvedProduct]() + var staticTargets = [ResolvedTarget]() + var systemModules = [ResolvedTarget]() + var libraryBinaryPaths: Set = [] + var availableTools = [String: AbsolutePath]() + + for dependency in allTargets { + switch dependency { + case .target(let target, _): + switch target.type { + // Executable target have historically only been included if they are directly in the product's + // target list. Otherwise they have always been just build-time dependencies. + // In tool version .v5_5 or greater, we also include executable modules implemented in Swift in + // any test products... this is to allow testing of executables. Note that they are also still + // built as separate products that the test can invoke as subprocesses. + case .executable, .snippet, .macro: + if product.targets.contains(target) { + staticTargets.append(target) + } else if product.type == .test && (target.underlyingTarget as? SwiftTarget)?.supportsTestableExecutablesFeature == true { + // Only "top-level" targets should really be considered here, not transitive ones. + let isTopLevel = topLevelDependencies.contains(target.underlyingTarget) || product.targets.contains(target) + if let toolsVersion = graph.package(for: product)?.manifest.toolsVersion, toolsVersion >= .v5_5, isTopLevel { + staticTargets.append(target) + } + } + // Test targets should be included only if they are directly in the product's target list. + case .test: + if product.targets.contains(target) { + staticTargets.append(target) + } + // Library targets should always be included. + case .library: + staticTargets.append(target) + // Add system target to system targets array. + case .systemModule: + systemModules.append(target) + // Add binary to binary paths set. + case .binary: + guard let binaryTarget = target.underlyingTarget as? BinaryTarget else { + throw InternalError("invalid binary target '\(target.name)'") + } + switch binaryTarget.kind { + case .xcframework: + let libraries = try self.parseXCFramework(for: binaryTarget) + for library in libraries { + libraryBinaryPaths.insert(library.libraryPath) + } + case .artifactsArchive: + let tools = try self.parseArtifactsArchive(for: binaryTarget) + tools.forEach { availableTools[$0.name] = $0.executablePath } + case.unknown: + throw InternalError("unknown binary target '\(target.name)' type") + } + case .plugin: + continue + } + + case .product(let product, _): + // Add the dynamic products to array of libraries to link. + if product.type == .library(.dynamic) { + linkLibraries.append(product) + } + } + } + + // Add derived test targets, if necessary + if buildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets { + if product.type == .test, let derivedTestTargets = derivedTestTargetsMap[product] { + staticTargets.append(contentsOf: derivedTestTargets) + } + } + + return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools) + } + + /// Extracts the artifacts from an artifactsArchive + private func parseArtifactsArchive(for target: BinaryTarget) throws -> [ExecutableInfo] { + try self.externalExecutablesCache.memoize(key: target) { + let execInfos = try target.parseArtifactArchives(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) + return execInfos.filter{!$0.supportedTriples.isEmpty} + } + } +} diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift new file mode 100644 index 00000000000..8ce1a5993b8 --- /dev/null +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.InternalError +import class PackageModel.BinaryTarget +import class PackageModel.ClangTarget +import class PackageModel.MixedTarget +import class PackageModel.SystemLibraryTarget + +extension BuildPlan { + func plan(swiftTarget: SwiftTargetBuildDescription) throws { + // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target + // depends on. + for case .target(let dependency, _) in try swiftTarget.target.recursiveDependencies(satisfying: buildEnvironment) { + switch dependency.underlyingTarget { + case let underlyingTarget as ClangTarget where underlyingTarget.type == .library: + guard case let .clang(target)? = targetMap[dependency] else { + throw InternalError("unexpected clang target \(underlyingTarget)") + } + // Add the path to modulemap of the dependency. Currently we require that all Clang targets have a + // modulemap but we may want to remove that requirement since it is valid for a target to exist without + // one. However, in that case it will not be importable in Swift targets. We may want to emit a warning + // in that case from here. + guard let moduleMap = target.moduleMap else { break } + swiftTarget.appendClangFlags( + "-fmodule-map-file=\(moduleMap.pathString)", + "-I", target.clangTarget.includeDir.pathString + ) + case let target as SystemLibraryTarget: + swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"] + swiftTarget.additionalFlags += try pkgConfig(for: target).cFlags + case let target as BinaryTarget: + if case .xcframework = target.kind { + let libraries = try self.parseXCFramework(for: target) + for library in libraries { + library.headersPaths.forEach { + swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString] + } + swiftTarget.libraryBinaryPaths.insert(library.libraryPath) + } + } + case let underlyingTarget as MixedTarget where underlyingTarget.type == .library: + guard case let .mixed(target)? = targetMap[dependency] else { + throw InternalError("unexpected mixed target \(underlyingTarget)") + } + + // Add the dependency's modulemap. + swiftTarget.appendClangFlags( + "-fmodule-map-file=\(target.moduleMap.pathString)" + ) + + // Add the dependency's public headers. + swiftTarget.appendClangFlags("-I", target.publicHeadersDir.pathString) + default: + break + } + } + } +} diff --git a/Sources/Build/BuildPlan/BuildPlan+Test.swift b/Sources/Build/BuildPlan/BuildPlan+Test.swift new file mode 100644 index 00000000000..df71637ad6f --- /dev/null +++ b/Sources/Build/BuildPlan/BuildPlan+Test.swift @@ -0,0 +1,219 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 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 class Basics.ObservabilityScope +import struct Basics.InternalError +import struct Basics.AbsolutePath +import struct LLBuildManifest.TestDiscoveryTool +import struct LLBuildManifest.TestEntryPointTool +import struct PackageGraph.PackageGraph +import class PackageGraph.ResolvedProduct +import class PackageGraph.ResolvedTarget +import struct PackageModel.Sources +import class PackageModel.SwiftTarget +import class PackageModel.Target +import struct SPMBuildCore.BuildParameters +import protocol TSCBasic.FileSystem + +extension BuildPlan { + static func makeDerivedTestTargets( + _ buildParameters: BuildParameters, + _ graph: PackageGraph, + _ fileSystem: FileSystem, + _ observabilityScope: ObservabilityScope + ) throws -> [(product: ResolvedProduct, discoveryTargetBuildDescription: SwiftTargetBuildDescription?, entryPointTargetBuildDescription: SwiftTargetBuildDescription)] { + guard buildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets, + case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath) = + buildParameters.testingParameters.testProductStyle + else { + throw InternalError("makeTestManifestTargets should not be used for build plan which does not require additional derived test targets") + } + + let isEntryPointPathSpecifiedExplicitly = explicitlySpecifiedPath != nil + + var isDiscoveryEnabledRedundantly = explicitlyEnabledDiscovery && !isEntryPointPathSpecifiedExplicitly + var result: [(ResolvedProduct, SwiftTargetBuildDescription?, SwiftTargetBuildDescription)] = [] + for testProduct in graph.allProducts where testProduct.type == .test { + guard let package = graph.package(for: testProduct) else { + throw InternalError("package not found for \(testProduct)") + } + isDiscoveryEnabledRedundantly = isDiscoveryEnabledRedundantly && nil == testProduct.testEntryPointTarget + // If a non-explicitly specified test entry point file exists, prefer that over test discovery. + // This is designed as an escape hatch when test discovery is not appropriate and for backwards + // compatibility for projects that have existing test entry point files (e.g. XCTMain.swift, LinuxMain.swift). + let toolsVersion = graph.package(for: testProduct)?.manifest.toolsVersion ?? .v5_5 + + // If `testProduct.testEntryPointTarget` is non-nil, it may either represent an `XCTMain.swift` (formerly `LinuxMain.swift`) file + // if such a file is located in the package, or it may represent a test entry point file at a path specified by the option + // `--experimental-test-entry-point-path `. The latter is useful because it still performs test discovery and places the discovered + // tests into a separate target/module named "PackageDiscoveredTests". Then, that entry point file may import that module and + // obtain that list to pass it to the `XCTMain(...)` function and avoid needing to maintain a list of tests itself. + if testProduct.testEntryPointTarget != nil && explicitlyEnabledDiscovery && !isEntryPointPathSpecifiedExplicitly { + let testEntryPointName = testProduct.underlyingProduct.testEntryPointPath?.basename ?? SwiftTarget.defaultTestEntryPointName + observabilityScope.emit(warning: "'--enable-test-discovery' was specified so the '\(testEntryPointName)' entry point file for '\(testProduct.name)' will be ignored and an entry point will be generated automatically. To use test discovery with a custom entry point file, pass '--experimental-test-entry-point-path '.") + } else if testProduct.testEntryPointTarget == nil, let testEntryPointPath = explicitlySpecifiedPath, !fileSystem.exists(testEntryPointPath) { + observabilityScope.emit(error: "'--experimental-test-entry-point-path' was specified but the file '\(testEntryPointPath)' could not be found.") + } + + /// Generates test discovery targets, which contain derived sources listing the discovered tests. + func generateDiscoveryTargets() throws -> (target: SwiftTarget, resolved: ResolvedTarget, buildDescription: SwiftTargetBuildDescription) { + let discoveryTargetName = "\(package.manifest.displayName)PackageDiscoveredTests" + let discoveryDerivedDir = buildParameters.buildPath.appending(components: "\(discoveryTargetName).derived") + let discoveryMainFile = discoveryDerivedDir.appending(component: TestDiscoveryTool.mainFileName) + + var discoveryPaths: [AbsolutePath] = [] + discoveryPaths.append(discoveryMainFile) + for testTarget in testProduct.targets { + let path = discoveryDerivedDir.appending(components: testTarget.name + ".swift") + discoveryPaths.append(path) + } + + let discoveryTarget = SwiftTarget( + name: discoveryTargetName, + dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) }, + packageAccess: true, // test target is allowed access to package decls by default + testDiscoverySrc: Sources(paths: discoveryPaths, root: discoveryDerivedDir) + ) + let discoveryResolvedTarget = ResolvedTarget( + target: discoveryTarget, + dependencies: testProduct.targets.map { .target($0, conditions: []) }, + defaultLocalization: testProduct.defaultLocalization, + platforms: testProduct.platforms + ) + let discoveryTargetBuildDescription = try SwiftTargetBuildDescription( + package: package, + target: discoveryResolvedTarget, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + testTargetRole: .discovery, + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) + + return (discoveryTarget, discoveryResolvedTarget, discoveryTargetBuildDescription) + } + + /// Generates a synthesized test entry point target, consisting of a single "main" file which calls the test entry + /// point API and leverages the test discovery target to reference which tests to run. + func generateSynthesizedEntryPointTarget(discoveryTarget: SwiftTarget, discoveryResolvedTarget: ResolvedTarget) throws -> SwiftTargetBuildDescription { + let entryPointDerivedDir = buildParameters.buildPath.appending(components: "\(testProduct.name).derived") + let entryPointMainFile = entryPointDerivedDir.appending(component: TestEntryPointTool.mainFileName) + let entryPointSources = Sources(paths: [entryPointMainFile], root: entryPointDerivedDir) + + let entryPointTarget = SwiftTarget( + name: testProduct.name, + type: .library, + dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) } + [.target(discoveryTarget, conditions: [])], + packageAccess: true, // test target is allowed access to package decls + testEntryPointSources: entryPointSources + ) + let entryPointResolvedTarget = ResolvedTarget( + target: entryPointTarget, + dependencies: testProduct.targets.map { .target($0, conditions: []) } + [.target(discoveryResolvedTarget, conditions: [])], + defaultLocalization: testProduct.defaultLocalization, + platforms: testProduct.platforms + ) + return try SwiftTargetBuildDescription( + package: package, + target: entryPointResolvedTarget, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + testTargetRole: .entryPoint(isSynthesized: true), + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) + } + + if let entryPointResolvedTarget = testProduct.testEntryPointTarget { + if isEntryPointPathSpecifiedExplicitly || explicitlyEnabledDiscovery { + let discoveryTargets = try generateDiscoveryTargets() + + if isEntryPointPathSpecifiedExplicitly { + // Allow using the explicitly-specified test entry point target, but still perform test discovery and thus declare a dependency on the discovery targets. + let entryPointTarget = SwiftTarget( + name: entryPointResolvedTarget.underlyingTarget.name, + dependencies: entryPointResolvedTarget.underlyingTarget.dependencies + [.target(discoveryTargets.target, conditions: [])], + packageAccess: entryPointResolvedTarget.packageAccess, + testEntryPointSources: entryPointResolvedTarget.underlyingTarget.sources + ) + let entryPointResolvedTarget = ResolvedTarget( + target: entryPointTarget, + dependencies: entryPointResolvedTarget.dependencies + [.target(discoveryTargets.resolved, conditions: [])], + defaultLocalization: testProduct.defaultLocalization, + platforms: testProduct.platforms + ) + let entryPointTargetBuildDescription = try SwiftTargetBuildDescription( + package: package, + target: entryPointResolvedTarget, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + testTargetRole: .entryPoint(isSynthesized: false), + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) + + result.append((testProduct, discoveryTargets.buildDescription, entryPointTargetBuildDescription)) + } else { + // Ignore test entry point and synthesize one, declaring a dependency on the test discovery targets created above. + let entryPointTargetBuildDescription = try generateSynthesizedEntryPointTarget(discoveryTarget: discoveryTargets.target, discoveryResolvedTarget: discoveryTargets.resolved) + result.append((testProduct, discoveryTargets.buildDescription, entryPointTargetBuildDescription)) + } + } else { + // Use the test entry point as-is, without performing test discovery. + let entryPointTargetBuildDescription = try SwiftTargetBuildDescription( + package: package, + target: entryPointResolvedTarget, + toolsVersion: toolsVersion, + buildParameters: buildParameters, + testTargetRole: .entryPoint(isSynthesized: false), + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) + result.append((testProduct, nil, entryPointTargetBuildDescription)) + } + } else { + // Synthesize a test entry point target, declaring a dependency on the test discovery targets. + let discoveryTargets = try generateDiscoveryTargets() + let entryPointTargetBuildDescription = try generateSynthesizedEntryPointTarget(discoveryTarget: discoveryTargets.target, discoveryResolvedTarget: discoveryTargets.resolved) + result.append((testProduct, discoveryTargets.buildDescription, entryPointTargetBuildDescription)) + } + } + + if isDiscoveryEnabledRedundantly { + observabilityScope.emit(warning: "'--enable-test-discovery' option is deprecated; tests are automatically discovered on all platforms") + } + + return result + } +} + +private extension PackageModel.SwiftTarget { + /// Initialize a SwiftTarget representing a test entry point. + convenience init( + name: String, + type: PackageModel.Target.Kind? = nil, + dependencies: [PackageModel.Target.Dependency], + packageAccess: Bool, + testEntryPointSources sources: Sources + ) { + self.init( + name: name, + type: type ?? .executable, + path: .root, + sources: sources, + dependencies: dependencies, + packageAccess: packageAccess, + swiftVersion: .v5, + usesUnsafeFlags: false + ) + } +} diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 05e5a313838..e6db4f3f362 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2015-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2015-2023 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 @@ -18,10 +18,14 @@ import PackageGraph import PackageLoading import PackageModel import SPMBuildCore + +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SwiftDriver +#else +import SwiftDriver +#endif import enum TSCBasic.ProcessEnv -import func TSCBasic.topologicalSort import enum TSCUtility.Diagnostics import var TSCUtility.verbosity @@ -128,10 +132,9 @@ extension BuildParameters { var args = ["-target"] // Compute the triple string for Darwin platform using the platform version. if targetTriple.isDarwin() { - guard let macOSSupportedPlatform = target.platforms.getDerived(for: .macOS) else { - throw StringError("the target \(target) doesn't support building for macOS") - } - args += [targetTriple.tripleString(forPlatformVersion: macOSSupportedPlatform.version.versionString)] + let platform = buildEnvironment.platform + let supportedPlatform = target.platforms.getDerived(for: platform, usingXCTest: target.type == .test) + args += [targetTriple.tripleString(forPlatformVersion: supportedPlatform.version.versionString)] } else { args += [targetTriple.tripleString] } @@ -181,7 +184,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { public let buildParameters: BuildParameters /// The build environment. - private var buildEnvironment: BuildEnvironment { + var buildEnvironment: BuildEnvironment { buildParameters.buildEnvironment } @@ -215,7 +218,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// source files as well as directories to which any changes should cause us to reevaluate the build plan. public let prebuildCommandResults: [ResolvedTarget: [PrebuildCommandResult]] - private var derivedTestTargetsMap: [ResolvedProduct: [ResolvedTarget]] = [:] + private(set) var derivedTestTargetsMap: [ResolvedProduct: [ResolvedTarget]] = [:] /// Cache for pkgConfig flags. private var pkgConfigCache = [SystemLibraryTarget: (cFlags: [String], libs: [String])]() @@ -224,182 +227,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan { private var externalLibrariesCache = [BinaryTarget: [LibraryInfo]]() /// Cache for tools information. - private var externalExecutablesCache = [BinaryTarget: [ExecutableInfo]]() + var externalExecutablesCache = [BinaryTarget: [ExecutableInfo]]() /// The filesystem to operate on. - private let fileSystem: FileSystem + let fileSystem: FileSystem /// ObservabilityScope with which to emit diagnostics - private let observabilityScope: ObservabilityScope - - private static func makeDerivedTestTargets( - _ buildParameters: BuildParameters, - _ graph: PackageGraph, - _ fileSystem: FileSystem, - _ observabilityScope: ObservabilityScope - ) throws -> [(product: ResolvedProduct, discoveryTargetBuildDescription: SwiftTargetBuildDescription?, entryPointTargetBuildDescription: SwiftTargetBuildDescription)] { - guard buildParameters.testProductStyle.requiresAdditionalDerivedTestTargets, - case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath) = buildParameters.testProductStyle - else { - throw InternalError("makeTestManifestTargets should not be used for build plan which does not require additional derived test targets") - } - - let isEntryPointPathSpecifiedExplicitly = explicitlySpecifiedPath != nil - - var isDiscoveryEnabledRedundantly = explicitlyEnabledDiscovery && !isEntryPointPathSpecifiedExplicitly - var result: [(ResolvedProduct, SwiftTargetBuildDescription?, SwiftTargetBuildDescription)] = [] - for testProduct in graph.allProducts where testProduct.type == .test { - guard let package = graph.package(for: testProduct) else { - throw InternalError("package not found for \(testProduct)") - } - isDiscoveryEnabledRedundantly = isDiscoveryEnabledRedundantly && nil == testProduct.testEntryPointTarget - // If a non-explicitly specified test entry point file exists, prefer that over test discovery. - // This is designed as an escape hatch when test discovery is not appropriate and for backwards - // compatibility for projects that have existing test entry point files (e.g. XCTMain.swift, LinuxMain.swift). - let toolsVersion = graph.package(for: testProduct)?.manifest.toolsVersion ?? .v5_5 - - // If `testProduct.testEntryPointTarget` is non-nil, it may either represent an `XCTMain.swift` (formerly `LinuxMain.swift`) file - // if such a file is located in the package, or it may represent a test entry point file at a path specified by the option - // `--experimental-test-entry-point-path `. The latter is useful because it still performs test discovery and places the discovered - // tests into a separate target/module named "PackageDiscoveredTests". Then, that entry point file may import that module and - // obtain that list to pass it to the `XCTMain(...)` function and avoid needing to maintain a list of tests itself. - if testProduct.testEntryPointTarget != nil && explicitlyEnabledDiscovery && !isEntryPointPathSpecifiedExplicitly { - let testEntryPointName = testProduct.underlyingProduct.testEntryPointPath?.basename ?? SwiftTarget.defaultTestEntryPointName - observabilityScope.emit(warning: "'--enable-test-discovery' was specified so the '\(testEntryPointName)' entry point file for '\(testProduct.name)' will be ignored and an entry point will be generated automatically. To use test discovery with a custom entry point file, pass '--experimental-test-entry-point-path '.") - } else if testProduct.testEntryPointTarget == nil, let testEntryPointPath = explicitlySpecifiedPath, !fileSystem.exists(testEntryPointPath) { - observabilityScope.emit(error: "'--experimental-test-entry-point-path' was specified but the file '\(testEntryPointPath)' could not be found.") - } - - /// Generates test discovery targets, which contain derived sources listing the discovered tests. - func generateDiscoveryTargets() throws -> (target: SwiftTarget, resolved: ResolvedTarget, buildDescription: SwiftTargetBuildDescription) { - let discoveryTargetName = "\(package.manifest.displayName)PackageDiscoveredTests" - let discoveryDerivedDir = buildParameters.buildPath.appending(components: "\(discoveryTargetName).derived") - let discoveryMainFile = discoveryDerivedDir.appending(component: LLBuildManifest.TestDiscoveryTool.mainFileName) - - var discoveryPaths: [AbsolutePath] = [] - discoveryPaths.append(discoveryMainFile) - for testTarget in testProduct.targets { - let path = discoveryDerivedDir.appending(components: testTarget.name + ".swift") - discoveryPaths.append(path) - } - - let discoveryTarget = SwiftTarget( - name: discoveryTargetName, - dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) }, - packageAccess: true, // test target is allowed access to package decls by default - testDiscoverySrc: Sources(paths: discoveryPaths, root: discoveryDerivedDir) - ) - let discoveryResolvedTarget = ResolvedTarget( - target: discoveryTarget, - dependencies: testProduct.targets.map { .target($0, conditions: []) }, - defaultLocalization: .none, // safe since this is a derived target - platforms: .init(declared: [], derived: []) // safe since this is a derived target - ) - let discoveryTargetBuildDescription = try SwiftTargetBuildDescription( - package: package, - target: discoveryResolvedTarget, - toolsVersion: toolsVersion, - buildParameters: buildParameters, - testTargetRole: .discovery, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - - return (discoveryTarget, discoveryResolvedTarget, discoveryTargetBuildDescription) - } - - /// Generates a synthesized test entry point target, consisting of a single "main" file which calls the test entry - /// point API and leverages the test discovery target to reference which tests to run. - func generateSynthesizedEntryPointTarget(discoveryTarget: SwiftTarget, discoveryResolvedTarget: ResolvedTarget) throws -> SwiftTargetBuildDescription { - let entryPointDerivedDir = buildParameters.buildPath.appending(components: "\(testProduct.name).derived") - let entryPointMainFile = entryPointDerivedDir.appending(component: LLBuildManifest.TestEntryPointTool.mainFileName) - let entryPointSources = Sources(paths: [entryPointMainFile], root: entryPointDerivedDir) - - let entryPointTarget = SwiftTarget( - name: testProduct.name, - type: .library, - dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) } + [.target(discoveryTarget, conditions: [])], - packageAccess: true, // test target is allowed access to package decls - testEntryPointSources: entryPointSources - ) - let entryPointResolvedTarget = ResolvedTarget( - target: entryPointTarget, - dependencies: testProduct.targets.map { .target($0, conditions: []) } + [.target(discoveryResolvedTarget, conditions: [])], - defaultLocalization: .none, // safe since this is a derived target - platforms: .init(declared: [], derived: []) // safe since this is a derived target - ) - return try SwiftTargetBuildDescription( - package: package, - target: entryPointResolvedTarget, - toolsVersion: toolsVersion, - buildParameters: buildParameters, - testTargetRole: .entryPoint(isSynthesized: true), - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - } - - if let entryPointResolvedTarget = testProduct.testEntryPointTarget { - if isEntryPointPathSpecifiedExplicitly || explicitlyEnabledDiscovery { - let discoveryTargets = try generateDiscoveryTargets() - - if isEntryPointPathSpecifiedExplicitly { - // Allow using the explicitly-specified test entry point target, but still perform test discovery and thus declare a dependency on the discovery targets. - let entryPointTarget = SwiftTarget( - name: entryPointResolvedTarget.underlyingTarget.name, - dependencies: entryPointResolvedTarget.underlyingTarget.dependencies + [.target(discoveryTargets.target, conditions: [])], - packageAccess: entryPointResolvedTarget.packageAccess, - testEntryPointSources: entryPointResolvedTarget.underlyingTarget.sources - ) - let entryPointResolvedTarget = ResolvedTarget( - target: entryPointTarget, - dependencies: entryPointResolvedTarget.dependencies + [.target(discoveryTargets.resolved, conditions: [])], - defaultLocalization: .none, // safe since this is a derived target - platforms: .init(declared: [], derived: []) // safe since this is a derived target - ) - let entryPointTargetBuildDescription = try SwiftTargetBuildDescription( - package: package, - target: entryPointResolvedTarget, - toolsVersion: toolsVersion, - buildParameters: buildParameters, - testTargetRole: .entryPoint(isSynthesized: false), - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - - result.append((testProduct, discoveryTargets.buildDescription, entryPointTargetBuildDescription)) - } else { - // Ignore test entry point and synthesize one, declaring a dependency on the test discovery targets created above. - let entryPointTargetBuildDescription = try generateSynthesizedEntryPointTarget(discoveryTarget: discoveryTargets.target, discoveryResolvedTarget: discoveryTargets.resolved) - result.append((testProduct, discoveryTargets.buildDescription, entryPointTargetBuildDescription)) - } - } else { - // Use the test entry point as-is, without performing test discovery. - let entryPointTargetBuildDescription = try SwiftTargetBuildDescription( - package: package, - target: entryPointResolvedTarget, - toolsVersion: toolsVersion, - buildParameters: buildParameters, - testTargetRole: .entryPoint(isSynthesized: false), - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - result.append((testProduct, nil, entryPointTargetBuildDescription)) - } - } else { - // Synthesize a test entry point target, declaring a dependency on the test discovery targets. - let discoveryTargets = try generateDiscoveryTargets() - let entryPointTargetBuildDescription = try generateSynthesizedEntryPointTarget(discoveryTarget: discoveryTargets.target, discoveryResolvedTarget: discoveryTargets.resolved) - result.append((testProduct, discoveryTargets.buildDescription, entryPointTargetBuildDescription)) - } - } - - if isDiscoveryEnabledRedundantly { - observabilityScope.emit(warning: "'--enable-test-discovery' option is deprecated; tests are automatically discovered on all platforms") - } - - return result - } + let observabilityScope: ObservabilityScope /// Create a build plan with build parameters and a package graph. public init( @@ -448,6 +282,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // given to LLBuild. var targetMap = [ResolvedTarget: TargetBuildDescription]() var pluginDescriptions = [PluginDescription]() + var shouldGenerateTestObservation = true for target in graph.allTargets.sorted(by: { $0.name < $1.name }) { // Validate the product dependencies of this target. for dependency in target.dependencies { @@ -480,6 +315,12 @@ public class BuildPlan: SPMBuildCore.BuildPlan { let requiredMacroProducts = try target.recursiveTargetDependencies().filter { $0.underlyingTarget.type == .macro }.compactMap { macroProductsByTarget[$0] } + var generateTestObservation = false + if target.type == .test && shouldGenerateTestObservation { + generateTestObservation = true + shouldGenerateTestObservation = false // Only generate the code once. + } + targetMap[target] = try .swift(SwiftTargetBuildDescription( package: package, target: target, @@ -489,6 +330,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [], prebuildCommandResults: prebuildCommandResults[target] ?? [], requiredMacroProducts: requiredMacroProducts, + shouldGenerateTestObservation: generateTestObservation, fileSystem: fileSystem, observabilityScope: observabilityScope) ) @@ -503,6 +345,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { fileSystem: fileSystem, observabilityScope: observabilityScope) ) + // TODO(ncooke3): I guess pass in `shouldGenerateTestObservation: generateTestObservation`? case is MixedTarget: guard let package = graph.package(for: target) else { throw InternalError("package not found for \(target)") @@ -542,8 +385,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } // Plan the derived test targets, if necessary. - if buildParameters.testProductStyle.requiresAdditionalDerivedTestTargets { - let derivedTestTargets = try Self.makeDerivedTestTargets(buildParameters, graph, self.fileSystem, self.observabilityScope) + if buildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets { + let derivedTestTargets = try Self.makeDerivedTestTargets( + buildParameters, + graph, + self.fileSystem, + self.observabilityScope + ) for item in derivedTestTargets { var derivedTestTargets = [item.entryPointTargetBuildDescription.target] @@ -573,12 +421,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan { ) throws { // Supported platforms are defined at the package level. // This will need to become a bit complicated once we have target-level or product-level platform support. - guard let productPlatform = product.platforms.getDerived(for: .macOS) else { - throw StringError("Expected supported platform macOS in product \(product)") - } - guard let targetPlatform = target.platforms.getDerived(for: .macOS) else { - throw StringError("Expected supported platform macOS in target \(target)") - } + let productPlatform = product.platforms.getDerived(for: .macOS, usingXCTest: product.isLinkingXCTest) + let targetPlatform = target.platforms.getDerived(for: .macOS, usingXCTest: target.type == .test) // Check if the version requirement is satisfied. // @@ -609,351 +453,14 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Plan products. for buildProduct in buildProducts { - try plan(buildProduct as! ProductBuildDescription) + try plan(buildProduct: buildProduct as! ProductBuildDescription) } // FIXME: We need to find out if any product has a target on which it depends // both static and dynamically and then issue a suitable diagnostic or auto // handle that situation. } - /// Plan a product. - private func plan(_ buildProduct: ProductBuildDescription) throws { - // Compute the product's dependency. - let dependencies = try computeDependencies(of: buildProduct.product) - - // Add flags for system targets. - for systemModule in dependencies.systemModules { - guard case let target as SystemLibraryTarget = systemModule.underlyingTarget else { - throw InternalError("This should not be possible.") - } - // Add pkgConfig libs arguments. - buildProduct.additionalFlags += try pkgConfig(for: target).libs - } - - // Add flags for binary dependencies. - for binaryPath in dependencies.libraryBinaryPaths { - if binaryPath.extension == "framework" { - buildProduct.additionalFlags += ["-framework", binaryPath.basenameWithoutExt] - } else if binaryPath.basename.starts(with: "lib") { - buildProduct.additionalFlags += ["-l\(binaryPath.basenameWithoutExt.dropFirst(3))"] - } else { - self.observabilityScope.emit(error: "unexpected binary framework") - } - } - - // Link C++ if needed. - // Note: This will come from build settings in future. - for target in dependencies.staticTargets { - if case let target as ClangTarget = target.underlyingTarget, target.isCXX { - if buildParameters.targetTriple.isDarwin() { - buildProduct.additionalFlags += ["-lc++"] - } else if buildParameters.targetTriple.isWindows() { - // Don't link any C++ library. - } else { - buildProduct.additionalFlags += ["-lstdc++"] - } - break - } - } - - for target in dependencies.staticTargets { - switch target.underlyingTarget { - case is SwiftTarget: - // Swift targets are guaranteed to have a corresponding Swift description. - guard case .swift(let description) = targetMap[target] else { - throw InternalError("unknown target \(target)") - } - - // Based on the debugging strategy, we either need to pass swiftmodule paths to the - // product or link in the wrapped module object. This is required for properly debugging - // Swift products. Debugging strategy is computed based on the current platform we're - // building for and is nil for the release configuration. - switch buildParameters.debuggingStrategy { - case .swiftAST: - buildProduct.swiftASTs.insert(description.moduleOutputPath) - case .modulewrap: - buildProduct.objects += [description.wrappedModuleOutputPath] - case nil: - break - } - default: break - } - } - - buildProduct.staticTargets = dependencies.staticTargets - buildProduct.dylibs = try dependencies.dylibs.map{ - guard let product = productMap[$0] else { - throw InternalError("unknown product \($0)") - } - return product - } - buildProduct.objects += try dependencies.staticTargets.flatMap{ targetName -> [AbsolutePath] in - guard let target = targetMap[targetName] else { - throw InternalError("unknown target \(targetName)") - } - return try target.objects - } - buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths - - buildProduct.availableTools = dependencies.availableTools - } - - /// Computes the dependencies of a product. - private func computeDependencies( - of product: ResolvedProduct - ) throws -> ( - dylibs: [ResolvedProduct], - staticTargets: [ResolvedTarget], - systemModules: [ResolvedTarget], - libraryBinaryPaths: Set, - availableTools: [String: AbsolutePath] - ) { - /* Prior to tools-version 5.9, we used to errorneously recursively traverse executable/plugin dependencies and statically include their - targets. For compatibility reasons, we preserve that behavior for older tools-versions. */ - let shouldExcludeExecutablesAndPlugins: Bool - if let toolsVersion = self.graph.package(for: product)?.manifest.toolsVersion { - shouldExcludeExecutablesAndPlugins = toolsVersion >= .v5_9 - } else { - shouldExcludeExecutablesAndPlugins = false - } - - // For test targets, we need to consider the first level of transitive dependencies since the first level is always test targets. - let topLevelDependencies: [PackageModel.Target] - if product.type == .test { - topLevelDependencies = product.targets.flatMap { $0.underlyingTarget.dependencies }.compactMap { - switch $0 { - case .product: - return nil - case .target(let target, _): - return target - } - } - } else { - topLevelDependencies = [] - } - - // Sort the product targets in topological order. - let nodes: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) } - let allTargets = try topologicalSort(nodes, successors: { dependency in - switch dependency { - // Include all the dependencies of a target. - case .target(let target, _): - let isTopLevel = topLevelDependencies.contains(target.underlyingTarget) || product.targets.contains(target) - let topLevelIsExecutable = isTopLevel && product.type == .executable - let topLevelIsMacro = isTopLevel && product.type == .macro - let topLevelIsPlugin = isTopLevel && product.type == .plugin - let topLevelIsTest = isTopLevel && product.type == .test - - if !topLevelIsMacro && !topLevelIsTest && target.type == .macro { - return [] - } - if shouldExcludeExecutablesAndPlugins, !topLevelIsPlugin && !topLevelIsTest && target.type == .plugin { - return [] - } - if shouldExcludeExecutablesAndPlugins, !topLevelIsExecutable && topLevelIsTest && target.type == .executable { - return [] - } - return target.dependencies.filter { $0.satisfies(self.buildEnvironment) } - - // For a product dependency, we only include its content only if we - // need to statically link it. - case .product(let product, _): - guard dependency.satisfies(self.buildEnvironment) else { - return [] - } - - let productDependencies: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) } - switch product.type { - case .library(.automatic), .library(.static): - return productDependencies - case .plugin: - return shouldExcludeExecutablesAndPlugins ? [] : productDependencies - case .library(.dynamic), .test, .executable, .snippet, .macro: - return [] - } - } - }) - - // Create empty arrays to collect our results. - var linkLibraries = [ResolvedProduct]() - var staticTargets = [ResolvedTarget]() - var systemModules = [ResolvedTarget]() - var libraryBinaryPaths: Set = [] - var availableTools = [String: AbsolutePath]() - - for dependency in allTargets { - switch dependency { - case .target(let target, _): - switch target.type { - // Executable target have historically only been included if they are directly in the product's - // target list. Otherwise they have always been just build-time dependencies. - // In tool version .v5_5 or greater, we also include executable modules implemented in Swift in - // any test products... this is to allow testing of executables. Note that they are also still - // built as separate products that the test can invoke as subprocesses. - case .executable, .snippet, .macro: - if product.targets.contains(target) { - staticTargets.append(target) - } else if product.type == .test && (target.underlyingTarget as? SwiftTarget)?.supportsTestableExecutablesFeature == true { - if let toolsVersion = graph.package(for: product)?.manifest.toolsVersion, toolsVersion >= .v5_5 { - staticTargets.append(target) - } - } - // Test targets should be included only if they are directly in the product's target list. - case .test: - if product.targets.contains(target) { - staticTargets.append(target) - } - // Library targets should always be included. - case .library: - staticTargets.append(target) - // Add system target to system targets array. - case .systemModule: - systemModules.append(target) - // Add binary to binary paths set. - case .binary: - guard let binaryTarget = target.underlyingTarget as? BinaryTarget else { - throw InternalError("invalid binary target '\(target.name)'") - } - switch binaryTarget.kind { - case .xcframework: - let libraries = try self.parseXCFramework(for: binaryTarget) - for library in libraries { - libraryBinaryPaths.insert(library.libraryPath) - } - case .artifactsArchive: - let tools = try self.parseArtifactsArchive(for: binaryTarget) - tools.forEach { availableTools[$0.name] = $0.executablePath } - case.unknown: - throw InternalError("unknown binary target '\(target.name)' type") - } - case .plugin: - continue - } - - case .product(let product, _): - // Add the dynamic products to array of libraries to link. - if product.type == .library(.dynamic) { - linkLibraries.append(product) - } - } - } - - // Add derived test targets, if necessary - if buildParameters.testProductStyle.requiresAdditionalDerivedTestTargets { - if product.type == .test, let derivedTestTargets = derivedTestTargetsMap[product] { - staticTargets.append(contentsOf: derivedTestTargets) - } - } - - return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools) - } - - /// Plan a Clang target. - private func plan(clangTarget: ClangTargetBuildDescription) throws { - for case .target(let dependency, _) in try clangTarget.target.recursiveDependencies(satisfying: buildEnvironment) { - switch dependency.underlyingTarget { - case is SwiftTarget: - if case let .swift(dependencyTargetDescription)? = targetMap[dependency] { - if let moduleMap = dependencyTargetDescription.moduleMap { - clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] - } - } - - case let target as ClangTarget where target.type == .library: - // Setup search paths for C dependencies: - clangTarget.additionalFlags += ["-I", target.includeDir.pathString] - - // Add the modulemap of the dependency if it has one. - if case let .clang(dependencyTargetDescription)? = targetMap[dependency] { - if let moduleMap = dependencyTargetDescription.moduleMap { - clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] - } - } - case let target as MixedTarget where target.type == .library: - // Add the modulemap of the dependency. - if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { - // Add the dependency's modulemap. - clangTarget.additionalFlags.append( - "-fmodule-map-file=\(dependencyTargetDescription.moduleMap.pathString)" - ) - - // Add the dependency's public headers. - clangTarget.additionalFlags += [ "-I", dependencyTargetDescription.publicHeadersDir.pathString ] - - // Add the dependency's public VFS overlay. - clangTarget.additionalFlags += [ - "-ivfsoverlay", dependencyTargetDescription.allProductHeadersOverlay.pathString - ] - } - case let target as SystemLibraryTarget: - clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"] - clangTarget.additionalFlags += try pkgConfig(for: target).cFlags - case let target as BinaryTarget: - if case .xcframework = target.kind { - let libraries = try self.parseXCFramework(for: target) - for library in libraries { - library.headersPaths.forEach { - clangTarget.additionalFlags += ["-I", $0.pathString] - } - clangTarget.libraryBinaryPaths.insert(library.libraryPath) - } - } - default: continue - } - } - } - - /// Plan a Swift target. - private func plan(swiftTarget: SwiftTargetBuildDescription) throws { - // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target - // depends on. - for case .target(let dependency, _) in try swiftTarget.target.recursiveDependencies(satisfying: buildEnvironment) { - switch dependency.underlyingTarget { - case let underlyingTarget as ClangTarget where underlyingTarget.type == .library: - guard case let .clang(target)? = targetMap[dependency] else { - throw InternalError("unexpected clang target \(underlyingTarget)") - } - // Add the path to modulemap of the dependency. Currently we require that all Clang targets have a - // modulemap but we may want to remove that requirement since it is valid for a target to exist without - // one. However, in that case it will not be importable in Swift targets. We may want to emit a warning - // in that case from here. - guard let moduleMap = target.moduleMap else { break } - swiftTarget.appendClangFlags( - "-fmodule-map-file=\(moduleMap.pathString)", - "-I", target.clangTarget.includeDir.pathString - ) - case let target as SystemLibraryTarget: - swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"] - swiftTarget.additionalFlags += try pkgConfig(for: target).cFlags - case let target as BinaryTarget: - if case .xcframework = target.kind { - let libraries = try self.parseXCFramework(for: target) - for library in libraries { - library.headersPaths.forEach { - swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString] - } - swiftTarget.libraryBinaryPaths.insert(library.libraryPath) - } - } - case let underlyingTarget as MixedTarget where underlyingTarget.type == .library: - guard case let .mixed(target)? = targetMap[dependency] else { - throw InternalError("unexpected mixed target \(underlyingTarget)") - } - - // Add the dependency's modulemap. - swiftTarget.appendClangFlags( - "-fmodule-map-file=\(target.moduleMap.pathString)" - ) - - // Add the dependency's public headers. - swiftTarget.appendClangFlags("-I", target.publicHeadersDir.pathString) - - default: - break - } - } - } - + // TODO(ncooke3): Post-merge–– move to own file. /// Plan a Mixed target. private func plan(mixedTarget: MixedTargetBuildDescription) throws { try plan(swiftTarget: mixedTarget.swiftTargetBuildDescription) @@ -1045,7 +552,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } /// Get pkgConfig arguments for a system library target. - private func pkgConfig(for target: SystemLibraryTarget) throws -> (cFlags: [String], libs: [String]) { + func pkgConfig(for target: SystemLibraryTarget) throws -> (cFlags: [String], libs: [String]) { // If we already have these flags, we're done. if let flags = pkgConfigCache[target] { return flags @@ -1056,6 +563,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { let results = try pkgConfigArgs( for: target, pkgConfigDirectories: buildParameters.pkgConfigDirectories, + sdkRootPath: buildParameters.toolchain.sdkRootPath, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -1081,41 +589,11 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } /// Extracts the library information from an XCFramework. - private func parseXCFramework(for target: BinaryTarget) throws -> [LibraryInfo] { + func parseXCFramework(for target: BinaryTarget) throws -> [LibraryInfo] { try self.externalLibrariesCache.memoize(key: target) { return try target.parseXCFrameworks(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) } } - - /// Extracts the artifacts from an artifactsArchive - private func parseArtifactsArchive(for target: BinaryTarget) throws -> [ExecutableInfo] { - try self.externalExecutablesCache.memoize(key: target) { - let execInfos = try target.parseArtifactArchives(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) - return execInfos.filter{!$0.supportedTriples.isEmpty} - } - } -} - -private extension PackageModel.SwiftTarget { - /// Initialize a SwiftTarget representing a test entry point. - convenience init( - name: String, - type: PackageModel.Target.Kind? = nil, - dependencies: [PackageModel.Target.Dependency], - packageAccess: Bool, - testEntryPointSources sources: Sources - ) { - self.init( - name: name, - type: type ?? .executable, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - swiftVersion: .v5, - usesUnsafeFlags: false - ) - } } extension Basics.Diagnostic { diff --git a/Sources/Build/CMakeLists.txt b/Sources/Build/CMakeLists.txt index 8a975a4f7ea..68689dec830 100644 --- a/Sources/Build/CMakeLists.txt +++ b/Sources/Build/CMakeLists.txt @@ -14,11 +14,20 @@ add_library(Build BuildDescription/SwiftTargetBuildDescription.swift BuildDescription/MixedTargetBuildDescription.swift BuildDescription/TargetBuildDescription.swift + BuildManifest/LLBuildManifestBuilder.swift + BuildManifest/LLBuildManifestBuilder+Clang.swift + BuildManifest/LLBuildManifestBuilder+Product.swift + BuildManifest/LLBuildManifestBuilder+Resources.swift + BuildManifest/LLBuildManifestBuilder+Swift.swift BuildOperationBuildSystemDelegateHandler.swift BuildOperation.swift - BuildPlan.swift - LLBuildManifestBuilder.swift - SwiftCompilerOutputParser.swift) + BuildPlan/BuildPlan.swift + BuildPlan/BuildPlan+Clang.swift + BuildPlan/BuildPlan+Product.swift + BuildPlan/BuildPlan+Swift.swift + BuildPlan/BuildPlan+Test.swift + SwiftCompilerOutputParser.swift + TestObservation.swift) target_link_libraries(Build PUBLIC TSCBasic Basics diff --git a/Sources/Build/TestObservation.swift b/Sources/Build/TestObservation.swift new file mode 100644 index 00000000000..30001761c70 --- /dev/null +++ b/Sources/Build/TestObservation.swift @@ -0,0 +1,509 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 SPMBuildCore + +public func generateTestObservationCode(buildParameters: BuildParameters) -> String { + guard buildParameters.targetTriple.supportsTestSummary else { + return "" + } + + let content = + """ + import Foundation + import XCTest + + public final class SwiftPMXCTestObserver: NSObject { + public override init() { + super.init() + XCTestObservationCenter.shared.addTestObserver(self) + } + } + + extension SwiftPMXCTestObserver: XCTestObservation { + var testOutputPath: String { + return "\(buildParameters.testOutputPath)" + } + + private func write(record: Encodable) { + let lock = FileLock(at: URL(fileURLWithPath: self.testOutputPath + ".lock")) + _ = try? lock.withLock { + self._write(record: record) + } + } + + private func _write(record: Encodable) { + if let data = try? JSONEncoder().encode(record) { + if let fileHandle = FileHandle(forWritingAtPath: self.testOutputPath) { + defer { fileHandle.closeFile() } + fileHandle.seekToEndOfFile() + fileHandle.write("\\n".data(using: .utf8)!) + fileHandle.write(data) + } else { + _ = try? data.write(to: URL(fileURLWithPath: self.testOutputPath)) + } + } + } + + public func testBundleWillStart(_ testBundle: Bundle) { + let record = TestBundleEventRecord(bundle: .init(testBundle), event: .start) + write(record: TestEventRecord(bundleEvent: record)) + } + + public func testSuiteWillStart(_ testSuite: XCTestSuite) { + let record = TestSuiteEventRecord(suite: .init(testSuite), event: .start) + write(record: TestEventRecord(suiteEvent: record)) + } + + public func testCaseWillStart(_ testCase: XCTestCase) { + let record = TestCaseEventRecord(testCase: .init(testCase), event: .start) + write(record: TestEventRecord(caseEvent: record)) + } + + #if canImport(Darwin) + public func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) { + let record = TestCaseFailureRecord(testCase: .init(testCase), issue: .init(issue), failureKind: .unexpected) + write(record: TestEventRecord(caseFailure: record)) + } + + public func testCase(_ testCase: XCTestCase, didRecord expectedFailure: XCTExpectedFailure) { + let record = TestCaseFailureRecord(testCase: .init(testCase), issue: .init(expectedFailure.issue), failureKind: .expected(failureReason: expectedFailure.failureReason)) + write(record: TestEventRecord(caseFailure: record)) + } + #else + public func testCase(_ testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: Int) { + let issue = TestIssue(description: description, inFile: filePath, atLine: lineNumber) + let record = TestCaseFailureRecord(testCase: .init(testCase), issue: issue, failureKind: .unexpected) + write(record: TestEventRecord(caseFailure: record)) + } + #endif + + public func testCaseDidFinish(_ testCase: XCTestCase) { + let record = TestCaseEventRecord(testCase: .init(testCase), event: .finish) + write(record: TestEventRecord(caseEvent: record)) + } + + #if canImport(Darwin) + public func testSuite(_ testSuite: XCTestSuite, didRecord issue: XCTIssue) { + let record = TestSuiteFailureRecord(suite: .init(testSuite), issue: .init(issue), failureKind: .unexpected) + write(record: TestEventRecord(suiteFailure: record)) + } + + public func testSuite(_ testSuite: XCTestSuite, didRecord expectedFailure: XCTExpectedFailure) { + let record = TestSuiteFailureRecord(suite: .init(testSuite), issue: .init(expectedFailure.issue), failureKind: .expected(failureReason: expectedFailure.failureReason)) + write(record: TestEventRecord(suiteFailure: record)) + } + #else + public func testSuite(_ testSuite: XCTestSuite, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: Int) { + let issue = TestIssue(description: description, inFile: filePath, atLine: lineNumber) + let record = TestSuiteFailureRecord(suite: .init(testSuite), issue: issue, failureKind: .unexpected) + write(record: TestEventRecord(suiteFailure: record)) + } + #endif + + public func testSuiteDidFinish(_ testSuite: XCTestSuite) { + let record = TestSuiteEventRecord(suite: .init(testSuite), event: .finish) + write(record: TestEventRecord(suiteEvent: record)) + } + + public func testBundleDidFinish(_ testBundle: Bundle) { + let record = TestBundleEventRecord(bundle: .init(testBundle), event: .finish) + write(record: TestEventRecord(bundleEvent: record)) + } + } + + // FIXME: Copied from `Lock.swift` in TSCBasic, would be nice if we had a better way + + #if canImport(Glibc) + @_exported import Glibc + #elseif canImport(Musl) + @_exported import Musl + #elseif os(Windows) + @_exported import CRT + @_exported import WinSDK + #else + @_exported import Darwin.C + #endif + + import Foundation + + public final class FileLock { + #if os(Windows) + private var handle: HANDLE? + #else + private var fileDescriptor: CInt? + #endif + + private let lockFile: URL + + public init(at lockFile: URL) { + self.lockFile = lockFile + } + + public func lock() throws { + #if os(Windows) + if handle == nil { + let h: HANDLE = lockFile.path.withCString(encodedAs: UTF16.self, { + CreateFileW( + $0, + UInt32(GENERIC_READ) | UInt32(GENERIC_WRITE), + UInt32(FILE_SHARE_READ) | UInt32(FILE_SHARE_WRITE), + nil, + DWORD(OPEN_ALWAYS), + DWORD(FILE_ATTRIBUTE_NORMAL), + nil + ) + }) + if h == INVALID_HANDLE_VALUE { + throw FileSystemError(errno: Int32(GetLastError()), lockFile) + } + self.handle = h + } + var overlapped = OVERLAPPED() + overlapped.Offset = 0 + overlapped.OffsetHigh = 0 + overlapped.hEvent = nil + if !LockFileEx(handle, DWORD(LOCKFILE_EXCLUSIVE_LOCK), 0, + UInt32.max, UInt32.max, &overlapped) { + throw ProcessLockError.unableToAquireLock(errno: Int32(GetLastError())) + } + #else + if fileDescriptor == nil { + let fd = open(lockFile.path, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666) + if fd == -1 { + fatalError("errno: \\(errno), lockFile: \\(lockFile)") + } + self.fileDescriptor = fd + } + while true { + if flock(fileDescriptor!, LOCK_EX) == 0 { + break + } + if errno == EINTR { continue } + fatalError("unable to aquire lock, errno: \\(errno)") + } + #endif + } + + public func unlock() { + #if os(Windows) + var overlapped = OVERLAPPED() + overlapped.Offset = 0 + overlapped.OffsetHigh = 0 + overlapped.hEvent = nil + UnlockFileEx(handle, 0, UInt32.max, UInt32.max, &overlapped) + #else + guard let fd = fileDescriptor else { return } + flock(fd, LOCK_UN) + #endif + } + + deinit { + #if os(Windows) + guard let handle = handle else { return } + CloseHandle(handle) + #else + guard let fd = fileDescriptor else { return } + close(fd) + #endif + } + + public func withLock(_ body: () throws -> T) throws -> T { + try lock() + defer { unlock() } + return try body() + } + + public func withLock(_ body: () async throws -> T) async throws -> T { + try lock() + defer { unlock() } + return try await body() + } + } + + // FIXME: Copied from `XCTEvents.swift`, would be nice if we had a better way + + struct TestEventRecord: Codable { + let caseFailure: TestCaseFailureRecord? + let suiteFailure: TestSuiteFailureRecord? + + let bundleEvent: TestBundleEventRecord? + let suiteEvent: TestSuiteEventRecord? + let caseEvent: TestCaseEventRecord? + + init( + caseFailure: TestCaseFailureRecord? = nil, + suiteFailure: TestSuiteFailureRecord? = nil, + bundleEvent: TestBundleEventRecord? = nil, + suiteEvent: TestSuiteEventRecord? = nil, + caseEvent: TestCaseEventRecord? = nil + ) { + self.caseFailure = caseFailure + self.suiteFailure = suiteFailure + self.bundleEvent = bundleEvent + self.suiteEvent = suiteEvent + self.caseEvent = caseEvent + } + } + + // MARK: - Records + + struct TestAttachment: Codable { + let name: String? + // TODO: Handle `userInfo: [AnyHashable : Any]?` + let uniformTypeIdentifier: String + let payload: Data? + } + + struct TestBundleEventRecord: Codable { + let bundle: TestBundle + let event: TestEvent + } + + struct TestCaseEventRecord: Codable { + let testCase: TestCase + let event: TestEvent + } + + struct TestCaseFailureRecord: Codable, CustomStringConvertible { + let testCase: TestCase + let issue: TestIssue + let failureKind: TestFailureKind + + var description: String { + return "\\(issue.sourceCodeContext.description)\\(testCase) \\(issue.compactDescription)" + } + } + + struct TestSuiteEventRecord: Codable { + let suite: TestSuiteRecord + let event: TestEvent + } + + struct TestSuiteFailureRecord: Codable { + let suite: TestSuiteRecord + let issue: TestIssue + let failureKind: TestFailureKind + } + + // MARK: Primitives + + struct TestBundle: Codable { + let bundleIdentifier: String? + let bundlePath: String + } + + struct TestCase: Codable { + let name: String + } + + struct TestErrorInfo: Codable { + let description: String + let type: String + } + + enum TestEvent: Codable { + case start + case finish + } + + enum TestFailureKind: Codable, Equatable { + case unexpected + case expected(failureReason: String?) + + var isExpected: Bool { + switch self { + case .expected: return true + case .unexpected: return false + } + } + } + + struct TestIssue: Codable { + let type: TestIssueType + let compactDescription: String + let detailedDescription: String? + let associatedError: TestErrorInfo? + let sourceCodeContext: TestSourceCodeContext + let attachments: [TestAttachment] + } + + enum TestIssueType: Codable { + case assertionFailure + case performanceRegression + case system + case thrownError + case uncaughtException + case unmatchedExpectedFailure + case unknown + } + + struct TestLocation: Codable, CustomStringConvertible { + let file: String + let line: Int + + var description: String { + return "\\(file):\\(line) " + } + } + + struct TestSourceCodeContext: Codable, CustomStringConvertible { + let callStack: [TestSourceCodeFrame] + let location: TestLocation? + + var description: String { + return location?.description ?? "" + } + } + + struct TestSourceCodeFrame: Codable { + let address: UInt64 + let symbolInfo: TestSourceCodeSymbolInfo? + let symbolicationError: TestErrorInfo? + } + + struct TestSourceCodeSymbolInfo: Codable { + let imageName: String + let symbolName: String + let location: TestLocation? + } + + struct TestSuiteRecord: Codable { + let name: String + } + + // MARK: XCTest compatibility + + extension TestIssue { + init(description: String, inFile filePath: String?, atLine lineNumber: Int) { + let location: TestLocation? + if let filePath = filePath { + location = .init(file: filePath, line: lineNumber) + } else { + location = nil + } + self.init(type: .assertionFailure, compactDescription: description, detailedDescription: description, associatedError: nil, sourceCodeContext: .init(callStack: [], location: location), attachments: []) + } + } + + import XCTest + + #if canImport(Darwin) // XCTAttachment is unavailable in swift-corelibs-xctest. + extension TestAttachment { + init(_ attachment: XCTAttachment) { + self.init( + name: attachment.name, + uniformTypeIdentifier: attachment.uniformTypeIdentifier, + payload: attachment.value(forKey: "payload") as? Data + ) + } + } + #endif + + extension TestBundle { + init(_ testBundle: Bundle) { + self.init( + bundleIdentifier: testBundle.bundleIdentifier, + bundlePath: testBundle.bundlePath + ) + } + } + + extension TestCase { + init(_ testCase: XCTestCase) { + self.init(name: testCase.name) + } + } + + extension TestErrorInfo { + init(_ error: Swift.Error) { + self.init(description: "\\(error)", type: "\\(Swift.type(of: error))") + } + } + + #if canImport(Darwin) // XCTIssue is unavailable in swift-corelibs-xctest. + extension TestIssue { + init(_ issue: XCTIssue) { + self.init( + type: .init(issue.type), + compactDescription: issue.compactDescription, + detailedDescription: issue.detailedDescription, + associatedError: issue.associatedError.map { .init($0) }, + sourceCodeContext: .init(issue.sourceCodeContext), + attachments: issue.attachments.map { .init($0) } + ) + } + } + + extension TestIssueType { + init(_ type: XCTIssue.IssueType) { + switch type { + case .assertionFailure: self = .assertionFailure + case .thrownError: self = .thrownError + case .uncaughtException: self = .uncaughtException + case .performanceRegression: self = .performanceRegression + case .system: self = .system + case .unmatchedExpectedFailure: self = .unmatchedExpectedFailure + @unknown default: self = .unknown + } + } + } + #endif + + #if canImport(Darwin) // XCTSourceCodeLocation/XCTSourceCodeContext/XCTSourceCodeFrame/XCTSourceCodeSymbolInfo is unavailable in swift-corelibs-xctest. + extension TestLocation { + init(_ location: XCTSourceCodeLocation) { + self.init( + file: location.fileURL.absoluteString, + line: location.lineNumber + ) + } + } + + extension TestSourceCodeContext { + init(_ context: XCTSourceCodeContext) { + self.init( + callStack: context.callStack.map { .init($0) }, + location: context.location.map { .init($0) } + ) + } + } + + extension TestSourceCodeFrame { + init(_ frame: XCTSourceCodeFrame) { + self.init( + address: frame.address, + symbolInfo: (try? frame.symbolInfo()).map { .init($0) }, + symbolicationError: frame.symbolicationError.map { .init($0) } + ) + } + } + + extension TestSourceCodeSymbolInfo { + init(_ symbolInfo: XCTSourceCodeSymbolInfo) { + self.init( + imageName: symbolInfo.imageName, + symbolName: symbolInfo.symbolName, + location: symbolInfo.location.map { .init($0) } + ) + } + } + #endif + + extension TestSuiteRecord { + init(_ testSuite: XCTestSuite) { + self.init(name: testSuite.name) + } + } + """ + return content +} diff --git a/Sources/Commands/CMakeLists.txt b/Sources/Commands/CMakeLists.txt index 9c25daf1f80..9a08d7666e7 100644 --- a/Sources/Commands/CMakeLists.txt +++ b/Sources/Commands/CMakeLists.txt @@ -44,7 +44,8 @@ add_library(Commands Utilities/MultiRootSupport.swift Utilities/PluginDelegate.swift Utilities/SymbolGraphExtract.swift - Utilities/TestingSupport.swift) + Utilities/TestingSupport.swift + Utilities/XCTEvents.swift) target_link_libraries(Commands PUBLIC ArgumentParser Basics diff --git a/Sources/Commands/PackageTools/ArchiveSource.swift b/Sources/Commands/PackageTools/ArchiveSource.swift index c51ee8159d9..b45e4cf586c 100644 --- a/Sources/Commands/PackageTools/ArchiveSource.swift +++ b/Sources/Commands/PackageTools/ArchiveSource.swift @@ -66,7 +66,8 @@ extension SwiftPackageTool { cancellator: Cancellator? ) throws { let gitRepositoryProvider = GitRepositoryProvider() - if gitRepositoryProvider.repositoryExists(at: packageDirectory) { + if gitRepositoryProvider.repositoryExists(at: packageDirectory) && + (try? gitRepositoryProvider.isValidDirectory(packageDirectory)) == true { let repository = GitRepository(path: packageDirectory, cancellator: cancellator) try repository.archive(to: archivePath) } else { diff --git a/Sources/Commands/PackageTools/ComputeChecksum.swift b/Sources/Commands/PackageTools/ComputeChecksum.swift index 192cbaf1bf1..af22f838671 100644 --- a/Sources/Commands/PackageTools/ComputeChecksum.swift +++ b/Sources/Commands/PackageTools/ComputeChecksum.swift @@ -33,6 +33,7 @@ struct ComputeChecksum: SwiftCommand { authorizationProvider: swiftTool.getAuthorizationProvider(), hostToolchain: swiftTool.getHostToolchain(), checksumAlgorithm: SHA256(), + cachePath: .none, customHTTPClient: .none, customArchiver: .none, delegate: .none diff --git a/Sources/Commands/PackageTools/Config.swift b/Sources/Commands/PackageTools/Config.swift index f9ebed2174b..2e8d8c72d7d 100644 --- a/Sources/Commands/PackageTools/Config.swift +++ b/Sources/Commands/PackageTools/Config.swift @@ -80,7 +80,7 @@ extension SwiftPackageTool.Config { } try config.applyLocal { mirrors in - mirrors.set(mirror: mirror, for: original) + try mirrors.set(mirror: mirror, for: original) } } } diff --git a/Sources/Commands/PackageTools/DumpCommands.swift b/Sources/Commands/PackageTools/DumpCommands.swift index 7d9ac0cbcd3..53e1815ceb1 100644 --- a/Sources/Commands/PackageTools/DumpCommands.swift +++ b/Sources/Commands/PackageTools/DumpCommands.swift @@ -66,15 +66,25 @@ struct DumpSymbolGraph: SwiftCommand { // Run the tool once for every library and executable target in the root package. let buildPlan = try buildSystem.buildPlan let symbolGraphDirectory = buildPlan.buildParameters.dataPath.appending("symbolgraph") - let targets = try buildSystem.getPackageGraph().rootPackages.flatMap{ $0.targets }.filter{ $0.type == .library || $0.type == .executable } + let targets = try buildSystem.getPackageGraph().rootPackages.flatMap{ $0.targets }.filter{ $0.type == .library } for target in targets { print("-- Emitting symbol graph for", target.name) - try symbolGraphExtractor.extractSymbolGraph( + let result = try symbolGraphExtractor.extractSymbolGraph( target: target, buildPlan: buildPlan, + outputRedirection: .collect(redirectStderr: true), outputDirectory: symbolGraphDirectory, verboseOutput: swiftTool.logLevel <= .info ) + + if result.exitStatus != .terminated(code: 0) { + switch result.output { + case .success(let value): + swiftTool.observabilityScope.emit(error: "Failed to emit symbol graph for '\(target.c99name)': \(String(decoding: value, as: UTF8.self))") + case .failure(let error): + swiftTool.observabilityScope.emit(error: "Internal error while emitting symbol graph for '\(target.c99name)': \(error)") + } + } } print("Files written to", symbolGraphDirectory.pathString) @@ -112,7 +122,7 @@ struct DumpPackage: SwiftCommand { encoder.userInfo[Manifest.dumpPackageKey] = true let jsonData = try encoder.encode(rootManifest) - let jsonString = String(data: jsonData, encoding: .utf8)! + let jsonString = String(decoding: jsonData, as: UTF8.self) print(jsonString) } } diff --git a/Sources/Commands/PackageTools/Init.swift b/Sources/Commands/PackageTools/Init.swift index c282db1c299..221840107ce 100644 --- a/Sources/Commands/PackageTools/Init.swift +++ b/Sources/Commands/PackageTools/Init.swift @@ -51,6 +51,7 @@ extension SwiftPackageTool { name: packageName, packageType: initMode, destinationPath: cwd, + installedSwiftPMConfiguration: swiftTool.getHostToolchain().installedSwiftPMConfiguration, fileSystem: swiftTool.fileSystem ) initPackage.progressReporter = { message in diff --git a/Sources/Commands/PackageTools/PluginCommand.swift b/Sources/Commands/PackageTools/PluginCommand.swift index c2cf6f5e47b..648656ebc6a 100644 --- a/Sources/Commands/PackageTools/PluginCommand.swift +++ b/Sources/Commands/PackageTools/PluginCommand.swift @@ -57,6 +57,12 @@ struct PluginCommand: SwiftCommand { @Option(name: .customLong("allow-network-connections")) var allowNetworkConnections: NetworkPermission = .none + + @Option( + name: .customLong("package"), + help: "Limit available plugins to a single package with the given identity" + ) + var packageIdentity: String? = nil } @OptionGroup() @@ -80,9 +86,9 @@ struct PluginCommand: SwiftCommand { // List the available plugins, if asked to. if self.listCommands { let packageGraph = try swiftTool.loadPackageGraph() - let allPlugins = PluginCommand.availableCommandPlugins(in: packageGraph) + let allPlugins = PluginCommand.availableCommandPlugins(in: packageGraph, limitedTo: self.pluginOptions.packageIdentity) for plugin in allPlugins.sorted(by: { $0.name < $1.name }) { - guard case .command(let intent, _) = plugin.capability else { return } + guard case .command(let intent, _) = plugin.capability else { continue } var line = "‘\(intent.invocationVerb)’ (plugin ‘\(plugin.name)’" if let package = packageGraph.packages .first(where: { $0.targets.contains(where: { $0.name == plugin.name }) }) @@ -113,7 +119,7 @@ struct PluginCommand: SwiftCommand { let packageGraph = try swiftTool.loadPackageGraph() swiftTool.observabilityScope.emit(info: "Finding plugin for command ‘\(command)’") - let matchingPlugins = PluginCommand.findPlugins(matching: command, in: packageGraph) + let matchingPlugins = PluginCommand.findPlugins(matching: command, in: packageGraph, limitedTo: options.packageIdentity) // Complain if we didn't find exactly one. if matchingPlugins.isEmpty { @@ -275,7 +281,8 @@ struct PluginCommand: SwiftCommand { let delegateQueue = DispatchQueue(label: "plugin-invocation") // Run the command plugin. - let buildEnvironment = try swiftTool.buildParameters().buildEnvironment + let buildParameters = try swiftTool.buildParameters() + let buildEnvironment = buildParameters.buildEnvironment let _ = try temp_await { plugin.invoke( action: .performCommand(package: package, arguments: arguments), buildEnvironment: buildEnvironment, @@ -288,6 +295,7 @@ struct PluginCommand: SwiftCommand { readOnlyDirectories: readOnlyDirectories, allowNetworkConnections: allowNetworkConnections, pkgConfigDirectories: swiftTool.options.locations.pkgConfigDirectories, + sdkRootPath: buildParameters.toolchain.sdkRootPath, fileSystem: swiftTool.fileSystem, observabilityScope: swiftTool.observabilityScope, callbackQueue: delegateQueue, @@ -298,13 +306,23 @@ struct PluginCommand: SwiftCommand { // TODO: We should also emit a final line of output regarding the result. } - static func availableCommandPlugins(in graph: PackageGraph) -> [PluginTarget] { - graph.allTargets.compactMap { $0.underlyingTarget as? PluginTarget } + static func availableCommandPlugins(in graph: PackageGraph, limitedTo packageIdentity: String?) -> [PluginTarget] { + // All targets from plugin products of direct dependencies are "available". + let directDependencyPackages = graph.rootPackages.flatMap { $0.dependencies }.filter { $0.matching(identity: packageIdentity) } + let directDependencyPluginTargets = directDependencyPackages.flatMap { $0.products.filter { $0.type == .plugin } }.flatMap { $0.targets } + // As well as any plugin targets in root packages. + let rootPackageTargets = graph.rootPackages.filter { $0.matching(identity: packageIdentity) }.flatMap { $0.targets } + return (directDependencyPluginTargets + rootPackageTargets).compactMap { $0.underlyingTarget as? PluginTarget }.filter { + switch $0.capability { + case .buildTool: return false + case .command: return true + } + } } - static func findPlugins(matching verb: String, in graph: PackageGraph) -> [PluginTarget] { + static func findPlugins(matching verb: String, in graph: PackageGraph, limitedTo packageIdentity: String?) -> [PluginTarget] { // Find and return the command plugins that match the command. - Self.availableCommandPlugins(in: graph).filter { + Self.availableCommandPlugins(in: graph, limitedTo: packageIdentity).filter { // Filter out any non-command plugins and any whose verb is different. guard case .command(let intent, _) = $0.capability else { return false } return verb == intent.invocationVerb @@ -376,3 +394,13 @@ extension SandboxNetworkPermission { } } } + +extension ResolvedPackage { + fileprivate func matching(identity: String?) -> Bool { + if let identity { + return self.identity == .plain(identity) + } else { + return true + } + } +} diff --git a/Sources/Commands/PackageTools/SwiftPackageTool.swift b/Sources/Commands/PackageTools/SwiftPackageTool.swift index 67f90c947d1..3574736c5a3 100644 --- a/Sources/Commands/PackageTools/SwiftPackageTool.swift +++ b/Sources/Commands/PackageTools/SwiftPackageTool.swift @@ -126,7 +126,7 @@ extension PluginCommand.PluginOptions { func merged(with other: Self) -> Self { // validate against developer mistake assert( - Mirror(reflecting: self).children.count == 3, + Mirror(reflecting: self).children.count == 4, "Property added to PluginOptions without updating merged(with:)!" ) // actual merge @@ -137,6 +137,9 @@ extension PluginCommand.PluginOptions { if other.allowNetworkConnections != .none { merged.allowNetworkConnections = other.allowNetworkConnections } + if other.packageIdentity != nil { + merged.packageIdentity = other.packageIdentity + } return merged } } diff --git a/Sources/Commands/PackageTools/Update.swift b/Sources/Commands/PackageTools/Update.swift index 5fd5c480a2b..395d34d6ce6 100644 --- a/Sources/Commands/PackageTools/Update.swift +++ b/Sources/Commands/PackageTools/Update.swift @@ -42,14 +42,6 @@ extension SwiftPackageTool { observabilityScope: swiftTool.observabilityScope ) - // try to load the graph which will emit any errors - if !swiftTool.observabilityScope.errorsReported { - _ = try workspace.loadPackageGraph( - rootInput: swiftTool.getWorkspaceRoot(), - observabilityScope: swiftTool.observabilityScope - ) - } - if self.dryRun, let changes = changes, let pinsStore = swiftTool.observabilityScope.trap({ try workspace.pinsStore.load() }){ self.logPackageChanges(changes: changes, pins: pinsStore) } diff --git a/Sources/Commands/SwiftBuildTool.swift b/Sources/Commands/SwiftBuildTool.swift index abf5468aef3..243d61dc543 100644 --- a/Sources/Commands/SwiftBuildTool.swift +++ b/Sources/Commands/SwiftBuildTool.swift @@ -85,6 +85,10 @@ struct BuildToolOptions: ParsableArguments { /// Specific product to build. @Option(help: "Build the specified product") var product: String? + + /// If should link the Swift stdlib statically. + @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically") + public var shouldLinkStaticSwiftStdlib: Bool = false } /// swift-build tool namespace @@ -122,17 +126,12 @@ public struct SwiftBuildTool: SwiftCommand { return } - #if os(Linux) - // Emit warning if clang is older than version 3.6 on Linux. - // See: SR-2299 Swift isn't using Gold by default on stock 14.04. - checkClangVersion(observabilityScope: swiftTool.observabilityScope) - #endif - guard let subset = options.buildSubset(observabilityScope: swiftTool.observabilityScope) else { throw ExitCode.failure } let buildSystem = try swiftTool.createBuildSystem( explicitProduct: options.product, + shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, // command result output goes on stdout // ie "swift build" should output to stdout customOutputStream: TSCBasic.stdoutStream @@ -144,25 +143,11 @@ public struct SwiftBuildTool: SwiftCommand { } } - private func checkClangVersion(observabilityScope: ObservabilityScope) { - // We only care about this on Ubuntu 14.04 - guard let uname = try? TSCBasic.Process.checkNonZeroExit(args: "lsb_release", "-r").spm_chomp(), - uname.hasSuffix("14.04"), - let clangVersionOutput = try? TSCBasic.Process.checkNonZeroExit(args: "clang", "--version").spm_chomp(), - let clang = getClangVersion(versionOutput: clangVersionOutput) else { - return - } - - if clang < Version(3, 6, 0) { - observabilityScope.emit(warning: "minimum recommended clang is version 3.6, otherwise you may encounter linker errors.") - } - } - public init() {} } public extension _SwiftCommand { func buildSystemProvider(_ swiftTool: SwiftTool) throws -> BuildSystemProvider { - return try swiftTool.defaultBuildSystemProvider + swiftTool.defaultBuildSystemProvider } } diff --git a/Sources/Commands/SwiftTestTool.swift b/Sources/Commands/SwiftTestTool.swift index 7d44ea30644..ea0954c98c8 100644 --- a/Sources/Commands/SwiftTestTool.swift +++ b/Sources/Commands/SwiftTestTool.swift @@ -14,8 +14,7 @@ import ArgumentParser import Basics import CoreCommands import Dispatch -import class Foundation.NSLock -import class Foundation.ProcessInfo +import Foundation import PackageGraph import PackageModel import SPMBuildCore @@ -35,8 +34,10 @@ import class TSCUtility.PercentProgressAnimation import protocol TSCUtility.ProgressAnimationProtocol private enum TestError: Swift.Error { - case invalidListTestJSONData - case testsExecutableNotFound + case invalidListTestJSONData(context: String, underlyingError: Error? = nil) + case testsNotFound + case testProductNotFound(productName: String) + case productIsNotTest(productName: String) case multipleTestProducts([String]) case xctestNotAvailable } @@ -44,10 +45,15 @@ private enum TestError: Swift.Error { extension TestError: CustomStringConvertible { var description: String { switch self { - case .testsExecutableNotFound: + case .testsNotFound: return "no tests found; create a target in the 'Tests' directory" - case .invalidListTestJSONData: - return "invalid list test JSON structure" + case .testProductNotFound(let productName): + return "there is no test product named '\(productName)'" + case .productIsNotTest(let productName): + return "the product '\(productName)' is not a test" + case .invalidListTestJSONData(let context, let underlyingError): + let underlying = underlyingError != nil ? ", underlying error: \(underlyingError!)" : "" + return "invalid list test JSON structure, produced by \(context)\(underlying)" case .multipleTestProducts(let products): return "found multiple test products: \(products.joined(separator: ", ")); use --test-product to select one" case .xctestNotAvailable: @@ -63,11 +69,29 @@ struct SharedOptions: ParsableArguments { /// The test product to use. This is useful when there are multiple test products /// to choose from (usually in multiroot packages). - @Option(help: "Test the specified product.") + @Option(help: .hidden) var testProduct: String? + + /// Whether to enable support for XCTest. + @Flag(name: .customLong("xctest"), + inversion: .prefixedEnableDisable, + help: "Enable support for XCTest") + var enableXCTestSupport: Bool = true + + /// Whether to enable support for swift-testing. + @Flag(name: .customLong("experimental-swift-testing"), + inversion: .prefixedEnableDisable, + help: "Enable experimental support for swift-testing") + var enableSwiftTestingLibrarySupport: Bool = false } struct TestToolOptions: ParsableArguments { + @OptionGroup() + var globalOptions: GlobalOptions + + @OptionGroup() + var sharedOptions: SharedOptions + /// If tests should run in parallel mode. @Flag(name: .customLong("parallel"), help: "Run the tests in parallel.") @@ -123,6 +147,14 @@ struct TestToolOptions: ParsableArguments { inversion: .prefixedEnableDisable, help: "Enable code coverage") var enableCodeCoverage: Bool = false + + /// Configure test output. + @Option(help: ArgumentHelp("", visibility: .hidden)) + public var testOutput: TestOutput = .default + + var enableExperimentalTestOutput: Bool { + return testOutput == .experimentalSummary + } } /// Tests filtering specifier, which is used to filter tests to run. @@ -140,6 +172,18 @@ public enum TestCaseSpecifier { case skip([String]) } +/// Different styles of test output. +public enum TestOutput: String, ExpressibleByArgument { + /// Whatever `xctest` emits to the console. + case `default` + + /// Capture XCTest events and provide a summary. + case experimentalSummary + + /// Let the test process emit parseable output to the console. + case experimentalParseable +} + /// swift-test tool namespace public struct SwiftTestTool: SwiftCommand { public static var configuration = CommandConfiguration( @@ -149,136 +193,55 @@ public struct SwiftTestTool: SwiftCommand { discussion: "SEE ALSO: swift build, swift run, swift package", version: SwiftVersion.current.completeDisplayString, subcommands: [ - List.self, + List.self, Last.self ], helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) - @OptionGroup() - public var globalOptions: GlobalOptions - - @OptionGroup() - var sharedOptions: SharedOptions + public var globalOptions: GlobalOptions { + options.globalOptions + } @OptionGroup() var options: TestToolOptions - public func run(_ swiftTool: SwiftTool) throws { - do { - // Validate commands arguments - try self.validateArguments(observabilityScope: swiftTool.observabilityScope) + // MARK: - XCTest - // validate XCTest available on darwin based systems - let toolchain = try swiftTool.getTargetToolchain() - if toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil { - throw TestError.xctestNotAvailable - } - } catch { - swiftTool.observabilityScope.emit(error) - throw ExitCode.failure + private func xctestRun(_ swiftTool: SwiftTool) throws { + // validate XCTest available on darwin based systems + let toolchain = try swiftTool.getTargetToolchain() + let isHostTestingAvailable = try swiftTool.getHostToolchain().swiftSDK.supportsTesting + if (toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil) || !isHostTestingAvailable { + throw TestError.xctestNotAvailable } - if self.options.shouldPrintCodeCovPath { - let command = try PrintCodeCovPath.parse() - try command.run(swiftTool) - } else if self.options._deprecated_shouldListTests { - // backward compatibility 6/2022 for deprecation of flag into a subcommand - let command = try List.parse() - try command.run(swiftTool) - } else if !self.options.shouldRunInParallel { - let toolchain = try swiftTool.getTargetToolchain() - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool) - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, sharedOptions: self.sharedOptions) - - // Clean out the code coverage directory that may contain stale - // profraw files from a previous run of the code coverage tool. - if self.options.enableCodeCoverage { - try swiftTool.fileSystem.removeFileTree(buildParameters.codeCovPath) - } - - let xctestArg: String? - - switch options.testCaseSpecifier { - case .none: - if case .skip = options.skippedTests(fileSystem: swiftTool.fileSystem) { - fallthrough - } else { - xctestArg = nil - } - - case .regex, .specific, .skip: - // If old specifier `-s` option was used, emit deprecation notice. - if case .specific = options.testCaseSpecifier { - swiftTool.observabilityScope.emit(warning: "'--specifier' option is deprecated; use '--filter' instead") - } - - // Find the tests we need to run. - let testSuites = try TestingSupport.getTestSuites( - in: testProducts, - swiftTool: swiftTool, - enableCodeCoverage: options.enableCodeCoverage, - shouldSkipBuilding: sharedOptions.shouldSkipBuilding, - sanitizers: globalOptions.build.sanitizers - ) - let tests = try testSuites - .filteredTests(specifier: options.testCaseSpecifier) - .skippedTests(specifier: options.skippedTests(fileSystem: swiftTool.fileSystem)) - - // If there were no matches, emit a warning. - if tests.isEmpty { - swiftTool.observabilityScope.emit(.noMatchingTests) - xctestArg = "''" - } else { - xctestArg = tests.map { $0.specifier }.joined(separator: ",") - } - } - - let testEnv = try TestingSupport.constructTestEnvironment( - toolchain: toolchain, - buildParameters: buildParameters, - sanitizers: globalOptions.build.sanitizers - ) - - let runner = TestRunner( - bundlePaths: testProducts.map { $0.bundlePath }, - xctestArg: xctestArg, - cancellator: swiftTool.cancellator, - toolchain: toolchain, - testEnv: testEnv, - observabilityScope: swiftTool.observabilityScope - ) - - // Finally, run the tests. - let ranSuccessfully = runner.test(outputHandler: { - // command's result output goes on stdout - // ie "swift test" should output to stdout - print($0) - }) - if !ranSuccessfully { - swiftTool.executionStatus = .failure - } + let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: .xctest) - if self.options.enableCodeCoverage { - try processCodeCoverage(testProducts, swiftTool: swiftTool) - } + // Remove test output from prior runs and validate priors. + if self.options.enableExperimentalTestOutput && buildParameters.targetTriple.supportsTestSummary { + _ = try? localFileSystem.removeFileTree(buildParameters.testOutputPath) + } + let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, library: .xctest) + if !self.options.shouldRunInParallel { + let xctestArgs = try xctestArgs(for: testProducts, swiftTool: swiftTool) + try runTestProducts(testProducts, additionalArguments: xctestArgs, buildParameters: buildParameters, swiftTool: swiftTool, library: .xctest) } else { - let toolchain = try swiftTool.getTargetToolchain() - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool) let testSuites = try TestingSupport.getTestSuites( in: testProducts, swiftTool: swiftTool, enableCodeCoverage: options.enableCodeCoverage, - shouldSkipBuilding: sharedOptions.shouldSkipBuilding, + shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, + experimentalTestOutput: options.enableExperimentalTestOutput, sanitizers: globalOptions.build.sanitizers ) let tests = try testSuites .filteredTests(specifier: options.testCaseSpecifier) .skippedTests(specifier: options.skippedTests(fileSystem: swiftTool.fileSystem)) - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, sharedOptions: self.sharedOptions) // If there were no matches, emit a warning and exit. if tests.isEmpty { swiftTool.observabilityScope.emit(.noMatchingTests) + try generateXUnitOutputIfRequested(for: [], swiftTool: swiftTool) return } @@ -302,28 +265,185 @@ public struct SwiftTestTool: SwiftCommand { let testResults = try runner.run(tests) - // Generate xUnit file if requested - if let xUnitOutput = options.xUnitOutput { - let generator = XUnitGenerator( - fileSystem: swiftTool.fileSystem, - results: testResults - ) - try generator.generate(at: xUnitOutput) - } + try generateXUnitOutputIfRequested(for: testResults, swiftTool: swiftTool) // process code Coverage if request - if self.options.enableCodeCoverage { - try processCodeCoverage(testProducts, swiftTool: swiftTool) + if self.options.enableCodeCoverage, runner.ranSuccessfully { + try processCodeCoverage(testProducts, swiftTool: swiftTool, library: .xctest) } if !runner.ranSuccessfully { swiftTool.executionStatus = .failure } + + if self.options.enableExperimentalTestOutput, !runner.ranSuccessfully { + try Self.handleTestOutput(buildParameters: buildParameters, packagePath: testProducts[0].packagePath) + } + } + } + + private func xctestArgs(for testProducts: [BuiltTestProduct], swiftTool: SwiftTool) throws -> [String] { + switch options.testCaseSpecifier { + case .none: + if case .skip = options.skippedTests(fileSystem: swiftTool.fileSystem) { + fallthrough + } else { + return [] + } + + case .regex, .specific, .skip: + // If old specifier `-s` option was used, emit deprecation notice. + if case .specific = options.testCaseSpecifier { + swiftTool.observabilityScope.emit(warning: "'--specifier' option is deprecated; use '--filter' instead") + } + + // Find the tests we need to run. + let testSuites = try TestingSupport.getTestSuites( + in: testProducts, + swiftTool: swiftTool, + enableCodeCoverage: options.enableCodeCoverage, + shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, + experimentalTestOutput: options.enableExperimentalTestOutput, + sanitizers: globalOptions.build.sanitizers + ) + let tests = try testSuites + .filteredTests(specifier: options.testCaseSpecifier) + .skippedTests(specifier: options.skippedTests(fileSystem: swiftTool.fileSystem)) + + // If there were no matches, emit a warning. + if tests.isEmpty { + swiftTool.observabilityScope.emit(.noMatchingTests) + } + + return TestRunner.xctestArguments(forTestSpecifiers: tests.map(\.specifier)) + } + } + + /// Generate xUnit file if requested. + private func generateXUnitOutputIfRequested(for testResults: [ParallelTestRunner.TestResult], swiftTool: SwiftTool) throws { + guard let xUnitOutput = options.xUnitOutput else { + return + } + + let generator = XUnitGenerator( + fileSystem: swiftTool.fileSystem, + results: testResults + ) + try generator.generate(at: xUnitOutput) + } + + // MARK: - swift-testing + + private func swiftTestingRun(_ swiftTool: SwiftTool) throws { + let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: .swiftTesting) + let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, library: .swiftTesting) + let additionalArguments = Array(CommandLine.arguments.dropFirst()) + try runTestProducts(testProducts, additionalArguments: additionalArguments, buildParameters: buildParameters, swiftTool: swiftTool, library: .swiftTesting) + } + + // MARK: - Common implementation + + public func run(_ swiftTool: SwiftTool) throws { + do { + // Validate commands arguments + try self.validateArguments(observabilityScope: swiftTool.observabilityScope) + } catch { + swiftTool.observabilityScope.emit(error) + throw ExitCode.failure + } + + if self.options.shouldPrintCodeCovPath { + try printCodeCovPath(swiftTool) + } else if self.options._deprecated_shouldListTests { + // backward compatibility 6/2022 for deprecation of flag into a subcommand + let command = try List.parse() + try command.run(swiftTool) + } else { + if options.sharedOptions.enableSwiftTestingLibrarySupport { + try swiftTestingRun(swiftTool) + } + if options.sharedOptions.enableXCTestSupport { + try xctestRun(swiftTool) + } + } + } + + private func runTestProducts(_ testProducts: [BuiltTestProduct], additionalArguments: [String], buildParameters: BuildParameters, swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { + // Clean out the code coverage directory that may contain stale + // profraw files from a previous run of the code coverage tool. + if self.options.enableCodeCoverage { + try swiftTool.fileSystem.removeFileTree(buildParameters.codeCovPath) + } + + let toolchain = try swiftTool.getTargetToolchain() + let testEnv = try TestingSupport.constructTestEnvironment( + toolchain: toolchain, + buildParameters: buildParameters, + sanitizers: globalOptions.build.sanitizers + ) + + let runner = TestRunner( + bundlePaths: testProducts.map { library == .xctest ? $0.bundlePath : $0.binaryPath }, + additionalArguments: additionalArguments, + cancellator: swiftTool.cancellator, + toolchain: toolchain, + testEnv: testEnv, + observabilityScope: swiftTool.observabilityScope, + library: library + ) + + // Finally, run the tests. + let ranSuccessfully = runner.test(outputHandler: { + // command's result output goes on stdout + // ie "swift test" should output to stdout + print($0, terminator: "") + }) + if !ranSuccessfully { + swiftTool.executionStatus = .failure + } + + if self.options.enableCodeCoverage, ranSuccessfully { + try processCodeCoverage(testProducts, swiftTool: swiftTool, library: library) + } + + if self.options.enableExperimentalTestOutput, !ranSuccessfully { + try Self.handleTestOutput(buildParameters: buildParameters, packagePath: testProducts[0].packagePath) } } + private static func handleTestOutput(buildParameters: BuildParameters, packagePath: AbsolutePath) throws { + guard localFileSystem.exists(buildParameters.testOutputPath) else { + print("No existing test output found.") + return + } + + let lines = try String(contentsOfFile: buildParameters.testOutputPath.pathString).components(separatedBy: "\n") + let events = try lines.map { try JSONDecoder().decode(TestEventRecord.self, from: $0) } + + let caseEvents = events.compactMap { $0.caseEvent } + let failureRecords = events.compactMap { $0.caseFailure } + let expectedFailures = failureRecords.filter({ $0.failureKind.isExpected == true }) + let unexpectedFailures = failureRecords.filter { $0.failureKind.isExpected == false }.sorted(by: { lhs, rhs in + guard let lhsLocation = lhs.issue.sourceCodeContext.location, let rhsLocation = rhs.issue.sourceCodeContext.location else { + return lhs.description < rhs.description + } + + if lhsLocation.file == rhsLocation.file { + return lhsLocation.line < rhsLocation.line + } else { + return lhsLocation.file < rhsLocation.file + } + }).map { $0.description(with: packagePath.pathString) } + + let startedTests = caseEvents.filter { $0.event == .start }.count + let finishedTests = caseEvents.filter { $0.event == .finish }.count + let totalFailures = expectedFailures.count + unexpectedFailures.count + print("\nRan \(finishedTests)/\(startedTests) tests, \(totalFailures) failures (\(unexpectedFailures.count) unexpected):\n") + print("\(unexpectedFailures.joined(separator: "\n"))") + } + /// Processes the code coverage data and emits a json. - private func processCodeCoverage(_ testProducts: [BuiltTestProduct], swiftTool: SwiftTool) throws { + private func processCodeCoverage(_ testProducts: [BuiltTestProduct], swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { let workspace = try swiftTool.getActiveWorkspace() let root = try swiftTool.getWorkspaceRoot() let rootManifests = try temp_await { @@ -338,23 +458,23 @@ public struct SwiftTestTool: SwiftCommand { } // Merge all the profraw files to produce a single profdata file. - try mergeCodeCovRawDataFiles(swiftTool: swiftTool) + try mergeCodeCovRawDataFiles(swiftTool: swiftTool, library: library) - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, sharedOptions: self.sharedOptions) + let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) for product in testProducts { // Export the codecov data as JSON. let jsonPath = buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName) - try exportCodeCovAsJSON(to: jsonPath, testBinary: product.binaryPath, swiftTool: swiftTool) + try exportCodeCovAsJSON(to: jsonPath, testBinary: product.binaryPath, swiftTool: swiftTool, library: library) } } /// Merges all profraw profiles in codecoverage directory into default.profdata file. - private func mergeCodeCovRawDataFiles(swiftTool: SwiftTool) throws { + private func mergeCodeCovRawDataFiles(swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { // Get the llvm-prof tool. let llvmProf = try swiftTool.getTargetToolchain().getLLVMProf() // Get the profraw files. - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, sharedOptions: self.sharedOptions) + let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) let codeCovFiles = try swiftTool.fileSystem.getDirectoryContents(buildParameters.codeCovPath) // Construct arguments for invoking the llvm-prof tool. @@ -371,10 +491,10 @@ public struct SwiftTestTool: SwiftCommand { } /// Exports profdata as a JSON file. - private func exportCodeCovAsJSON(to path: AbsolutePath, testBinary: AbsolutePath, swiftTool: SwiftTool) throws { + private func exportCodeCovAsJSON(to path: AbsolutePath, testBinary: AbsolutePath, swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { // Export using the llvm-cov tool. let llvmCov = try swiftTool.getTargetToolchain().getLLVMCov() - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, sharedOptions: self.sharedOptions) + let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) let args = [ llvmCov.pathString, "export", @@ -393,28 +513,9 @@ public struct SwiftTestTool: SwiftCommand { /// Builds the "test" target if enabled in options. /// /// - Returns: The paths to the build test products. - private func buildTestsIfNeeded(swiftTool: SwiftTool) throws -> [BuiltTestProduct] { - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, sharedOptions: self.sharedOptions) - let buildSystem = try swiftTool.createBuildSystem(customBuildParameters: buildParameters) - - let subset = self.sharedOptions.testProduct.map(BuildSubset.product) ?? .allIncludingTests - try buildSystem.build(subset: subset) - - // Find the test product. - let testProducts = buildSystem.builtTestProducts - guard !testProducts.isEmpty else { - throw TestError.testsExecutableNotFound - } - - if let testProductName = self.sharedOptions.testProduct { - guard let selectedTestProduct = testProducts.first(where: { $0.productName == testProductName }) else { - throw TestError.testsExecutableNotFound - } - - return [selectedTestProduct] - } else { - return testProducts - } + private func buildTestsIfNeeded(swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws -> [BuiltTestProduct] { + let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) + return try Commands.buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters, testProduct: self.options.sharedOptions.testProduct) } /// Private function that validates the commands arguments @@ -432,6 +533,10 @@ public struct SwiftTestTool: SwiftCommand { guard workers > 0 else { throw StringError("'--num-workers' must be greater than zero") } + + if !options.sharedOptions.enableXCTestSupport { + throw StringError("'--num-workers' is only supported when testing with XCTest") + } } if options._deprecated_shouldListTests { @@ -443,40 +548,37 @@ public struct SwiftTestTool: SwiftCommand { } extension SwiftTestTool { - struct PrintCodeCovPath: SwiftCommand { - static let configuration = CommandConfiguration( - commandName: "show-codecov-path", - abstract: "Print the path of the exported code coverage JSON file" - ) - - @OptionGroup(visibility: .hidden) - var globalOptions: GlobalOptions - - // for deprecated passthrough from SwiftTestTool (parse will fail otherwise) - - @Flag(name: [.customLong("show-codecov-path"), .customLong("show-code-coverage-path"), .customLong("show-coverage-path")], help: .hidden) - var _deprecated_passthrough: Bool = false - - func run(_ swiftTool: SwiftTool) throws { - let workspace = try swiftTool.getActiveWorkspace() - let root = try swiftTool.getWorkspaceRoot() - let rootManifests = try temp_await { - workspace.loadRootManifests( - packages: root.packages, - observabilityScope: swiftTool.observabilityScope, - completion: $0 - ) - } - guard let rootManifest = rootManifests.values.first else { - throw StringError("invalid manifests at \(root.packages)") - } - let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: true) - print(buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) - } + func printCodeCovPath(_ swiftTool: SwiftTool) throws { + let workspace = try swiftTool.getActiveWorkspace() + let root = try swiftTool.getWorkspaceRoot() + let rootManifests = try temp_await { + workspace.loadRootManifests( + packages: root.packages, + observabilityScope: swiftTool.observabilityScope, + completion: $0 + ) + } + guard let rootManifest = rootManifests.values.first else { + throw StringError("invalid manifests at \(root.packages)") + } + let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: true, library: .xctest) + print(buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) } } extension SwiftTestTool { + struct Last: SwiftCommand { + @OptionGroup(visibility: .hidden) + var globalOptions: GlobalOptions + + func run(_ swiftTool: SwiftTool) throws { + try SwiftTestTool.handleTestOutput( + buildParameters: try swiftTool.buildParameters(), + packagePath: localFileSystem.currentWorkingDirectory ?? .root // by definition runs in the current working directory + ) + } + } + struct List: SwiftCommand { static let configuration = CommandConfiguration( abstract: "Lists test methods in specifier format" @@ -492,13 +594,17 @@ extension SwiftTestTool { @Flag(name: [.customLong("list-tests"), .customShort("l")], help: .hidden) var _deprecated_passthrough: Bool = false - func run(_ swiftTool: SwiftTool) throws { - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool) + // MARK: - XCTest + + private func xctestRun(_ swiftTool: SwiftTool) throws { + let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, library: .xctest) + let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters) let testSuites = try TestingSupport.getTestSuites( in: testProducts, swiftTool: swiftTool, enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, + experimentalTestOutput: false, sanitizers: globalOptions.build.sanitizers ) @@ -508,29 +614,54 @@ extension SwiftTestTool { } } - private func buildTestsIfNeeded(swiftTool: SwiftTool) throws -> [BuiltTestProduct] { - let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding) - let buildSystem = try swiftTool.createBuildSystem(customBuildParameters: buildParameters) + // MARK: - swift-testing + + private func swiftTestingRun(_ swiftTool: SwiftTool) throws { + let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, library: .swiftTesting) + let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters) + + let toolchain = try swiftTool.getTargetToolchain() + let testEnv = try TestingSupport.constructTestEnvironment( + toolchain: toolchain, + buildParameters: buildParameters, + sanitizers: globalOptions.build.sanitizers + ) - let subset = self.sharedOptions.testProduct.map(BuildSubset.product) ?? .allIncludingTests - try buildSystem.build(subset: subset) + let runner = TestRunner( + bundlePaths: testProducts.map(\.binaryPath), + additionalArguments: ["--list-tests"], + cancellator: swiftTool.cancellator, + toolchain: toolchain, + testEnv: testEnv, + observabilityScope: swiftTool.observabilityScope, + library: .swiftTesting + ) - // Find the test product. - let testProducts = buildSystem.builtTestProducts - guard !testProducts.isEmpty else { - throw TestError.testsExecutableNotFound + // Finally, run the tests. + let ranSuccessfully = runner.test(outputHandler: { + // command's result output goes on stdout + // ie "swift test" should output to stdout + print($0, terminator: "") + }) + if !ranSuccessfully { + swiftTool.executionStatus = .failure } + } - if let testProductName = self.sharedOptions.testProduct { - guard let selectedTestProduct = testProducts.first(where: { $0.productName == testProductName }) else { - throw TestError.testsExecutableNotFound - } + // MARK: - Common implementation - return [selectedTestProduct] - } else { - return testProducts + func run(_ swiftTool: SwiftTool) throws { + if sharedOptions.enableSwiftTestingLibrarySupport { + try swiftTestingRun(swiftTool) + } + if sharedOptions.enableXCTestSupport { + try xctestRun(swiftTool) } } + + private func buildTestsIfNeeded(swiftTool: SwiftTool, buildParameters: BuildParameters) throws -> [BuiltTestProduct] { + return try Commands.buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters, testProduct: self.sharedOptions.testProduct) + } } } @@ -559,8 +690,8 @@ final class TestRunner { /// Path to valid XCTest binaries. private let bundlePaths: [AbsolutePath] - /// Arguments to pass to XCTest if any. - private let xctestArg: String? + /// Arguments to pass to the test runner process, if any. + private let additionalArguments: [String] private let cancellator: Cancellator @@ -572,25 +703,46 @@ final class TestRunner { /// ObservabilityScope to emit diagnostics. private let observabilityScope: ObservabilityScope + /// Which testing library to use with this test run. + private let library: BuildParameters.Testing.Library + + /// Get the arguments used on this platform to pass test specifiers to XCTest. + static func xctestArguments(forTestSpecifiers testSpecifiers: S) -> [String] where S: Collection, S.Element == String { + let testSpecifier: String + if testSpecifiers.isEmpty { + testSpecifier = "''" + } else { + testSpecifier = testSpecifiers.joined(separator: ",") + } + +#if os(macOS) + return ["-XCTest", testSpecifier] +#else + return [testSpecifier] +#endif + } + /// Creates an instance of TestRunner. /// /// - Parameters: /// - testPaths: Paths to valid XCTest binaries. - /// - xctestArg: Arguments to pass to XCTest. + /// - additionalArguments: Arguments to pass to the test runner process. init( bundlePaths: [AbsolutePath], - xctestArg: String? = nil, + additionalArguments: [String], cancellator: Cancellator, toolchain: UserToolchain, testEnv: [String: String], - observabilityScope: ObservabilityScope + observabilityScope: ObservabilityScope, + library: BuildParameters.Testing.Library ) { self.bundlePaths = bundlePaths - self.xctestArg = xctestArg + self.additionalArguments = additionalArguments self.cancellator = cancellator self.toolchain = toolchain self.testEnv = testEnv self.observabilityScope = observabilityScope.makeChildScope(description: "Test Runner") + self.library = library } /// Executes and returns execution status. Prints test output on standard streams if requested @@ -608,20 +760,20 @@ final class TestRunner { private func args(forTestAt testPath: AbsolutePath) throws -> [String] { var args: [String] = [] #if os(macOS) - guard let xctestPath = self.toolchain.xctestPath else { - throw TestError.xctestNotAvailable - } - args = [xctestPath.pathString] - if let xctestArg { - args += ["-XCTest", xctestArg] - } - args += [testPath.pathString] - #else - args += [testPath.description] - if let xctestArg { - args += [xctestArg] + if library == .xctest { + guard let xctestPath = self.toolchain.xctestPath else { + throw TestError.xctestNotAvailable + } + args = [xctestPath.pathString] + args += additionalArguments + args += [testPath.pathString] + return args } #endif + + args += [testPath.description] + args += additionalArguments + return args } @@ -630,7 +782,7 @@ final class TestRunner { do { let outputHandler = { (bytes: [UInt8]) in - if let output = String(bytes: bytes, encoding: .utf8)?.spm_chuzzle() { + if let output = String(bytes: bytes, encoding: .utf8) { outputHandler(output) } } @@ -649,8 +801,8 @@ final class TestRunner { case .terminated(code: 0): return true #if !os(Windows) - case .signalled(let signal): - testObservabilityScope.emit(error: "Exited with signal code \(signal)") + case .signalled(let signal) where ![SIGINT, SIGKILL, SIGTERM].contains(signal): + testObservabilityScope.emit(error: "Exited with unexpected signal code \(signal)") return false #endif default: @@ -778,13 +930,15 @@ final class ParallelTestRunner { let thread = Thread { // Dequeue a specifier and run it till we encounter nil. while let test = self.pendingTests.dequeue() { + let additionalArguments = TestRunner.xctestArguments(forTestSpecifiers: CollectionOfOne(test.specifier)) let testRunner = TestRunner( bundlePaths: [test.productPath], - xctestArg: test.specifier, + additionalArguments: additionalArguments, cancellator: self.cancellator, toolchain: self.toolchain, testEnv: testEnv, - observabilityScope: self.observabilityScope + observabilityScope: self.observabilityScope, + library: .xctest ) var output = "" let outputLock = NSLock() @@ -831,7 +985,7 @@ final class ParallelTestRunner { // Print test results. for test in processedTests.get() { - if !test.success || shouldOutputSuccess { + if (!test.success || shouldOutputSuccess) && !buildParameters.testingParameters.experimentalTestOutput { // command's result output goes on stdout // ie "swift test" should output to stdout print(test.output) @@ -865,46 +1019,53 @@ struct TestSuite { /// /// - Parameters: /// - jsonString: JSON string to be parsed. + /// - context: the commandline which produced the given JSON. /// /// - Throws: JSONDecodingError, TestError /// /// - Returns: Array of TestSuite. - static func parse(jsonString: String) throws -> [TestSuite] { - let json = try JSON(string: jsonString) - return try TestSuite.parse(json: json) + static func parse(jsonString: String, context: String) throws -> [TestSuite] { + let json: JSON + do { + json = try JSON(string: jsonString) + } catch { + throw TestError.invalidListTestJSONData(context: context, underlyingError: error) + } + return try TestSuite.parse(json: json, context: context) } /// Parses the JSON object into array of TestSuite. /// /// - Parameters: /// - json: An object of JSON. + /// - context: the commandline which produced the given JSON. /// /// - Throws: TestError /// /// - Returns: Array of TestSuite. - static func parse(json: JSON) throws -> [TestSuite] { + static func parse(json: JSON, context: String) throws -> [TestSuite] { guard case let .dictionary(contents) = json, case let .array(testSuites)? = contents["tests"] else { - throw TestError.invalidListTestJSONData + throw TestError.invalidListTestJSONData(context: context) } return try testSuites.map({ testSuite in guard case let .dictionary(testSuiteData) = testSuite, case let .string(name)? = testSuiteData["name"], case let .array(allTestsData)? = testSuiteData["tests"] else { - throw TestError.invalidListTestJSONData + throw TestError.invalidListTestJSONData(context: context) } let testCases: [TestSuite.TestCase] = try allTestsData.map({ testCase in guard case let .dictionary(testCaseData) = testCase, case let .string(name)? = testCaseData["name"], case let .array(tests)? = testCaseData["tests"] else { - throw TestError.invalidListTestJSONData + throw TestError.invalidListTestJSONData(context: context) } let testMethods: [String] = try tests.map({ test in guard case let .dictionary(testData) = test, case let .string(testMethod)? = testData["name"] else { - throw TestError.invalidListTestJSONData + throw TestError.invalidListTestJSONData(context: context) } return testMethod }) @@ -1040,12 +1201,18 @@ final class XUnitGenerator { } extension SwiftTool { - func buildParametersForTest(options: TestToolOptions, sharedOptions: SharedOptions) throws -> BuildParameters { - try self.buildParametersForTest( + func buildParametersForTest(options: TestToolOptions, library: BuildParameters.Testing.Library) throws -> BuildParameters { + var result = try self.buildParametersForTest( enableCodeCoverage: options.enableCodeCoverage, enableTestability: options.enableTestableImports, - shouldSkipBuilding: sharedOptions.shouldSkipBuilding + shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, + experimentalTestOutput: options.enableExperimentalTestOutput, + library: library ) + if options.sharedOptions.enableSwiftTestingLibrarySupport { + result.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] + } + return result } } @@ -1097,3 +1264,33 @@ private extension Basics.Diagnostic { .warning("No matching test cases were run") } } + +/// Builds the "test" target if enabled in options. +/// +/// - Returns: The paths to the build test products. +private func buildTestsIfNeeded(swiftTool: SwiftTool, buildParameters: BuildParameters, testProduct: String?) throws -> [BuiltTestProduct] { + let buildSystem = try swiftTool.createBuildSystem(customBuildParameters: buildParameters) + + let subset = testProduct.map(BuildSubset.product) ?? .allIncludingTests + try buildSystem.build(subset: subset) + + // Find the test product. + let testProducts = buildSystem.builtTestProducts + guard !testProducts.isEmpty else { + if let testProduct { + throw TestError.productIsNotTest(productName: testProduct) + } else { + throw TestError.testsNotFound + } + } + + if let testProductName = testProduct { + guard let selectedTestProduct = testProducts.first(where: { $0.productName == testProductName }) else { + throw TestError.testProductNotFound(productName: testProductName) + } + + return [selectedTestProduct] + } else { + return testProducts + } +} diff --git a/Sources/Commands/ToolWorkspaceDelegate.swift b/Sources/Commands/ToolWorkspaceDelegate.swift index 26cbc05d759..983b0a75a68 100644 --- a/Sources/Commands/ToolWorkspaceDelegate.swift +++ b/Sources/Commands/ToolWorkspaceDelegate.swift @@ -81,7 +81,7 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { } } - self.outputHandler("Fetched \(packageLocation ?? package.description) (\(duration.descriptionInSeconds))", false) + self.outputHandler("Fetched \(packageLocation ?? package.description) from cache (\(duration.descriptionInSeconds))", false) } func fetchingPackage(package: PackageIdentity, packageLocation: String?, progress: Int64, total: Int64?) { @@ -135,12 +135,16 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { self.outputHandler("Computed \(location) at \(version) (\(duration.descriptionInSeconds))", false) } - func willDownloadBinaryArtifact(from url: String) { - self.outputHandler("Downloading binary artifact \(url)", false) + func willDownloadBinaryArtifact(from url: String, fromCache: Bool) { + if fromCache { + self.outputHandler("Fetching binary artifact \(url) from cache", false) + } else { + self.outputHandler("Downloading binary artifact \(url)", false) + } } - func didDownloadBinaryArtifact(from url: String, result: Result, duration: DispatchTimeInterval) { - guard case .success = result, !self.observabilityScope.errorsReported else { + func didDownloadBinaryArtifact(from url: String, result: Result<(path: AbsolutePath, fromCache: Bool), Error>, duration: DispatchTimeInterval) { + guard case .success(let fetchDetails) = result, !self.observabilityScope.errorsReported else { return } @@ -155,7 +159,11 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { } } - self.outputHandler("Downloaded \(url) (\(duration.descriptionInSeconds))", false) + if fetchDetails.fromCache { + self.outputHandler("Fetched \(url) from cache (\(duration.descriptionInSeconds))", false) + } else { + self.outputHandler("Downloaded \(url) (\(duration.descriptionInSeconds))", false) + } } func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { @@ -210,7 +218,7 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { } public func didUpdateDependencies(duration: DispatchTimeInterval) { - self.observabilityScope.emit(debug: "Dependencies updated (\(duration.descriptionInSeconds))") + self.observabilityScope.emit(debug: "Dependencies updated in (\(duration.descriptionInSeconds))") os_signpost(.end, name: SignpostName.updatingDependencies) } @@ -220,7 +228,7 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { } public func didResolveDependencies(duration: DispatchTimeInterval) { - self.observabilityScope.emit(debug: "Dependencies resolved (\(duration.descriptionInSeconds))") + self.observabilityScope.emit(debug: "Dependencies resolved in (\(duration.descriptionInSeconds))") os_signpost(.end, name: SignpostName.resolvingDependencies) } @@ -230,18 +238,30 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { } func didLoadGraph(duration: DispatchTimeInterval) { - self.observabilityScope.emit(debug: "Graph loaded (\(duration.descriptionInSeconds))") + self.observabilityScope.emit(debug: "Graph loaded in (\(duration.descriptionInSeconds))") os_signpost(.end, name: SignpostName.loadingGraph) } - // noop + func didCompileManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + self.observabilityScope.emit(debug: "Compiled manifest for '\(packageIdentity)' (from '\(packageLocation)') in \(duration.descriptionInSeconds)") + } - func willLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) {} - func didLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind, manifest: Manifest?, diagnostics: [Basics.Diagnostic], duration: DispatchTimeInterval) {} + func didEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + self.observabilityScope.emit(debug: "Evaluated manifest for '\(packageIdentity)' (from '\(packageLocation)') in \(duration.descriptionInSeconds)") + } + + func didLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind, manifest: Manifest?, diagnostics: [Basics.Diagnostic], duration: DispatchTimeInterval) { + self.observabilityScope.emit(debug: "Loaded manifest for '\(packageIdentity)' (from '\(url)') in \(duration.descriptionInSeconds)") + } + + // noop func willCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath) {} func didCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath, duration: DispatchTimeInterval) {} func resolvedFileChanged() {} func didDownloadAllBinaryArtifacts() {} + func willCompileManifest(packageIdentity: PackageIdentity, packageLocation: String) {} + func willEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String) {} + func willLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) {} } public extension _SwiftCommand { diff --git a/Sources/Commands/Utilities/DOTManifestSerializer.swift b/Sources/Commands/Utilities/DOTManifestSerializer.swift index dc35f75e8c6..503d08f38dc 100644 --- a/Sources/Commands/Utilities/DOTManifestSerializer.swift +++ b/Sources/Commands/Utilities/DOTManifestSerializer.swift @@ -18,10 +18,10 @@ import protocol TSCBasic.OutputByteStream public struct DOTManifestSerializer { var kindCounter = [String: Int]() var hasEmittedStyling = Set() - let manifest: LLBuildManifest.BuildManifest + let manifest: LLBuildManifest /// Creates a serializer that will serialize the given manifest. - public init(manifest: LLBuildManifest.BuildManifest) { + public init(manifest: LLBuildManifest) { self.manifest = manifest } diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index 9e964d8a707..f0378edd23b 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -171,8 +171,8 @@ final class PluginDelegate: PluginInvocationDelegate { // which ones they are until we've built them and can examine the binaries. let toolchain = try swiftTool.getTargetToolchain() var buildParameters = try swiftTool.buildParameters() - buildParameters.enableTestability = true - buildParameters.enableCodeCoverage = parameters.enableCodeCoverage + buildParameters.testingParameters.enableTestability = true + buildParameters.testingParameters.enableCodeCoverage = parameters.enableCodeCoverage let buildSystem = try swiftTool.createBuildSystem(customBuildParameters: buildParameters) try buildSystem.build(subset: .allIncludingTests) @@ -199,6 +199,7 @@ final class PluginDelegate: PluginInvocationDelegate { swiftTool: swiftTool, enableCodeCoverage: parameters.enableCodeCoverage, shouldSkipBuilding: false, + experimentalTestOutput: false, sanitizers: swiftTool.options.build.sanitizers ) for testSuite in testSuites { @@ -220,13 +221,15 @@ final class PluginDelegate: PluginInvocationDelegate { } // Configure a test runner. + let additionalArguments = TestRunner.xctestArguments(forTestSpecifiers: CollectionOfOne(testSpecifier)) let testRunner = TestRunner( bundlePaths: [testProduct.bundlePath], - xctestArg: testSpecifier, + additionalArguments: additionalArguments, cancellator: swiftTool.cancellator, toolchain: toolchain, testEnv: testEnvironment, - observabilityScope: swiftTool.observabilityScope) + observabilityScope: swiftTool.observabilityScope, + library: .xctest) // FIXME: support both libraries // Run the test — for now we run the sequentially so we can capture accurate timing results. let startTime = DispatchTime.now() diff --git a/Sources/Commands/Utilities/SymbolGraphExtract.swift b/Sources/Commands/Utilities/SymbolGraphExtract.swift index 3d501d8bb54..1fd25ea58ff 100644 --- a/Sources/Commands/Utilities/SymbolGraphExtract.swift +++ b/Sources/Commands/Utilities/SymbolGraphExtract.swift @@ -12,12 +12,18 @@ import ArgumentParser import Basics -@_implementationOnly import DriverSupport import PackageGraph import PackageModel import SPMBuildCore +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import DriverSupport +#else +import DriverSupport +#endif + import class TSCBasic.Process +import struct TSCBasic.ProcessResult /// A wrapper for swift-symbolgraph-extract tool. public struct SymbolGraphExtract { @@ -31,7 +37,6 @@ public struct SymbolGraphExtract { var includeSPISymbols = false var emitExtensionBlockSymbols = false var outputFormat = OutputFormat.json(pretty: false) - private let driverSupport = DriverSupport() /// Access control levels. public enum AccessLevel: String, RawRepresentable, CaseIterable, ExpressibleByArgument { @@ -52,7 +57,7 @@ public struct SymbolGraphExtract { outputRedirection: TSCBasic.Process.OutputRedirection = .none, outputDirectory: AbsolutePath, verboseOutput: Bool - ) throws { + ) throws -> ProcessResult { let buildParameters = buildPlan.buildParameters try self.fileSystem.createDirectory(outputDirectory, recursive: true) @@ -77,7 +82,7 @@ public struct SymbolGraphExtract { } let extensionBlockSymbolsFlag = emitExtensionBlockSymbols ? "-emit-extension-block-symbols" : "-omit-extension-block-symbols" - if driverSupport.checkSupportedFrontendFlags(flags: [extensionBlockSymbolsFlag.trimmingCharacters(in: ["-"])], toolchain: buildParameters.toolchain, fileSystem: fileSystem) { + if DriverSupport.checkSupportedFrontendFlags(flags: [extensionBlockSymbolsFlag.trimmingCharacters(in: ["-"])], toolchain: buildParameters.toolchain, fileSystem: fileSystem) { commandLine += [extensionBlockSymbolsFlag] } else { observabilityScope.emit(warning: "dropped \(extensionBlockSymbolsFlag) flag because it is not supported by this compiler version") @@ -97,6 +102,6 @@ public struct SymbolGraphExtract { outputRedirection: outputRedirection ) try process.launch() - try process.waitUntilExit() + return try process.waitUntilExit() } } diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index c28d41018a4..727f73cf63c 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -67,6 +67,7 @@ enum TestingSupport { swiftTool: SwiftTool, enableCodeCoverage: Bool, shouldSkipBuilding: Bool, + experimentalTestOutput: Bool, sanitizers: [Sanitizer] ) throws -> [AbsolutePath: [TestSuite]] { let testSuitesByProduct = try testProducts @@ -77,6 +78,7 @@ enum TestingSupport { swiftTool: swiftTool, enableCodeCoverage: enableCodeCoverage, shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, sanitizers: sanitizers ) )} @@ -98,27 +100,25 @@ enum TestingSupport { swiftTool: SwiftTool, enableCodeCoverage: Bool, shouldSkipBuilding: Bool, + experimentalTestOutput: Bool, sanitizers: [Sanitizer] ) throws -> [TestSuite] { // Run the correct tool. + var args = [String]() #if os(macOS) let data: String = try withTemporaryFile { tempFile in - let args = [try Self.xctestHelperPath(swiftTool: swiftTool).pathString, path.pathString, tempFile.path.pathString] - var env = try Self.constructTestEnvironment( + args = [try Self.xctestHelperPath(swiftTool: swiftTool).pathString, path.pathString, tempFile.path.pathString] + let env = try Self.constructTestEnvironment( toolchain: try swiftTool.getTargetToolchain(), buildParameters: swiftTool.buildParametersForTest( enableCodeCoverage: enableCodeCoverage, - shouldSkipBuilding: shouldSkipBuilding + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, + library: .xctest ), sanitizers: sanitizers ) - // Add the sdk platform path if we have it. If this is not present, we might always end up failing. - let sdkPlatformFrameworksPath = try SwiftSDK.sdkPlatformFrameworkPaths() - // appending since we prefer the user setting (if set) to the one we inject - env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString) - env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString) - try TSCBasic.Process.checkNonZeroExit(arguments: args, environment: env) // Read the temporary file's content. return try swiftTool.fileSystem.readFileContents(AbsolutePath(tempFile.path)) @@ -128,15 +128,16 @@ enum TestingSupport { toolchain: try swiftTool.getTargetToolchain(), buildParameters: swiftTool.buildParametersForTest( enableCodeCoverage: enableCodeCoverage, - shouldSkipBuilding: shouldSkipBuilding + shouldSkipBuilding: shouldSkipBuilding, + library: .xctest ), sanitizers: sanitizers ) - let args = [path.description, "--dump-tests-json"] + args = [path.description, "--dump-tests-json"] let data = try Process.checkNonZeroExit(arguments: args, environment: env) #endif // Parse json and return TestSuites. - return try TestSuite.parse(jsonString: data) + return try TestSuite.parse(jsonString: data, context: args.joined(separator: " ")) } /// Creates the environment needed to test related tools. @@ -156,7 +157,7 @@ enum TestingSupport { } // Add the code coverage related variables. - if buildParameters.enableCodeCoverage { + if buildParameters.testingParameters.enableCodeCoverage { // Defines the path at which the profraw files will be written on test execution. // // `%m` will create a pool of profraw files and append the data from @@ -175,6 +176,12 @@ enum TestingSupport { #endif return env #else + // Add the sdk platform path if we have it. If this is not present, we might always end up failing. + let sdkPlatformFrameworksPath = try SwiftSDK.sdkPlatformFrameworkPaths() + // appending since we prefer the user setting (if set) to the one we inject + env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString) + env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString) + // Fast path when no sanitizers are enabled. if sanitizers.isEmpty { return env @@ -200,14 +207,35 @@ extension SwiftTool { func buildParametersForTest( enableCodeCoverage: Bool, enableTestability: Bool? = nil, - shouldSkipBuilding: Bool = false + shouldSkipBuilding: Bool = false, + experimentalTestOutput: Bool = false, + library: BuildParameters.Testing.Library ) throws -> BuildParameters { var parameters = try self.buildParameters() - parameters.enableCodeCoverage = enableCodeCoverage + + var explicitlyEnabledDiscovery = false + var explicitlySpecifiedPath: AbsolutePath? + if case let .entryPointExecutable( + explicitlyEnabledDiscoveryValue, + explicitlySpecifiedPathValue + ) = parameters.testingParameters.testProductStyle { + explicitlyEnabledDiscovery = explicitlyEnabledDiscoveryValue + explicitlySpecifiedPath = explicitlySpecifiedPathValue + } + parameters.testingParameters = .init( + configuration: parameters.configuration, + targetTriple: parameters.targetTriple, + forceTestDiscovery: explicitlyEnabledDiscovery, + testEntryPointPath: explicitlySpecifiedPath, + library: library + ) + + parameters.testingParameters.enableCodeCoverage = enableCodeCoverage // for test commands, we normally enable building with testability // but we let users override this with a flag - parameters.enableTestability = enableTestability ?? true + parameters.testingParameters.enableTestability = enableTestability ?? true parameters.shouldSkipBuilding = shouldSkipBuilding + parameters.testingParameters.experimentalTestOutput = experimentalTestOutput return parameters } } diff --git a/Sources/Commands/Utilities/XCTEvents.swift b/Sources/Commands/Utilities/XCTEvents.swift new file mode 100644 index 00000000000..d68ada249c2 --- /dev/null +++ b/Sources/Commands/Utilities/XCTEvents.swift @@ -0,0 +1,310 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 + +struct TestEventRecord: Codable { + let caseFailure: TestCaseFailureRecord? + let suiteFailure: TestSuiteFailureRecord? + + let bundleEvent: TestBundleEventRecord? + let suiteEvent: TestSuiteEventRecord? + let caseEvent: TestCaseEventRecord? + + init( + caseFailure: TestCaseFailureRecord? = nil, + suiteFailure: TestSuiteFailureRecord? = nil, + bundleEvent: TestBundleEventRecord? = nil, + suiteEvent: TestSuiteEventRecord? = nil, + caseEvent: TestCaseEventRecord? = nil + ) { + self.caseFailure = caseFailure + self.suiteFailure = suiteFailure + self.bundleEvent = bundleEvent + self.suiteEvent = suiteEvent + self.caseEvent = caseEvent + } +} + +// MARK: - Records + +struct TestAttachment: Codable { + let name: String? + // TODO: Handle `userInfo: [AnyHashable : Any]?` + let uniformTypeIdentifier: String + let payload: Data? +} + +struct TestBundleEventRecord: Codable { + let bundle: TestBundle + let event: TestEvent +} + +struct TestCaseEventRecord: Codable { + let testCase: TestCase + let event: TestEvent +} + +struct TestCaseFailureRecord: Codable, CustomStringConvertible { + let testCase: TestCase + let issue: TestIssue + let failureKind: TestFailureKind + + var description: String { + return "\(issue.sourceCodeContext.description)\(testCase) \(issue.compactDescription)" + } + + func description(with knownLocation: String) -> String { + return "\(issue.sourceCodeContext.description(with: knownLocation))\(testCase) \(issue.compactDescription)" + } +} + +struct TestSuiteEventRecord: Codable { + let suite: TestSuiteRecord + let event: TestEvent +} + +struct TestSuiteFailureRecord: Codable { + let suite: TestSuiteRecord + let issue: TestIssue + let failureKind: TestFailureKind +} + +// MARK: Primitives + +struct TestBundle: Codable { + let bundleIdentifier: String? + let bundlePath: String +} + +struct TestCase: Codable, CustomStringConvertible { + let name: String + + var description: String { + return name + } +} + +struct TestErrorInfo: Codable { + let description: String + let type: String +} + +enum TestEvent: Codable { + case start + case finish +} + +enum TestFailureKind: Codable, Equatable { + case unexpected + case expected(failureReason: String?) + + var isExpected: Bool { + switch self { + case .expected: return true + case .unexpected: return false + } + } +} + +struct TestIssue: Codable { + let type: TestIssueType + let compactDescription: String + let detailedDescription: String? + let associatedError: TestErrorInfo? + let sourceCodeContext: TestSourceCodeContext + let attachments: [TestAttachment] +} + +enum TestIssueType: Codable { + case assertionFailure + case performanceRegression + case system + case thrownError + case uncaughtException + case unmatchedExpectedFailure + case unknown +} + +struct TestLocation: Codable, CustomStringConvertible { + let file: String + let line: Int + + var description: String { + return "\(file):\(line) " + } + + func description(with knownLocation: String) -> String { + var file = self.file + ["file:/", knownLocation].forEach { + if file.hasPrefix($0) { + file = String(file.dropFirst($0.count + 1)) + } + } + return "\(file):\(line) " + } +} + +struct TestSourceCodeContext: Codable, CustomStringConvertible { + let callStack: [TestSourceCodeFrame] + let location: TestLocation? + + var description: String { + return location?.description ?? "" + } + + func description(with knownLocation: String) -> String { + return location?.description(with: knownLocation) ?? "" + } +} + +struct TestSourceCodeFrame: Codable { + let address: UInt64 + let symbolInfo: TestSourceCodeSymbolInfo? + let symbolicationError: TestErrorInfo? +} + +struct TestSourceCodeSymbolInfo: Codable { + let imageName: String + let symbolName: String + let location: TestLocation? +} + +struct TestSuiteRecord: Codable { + let name: String +} + +// MARK: XCTest compatibility + +extension TestIssue { + init(description: String, inFile filePath: String?, atLine lineNumber: Int) { + let location: TestLocation? + if let filePath = filePath { + location = .init(file: filePath, line: lineNumber) + } else { + location = nil + } + self.init(type: .assertionFailure, compactDescription: description, detailedDescription: description, associatedError: nil, sourceCodeContext: .init(callStack: [], location: location), attachments: []) + } +} + +#if false // This is just here for pre-flighting the code generation done in `SwiftTargetBuildDescription`. +import XCTest + +#if canImport(Darwin) // XCTAttachment is unavailable in swift-corelibs-xctest. +extension TestAttachment { + init(_ attachment: XCTAttachment) { + self.init( + name: attachment.name, + uniformTypeIdentifier: attachment.uniformTypeIdentifier, + payload: attachment.value(forKey: "payload") as? Data + ) + } +} +#endif + +extension TestBundle { + init(_ testBundle: Bundle) { + self.init( + bundleIdentifier: testBundle.bundleIdentifier, + bundlePath: testBundle.bundlePath + ) + } +} + +extension TestCase { + init(_ testCase: XCTestCase) { + self.init(name: testCase.name) + } +} + +extension TestErrorInfo { + init(_ error: Swift.Error) { + self.init(description: "\(error)", type: "\(Swift.type(of: error))") + } +} + +#if canImport(Darwin) // XCTIssue is unavailable in swift-corelibs-xctest. +extension TestIssue { + init(_ issue: XCTIssue) { + self.init( + type: .init(issue.type), + compactDescription: issue.compactDescription, + detailedDescription: issue.detailedDescription, + associatedError: issue.associatedError.map { .init($0) }, + sourceCodeContext: .init(issue.sourceCodeContext), + attachments: issue.attachments.map { .init($0) } + ) + } +} + +extension TestIssueType { + init(_ type: XCTIssue.IssueType) { + switch type { + case .assertionFailure: self = .assertionFailure + case .thrownError: self = .thrownError + case .uncaughtException: self = .uncaughtException + case .performanceRegression: self = .performanceRegression + case .system: self = .system + case .unmatchedExpectedFailure: self = .unmatchedExpectedFailure + @unknown default: self = .unknown + } + } +} +#endif + +#if canImport(Darwin) // XCTSourceCodeLocation/XCTSourceCodeContext/XCTSourceCodeFrame/XCTSourceCodeSymbolInfo is unavailable in swift-corelibs-xctest. +extension TestLocation { + init(_ location: XCTSourceCodeLocation) { + self.init( + file: location.fileURL.absoluteString, + line: location.lineNumber + ) + } +} + +extension TestSourceCodeContext { + init(_ context: XCTSourceCodeContext) { + self.init( + callStack: context.callStack.map { .init($0) }, + location: context.location.map { .init($0) } + ) + } +} + +extension TestSourceCodeFrame { + init(_ frame: XCTSourceCodeFrame) { + self.init( + address: frame.address, + symbolInfo: (try? frame.symbolInfo()).map { .init($0) }, + symbolicationError: frame.symbolicationError.map { .init($0) } + ) + } +} + +extension TestSourceCodeSymbolInfo { + init(_ symbolInfo: XCTSourceCodeSymbolInfo) { + self.init( + imageName: symbolInfo.imageName, + symbolName: symbolInfo.symbolName, + location: symbolInfo.location.map { .init($0) } + ) + } +} +#endif + +extension TestSuiteRecord { + init(_ testSuite: XCTestSuite) { + self.init(name: testSuite.name) + } +} +#endif diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index 26fcc5372c1..e224c852aeb 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -19,41 +19,67 @@ import struct PackageGraph.PackageGraph import struct PackageLoading.FileRuleDescription import protocol TSCBasic.OutputByteStream +private struct NativeBuildSystemFactory: BuildSystemFactory { + let swiftTool: SwiftTool + + func makeBuildSystem( + explicitProduct: String?, + cacheBuildManifest: Bool, + customBuildParameters: BuildParameters?, + customPackageGraphLoader: (() throws -> PackageGraph)?, + customOutputStream: OutputByteStream?, + customLogLevel: Diagnostic.Severity?, + customObservabilityScope: ObservabilityScope? + ) throws -> any BuildSystem { + let testEntryPointPath = customBuildParameters?.testingParameters.testProductStyle.explicitlySpecifiedEntryPointPath + let graphLoader = { try self.swiftTool.loadPackageGraph(explicitProduct: explicitProduct, testEntryPointPath: testEntryPointPath) } + return try BuildOperation( + buildParameters: customBuildParameters ?? self.swiftTool.buildParameters(), + cacheBuildManifest: cacheBuildManifest && self.swiftTool.canUseCachedBuildManifest(), + packageGraphLoader: customPackageGraphLoader ?? graphLoader, + pluginConfiguration: .init( + scriptRunner: self.swiftTool.getPluginScriptRunner(), + workDirectory: try self.swiftTool.getActiveWorkspace().location.pluginWorkingDirectory, + disableSandbox: self.swiftTool.shouldDisableSandbox + ), + additionalFileRules: FileRuleDescription.swiftpmFileTypes, + pkgConfigDirectories: self.swiftTool.options.locations.pkgConfigDirectories, + outputStream: customOutputStream ?? self.swiftTool.outputStream, + logLevel: customLogLevel ?? self.swiftTool.logLevel, + fileSystem: self.swiftTool.fileSystem, + observabilityScope: customObservabilityScope ?? self.swiftTool.observabilityScope) + } +} + +private struct XcodeBuildSystemFactory: BuildSystemFactory { + let swiftTool: SwiftTool + + func makeBuildSystem( + explicitProduct: String?, + cacheBuildManifest: Bool, + customBuildParameters: BuildParameters?, + customPackageGraphLoader: (() throws -> PackageGraph)?, + customOutputStream: OutputByteStream?, + customLogLevel: Diagnostic.Severity?, + customObservabilityScope: ObservabilityScope? + ) throws -> any BuildSystem { + let graphLoader = { try self.swiftTool.loadPackageGraph(explicitProduct: explicitProduct) } + return try XcodeBuildSystem( + buildParameters: customBuildParameters ?? self.swiftTool.buildParameters(), + packageGraphLoader: customPackageGraphLoader ?? graphLoader, + outputStream: customOutputStream ?? self.swiftTool.outputStream, + logLevel: customLogLevel ?? self.swiftTool.logLevel, + fileSystem: self.swiftTool.fileSystem, + observabilityScope: customObservabilityScope ?? self.swiftTool.observabilityScope + ) + } +} + extension SwiftTool { public var defaultBuildSystemProvider: BuildSystemProvider { - get throws { - return .init(providers: [ - .native: { (explicitProduct: String?, cacheBuildManifest: Bool, customBuildParameters: BuildParameters?, customPackageGraphLoader: (() throws -> PackageGraph)?, customOutputStream: OutputByteStream?, customLogLevel: Diagnostic.Severity?, customObservabilityScope: ObservabilityScope?) throws -> BuildSystem in - let testEntryPointPath = customBuildParameters?.testProductStyle.explicitlySpecifiedEntryPointPath - let graphLoader = { try self.loadPackageGraph(explicitProduct: explicitProduct, testEntryPointPath: testEntryPointPath) } - return try BuildOperation( - buildParameters: customBuildParameters ?? self.buildParameters(), - cacheBuildManifest: cacheBuildManifest && self.canUseCachedBuildManifest(), - packageGraphLoader: customPackageGraphLoader ?? graphLoader, - pluginConfiguration: .init( - scriptRunner: self.getPluginScriptRunner(), - workDirectory: try self.getActiveWorkspace().location.pluginWorkingDirectory, - disableSandbox: self.shouldDisableSandbox - ), - additionalFileRules: FileRuleDescription.swiftpmFileTypes, - pkgConfigDirectories: self.options.locations.pkgConfigDirectories, - outputStream: customOutputStream ?? self.outputStream, - logLevel: customLogLevel ?? self.logLevel, - fileSystem: self.fileSystem, - observabilityScope: customObservabilityScope ?? self.observabilityScope) - }, - .xcode: { (explicitProduct: String?, cacheBuildManifest: Bool, customBuildParameters: BuildParameters?, customPackageGraphLoader: (() throws -> PackageGraph)?, customOutputStream: OutputByteStream?, customLogLevel: Diagnostic.Severity?, customObservabilityScope: ObservabilityScope?) throws -> BuildSystem in - let graphLoader = { try self.loadPackageGraph(explicitProduct: explicitProduct) } - return try XcodeBuildSystem( - buildParameters: customBuildParameters ?? self.buildParameters(), - packageGraphLoader: customPackageGraphLoader ?? graphLoader, - outputStream: customOutputStream ?? self.outputStream, - logLevel: customLogLevel ?? self.logLevel, - fileSystem: self.fileSystem, - observabilityScope: customObservabilityScope ?? self.observabilityScope - ) - }, - ]) - } + .init(providers: [ + .native: NativeBuildSystemFactory(swiftTool: self), + .xcode: XcodeBuildSystemFactory(swiftTool: self) + ]) } } diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index d09b28d6aff..4a72049effa 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -477,6 +477,14 @@ public struct BuildOptions: ParsableArguments { ) public var linkTimeOptimizationMode: LinkTimeOptimizationMode? + @Flag(inversion: .prefixedEnableDisable, help: .hidden) + public var getTaskAllowEntitlement: Bool? = nil + + // Whether to omit frame pointers + // this can be removed once the backtracer uses DWARF instead of frame pointers + @Flag(inversion: .prefixedNo, help: .hidden) + public var omitFramePointers: Bool? = nil + // @Flag works best when there is a default value present // if true, false aren't enough and a third state is needed // nil should not be the goto. Instead create an enum @@ -521,9 +529,9 @@ public struct LinkerOptions: ParsableArguments { ) public var linkerDeadStrip: Bool = true - /// If should link the Swift stdlib statically. - @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically") - public var shouldLinkStaticSwiftStdlib: Bool = false + /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying + @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default") + public var shouldDisableLocalRpath: Bool = false } // MARK: - Extensions diff --git a/Sources/CoreCommands/SwiftTool.swift b/Sources/CoreCommands/SwiftTool.swift index 35a336daa11..b40e9c59341 100644 --- a/Sources/CoreCommands/SwiftTool.swift +++ b/Sources/CoreCommands/SwiftTool.swift @@ -13,7 +13,6 @@ import ArgumentParser import Basics import Dispatch -@_implementationOnly import DriverSupport import class Foundation.NSLock import class Foundation.ProcessInfo import PackageGraph @@ -22,6 +21,12 @@ import PackageModel import SPMBuildCore import Workspace +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import DriverSupport +#else +import DriverSupport +#endif + #if canImport(WinSDK) import WinSDK #elseif canImport(Darwin) @@ -199,16 +204,16 @@ public final class SwiftTool { public let scratchDirectory: AbsolutePath /// Path to the shared security directory - public let sharedSecurityDirectory: AbsolutePath? + public let sharedSecurityDirectory: AbsolutePath /// Path to the shared cache directory - public let sharedCacheDirectory: AbsolutePath? + public let sharedCacheDirectory: AbsolutePath /// Path to the shared configuration directory - public let sharedConfigurationDirectory: AbsolutePath? + public let sharedConfigurationDirectory: AbsolutePath /// Path to the cross-compilation Swift SDKs directory. - public let sharedSwiftSDKsDirectory: AbsolutePath? + public let sharedSwiftSDKsDirectory: AbsolutePath /// Cancellator to handle cancellation of outstanding work when handling SIGINT public let cancellator: Cancellator @@ -248,8 +253,6 @@ public final class SwiftTool { fileprivate var buildSystemProvider: BuildSystemProvider? - private let driverSupport = DriverSupport() - /// Create an instance of this tool. /// /// - parameter options: The command line options to be passed to this tool. @@ -428,7 +431,7 @@ public final class SwiftTool { // TODO: should supportsAvailability be a flag as well? .init(url: $0, supportsAvailability: true) }, - restrictImports: .none + manifestImportRestrictions: .none ), cancellator: self.cancellator, initializationWarningHandler: { self.observabilityScope.emit(warning: $0) }, @@ -638,6 +641,7 @@ public final class SwiftTool { explicitBuildSystem: BuildSystemProvider.Kind? = .none, explicitProduct: String? = .none, cacheBuildManifest: Bool = true, + shouldLinkStaticSwiftStdlib: Bool = false, customBuildParameters: BuildParameters? = .none, customPackageGraphLoader: (() throws -> PackageGraph)? = .none, customOutputStream: OutputByteStream? = .none, @@ -648,6 +652,9 @@ public final class SwiftTool { fatalError("build system provider not initialized") } + var buildParameters = try customBuildParameters ?? self.buildParameters() + buildParameters.linkingParameters.shouldLinkStaticSwiftStdlib = shouldLinkStaticSwiftStdlib + let buildSystem = try buildSystemProvider.createBuildSystem( kind: explicitBuildSystem ?? options.build.buildSystem, explicitProduct: explicitProduct, @@ -664,42 +671,68 @@ public final class SwiftTool { return buildSystem } + static let entitlementsMacOSWarning = """ + `--disable-get-task-allow-entitlement` and `--disable-get-task-allow-entitlement` only have an effect \ + when building on macOS. + """ + private func _buildParams(toolchain: UserToolchain) throws -> BuildParameters { + let hostTriple = try self.getHostToolchain().targetTriple let targetTriple = toolchain.targetTriple let dataPath = self.scratchDirectory.appending( component: targetTriple.platformBuildPathComponent(buildSystem: options.build.buildSystem) ) + if options.build.getTaskAllowEntitlement != nil && !targetTriple.isMacOSX { + observabilityScope.emit(warning: Self.entitlementsMacOSWarning) + } + return try BuildParameters( dataPath: dataPath, configuration: options.build.configuration, toolchain: toolchain, + hostTriple: hostTriple, targetTriple: targetTriple, flags: options.build.buildFlags, pkgConfigDirectories: options.locations.pkgConfigDirectories, architectures: options.build.architectures, workers: options.build.jobs ?? UInt32(ProcessInfo.processInfo.activeProcessorCount), - shouldLinkStaticSwiftStdlib: options.linker.shouldLinkStaticSwiftStdlib, - canRenameEntrypointFunctionName: driverSupport.checkSupportedFrontendFlags( - flags: ["entry-point-function-name"], - toolchain: toolchain, - fileSystem: self.fileSystem - ), sanitizers: options.build.enabledSanitizers, - enableCodeCoverage: false, // set by test commands when appropriate indexStoreMode: options.build.indexStoreMode.buildParameter, - enableParseableModuleInterfaces: options.build.shouldEnableParseableModuleInterfaces, - useIntegratedSwiftDriver: options.build.useIntegratedSwiftDriver, - useExplicitModuleBuild: options.build.useExplicitModuleBuild, isXcodeBuildSystemEnabled: options.build.buildSystem == .xcode, - forceTestDiscovery: options.build.enableTestDiscovery, // backwards compatibility, remove with --enable-test-discovery - testEntryPointPath: options.build.testEntryPointPath, - explicitTargetDependencyImportCheckingMode: options.build.explicitTargetDependencyImportCheck.modeParameter, - linkerDeadStrip: options.linker.linkerDeadStrip, - verboseOutput: self.logLevel <= .info, - linkTimeOptimizationMode: options.build.linkTimeOptimizationMode?.buildParameter, - debugInfoFormat: options.build.debugInfoFormat.buildParameter + debuggingParameters: .init( + debugInfoFormat: options.build.debugInfoFormat.buildParameter, + targetTriple: targetTriple, + shouldEnableDebuggingEntitlement: + options.build.getTaskAllowEntitlement ?? (options.build.configuration == .debug), + omitFramePointers: options.build.omitFramePointers + ), + driverParameters: .init( + canRenameEntrypointFunctionName: DriverSupport.checkSupportedFrontendFlags( + flags: ["entry-point-function-name"], + toolchain: toolchain, + fileSystem: self.fileSystem + ), + enableParseableModuleInterfaces: options.build.shouldEnableParseableModuleInterfaces, + explicitTargetDependencyImportCheckingMode: options.build.explicitTargetDependencyImportCheck.modeParameter, + useIntegratedSwiftDriver: options.build.useIntegratedSwiftDriver, + useExplicitModuleBuild: options.build.useExplicitModuleBuild + ), + linkingParameters: .init( + linkerDeadStrip: options.linker.linkerDeadStrip, + linkTimeOptimizationMode: options.build.linkTimeOptimizationMode?.buildParameter, + shouldDisableLocalRpath: options.linker.shouldDisableLocalRpath + ), + outputParameters: .init( + isVerbose: self.logLevel <= .info + ), + testingParameters: .init( + configuration: options.build.configuration, + targetTriple: targetTriple, + forceTestDiscovery: options.build.enableTestDiscovery, // backwards compatibility, remove with --enable-test-discovery + testEntryPointPath: options.build.testEntryPointPath + ) ) } @@ -732,14 +765,14 @@ public final class SwiftTool { do { let hostToolchain = try _hostToolchain.get() hostSwiftSDK = hostToolchain.swiftSDK - let hostTriple = try Triple.getHostTriple(usingSwiftCompiler: hostToolchain.swiftCompilerPath) + let hostTriple = hostToolchain.targetTriple // Create custom toolchain if present. - if let customDestination = options.locations.customCompileDestination { + if let customDestination = self.options.locations.customCompileDestination { let swiftSDKs = try SwiftSDK.decode( fromFile: customDestination, - fileSystem: fileSystem, - observabilityScope: observabilityScope + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope ) if swiftSDKs.count == 1 { swiftSDK = swiftSDKs[0] @@ -756,13 +789,13 @@ public final class SwiftTool { { swiftSDK = targetSwiftSDK } else if let swiftSDKSelector = options.build.swiftSDKSelector { - swiftSDK = try SwiftSDKBundle.selectBundle( - fromBundlesAt: sharedSwiftSDKsDirectory, - fileSystem: fileSystem, - matching: swiftSDKSelector, - hostTriple: hostTriple, - observabilityScope: observabilityScope + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: self.sharedSwiftSDKsDirectory, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope, + outputHandler: { print($0.description) } ) + swiftSDK = try store.selectBundle(matching: swiftSDKSelector, hostTriple: hostTriple) } else { // Otherwise use the host toolchain. swiftSDK = hostSwiftSDK @@ -775,12 +808,22 @@ public final class SwiftTool { swiftSDK.targetTriple = triple } if let binDir = options.build.customCompileToolchain { + if !self.fileSystem.exists(binDir) { + self.observabilityScope.emit( + warning: """ + Toolchain directory specified through a command-line option doesn't exist and is ignored: `\( + binDir + )` + """ + ) + } + swiftSDK.add(toolsetRootPath: binDir.appending(components: "usr", "bin")) } if let sdk = options.build.customCompileSDK { swiftSDK.pathsConfiguration.sdkRootPath = sdk } - swiftSDK.architectures = options.build.architectures + swiftSDK.architectures = options.build.architectures.isEmpty ? nil : options.build.architectures // Check if we ended up with the host toolchain. if hostSwiftSDK == swiftSDK { @@ -794,7 +837,9 @@ public final class SwiftTool { private lazy var _hostToolchain: Result = { return Result(catching: { try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK( - originalWorkingDirectory: self.originalWorkingDirectory)) + originalWorkingDirectory: self.originalWorkingDirectory, + observabilityScope: self.observabilityScope + )) }) }() @@ -810,16 +855,16 @@ public final class SwiftTool { case (false, .local): cachePath = self.scratchDirectory case (false, .shared): - cachePath = self.sharedCacheDirectory.map{ Workspace.DefaultLocations.manifestsDirectory(at: $0) } + cachePath = Workspace.DefaultLocations.manifestsDirectory(at: self.sharedCacheDirectory) } var extraManifestFlags = self.options.build.manifestFlags // Disable the implicit concurrency import if the compiler in use supports it to avoid warnings if we are building against an older SDK that does not contain a Concurrency module. - if driverSupport.checkSupportedFrontendFlags(flags: ["disable-implicit-concurrency-module-import"], toolchain: try self.buildParameters().toolchain, fileSystem: self.fileSystem) { + if DriverSupport.checkSupportedFrontendFlags(flags: ["disable-implicit-concurrency-module-import"], toolchain: try self.buildParameters().toolchain, fileSystem: self.fileSystem) { extraManifestFlags += ["-Xfrontend", "-disable-implicit-concurrency-module-import"] } // Disable the implicit string processing import if the compiler in use supports it to avoid warnings if we are building against an older SDK that does not contain a StringProcessing module. - if driverSupport.checkSupportedFrontendFlags(flags: ["disable-implicit-string-processing-module-import"], toolchain: try self.buildParameters().toolchain, fileSystem: self.fileSystem) { + if DriverSupport.checkSupportedFrontendFlags(flags: ["disable-implicit-string-processing-module-import"], toolchain: try self.buildParameters().toolchain, fileSystem: self.fileSystem) { extraManifestFlags += ["-Xfrontend", "-disable-implicit-string-processing-module-import"] } @@ -833,7 +878,7 @@ public final class SwiftTool { isManifestSandboxEnabled: !self.shouldDisableSandbox, cacheDir: cachePath, extraManifestFlags: extraManifestFlags, - restrictImports: nil + importRestrictions: .none ) }) }() @@ -862,7 +907,7 @@ private func findPackageRoot(fileSystem: FileSystem) -> AbsolutePath? { return root } -private func getSharedSecurityDirectory(options: GlobalOptions, fileSystem: FileSystem) throws -> AbsolutePath? { +private func getSharedSecurityDirectory(options: GlobalOptions, fileSystem: FileSystem) throws -> AbsolutePath { if let explicitSecurityDirectory = options.locations.securityDirectory { // Create the explicit security path if necessary if !fileSystem.exists(explicitSecurityDirectory) { @@ -875,7 +920,7 @@ private func getSharedSecurityDirectory(options: GlobalOptions, fileSystem: File } } -private func getSharedConfigurationDirectory(options: GlobalOptions, fileSystem: FileSystem) throws -> AbsolutePath? { +private func getSharedConfigurationDirectory(options: GlobalOptions, fileSystem: FileSystem) throws -> AbsolutePath { if let explicitConfigurationDirectory = options.locations.configurationDirectory { // Create the explicit config path if necessary if !fileSystem.exists(explicitConfigurationDirectory) { @@ -888,7 +933,7 @@ private func getSharedConfigurationDirectory(options: GlobalOptions, fileSystem: } } -private func getSharedCacheDirectory(options: GlobalOptions, fileSystem: FileSystem) throws -> AbsolutePath? { +private func getSharedCacheDirectory(options: GlobalOptions, fileSystem: FileSystem) throws -> AbsolutePath { if let explicitCacheDirectory = options.locations.cacheDirectory { // Create the explicit cache path if necessary if !fileSystem.exists(explicitCacheDirectory) { diff --git a/Sources/DriverSupport/DriverSupportUtils.swift b/Sources/DriverSupport/DriverSupportUtils.swift index 238c703d8bf..9e5bf00a739 100644 --- a/Sources/DriverSupport/DriverSupportUtils.swift +++ b/Sources/DriverSupport/DriverSupportUtils.swift @@ -17,12 +17,11 @@ import class TSCBasic.Process import enum TSCBasic.ProcessEnv import struct TSCBasic.ProcessResult -public class DriverSupport { - private var flagsMap = ThreadSafeBox<[String: Set]>() - public init() {} +public enum DriverSupport { + private static var flagsMap = ThreadSafeBox<[String: Set]>() // This checks _frontend_ supported flags, which are not necessarily supported in the driver. - public func checkSupportedFrontendFlags( + public static func checkSupportedFrontendFlags( flags: Set, toolchain: PackageModel.Toolchain, fileSystem: FileSystem @@ -55,7 +54,7 @@ public class DriverSupport { // This checks if given flags are supported in the built-in toolchain driver. Currently // there's no good way to get the supported flags from it, so run `swiftc -h` directly // to get the flags and cache the result. - public func checkToolchainDriverFlags( + public static func checkToolchainDriverFlags( flags: Set, toolchain: PackageModel.Toolchain, fileSystem: FileSystem diff --git a/Sources/LLBuildManifest/CMakeLists.txt b/Sources/LLBuildManifest/CMakeLists.txt index 50f3085bb0c..f7227fc2d8a 100644 --- a/Sources/LLBuildManifest/CMakeLists.txt +++ b/Sources/LLBuildManifest/CMakeLists.txt @@ -7,9 +7,9 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_library(LLBuildManifest STATIC - BuildManifest.swift Command.swift - ManifestWriter.swift + LLBuildManifest.swift + LLBuildManifestWriter.swift Node.swift Target.swift Tools.swift) diff --git a/Sources/LLBuildManifest/BuildManifest.swift b/Sources/LLBuildManifest/LLBuildManifest.swift similarity index 68% rename from Sources/LLBuildManifest/BuildManifest.swift rename to Sources/LLBuildManifest/LLBuildManifest.swift index a75923f9ae4..1c67c8c8209 100644 --- a/Sources/LLBuildManifest/BuildManifest.swift +++ b/Sources/LLBuildManifest/LLBuildManifest.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2023 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 @@ -11,6 +11,9 @@ //===----------------------------------------------------------------------===// import Basics +import Foundation + +import class TSCBasic.Process public protocol AuxiliaryFileType { static var name: String { get } @@ -19,7 +22,37 @@ public protocol AuxiliaryFileType { } public enum WriteAuxiliary { - public static let fileTypes: [AuxiliaryFileType.Type] = [LinkFileList.self, SourcesFileList.self] + public static let fileTypes: [AuxiliaryFileType.Type] = [ + EntitlementPlist.self, + LinkFileList.self, + SourcesFileList.self, + SwiftGetVersion.self, + XCTestInfoPlist.self + ] + + public struct EntitlementPlist: AuxiliaryFileType { + public static let name = "entitlement-plist" + + public static func computeInputs(entitlement: String) -> [Node] { + [.virtual(Self.name), .virtual(entitlement)] + } + + public static func getFileContents(inputs: [Node]) throws -> String { + guard let entitlementName = inputs.last?.extractedVirtualNodeName else { + throw Error.undefinedEntitlementName + } + let encoder = PropertyListEncoder() + encoder.outputFormat = .xml + let result = try encoder.encode([entitlementName: true]) + + let contents = String(decoding: result, as: UTF8.self) + return contents + } + + private enum Error: Swift.Error { + case undefinedEntitlementName + } + } public struct LinkFileList: AuxiliaryFileType { public static let name = "link-file-list" @@ -76,9 +109,59 @@ public enum WriteAuxiliary { return contents } } + + public struct SwiftGetVersion: AuxiliaryFileType { + public static let name = "swift-get-version" + + public static func computeInputs(swiftCompilerPath: AbsolutePath) -> [Node] { + return [.virtual(Self.name), .file(swiftCompilerPath)] + } + + public static func getFileContents(inputs: [Node]) throws -> String { + guard let swiftCompilerPathString = inputs.first(where: { $0.kind == .file })?.name else { + throw Error.unknownSwiftCompilerPath + } + let swiftCompilerPath = try AbsolutePath(validating: swiftCompilerPathString) + return try TSCBasic.Process.checkNonZeroExit(args: swiftCompilerPath.pathString, "-version") + } + + private enum Error: Swift.Error { + case unknownSwiftCompilerPath + } + } + + public struct XCTestInfoPlist: AuxiliaryFileType { + public static let name = "xctest-info-plist" + + public static func computeInputs(principalClass: String) -> [Node] { + return [.virtual(Self.name), .virtual(principalClass)] + } + + public static func getFileContents(inputs: [Node]) throws -> String { + guard let principalClass = inputs.last?.extractedVirtualNodeName else { + throw Error.undefinedPrincipalClass + } + + let plist = InfoPlist(NSPrincipalClass: String(principalClass)) + let encoder = PropertyListEncoder() + encoder.outputFormat = .xml + let result = try encoder.encode(plist) + + let contents = String(decoding: result, as: UTF8.self) + return contents + } + + private struct InfoPlist: Codable { + let NSPrincipalClass: String + } + + private enum Error: Swift.Error { + case undefinedPrincipalClass + } + } } -public struct BuildManifest { +public struct LLBuildManifest { public typealias TargetName = String public typealias CmdName = String @@ -153,6 +236,13 @@ public struct BuildManifest { commands[name] = Command(name: name, tool: tool) } + public mutating func addEntitlementPlistCommand(entitlement: String, outputPath: AbsolutePath) { + let inputs = WriteAuxiliary.EntitlementPlist.computeInputs(entitlement: entitlement) + let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) + let name = outputPath.pathString + commands[name] = Command(name: name, tool: tool) + } + public mutating func addWriteLinkFileListCommand( objects: [AbsolutePath], linkFileListPath: AbsolutePath @@ -173,6 +263,23 @@ public struct BuildManifest { commands[name] = Command(name: name, tool: tool) } + public mutating func addSwiftGetVersionCommand( + swiftCompilerPath: AbsolutePath, + swiftVersionFilePath: AbsolutePath + ) { + let inputs = WriteAuxiliary.SwiftGetVersion.computeInputs(swiftCompilerPath: swiftCompilerPath) + let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: swiftVersionFilePath, alwaysOutOfDate: true) + let name = swiftVersionFilePath.pathString + commands[name] = Command(name: name, tool: tool) + } + + public mutating func addWriteInfoPlistCommand(principalClass: String, outputPath: AbsolutePath) { + let inputs = WriteAuxiliary.XCTestInfoPlist.computeInputs(principalClass: principalClass) + let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) + let name = outputPath.pathString + commands[name] = Command(name: name, tool: tool) + } + public mutating func addPkgStructureCmd( name: String, inputs: [Node], diff --git a/Sources/LLBuildManifest/LLBuildManifestWriter.swift b/Sources/LLBuildManifest/LLBuildManifestWriter.swift new file mode 100644 index 00000000000..b85a65c5098 --- /dev/null +++ b/Sources/LLBuildManifest/LLBuildManifestWriter.swift @@ -0,0 +1,292 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2019 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 Basics + +private let namesToExclude = [".git", ".build"] + +public struct LLBuildManifestWriter { + private let manifest: LLBuildManifest + // FIXME: since JSON is a superset of YAML and we don't need to parse these manifests, + // we should just use `JSONEncoder` instead. + private var buffer = """ + client: + name: basic + file-system: device-agnostic + tools: {} + + """ + + private init(manifest: LLBuildManifest) { + self.manifest = manifest + + self.render(targets: manifest.targets) + + self.buffer += "default: \(manifest.defaultTarget.asJSON)\n" + + self.render(nodes: manifest.commands.values.flatMap { $0.tool.inputs + $0.tool.outputs }) + + self.render(commands: manifest.commands) + } + + public static func write(_ manifest: LLBuildManifest, at path: AbsolutePath, fileSystem: FileSystem) throws { + let writer = LLBuildManifestWriter(manifest: manifest) + + try fileSystem.writeFileContents(path, string: writer.buffer) + } + + private mutating func render(targets: [LLBuildManifest.TargetName: Target]) { + self.buffer += "targets:\n" + for (_, target) in targets.sorted(by: { $0.key < $1.key }) { + self.buffer += " \(target.name.asJSON): \(target.nodes.map(\.name).sorted().asJSON)\n" + } + } + + private mutating func render(nodes: [Node]) { + // We need to explicitly configure certain kinds of nodes. + let directoryStructureNodes = Set(nodes.filter { $0.kind == .directoryStructure }) + .sorted(by: { $0.name < $1.name }) + let commandTimestampNodes = Set(nodes.filter { $0.attributes?.isCommandTimestamp == true }) + .sorted(by: { $0.name < $1.name }) + let mutatedNodes = Set(nodes.filter { $0.attributes?.isMutated == true }) + .sorted(by: { $0.name < $1.name }) + + if !directoryStructureNodes.isEmpty || !mutatedNodes.isEmpty || !commandTimestampNodes.isEmpty { + self.buffer += "nodes:\n" + } + + for node in directoryStructureNodes { + self.render(directoryStructure: node) + } + + for node in commandTimestampNodes { + self.render(isCommandTimestamp: node) + } + + for node in mutatedNodes { + self.render(isMutated: node) + } + } + + private mutating func render(directoryStructure node: Node) { + self.buffer += """ + \(node.asJSON): + is-directory-structure: true + content-exclusion-patterns: \(namesToExclude.asJSON) + + """ + } + + private mutating func render(isCommandTimestamp node: Node) { + self.buffer += """ + \(node.asJSON): + is-command-timestamp: true + + """ + } + + private mutating func render(isMutated node: Node) { + self.buffer += """ + \(node.asJSON): + is-mutated: true + + """ + } + + private mutating func render(commands: [LLBuildManifest.CmdName: Command]) { + self.buffer += "commands:\n" + for (_, command) in commands.sorted(by: { $0.key < $1.key }) { + self.buffer += " \(command.name.asJSON):\n" + + let tool = command.tool + + var manifestToolWriter = ManifestToolStream() + manifestToolWriter["tool"] = tool + manifestToolWriter["inputs"] = tool.inputs + manifestToolWriter["outputs"] = tool.outputs + + if tool.alwaysOutOfDate { + manifestToolWriter["always-out-of-date"] = "true" + } + + tool.write(to: &manifestToolWriter) + + self.buffer += "\(manifestToolWriter.buffer)\n" + } + } +} + +public struct ManifestToolStream { + fileprivate var buffer = "" + + public subscript(key: String) -> Int { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.description.asJSON)\n" + } + } + + public subscript(key: String) -> String { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.asJSON)\n" + } + } + + public subscript(key: String) -> ToolProtocol { + get { fatalError() } + set { + self.buffer += " \(key): \(type(of: newValue).name)\n" + } + } + + public subscript(key: String) -> AbsolutePath { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.pathString.asJSON)\n" + } + } + + public subscript(key: String) -> [AbsolutePath] { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.map(\.pathString).asJSON)\n" + } + } + + public subscript(key: String) -> [Node] { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.map(\.encodingName).asJSON)\n" + } + } + + public subscript(key: String) -> Bool { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.description)\n" + } + } + + public subscript(key: String) -> [String] { + get { fatalError() } + set { + self.buffer += " \(key): \(newValue.asJSON)\n" + } + } + + public subscript(key: String) -> [String: String] { + get { fatalError() } + set { + self.buffer += " \(key):\n" + for (key, value) in newValue.sorted(by: { $0.key < $1.key }) { + self.buffer += " \(key.asJSON): \(value.asJSON)\n" + } + } + } +} + +extension [String] { + fileprivate var asJSON: String { + """ + [\(self.map(\.asJSON).joined(separator: ","))] + """ + } +} + +extension Node { + fileprivate var asJSON: String { + self.encodingName.asJSON + } +} + +extension Node { + fileprivate var encodingName: String { + switch kind { + case .virtual, .file: + return name + case .directory, .directoryStructure: + return name + "/" + } + } +} + +extension String { + fileprivate var asJSON: String { + "\"\(self.jsonEscaped)\"" + } + + private var jsonEscaped: String { + // See RFC7159 for reference: https://tools.ietf.org/html/rfc7159 + String(decoding: self.utf8.flatMap { character -> [UInt8] in + // Handle string escapes; we use constants here to directly match the RFC. + switch character { + // Literal characters. + case 0x20 ... 0x21, 0x23 ... 0x5B, 0x5D ... 0xFF: + return [character] + + // Single-character escaped characters. + case 0x22: // '"' + return [ + 0x5C, // '\' + 0x22, // '"' + ] + case 0x5C: // '\\' + return [ + 0x5C, // '\' + 0x5C, // '\' + ] + case 0x08: // '\b' + return [ + 0x5C, // '\' + 0x62, // 'b' + ] + case 0x0C: // '\f' + return [ + 0x5C, // '\' + 0x66, // 'b' + ] + case 0x0A: // '\n' + return [ + 0x5C, // '\' + 0x6E, // 'n' + ] + case 0x0D: // '\r' + return [ + 0x5C, // '\' + 0x72, // 'r' + ] + case 0x09: // '\t' + return [ + 0x5C, // '\' + 0x74, // 't' + ] + + // Multi-character escaped characters. + default: + return [ + 0x5C, // '\' + 0x75, // 'u' + hexdigit(0), + hexdigit(0), + hexdigit(character >> 4), + hexdigit(character & 0xF), + ] + } + }, as: UTF8.self) + } +} + +/// Convert an integer in 0..<16 to its hexadecimal ASCII character. +private func hexdigit(_ value: UInt8) -> UInt8 { + value < 10 ? (0x30 + value) : (0x41 + value - 10) +} diff --git a/Sources/LLBuildManifest/ManifestWriter.swift b/Sources/LLBuildManifest/ManifestWriter.swift deleted file mode 100644 index ab824d59395..00000000000 --- a/Sources/LLBuildManifest/ManifestWriter.swift +++ /dev/null @@ -1,177 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-2019 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 Basics -import protocol TSCBasic.ByteStreamable -import struct TSCBasic.Format -import protocol TSCBasic.OutputByteStream -import class TSCBasic.BufferedOutputByteStream - -public struct ManifestWriter { - let fileSystem: FileSystem - - public init(fileSystem: FileSystem) { - self.fileSystem = fileSystem - } - - public func write( - _ manifest: BuildManifest, - at path: AbsolutePath - ) throws { - let stream = BufferedOutputByteStream() - stream.send( - """ - client: - name: basic - tools: {} - targets:\n - """ - ) - - for (_, target) in manifest.targets.sorted(by: { $0.key < $1.key }) { - stream.send(" ").send(Format.asJSON(target.name)) - stream.send(": ").send(Format.asJSON(target.nodes.map(\.name).sorted())).send("\n") - } - - stream.send("default: ").send(Format.asJSON(manifest.defaultTarget)).send("\n") - - // We need to explicitly configure the directory structure nodes. - let directoryStructureNodes = Set(manifest.commands - .values - .flatMap{ $0.tool.inputs } - .filter{ $0.kind == .directoryStructure } - ) - - if !directoryStructureNodes.isEmpty { - stream.send("nodes:\n") - } - let namesToExclude = [".git", ".build"] - for node in directoryStructureNodes.sorted(by: { $0.name < $1.name }) { - stream.send(" ").send(Format.asJSON(node)).send(":\n") - .send(" is-directory-structure: true\n") - .send(" content-exclusion-patterns: ").send(Format.asJSON(namesToExclude)).send("\n") - } - - stream.send("commands:\n") - for (_, command) in manifest.commands.sorted(by: { $0.key < $1.key }) { - stream.send(" ").send(Format.asJSON(command.name)).send(":\n") - - let tool = command.tool - - let manifestToolWriter = ManifestToolStream(stream) - manifestToolWriter["tool"] = tool - manifestToolWriter["inputs"] = tool.inputs - manifestToolWriter["outputs"] = tool.outputs - - tool.write(to: manifestToolWriter) - - stream.send("\n") - } - - try self.fileSystem.writeFileContents(path, bytes: stream.bytes) - } -} - -public final class ManifestToolStream { - private let stream: OutputByteStream - - fileprivate init(_ stream: OutputByteStream) { - self.stream = stream - } - - public subscript(key: String) -> Int { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue)).send("\n") - } - } - - public subscript(key: String) -> String { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue)).send("\n") - } - } - - public subscript(key: String) -> ToolProtocol { - get { fatalError() } - set { - stream.send(" \(key): \(type(of: newValue).name)\n") - } - } - - public subscript(key: String) -> AbsolutePath { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue.pathString)).send("\n") - } - } - - public subscript(key: String) -> [AbsolutePath] { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue.map(\.pathString))).send("\n") - } - } - - public subscript(key: String) -> [Node] { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue)).send("\n") - } - } - - public subscript(key: String) -> Bool { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue)).send("\n") - } - } - - public subscript(key: String) -> [String] { - get { fatalError() } - set { - stream.send(" \(key): ").send(Format.asJSON(newValue)).send("\n") - } - } - - public subscript(key: String) -> [String: String] { - get { fatalError() } - set { - stream.send(" \(key):\n") - for (key, value) in newValue.sorted(by: { $0.key < $1.key }) { - stream.send(" ").send(Format.asJSON(key)).send(": ").send(Format.asJSON(value)).send("\n") - } - } - } -} - -extension Format { - static func asJSON(_ items: [Node]) -> ByteStreamable { - return asJSON(items.map { $0.encodingName }) - } - - static func asJSON(_ item: Node) -> ByteStreamable { - return asJSON(item.encodingName) - } -} - -extension Node { - fileprivate var encodingName: String { - switch kind { - case .virtual, .file: - return name - case .directory, .directoryStructure: - return name + "/" - } - } -} diff --git a/Sources/LLBuildManifest/Node.swift b/Sources/LLBuildManifest/Node.swift index 9800cb34e64..9dd898b6682 100644 --- a/Sources/LLBuildManifest/Node.swift +++ b/Sources/LLBuildManifest/Node.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2019 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2023 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 @@ -20,26 +20,52 @@ public struct Node: Hashable, Codable { case directoryStructure } + struct Attributes: Hashable, Codable { + var isMutated = false + var isCommandTimestamp = false + } + /// The name used to identify the node. public var name: String /// The kind of node. public var kind: Kind - private init(name: String, kind: Kind) { + let attributes: Attributes? + + private init(name: String, kind: Kind, attributes: Attributes? = nil) { self.name = name self.kind = kind + self.attributes = attributes + } + + /// Extracts `name` property if this node was constructed as `Node//virtual`. + public var extractedVirtualNodeName: String { + precondition(kind == .virtual) + return String(self.name.dropFirst().dropLast()) } - public static func virtual(_ name: String) -> Node { + public static func virtual(_ name: String, isCommandTimestamp: Bool = false) -> Node { precondition(name.first != "<" && name.last != ">", "<> will be inserted automatically") - return Node(name: "<" + name + ">", kind: .virtual) + return Node( + name: "<" + name + ">", + kind: .virtual, + attributes: isCommandTimestamp ? .init(isCommandTimestamp: isCommandTimestamp) : nil + ) } public static func file(_ name: AbsolutePath) -> Node { Node(name: name.pathString, kind: .file) } + public static func file(_ name: AbsolutePath, isMutated: Bool) -> Node { + Node( + name: name.pathString, + kind: .file, + attributes: .init(isMutated: isMutated) + ) + } + public static func directory(_ name: AbsolutePath) -> Node { Node(name: name.pathString, kind: .directory) } diff --git a/Sources/LLBuildManifest/Tools.swift b/Sources/LLBuildManifest/Tools.swift index 98702f48924..7c555903984 100644 --- a/Sources/LLBuildManifest/Tools.swift +++ b/Sources/LLBuildManifest/Tools.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2023 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 @@ -17,6 +17,9 @@ public protocol ToolProtocol: Codable { /// The name of the tool. static var name: String { get } + /// Whether or not the tool should run on every build instead of using dependency tracking. + var alwaysOutOfDate: Bool { get } + /// The list of inputs to declare. var inputs: [Node] { get } @@ -24,11 +27,13 @@ public protocol ToolProtocol: Codable { var outputs: [Node] { get } /// Write a description of the tool to the given output `stream`. - func write(to stream: ManifestToolStream) + func write(to stream: inout ManifestToolStream) } extension ToolProtocol { - public func write(to stream: ManifestToolStream) {} + public var alwaysOutOfDate: Bool { return false } + + public func write(to stream: inout ManifestToolStream) {} } public struct PhonyTool: ToolProtocol { @@ -80,7 +85,7 @@ public struct CopyTool: ToolProtocol { self.outputs = outputs } - public func write(to stream: ManifestToolStream) { + public func write(to stream: inout ManifestToolStream) { stream["description"] = "Copying \(inputs[0].name)" } } @@ -100,7 +105,7 @@ public struct PackageStructureTool: ToolProtocol { self.outputs = outputs } - public func write(to stream: ManifestToolStream) { + public func write(to stream: inout ManifestToolStream) { stream["description"] = "Planning build" stream["allow-missing-inputs"] = true } @@ -135,7 +140,7 @@ public struct ShellTool: ToolProtocol { self.allowMissingInputs = allowMissingInputs } - public func write(to stream: ManifestToolStream) { + public func write(to stream: inout ManifestToolStream) { stream["description"] = description stream["args"] = arguments if !environment.isEmpty { @@ -150,22 +155,24 @@ public struct ShellTool: ToolProtocol { } } -public struct WriteAuxiliaryFile: ToolProtocol { +public struct WriteAuxiliaryFile: Equatable, ToolProtocol { public static let name: String = "write-auxiliary-file" public let inputs: [Node] private let outputFilePath: AbsolutePath + public let alwaysOutOfDate: Bool - public init(inputs: [Node], outputFilePath: AbsolutePath) { + public init(inputs: [Node], outputFilePath: AbsolutePath, alwaysOutOfDate: Bool = false) { self.inputs = inputs self.outputFilePath = outputFilePath + self.alwaysOutOfDate = alwaysOutOfDate } public var outputs: [Node] { return [.file(outputFilePath)] } - public func write(to stream: ManifestToolStream) { + public func write(to stream: inout ManifestToolStream) { stream["description"] = "Write auxiliary file \(outputFilePath.pathString)" } } @@ -193,7 +200,7 @@ public struct ClangTool: ToolProtocol { self.dependencies = dependencies } - public func write(to stream: ManifestToolStream) { + public func write(to stream: inout ManifestToolStream) { stream["description"] = description stream["args"] = arguments if let dependencies { @@ -238,9 +245,8 @@ public struct SwiftFrontendTool: ToolProtocol { self.arguments = arguments } - public func write(to stream: ManifestToolStream) { - ShellTool(description: description, inputs: inputs, outputs: outputs, arguments: arguments) - .write(to: stream) + public func write(to stream: inout ManifestToolStream) { + ShellTool(description: description, inputs: inputs, outputs: outputs, arguments: arguments).write(to: &stream) } } @@ -335,7 +341,7 @@ public struct SwiftCompilerTool: ToolProtocol { return arguments } - public func write(to stream: ManifestToolStream) { - ShellTool(description: description, inputs: inputs, outputs: outputs, arguments: arguments).write(to: stream) + public func write(to stream: inout ManifestToolStream) { + ShellTool(description: description, inputs: inputs, outputs: outputs, arguments: arguments).write(to: &stream) } } diff --git a/Sources/PackageCollections/API.swift b/Sources/PackageCollections/API.swift index b7f794a9247..9899683b228 100644 --- a/Sources/PackageCollections/API.swift +++ b/Sources/PackageCollections/API.swift @@ -13,6 +13,7 @@ import struct Foundation.URL import PackageModel import SourceControl +import Basics // MARK: - Package collection @@ -27,6 +28,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - identifiers: Optional. If specified, only `PackageCollection`s with matching identifiers will be returned. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func listCollections( identifiers: Set?, callback: @escaping (Result<[PackageCollectionsModel.Collection], Error>) -> Void @@ -36,6 +38,7 @@ public protocol PackageCollectionsProtocol { /// /// - Parameters: /// - callback: The closure to invoke after triggering a refresh for the configured package collections. + @available(*, noasync, message: "Use the async alternative") func refreshCollections(callback: @escaping (Result<[PackageCollectionsModel.CollectionSource], Error>) -> Void) /// Refreshes a package collection. @@ -43,6 +46,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - source: The package collection to be refreshed /// - callback: The closure to invoke with the refreshed `PackageCollection` + @available(*, noasync, message: "Use the async alternative") func refreshCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void @@ -56,6 +60,7 @@ public protocol PackageCollectionsProtocol { /// By default the new collection is appended to the end (i.e., the least relevant order). /// - trustConfirmationProvider: The closure to invoke when the collection is not signed and user confirmation is required to proceed /// - callback: The closure to invoke with the newly added `PackageCollection` + @available(*, noasync, message: "Use the async alternative") func addCollection( _ source: PackageCollectionsModel.CollectionSource, order: Int?, @@ -68,6 +73,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - source: The package collection's source /// - callback: The closure to invoke with the result becomes available + @available(*, noasync, message: "Use the async alternative") func removeCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void @@ -79,6 +85,7 @@ public protocol PackageCollectionsProtocol { /// - source: The source of the `PackageCollection` to be reordered /// - order: The new order that the `PackageCollection` should be positioned after the move /// - callback: The closure to invoke with the result becomes available + @available(*, noasync, message: "Use the async alternative") func moveCollection( _ source: PackageCollectionsModel.CollectionSource, to order: Int, @@ -90,6 +97,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - source: The `PackageCollection` source to be updated /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func updateCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void @@ -101,6 +109,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - source: The package collection's source /// - callback: The closure to invoke with the `PackageCollection` + @available(*, noasync, message: "Use the async alternative") func getCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void @@ -115,6 +124,7 @@ public protocol PackageCollectionsProtocol { /// - identity: The package identity /// - location: The package location (optional for deduplication) /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func getPackageMetadata( identity: PackageIdentity, location: String?, @@ -132,6 +142,7 @@ public protocol PackageCollectionsProtocol { /// - collections: Optional. If specified, only look for package in these collections. Data from the most recently /// processed collection will be used. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func getPackageMetadata( identity: PackageIdentity, location: String?, @@ -144,6 +155,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - collections: Optional. If specified, only packages in these collections are included. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func listPackages( collections: Set?, callback: @escaping (Result) -> Void @@ -160,6 +172,7 @@ public protocol PackageCollectionsProtocol { /// - Parameters: /// - collections: Optional. If specified, only list targets within these collections. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func listTargets( collections: Set?, callback: @escaping (Result) -> Void @@ -176,6 +189,7 @@ public protocol PackageCollectionsProtocol { /// - query: The search query /// - collections: Optional. If specified, only search within these collections. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func findPackages( _ query: String, collections: Set?, @@ -193,6 +207,7 @@ public protocol PackageCollectionsProtocol { /// For more flexibility, use the `findPackages` API instead. /// - collections: Optional. If specified, only search within these collections. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func findTargets( _ query: String, searchType: PackageCollectionsModel.TargetSearchType?, @@ -201,6 +216,159 @@ public protocol PackageCollectionsProtocol { ) } +public extension PackageCollectionsProtocol { + func listCollections( + identifiers: Set? = nil + ) async throws -> [PackageCollectionsModel.Collection] { + try await safe_async { + self.listCollections(identifiers: identifiers, callback: $0) + } + } + + func refreshCollections() async throws -> [PackageCollectionsModel.CollectionSource] { + try await safe_async { + self.refreshCollections(callback: $0) + } + } + + func refreshCollection( + _ source: PackageCollectionsModel.CollectionSource + ) async throws -> PackageCollectionsModel.Collection { + try await safe_async { + self.refreshCollection( + source, + callback: $0 + ) + } + } + + func addCollection( + _ source: PackageCollectionsModel.CollectionSource, + order: Int? = nil, + trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)? = nil + ) async throws -> PackageCollectionsModel.Collection { + try await safe_async { + self.addCollection( + source, + order: order, + trustConfirmationProvider:trustConfirmationProvider, + callback: $0 + ) + } + } + + func removeCollection( + _ source: PackageCollectionsModel.CollectionSource + ) async throws { + try await safe_async { + self.removeCollection( + source, + callback: $0 + ) + } + } + + func moveCollection( + _ source: PackageCollectionsModel.CollectionSource, + to order: Int + ) async throws { + try await safe_async { + self.moveCollection( + source, + to: order, + callback: $0 + ) + } + } + + func updateCollection( + _ source: PackageCollectionsModel.CollectionSource + ) async throws -> PackageCollectionsModel.Collection { + try await safe_async { + self.updateCollection( + source, + callback: $0 + ) + } + } + + func getCollection( + _ source: PackageCollectionsModel.CollectionSource + ) async throws -> PackageCollectionsModel.Collection { + try await safe_async { + self.getCollection( + source, + callback: $0 + ) + } + } + + func getPackageMetadata( + identity: PackageIdentity, + location: String? = nil, + collections: Set? = nil + ) async throws -> PackageCollectionsModel.PackageMetadata { + try await safe_async { + self.getPackageMetadata( + identity: identity, + location: location, + collections: collections, + callback: $0 + ) + } + } + + func listPackages( + collections: Set? = nil + ) async throws -> PackageCollectionsModel.PackageSearchResult { + try await safe_async { + self.listPackages( + collections: collections, + callback: $0 + ) + } + } + + func listTargets( + collections: Set? = nil + ) async throws -> PackageCollectionsModel.TargetListResult { + try await safe_async { + self.listTargets( + collections: collections, + callback: $0 + ) + } + } + + func findPackages( + _ query: String, + collections: Set? = nil + ) async throws -> PackageCollectionsModel.PackageSearchResult { + try await safe_async { + self.findPackages( + query, + collections: collections, + callback: $0 + ) + } + } + + func findTargets( + _ query: String, + searchType: PackageCollectionsModel.TargetSearchType? = nil, + collections: Set? = nil + ) async throws -> PackageCollectionsModel.TargetSearchResult { + try await safe_async { + self.findTargets( + query, + searchType: searchType, + collections: collections, + callback: $0 + ) + } + } +} + public enum PackageCollectionError: Equatable, Error { /// Package collection is not signed and there is no record of user's trust selection case trustConfirmationRequired @@ -233,6 +401,7 @@ public protocol PackageIndexProtocol { /// - identity: The package identity /// - location: The package location (optional for deduplication) /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func getPackageMetadata( identity: PackageIdentity, location: String?, @@ -244,6 +413,7 @@ public protocol PackageIndexProtocol { /// - Parameters: /// - query: The search query /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func findPackages( _ query: String, callback: @escaping (Result) -> Void @@ -255,6 +425,7 @@ public protocol PackageIndexProtocol { /// - offset: Offset of the first item in the result /// - limit: Number of items to return in the result. Implementations might impose a threshold for this. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func listPackages( offset: Int, limit: Int, @@ -262,6 +433,53 @@ public protocol PackageIndexProtocol { ) } +public extension PackageIndexProtocol { + func getPackageMetadata( + identity: PackageIdentity, + location: String? + ) async throws -> PackageCollectionsModel.PackageMetadata { + try await safe_async { + self.getPackageMetadata( + identity: identity, + location: location, + callback: $0 + ) + } + } + + /// Finds and returns packages that match the query. + /// + /// - Parameters: + /// - query: The search query + /// - callback: The closure to invoke when result becomes available + func findPackages( + _ query: String + ) async throws -> PackageCollectionsModel.PackageSearchResult { + try await safe_async { + self.findPackages(query, callback: $0) + } + } + + /// A paginated list of packages in the index. + /// + /// - Parameters: + /// - offset: Offset of the first item in the result + /// - limit: Number of items to return in the result. Implementations might impose a threshold for this. + /// - callback: The closure to invoke when result becomes available + func listPackages( + offset: Int, + limit: Int + ) async throws -> PackageCollectionsModel.PaginatedPackageList { + try await safe_async { + self.listPackages( + offset: offset, + limit: limit, + callback: $0 + ) + } + } +} + public enum PackageIndexError: Equatable, Error { /// Package index support is disabled case featureDisabled diff --git a/Sources/PackageCollections/PackageCollections.swift b/Sources/PackageCollections/PackageCollections.swift index f8e9fd90fad..5e75b2eff8c 100644 --- a/Sources/PackageCollections/PackageCollections.swift +++ b/Sources/PackageCollections/PackageCollections.swift @@ -489,9 +489,11 @@ public struct PackageCollections: PackageCollectionsProtocol, Closable { // Fetch the collection from the network and store it in local storage // This helps avoid network access in normal operations - private func refreshCollectionFromSource(source: PackageCollectionsModel.CollectionSource, - trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)?, - callback: @escaping (Result) -> Void) { + private func refreshCollectionFromSource( + source: PackageCollectionsModel.CollectionSource, + trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)?, + callback: @escaping (Result) -> Void + ) { guard let provider = self.collectionProviders[source.type] else { return callback(.failure(UnknownProvider(source.type))) } diff --git a/Sources/PackageCollections/PackageIndexAndCollections.swift b/Sources/PackageCollections/PackageIndexAndCollections.swift index a8e1789eded..d4a8a3eff1a 100644 --- a/Sources/PackageCollections/PackageIndexAndCollections.swift +++ b/Sources/PackageCollections/PackageIndexAndCollections.swift @@ -71,20 +71,37 @@ public struct PackageIndexAndCollections: Closable { // MARK: - Package collection specific APIs + public func listCollections( + identifiers: Set? = nil + ) async throws -> [PackageCollectionsModel.Collection] { + try await self.collections.listCollections(identifiers: identifiers) + } + /// - SeeAlso: `PackageCollectionsProtocol.listCollections` + @available(*, noasync, message: "Use the async alternative") public func listCollections( identifiers: Set? = nil, callback: @escaping (Result<[PackageCollectionsModel.Collection], Error>) -> Void ) { self.collections.listCollections(identifiers: identifiers, callback: callback) } + + public func refreshCollections() async throws -> [PackageCollectionsModel.CollectionSource] { + try await self.collections.refreshCollections() + } /// - SeeAlso: `PackageCollectionsProtocol.refreshCollections` + @available(*, noasync, message: "Use the async alternative") public func refreshCollections(callback: @escaping (Result<[PackageCollectionsModel.CollectionSource], Error>) -> Void) { self.collections.refreshCollections(callback: callback) } + public func refreshCollection(_ source: PackageCollectionsModel.CollectionSource) async throws -> PackageCollectionsModel.Collection { + try await self.collections.refreshCollection(source) + } + /// - SeeAlso: `PackageCollectionsProtocol.refreshCollection` + @available(*, noasync, message: "Use the async alternative") public func refreshCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void @@ -92,7 +109,20 @@ public struct PackageIndexAndCollections: Closable { self.collections.refreshCollection(source, callback: callback) } + public func addCollection( + _ source: PackageCollectionsModel.CollectionSource, + order: Int? = nil, + trustConfirmationProvider: ((PackageCollectionsModel.Collection, @escaping (Bool) -> Void) -> Void)? = nil + ) async throws -> PackageCollectionsModel.Collection { + try await self.collections.addCollection( + source, + order: order, + trustConfirmationProvider: trustConfirmationProvider + ) + } + /// - SeeAlso: `PackageCollectionsProtocol.addCollection` + @available(*, noasync, message: "Use the async alternative") public func addCollection( _ source: PackageCollectionsModel.CollectionSource, order: Int? = nil, @@ -101,8 +131,15 @@ public struct PackageIndexAndCollections: Closable { ) { self.collections.addCollection(source, order: order, trustConfirmationProvider: trustConfirmationProvider, callback: callback) } + + public func removeCollection( + _ source: PackageCollectionsModel.CollectionSource + ) async throws { + try await self.collections.removeCollection(source) + } /// - SeeAlso: `PackageCollectionsProtocol.removeCollection` + @available(*, noasync, message: "Use the async alternative") public func removeCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void @@ -110,23 +147,44 @@ public struct PackageIndexAndCollections: Closable { self.collections.removeCollection(source, callback: callback) } + public func getCollection( + _ source: PackageCollectionsModel.CollectionSource + ) async throws -> PackageCollectionsModel.Collection { + try await self.collections.getCollection(source) + } + /// - SeeAlso: `PackageCollectionsProtocol.getCollection` + @available(*, noasync, message: "Use the async alternative") public func getCollection( _ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void ) { self.collections.getCollection(source, callback: callback) } + + public func listPackages( + collections: Set? = nil + ) async throws -> PackageCollectionsModel.PackageSearchResult { + try await self.collections.listPackages(collections: collections) + } /// - SeeAlso: `PackageCollectionsProtocol.listPackages` + @available(*, noasync, message: "Use the async alternative") public func listPackages( collections: Set? = nil, callback: @escaping (Result) -> Void ) { self.collections.listPackages(collections: collections, callback: callback) } + + public func listTargets( + collections: Set? = nil + ) async throws -> PackageCollectionsModel.TargetListResult { + try await self.collections.listTargets(collections: collections) + } /// - SeeAlso: `PackageCollectionsProtocol.listTargets` + @available(*, noasync, message: "Use the async alternative") public func listTargets( collections: Set? = nil, callback: @escaping (Result) -> Void @@ -134,7 +192,20 @@ public struct PackageIndexAndCollections: Closable { self.collections.listTargets(collections: collections, callback: callback) } + public func findTargets( + _ query: String, + searchType: PackageCollectionsModel.TargetSearchType? = nil, + collections: Set? = nil + ) async throws -> PackageCollectionsModel.TargetSearchResult { + try await self.collections.findTargets( + query, + searchType: searchType, + collections: collections + ) + } + /// - SeeAlso: `PackageCollectionsProtocol.findTargets` + @available(*, noasync, message: "Use the async alternative") public func findTargets( _ query: String, searchType: PackageCollectionsModel.TargetSearchType? = nil, @@ -150,8 +221,16 @@ public struct PackageIndexAndCollections: Closable { public func isIndexEnabled() -> Bool { self.index.isEnabled } + + public func listPackagesInIndex( + offset: Int, + limit: Int + ) async throws -> PackageCollectionsModel.PaginatedPackageList { + try await self.index.listPackages(offset: offset, limit: limit) + } /// - SeeAlso: `PackageIndexProtocol.listPackages` + @available(*, noasync, message: "Use the async alternative") public func listPackagesInIndex( offset: Int, limit: Int, @@ -161,7 +240,21 @@ public struct PackageIndexAndCollections: Closable { } // MARK: - APIs that make use of both package index and collections - + public func getPackageMetadata( + identity: PackageIdentity, + location: String? = nil, + collections: Set? = nil + ) async throws -> PackageCollectionsModel.PackageMetadata { + try await safe_async { + self.getPackageMetadata( + identity: identity, + location: location, + collections: collections, + callback: $0 + ) + } + } + /// Returns metadata for the package identified by the given `PackageIdentity`, using package index (if configured) /// and collections data. /// @@ -172,6 +265,7 @@ public struct PackageIndexAndCollections: Closable { /// - location: The package location (optional for deduplication) /// - collections: Optional. If specified, only these collections are used to construct the result. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") public func getPackageMetadata( identity: PackageIdentity, location: String? = nil, @@ -231,7 +325,20 @@ public struct PackageIndexAndCollections: Closable { } } } - + + public func findPackages( + _ query: String, + in searchIn: SearchIn = .both(collections: nil) + ) async throws -> PackageCollectionsModel.PackageSearchResult { + try await safe_async { + self.findPackages( + query, + in: searchIn, + callback: $0 + ) + } + } + /// Finds and returns packages that match the query. /// /// - Parameters: @@ -239,6 +346,7 @@ public struct PackageIndexAndCollections: Closable { /// - in: Indicates whether to search in the index only, collections only, or both. /// The optional `Set` in some enum cases restricts search within those collections only. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") public func findPackages( _ query: String, in searchIn: SearchIn = .both(collections: nil), diff --git a/Sources/PackageCollections/Providers/PackageCollectionProvider.swift b/Sources/PackageCollections/Providers/PackageCollectionProvider.swift index e7624bea37a..5c3b11e0eef 100644 --- a/Sources/PackageCollections/Providers/PackageCollectionProvider.swift +++ b/Sources/PackageCollections/Providers/PackageCollectionProvider.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Basics + /// `PackageCollection` provider. For example, package feeds, (future) Package Index. protocol PackageCollectionProvider { /// Retrieves `PackageCollection` from the specified source. @@ -17,5 +19,15 @@ protocol PackageCollectionProvider { /// - Parameters: /// - source: Where the `PackageCollection` is located /// - callback: The closure to invoke when result becomes available + /// + @available(*, noasync, message: "Use the async alternative") func get(_ source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void) } + +extension PackageCollectionProvider { + func get(_ source: Model.CollectionSource) async throws -> Model.Collection { + try await safe_async { + self.get(source, callback: $0) + } + } +} diff --git a/Sources/PackageCollections/Storage/PackageCollectionsSourcesStorage.swift b/Sources/PackageCollections/Storage/PackageCollectionsSourcesStorage.swift index 182b86642a0..937d09b9820 100644 --- a/Sources/PackageCollections/Storage/PackageCollectionsSourcesStorage.swift +++ b/Sources/PackageCollections/Storage/PackageCollectionsSourcesStorage.swift @@ -10,11 +10,14 @@ // //===----------------------------------------------------------------------===// +import Basics + public protocol PackageCollectionsSourcesStorage { /// Lists all `PackageCollectionSource`s. /// /// - Parameters: /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func list(callback: @escaping (Result<[PackageCollectionsModel.CollectionSource], Error>) -> Void) /// Adds the given source. @@ -24,6 +27,7 @@ public protocol PackageCollectionsSourcesStorage { /// - order: Optional. The order that the source should take after being added. /// By default the new source is appended to the end (i.e., the least relevant order). /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func add(source: PackageCollectionsModel.CollectionSource, order: Int?, callback: @escaping (Result) -> Void) @@ -34,6 +38,7 @@ public protocol PackageCollectionsSourcesStorage { /// - source: The `PackageCollectionSource` to remove /// - profile: The `Profile` to remove source /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func remove(source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void) @@ -43,6 +48,7 @@ public protocol PackageCollectionsSourcesStorage { /// - source: The `PackageCollectionSource` to move /// - order: The order that the source should take. /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func move(source: PackageCollectionsModel.CollectionSource, to order: Int, callback: @escaping (Result) -> Void) @@ -52,6 +58,7 @@ public protocol PackageCollectionsSourcesStorage { /// - Parameters: /// - source: The `PackageCollectionSource` /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func exists(source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void) @@ -60,6 +67,46 @@ public protocol PackageCollectionsSourcesStorage { /// - Parameters: /// - source: The `PackageCollectionSource` to update /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func update(source: PackageCollectionsModel.CollectionSource, callback: @escaping (Result) -> Void) } + +public extension PackageCollectionsSourcesStorage { + func list() async throws -> [PackageCollectionsModel.CollectionSource] { + try await safe_async { + self.list(callback: $0) + } + } + + func add(source: PackageCollectionsModel.CollectionSource, + order: Int? = nil) async throws { + try await safe_async { + self.add(source: source, order: order, callback: $0) + } + } + + func remove(source: PackageCollectionsModel.CollectionSource) async throws { + try await safe_async { + self.remove(source: source, callback: $0) + } + } + + func move(source: PackageCollectionsModel.CollectionSource, to order: Int) async throws { + try await safe_async { + self.move(source: source, to:order, callback: $0) + } + } + + func exists(source: PackageCollectionsModel.CollectionSource) async throws -> Bool { + try await safe_async { + self.exists(source: source, callback: $0) + } + } + + func update(source: PackageCollectionsModel.CollectionSource) async throws { + try await safe_async { + self.update(source: source, callback: $0) + } + } +} diff --git a/Sources/PackageCollections/Storage/PackageCollectionsStorage.swift b/Sources/PackageCollections/Storage/PackageCollectionsStorage.swift index 659ce90e89f..0df20588cd5 100644 --- a/Sources/PackageCollections/Storage/PackageCollectionsStorage.swift +++ b/Sources/PackageCollections/Storage/PackageCollectionsStorage.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import PackageModel +import Basics public protocol PackageCollectionsStorage { /// Writes `PackageCollection` to storage. @@ -18,6 +19,7 @@ public protocol PackageCollectionsStorage { /// - Parameters: /// - collection: The `PackageCollection` /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func put(collection: PackageCollectionsModel.Collection, callback: @escaping (Result) -> Void) @@ -26,6 +28,7 @@ public protocol PackageCollectionsStorage { /// - Parameters: /// - identifier: The identifier of the `PackageCollection` /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func remove(identifier: PackageCollectionsModel.CollectionIdentifier, callback: @escaping (Result) -> Void) @@ -34,6 +37,7 @@ public protocol PackageCollectionsStorage { /// - Parameters: /// - identifier: The identifier of the `PackageCollection` /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func get(identifier: PackageCollectionsModel.CollectionIdentifier, callback: @escaping (Result) -> Void) @@ -42,6 +46,7 @@ public protocol PackageCollectionsStorage { /// - Parameters: /// - identifiers: Optional. The identifiers of the `PackageCollection` /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func list(identifiers: [PackageCollectionsModel.CollectionIdentifier]?, callback: @escaping (Result<[PackageCollectionsModel.Collection], Error>) -> Void) @@ -51,6 +56,7 @@ public protocol PackageCollectionsStorage { /// - identifiers: Optional. The identifiers of the `PackageCollection`s /// - query: The search query expression /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func searchPackages(identifiers: [PackageCollectionsModel.CollectionIdentifier]?, query: String, callback: @escaping (Result) -> Void) @@ -74,8 +80,42 @@ public protocol PackageCollectionsStorage { /// - query: The search query expression /// - type: The search type /// - callback: The closure to invoke when result becomes available + @available(*, noasync, message: "Use the async alternative") func searchTargets(identifiers: [PackageCollectionsModel.CollectionIdentifier]?, query: String, type: PackageCollectionsModel.TargetSearchType, callback: @escaping (Result) -> Void) } + +public extension PackageCollectionsStorage { + func put(collection: PackageCollectionsModel.Collection) async throws -> PackageCollectionsModel.Collection { + try await safe_async { + self.put(collection: collection, callback: $0) + } + } + func remove(identifier: PackageCollectionsModel.CollectionIdentifier) async throws { + try await safe_async { + self.remove(identifier: identifier, callback: $0) + } + } + func get(identifier: PackageCollectionsModel.CollectionIdentifier) async throws -> PackageCollectionsModel.Collection { + try await safe_async { + self.get(identifier: identifier, callback: $0) + } + } + func list(identifiers: [PackageCollectionsModel.CollectionIdentifier]? = nil) async throws -> [PackageCollectionsModel.Collection] { + try await safe_async { + self.list(identifiers: identifiers, callback: $0) + } + } + + func searchTargets( + identifiers: [PackageCollectionsModel.CollectionIdentifier]? = nil, + query: String, + type: PackageCollectionsModel.TargetSearchType + ) async throws -> PackageCollectionsModel.TargetSearchResult { + try await safe_async { + self.searchTargets(identifiers: identifiers, query: query, type: type, callback: $0) + } + } +} diff --git a/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift b/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift index 4a921700908..32ac76476de 100644 --- a/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift +++ b/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift @@ -500,7 +500,6 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable } } } - func searchTargets(identifiers: [Model.CollectionIdentifier]? = nil, query: String, type: Model.TargetSearchType, @@ -838,6 +837,9 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable self.useSearchIndices.get() ?? false } } + internal func populateTargetTrie() async throws { + try await safe_async { self.populateTargetTrie(callback: $0) } + } internal func populateTargetTrie(callback: @escaping (Result) -> Void = { _ in }) { // Check to see if there is any data before submitting task to queue because otherwise it's no-op anyway diff --git a/Sources/PackageCollectionsModel/Formats/v1.md b/Sources/PackageCollectionsModel/Formats/v1.md index be21118c943..66235e133dd 100644 --- a/Sources/PackageCollectionsModel/Formats/v1.md +++ b/Sources/PackageCollectionsModel/Formats/v1.md @@ -28,7 +28,7 @@ To begin, define the top-level metadata about the collection: Each item in the `packages` array is a package object with the following properties: * `url`: The URL of the package. Currently only Git repository URLs are supported. URL should be HTTPS and may contain `.git` suffix. -* `identity`: The [identity](https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#36-package-identification) of the package if published to registry. **Optional.** +* `identity`: The [identity](https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#36-package-identification) of the package if published to registry. **Optional.** * `summary`: A description of the package. **Optional.** * `keywords`: An array of keywords that the package is associated with. **Optional.** * `readmeURL`: The URL of the package's README. **Optional.** @@ -102,7 +102,7 @@ A version object has metadata extracted from `Package.swift` and optionally addi * `name`: License name. [SPDX identifier](https://spdx.org/licenses/) (e.g., `Apache-2.0`, `MIT`, etc.) preferred. Omit if unknown. **Optional.** * `author`: The package version's author. **Optional.** * `name`: The author of the package version. -* `signer`: The signer of the package version. **Optional.** Refer to [documentation](https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageRegistryUsage.md#package-signing) on package signing for details. +* `signer`: The signer of the package version. **Optional.** Refer to [documentation](https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md#package-signing) on package signing for details. * `type`: The signer type. Currently the only valid value is `ADP` (Apple Developer Program). * `commonName`: The common name of the signing certificate's subject. * `organizationalUnitName`: The organizational unit name of the signing certificate's subject. diff --git a/Sources/PackageCollectionsModel/PackageCollectionModel+v1.swift b/Sources/PackageCollectionsModel/PackageCollectionModel+v1.swift index 8fd2dbfd3f1..58098db299b 100644 --- a/Sources/PackageCollectionsModel/PackageCollectionModel+v1.swift +++ b/Sources/PackageCollectionsModel/PackageCollectionModel+v1.swift @@ -82,7 +82,7 @@ extension PackageCollectionModel.V1.Collection { /// The URL of the package. Currently only Git repository URLs are supported. public let url: URL - /// Package identity for registry (https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md#36-package-identification). + /// Package identity for registry (https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#36-package-identification). public let identity: String? /// A description of the package. diff --git a/Sources/PackageCollectionsSigning/CertificatePolicy.swift b/Sources/PackageCollectionsSigning/CertificatePolicy.swift index ae036cc51f2..cc91f68db6b 100644 --- a/Sources/PackageCollectionsSigning/CertificatePolicy.swift +++ b/Sources/PackageCollectionsSigning/CertificatePolicy.swift @@ -14,8 +14,14 @@ import Dispatch import Foundation import Basics + +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SwiftASN1 @_implementationOnly import X509 +#else +import SwiftASN1 +import X509 +#endif public enum CertificatePolicyKey: Hashable, CustomStringConvertible { case `default`(subjectUserID: String? = nil, subjectOrganizationalUnit: String? = nil) @@ -402,27 +408,31 @@ struct _OCSPVerifierPolicy: VerifierPolicy { private struct _OCSPRequester: OCSPRequester { let httpClient: HTTPClient - func query(request: [UInt8], uri: String) async throws -> [UInt8] { + func query(request: [UInt8], uri: String) async -> OCSPRequesterQueryResult { guard let url = URL(string: uri), let host = url.host else { - throw SwiftOCSPRequesterError.invalidURL(uri) + return .terminalError(SwiftOCSPRequesterError.invalidURL(uri)) } - let response = try await self.httpClient.post( - url, - body: Data(request), - headers: [ - "Content-Type": "application/ocsp-request", - "Host": host, - ] - ) + do { + let response = try await self.httpClient.post( + url, + body: Data(request), + headers: [ + "Content-Type": "application/ocsp-request", + "Host": host, + ] + ) - guard response.statusCode == 200 else { - throw SwiftOCSPRequesterError.invalidResponse(statusCode: response.statusCode) - } - guard let responseBody = response.body else { - throw SwiftOCSPRequesterError.emptyResponse + guard response.statusCode == 200 else { + throw SwiftOCSPRequesterError.invalidResponse(statusCode: response.statusCode) + } + guard let responseBody = response.body else { + throw SwiftOCSPRequesterError.emptyResponse + } + return .response(Array(responseBody)) + } catch { + return .nonTerminalError(error) } - return Array(responseBody) } } diff --git a/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift b/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift index 325cf8eee0b..0903c0fccad 100644 --- a/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift +++ b/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift @@ -10,13 +10,21 @@ // //===----------------------------------------------------------------------===// -@_implementationOnly import _CryptoExtras + import Basics -@_implementationOnly import Crypto import Dispatch import Foundation import PackageCollectionsModel + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import _CryptoExtras +@_implementationOnly import Crypto @_implementationOnly import X509 +#else +import _CryptoExtras +import Crypto +import X509 +#endif public protocol PackageCollectionSigner { /// Signs package collection using the given certificate and key. @@ -239,9 +247,7 @@ public actor PackageCollectionSigning: PackageCollectionSigner, PackageCollectio signedCollection: Model.SignedCollection, certPolicyKey: CertificatePolicyKey = .default ) async throws { - guard let signatureBytes = signedCollection.signature.signature.data(using: .utf8)?.copyBytes() else { - throw PackageCollectionSigningError.invalidSignature - } + let signatureBytes = Data(signedCollection.signature.signature.utf8).copyBytes() // Parse the signature let certChainValidate = { certChainData in diff --git a/Sources/PackageCollectionsSigning/Signature.swift b/Sources/PackageCollectionsSigning/Signature.swift index e5488c11a09..76e27d45d10 100644 --- a/Sources/PackageCollectionsSigning/Signature.swift +++ b/Sources/PackageCollectionsSigning/Signature.swift @@ -25,9 +25,15 @@ import Foundation +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import _CryptoExtras @_implementationOnly import Crypto @_implementationOnly import X509 +#else +import _CryptoExtras +import Crypto +import X509 +#endif // The logic in this source file loosely follows https://www.rfc-editor.org/rfc/rfc7515.html // for JSON Web Signature (JWS). diff --git a/Sources/PackageCollectionsSigning/X509Extensions.swift b/Sources/PackageCollectionsSigning/X509Extensions.swift index dfaedca9428..bef5d7ca1ce 100644 --- a/Sources/PackageCollectionsSigning/X509Extensions.swift +++ b/Sources/PackageCollectionsSigning/X509Extensions.swift @@ -10,8 +10,13 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SwiftASN1 @_implementationOnly import X509 +#else +import SwiftASN1 +import X509 +#endif extension Certificate { func hasExtension(oid: ASN1ObjectIdentifier) -> Bool { @@ -59,29 +64,9 @@ extension DistinguishedName { private func stringAttribute(oid: ASN1ObjectIdentifier) -> String? { for relativeDistinguishedName in self { for attribute in relativeDistinguishedName where attribute.type == oid { - if let stringValue = attribute.stringValue { - return stringValue - } + return attribute.value.description } } return nil } } - -extension RelativeDistinguishedName.Attribute { - fileprivate var stringValue: String? { - let asn1StringBytes: ArraySlice? - do { - asn1StringBytes = try ASN1PrintableString(asn1Any: self.value).bytes - } catch { - asn1StringBytes = try? ASN1UTF8String(asn1Any: self.value).bytes - } - - guard let asn1StringBytes, - let stringValue = String(bytes: asn1StringBytes, encoding: .utf8) - else { - return nil - } - return stringValue - } -} diff --git a/Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift b/Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift index 2be6a168a05..0e9ddd5bad0 100644 --- a/Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift +++ b/Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift @@ -380,7 +380,7 @@ private func optionalRow(_ title: String, _ contents: String?, indentationLevel: private extension JSONEncoder { func print(_ value: T) throws where T: Encodable { let jsonData = try self.encode(value) - let jsonString = String(data: jsonData, encoding: .utf8)! + let jsonString = String(decoding: jsonData, as: UTF8.self) Swift.print(jsonString) } } diff --git a/Sources/PackageDescription/PackageDependency.swift b/Sources/PackageDescription/PackageDependency.swift index e8460d61de7..21fe250877e 100644 --- a/Sources/PackageDescription/PackageDependency.swift +++ b/Sources/PackageDescription/PackageDependency.swift @@ -85,8 +85,7 @@ extension Package { /// Module aliases for targets in this dependency. The key is an original target name and /// the value is a new unique name mapped to the name of the .swiftmodule binary. - @available(_PackageDescription, introduced: 5.7) - public var moduleAliases: [String: String]? + internal var moduleAliases: [String: String]? /// The dependency requirement of the package dependency. @available(_PackageDescription, deprecated: 5.6, message: "use kind instead") @@ -424,14 +423,14 @@ extension Package.Dependency { /// versions between 1.0.0 and 2.0.0 /// /// ```swift - /// .package(url: "https://example.com/example-package.git", .upToNextMajor("1.0.0"), + /// .package(url: "https://example.com/example-package.git", .upToNextMajor(from: "1.0.0"), /// ``` /// /// The following example allows the Swift Package Manager to pick /// versions between 1.0.0 and 1.1.0 /// /// ```swift - /// .package(url: "https://example.com/example-package.git", .upToNextMinor("1.0.0"), + /// .package(url: "https://example.com/example-package.git", .upToNextMinor(from: "1.0.0"), /// ``` /// /// - Parameters: @@ -623,14 +622,14 @@ extension Package.Dependency { /// versions between 1.0.0 and 2.0.0 /// /// ```swift - /// .package(id: "scope.name", .upToNextMajor("1.0.0"), + /// .package(id: "scope.name", .upToNextMajor(from: "1.0.0"), /// ``` /// /// The following example allows the Swift Package Manager to pick /// versions between 1.0.0 and 1.1.0 /// /// ```swift - /// .package(id: "scope.name", .upToNextMinor("1.0.0"), + /// .package(id: "scope.name", .upToNextMinor(from: "1.0.0"), /// ``` /// /// - Parameters: diff --git a/Sources/PackageDescription/PackageDescription.swift b/Sources/PackageDescription/PackageDescription.swift index b7d263c05b1..73e61456a18 100644 --- a/Sources/PackageDescription/PackageDescription.swift +++ b/Sources/PackageDescription/PackageDescription.swift @@ -442,7 +442,7 @@ private func manifestToJSON(_ package: Package) -> String { let encoder = JSONEncoder() encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes] let data = try! encoder.encode(Output(package: .init(package), errors: errors, version: 2)) - return String(data: data, encoding: .utf8)! + return String(decoding: data, as: UTF8.self) } var errors: [String] = [] diff --git a/Sources/PackageDescription/PackageDescriptionSerialization.swift b/Sources/PackageDescription/PackageDescriptionSerialization.swift index e239a4bc55c..a16cc995b81 100644 --- a/Sources/PackageDescription/PackageDescriptionSerialization.swift +++ b/Sources/PackageDescription/PackageDescriptionSerialization.swift @@ -10,7 +10,11 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import Foundation +#else +import Foundation +#endif enum Serialization { // MARK: - build settings serialization diff --git a/Sources/PackageDescription/Product.swift b/Sources/PackageDescription/Product.swift index b83cb6af72e..d022eb91c50 100644 --- a/Sources/PackageDescription/Product.swift +++ b/Sources/PackageDescription/Product.swift @@ -132,8 +132,7 @@ public class Product { /// don't support both linkage types, use /// ``Product/Library/LibraryType/static`` or /// ``Product/Library/LibraryType/dynamic`` for this parameter. - /// - /// - targets: The targets that are bundled into a library product. + /// - targets: The targets that are bundled into a library product. /// /// - Returns: A `Product` instance. public static func library( diff --git a/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift b/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift index 1e78a9dd508..06a23d1645e 100644 --- a/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift +++ b/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift @@ -165,7 +165,7 @@ public struct FilePackageFingerprintStorage: PackageFingerprintStorage { } private func loadFromDisk(reference: FingerprintReference) throws -> PackageFingerprints { - let path = try self.directoryPath.appending(component: reference.fingerprintsFilename()) + let path = try self.directoryPath.appending(component: reference.fingerprintsFilename) guard self.fileSystem.exists(path) else { return .init() @@ -185,7 +185,7 @@ public struct FilePackageFingerprintStorage: PackageFingerprintStorage { } let buffer = try StorageModel.encode(packageFingerprints: fingerprints, encoder: self.encoder) - let path = try self.directoryPath.appending(component: reference.fingerprintsFilename()) + let path = try self.directoryPath.appending(component: reference.fingerprintsFilename) try self.fileSystem.writeFileContents(path, data: buffer) } @@ -382,25 +382,27 @@ extension Fingerprint.ContentType { } protocol FingerprintReference { - func fingerprintsFilename() throws -> String + var fingerprintsFilename: String { get throws } } extension PackageIdentity: FingerprintReference { - func fingerprintsFilename() -> String { + var fingerprintsFilename: String { "\(self.description).json" } } extension PackageReference: FingerprintReference { - func fingerprintsFilename() throws -> String { - guard case .remoteSourceControl(let sourceControlURL) = self.kind else { - throw StringError("Package kind [\(self.kind)] does not support fingerprints") + var fingerprintsFilename: String { + get throws { + guard case .remoteSourceControl(let sourceControlURL) = self.kind else { + throw StringError("Package kind [\(self.kind)] does not support fingerprints") + } + + let canonicalLocation = CanonicalPackageLocation(sourceControlURL.absoluteString) + // Cannot use hashValue because it is not consistent across executions + let locationHash = canonicalLocation.description.sha256Checksum.prefix(8) + return "\(self.identity.description)-\(locationHash).json" } - - let canonicalLocation = CanonicalPackageLocation(sourceControlURL.absoluteString) - // Cannot use hashValue because it is not consistent across executions - let locationHash = canonicalLocation.description.sha256Checksum.prefix(8) - return "\(self.identity.description)-\(locationHash).json" } } diff --git a/Sources/PackageGraph/CMakeLists.txt b/Sources/PackageGraph/CMakeLists.txt index bd066e5c229..9d70abc6546 100644 --- a/Sources/PackageGraph/CMakeLists.txt +++ b/Sources/PackageGraph/CMakeLists.txt @@ -9,8 +9,6 @@ add_library(PackageGraph BoundVersion.swift DependencyMirrors.swift - DependencyResolutionNode.swift - DependencyResolver.swift Diagnostics.swift GraphLoadingNode.swift ModuleAliasTracker.swift @@ -21,17 +19,21 @@ add_library(PackageGraph PackageModel+Extensions.swift PackageRequirement.swift PinsStore.swift - PubGrub/Assignment.swift - PubGrub/ContainerProvider.swift - PubGrub/DiagnosticReportBuilder.swift - PubGrub/Incompatibility.swift - PubGrub/PartialSolution.swift - PubGrub/PubGrubDependencyResolver.swift - PubGrub/PubGrubPackageContainer.swift - PubGrub/Term.swift - ResolvedPackage.swift - ResolvedProduct.swift - ResolvedTarget.swift + Resolution/PubGrub/Assignment.swift + Resolution/PubGrub/ContainerProvider.swift + Resolution/PubGrub/DiagnosticReportBuilder.swift + Resolution/PubGrub/Incompatibility.swift + Resolution/PubGrub/PartialSolution.swift + Resolution/PubGrub/PubGrubDependencyResolver.swift + Resolution/PubGrub/PubGrubPackageContainer.swift + Resolution/PubGrub/Term.swift + Resolution/DependencyResolutionNode.swift + Resolution/DependencyResolverBinding.swift + Resolution/DependencyResolverDelegate.swift + Resolution/DependencyResolverError.swift + Resolution/ResolvedPackage.swift + Resolution/ResolvedProduct.swift + Resolution/ResolvedTarget.swift Version+Extensions.swift VersionSetSpecifier.swift) target_link_libraries(PackageGraph PUBLIC diff --git a/Sources/PackageGraph/DependencyMirrors.swift b/Sources/PackageGraph/DependencyMirrors.swift index 6ba162d8664..167296dfa20 100644 --- a/Sources/PackageGraph/DependencyMirrors.swift +++ b/Sources/PackageGraph/DependencyMirrors.swift @@ -20,6 +20,7 @@ import struct TSCBasic.StringError /// A collection of dependency mirrors. public final class DependencyMirrors: Equatable { private var index: [String: String] + private var mirrorIndex: [PackageIdentity: PackageIdentity] private var reverseIndex: [String: [String]] private var visited: OrderedCollections.OrderedSet private let lock = NSLock() @@ -30,11 +31,13 @@ public final class DependencyMirrors: Equatable { } } - public init(_ mirrors: [String: String]) { + public init(_ mirrors: [String: String] = [:]) throws { self.index = mirrors self.reverseIndex = [String: [String]]() + self.mirrorIndex = [PackageIdentity: PackageIdentity]() for entry in mirrors { self.reverseIndex[entry.value, default: []].append(entry.key) + self.mirrorIndex[try Self.parseLocation(entry.key)] = try Self.parseLocation(entry.value) } self.visited = .init() } @@ -47,10 +50,11 @@ public final class DependencyMirrors: Equatable { /// - Parameters: /// - mirror: The mirror /// - for: The original - public func set(mirror: String, for key: String) { - self.lock.withLock { + public func set(mirror: String, for key: String) throws { + try self.lock.withLock { self.index[key] = mirror self.reverseIndex[mirror, default: []].append(key) + self.mirrorIndex[try Self.parseLocation(key)] = try Self.parseLocation(mirror) } } @@ -63,9 +67,11 @@ public final class DependencyMirrors: Equatable { if let value = self.index[originalOrMirror] { self.index[originalOrMirror] = nil self.reverseIndex[value] = nil + self.mirrorIndex[try Self.parseLocation(value)] = nil } else if let mirror = self.index.first(where: { $0.value == originalOrMirror }) { self.index[mirror.key] = nil self.reverseIndex[originalOrMirror] = nil + self.mirrorIndex[try Self.parseLocation(originalOrMirror)] = nil } else { throw StringError("Mirror not found for '\(originalOrMirror)'") } @@ -75,9 +81,9 @@ public final class DependencyMirrors: Equatable { /// Append the content of a different DependencyMirrors into this one /// - Parameters: /// - contentsOf: The DependencyMirrors to append from. - public func append(contentsOf mirrors: DependencyMirrors) { - mirrors.index.forEach { - self.set(mirror: $0.value, for: $0.key) + public func append(contentsOf mirrors: DependencyMirrors) throws { + try mirrors.index.forEach { + try self.set(mirror: $0.value, for: $0.key) } } @@ -153,15 +159,10 @@ public final class DependencyMirrors: Equatable { } public func effectiveIdentity(for identity: PackageIdentity) throws -> PackageIdentity { - // TODO: cache - let mirrorIndex = try self.mapping.reduce(into: [PackageIdentity: PackageIdentity]()) { partial, item in - try partial[parseLocation(item.key)] = parseLocation(item.value) - } - return mirrorIndex[identity] ?? identity } - private func parseLocation(_ location: String) throws -> PackageIdentity { + private static func parseLocation(_ location: String) throws -> PackageIdentity { if PackageIdentity.plain(location).isRegistry { return PackageIdentity.plain(location) } else if let path = try? AbsolutePath(validating: location) { @@ -200,9 +201,3 @@ extension DependencyMirrors: Collection { } } } - -extension DependencyMirrors: ExpressibleByDictionaryLiteral { - public convenience init(dictionaryLiteral elements: (String, String)...) { - self.init(Dictionary(elements, uniquingKeysWith: { first, _ in first })) - } -} diff --git a/Sources/PackageGraph/GraphLoadingNode.swift b/Sources/PackageGraph/GraphLoadingNode.swift index 453f1d8080e..dc28a0d8652 100644 --- a/Sources/PackageGraph/GraphLoadingNode.swift +++ b/Sources/PackageGraph/GraphLoadingNode.swift @@ -18,9 +18,8 @@ import PackageModel /// /// This node uses the product filter that was already finalized during resolution. /// -/// - SeeAlso: DependencyResolutionNode +/// - SeeAlso: ``DependencyResolutionNode`` public struct GraphLoadingNode: Equatable, Hashable { - /// The package identity. public let identity: PackageIdentity @@ -30,29 +29,15 @@ public struct GraphLoadingNode: Equatable, Hashable { /// The product filter applied to the package. public let productFilter: ProductFilter - /// The file system to use for loading the given package. - public let fileSystem: FileSystem - - public init(identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter, fileSystem: FileSystem) { + public init(identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter) { self.identity = identity self.manifest = manifest self.productFilter = productFilter - self.fileSystem = fileSystem } /// Returns the dependencies required by this node. - internal func requiredDependencies() -> [PackageDependency] { - return manifest.dependenciesRequired(for: productFilter) - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(identity) - hasher.combine(manifest) - hasher.combine(productFilter) - } - - public static func == (lhs: GraphLoadingNode, rhs: GraphLoadingNode) -> Bool { - return lhs.identity == rhs.identity && lhs.manifest == rhs.manifest && lhs.productFilter == rhs.productFilter + internal var requiredDependencies: [PackageDependency] { + return self.manifest.dependenciesRequired(for: self.productFilter) } } diff --git a/Sources/PackageGraph/ModuleAliasTracker.swift b/Sources/PackageGraph/ModuleAliasTracker.swift index 8ae51381500..b57aac58917 100644 --- a/Sources/PackageGraph/ModuleAliasTracker.swift +++ b/Sources/PackageGraph/ModuleAliasTracker.swift @@ -24,6 +24,7 @@ class ModuleAliasTracker { var parentToChildProducts = [String: [String]]() var parentToChildIDs = [PackageIdentity: [PackageIdentity]]() var childToParentID = [PackageIdentity: PackageIdentity]() + var appliedAliases = Set() init() {} func addTargetAliases(targets: [Target], package: PackageIdentity) throws { @@ -63,7 +64,7 @@ class ModuleAliasTracker { } for (originalName, newName) in aliases { - let model = ModuleAliasModel(name: originalName, alias: newName, originPackage: originPackage, consumingPackage: consumingPackage) + let model = ModuleAliasModel(name: originalName, alias: newName, originPackage: originPackage, consumingPackage: consumingPackage, productName: productName) idToAliasMap[originPackage, default: [:]][productID, default: []].append(model) aliasMap[productID, default: []].append(model) } @@ -84,7 +85,7 @@ class ModuleAliasTracker { func trackTargetsPerProduct(product: Product, package: PackageIdentity) { let targetDeps = product.targets.map{$0.dependencies}.flatMap{$0} - var allTargetDeps = product.targets.map{$0.recursiveDependentTargets().map{$0.dependencies}}.flatMap{$0}.flatMap{$0} + var allTargetDeps = product.targets.map{$0.recursiveDependentTargets.map{$0.dependencies}}.flatMap{$0}.flatMap{$0} allTargetDeps.append(contentsOf: targetDeps) for dep in allTargetDeps { if case let .product(depRef, _) = dep { @@ -158,18 +159,27 @@ class ModuleAliasTracker { } if let curDirectTargets = productToDirectTargets[productID] { - var relevantTargets = curDirectTargets.map{$0.recursiveDependentTargets()}.flatMap{$0} + var relevantTargets = curDirectTargets.map{$0.recursiveDependentTargets}.flatMap{$0} relevantTargets.append(contentsOf: curDirectTargets) for relTarget in relevantTargets { if let val = lookupAlias(key: relTarget.name, in: aliasBuffer) { + appliedAliases.insert(relTarget.name) relTarget.addModuleAlias(for: relTarget.name, as: val) if let prechainVal = aliasBuffer[relTarget.name], prechainVal.alias != val { relTarget.addPrechainModuleAlias(for: relTarget.name, as: prechainVal.alias) + appliedAliases.insert(prechainVal.alias) relTarget.addPrechainModuleAlias(for: prechainVal.alias, as: val) observabilityScope.emit(info: "Module alias '\(prechainVal.alias)' defined in package '\(prechainVal.consumingPackage)' for target '\(relTarget.name)' in package/product '\(productID)' is overridden by alias '\(val)'; if this override is not intended, remove '\(val)' from 'moduleAliases' in its manifest") aliasBuffer.removeValue(forKey: prechainVal.alias) + + // Since we're overriding an alias here, we have to pretend it was applied to avoid follow-on warnings. + var currentAlias: String? = val + while let _currentAlias = currentAlias, !appliedAliases.contains(_currentAlias) { + appliedAliases.insert(_currentAlias) + currentAlias = aliasBuffer.values.first { $0.alias == _currentAlias }?.name + } } aliasBuffer.removeValue(forKey: relTarget.name) } @@ -198,7 +208,7 @@ class ModuleAliasTracker { } if let curDirectTargets = productToDirectTargets[productID] { - let depTargets = curDirectTargets.map{$0.recursiveDependentTargets()}.flatMap{$0} + let depTargets = curDirectTargets.map{$0.recursiveDependentTargets}.flatMap{$0} let depTargetAliases = toDictionary(depTargets.compactMap{$0.moduleAliases}) let depChildTargets = dependencyProductTargets(of: depTargets) let depChildAliases = toDictionary(depChildTargets.compactMap{$0.moduleAliases}) @@ -255,8 +265,9 @@ class ModuleAliasTracker { let unAliased = productTargets.contains{$0.moduleAliases == nil} if unAliased { for target in productTargets { - let depAliases = target.recursiveDependentTargets().compactMap{$0.moduleAliases}.flatMap{$0} + let depAliases = target.recursiveDependentTargets.compactMap{$0.moduleAliases}.flatMap{$0} for (key, alias) in depAliases { + appliedAliases.insert(key) target.addModuleAlias(for: key, as: alias) } } @@ -269,6 +280,16 @@ class ModuleAliasTracker { } } + func diagnoseUnappliedAliases(observabilityScope: ObservabilityScope) { + for aliasList in aliasMap.values { + for productAlias in aliasList { + if !appliedAliases.contains(productAlias.name) { + observabilityScope.emit(warning: "module alias for target '\(productAlias.name)', declared in package '\(productAlias.consumingPackage)', does not match any recursive target dependency of product '\(productAlias.productName)' from package '\(productAlias.originPackage)'") + } + } + } + } + private func chainModuleAliases(targets: [Target], checkedTargets: [Target], targetAliases: [String: [String]], @@ -336,11 +357,13 @@ class ModuleAliasTracker { for target in targets { for (key, val) in aliasDict { + appliedAliases.insert(key) target.addModuleAlias(for: key, as: val) } for (key, valList) in prechainAliasDict { if let val = valList.first, valList.count <= 1 { + appliedAliases.insert(key) target.addModuleAlias(for: key, as: val) target.addPrechainModuleAlias(for: key, as: val) } @@ -401,18 +424,20 @@ class ModuleAliasModel { var alias: String let originPackage: PackageIdentity let consumingPackage: PackageIdentity + let productName: String - init(name: String, alias: String, originPackage: PackageIdentity, consumingPackage: PackageIdentity) { + init(name: String, alias: String, originPackage: PackageIdentity, consumingPackage: PackageIdentity, productName: String) { self.name = name self.alias = alias self.originPackage = originPackage self.consumingPackage = consumingPackage + self.productName = productName } } extension Target { func dependsOn(productID: String) -> Bool { - return dependencies.contains { dep in + return self.dependencies.contains { dep in if case let .product(prodRef, _) = dep { return prodRef.identity == productID } @@ -420,9 +445,9 @@ extension Target { } } - func recursiveDependentTargets() -> [Target] { + var recursiveDependentTargets: [Target] { var list = [Target]() - var nextDeps = dependencies + var nextDeps = self.dependencies while !nextDeps.isEmpty { let nextTargets = nextDeps.compactMap{$0.target} list.append(contentsOf: nextTargets) diff --git a/Sources/PackageGraph/PackageContainer.swift b/Sources/PackageGraph/PackageContainer.swift index 091310acfc3..c40b56d43b7 100644 --- a/Sources/PackageGraph/PackageContainer.swift +++ b/Sources/PackageGraph/PackageContainer.swift @@ -164,6 +164,8 @@ extension PackageContainerConstraint: CustomStringConvertible { /// An interface for resolving package containers. public protocol PackageContainerProvider { /// Get the container for a particular identifier asynchronously. + + @available(*, noasync, message: "Use the async alternative") func getContainer( for package: PackageReference, updateStrategy: ContainerUpdateStrategy, @@ -173,6 +175,24 @@ public protocol PackageContainerProvider { ) } +public extension PackageContainerProvider { + func getContainer( + for package: PackageReference, + updateStrategy: ContainerUpdateStrategy, + observabilityScope: ObservabilityScope, + on queue: DispatchQueue + ) async throws -> PackageContainer { + try await safe_async { + self.getContainer( + for: package, + updateStrategy: updateStrategy, + observabilityScope: observabilityScope, + on: queue, + completion: $0) + } + } +} + /// Only used for source control containers and as such a mirror of RepositoryUpdateStrategy /// This duplication is unfortunate - ideally this is not a concern of the ContainerProvider at all /// but it is required give how PackageContainerProvider currently integrated into the resolver diff --git a/Sources/PackageGraph/PackageGraph+Loading.swift b/Sources/PackageGraph/PackageGraph+Loading.swift index ea4fed03474..c70c128216d 100644 --- a/Sources/PackageGraph/PackageGraph+Loading.swift +++ b/Sources/PackageGraph/PackageGraph+Loading.swift @@ -16,6 +16,7 @@ import PackageLoading import PackageModel import func TSCBasic.topologicalSort +import func TSCBasic.bestMatch extension PackageGraph { @@ -25,7 +26,7 @@ extension PackageGraph { identityResolver: IdentityResolver, additionalFileRules: [FileRuleDescription] = [], externalManifests: OrderedCollections.OrderedDictionary, - requiredDependencies: Set = [], + requiredDependencies: [PackageReference] = [], unsafeAllowedPackages: Set = [], binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]], shouldCreateMultipleTestProducts: Bool = false, @@ -45,10 +46,14 @@ extension PackageGraph { root.manifests.forEach { manifestMap[$0.key] = ($0.value, fileSystem) } - let successors: (GraphLoadingNode) -> [GraphLoadingNode] = { node in - node.requiredDependencies().compactMap{ dependency in - return manifestMap[dependency.identity].map { (manifest, fileSystem) in - GraphLoadingNode(identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, fileSystem: fileSystem) + func nodeSuccessorsProvider(node: GraphLoadingNode) -> [GraphLoadingNode] { + node.requiredDependencies.compactMap { dependency in + manifestMap[dependency.identity].map { (manifest, fileSystem) in + GraphLoadingNode( + identity: dependency.identity, + manifest: manifest, + productFilter: dependency.productFilter + ) } } } @@ -58,11 +63,15 @@ extension PackageGraph { manifestMap[$0.identity]?.manifest }) let rootManifestNodes = root.packages.map { identity, package in - GraphLoadingNode(identity: identity, manifest: package.manifest, productFilter: .everything, fileSystem: fileSystem) + GraphLoadingNode(identity: identity, manifest: package.manifest, productFilter: .everything) } - let rootDependencyNodes = root.dependencies.lazy.compactMap { (dependency: PackageDependency) -> GraphLoadingNode? in + let rootDependencyNodes = root.dependencies.lazy.compactMap { dependency in manifestMap[dependency.identity].map { - GraphLoadingNode(identity: dependency.identity, manifest: $0.manifest, productFilter: dependency.productFilter, fileSystem: $0.fs) + GraphLoadingNode( + identity: dependency.identity, + manifest: $0.manifest, + productFilter: dependency.productFilter + ) } } let inputManifests = rootManifestNodes + rootDependencyNodes @@ -71,13 +80,13 @@ extension PackageGraph { var allNodes: [GraphLoadingNode] // Detect cycles in manifest dependencies. - if let cycle = findCycle(inputManifests, successors: successors) { + if let cycle = findCycle(inputManifests, successors: nodeSuccessorsProvider) { observabilityScope.emit(PackageGraphError.cycleDetected(cycle)) // Break the cycle so we can build a partial package graph. allNodes = inputManifests.filter({ $0.manifest != cycle.cycle[0] }) } else { - // Sort all manifests toplogically. - allNodes = try topologicalSort(inputManifests, successors: successors) + // Sort all manifests topologically. + allNodes = try topologicalSort(inputManifests, successors: nodeSuccessorsProvider) } var flattenedManifests: [PackageIdentity: GraphLoadingNode] = [:] @@ -86,8 +95,7 @@ extension PackageGraph { let merged = GraphLoadingNode( identity: node.identity, manifest: node.manifest, - productFilter: existing.productFilter.union(node.productFilter), - fileSystem: node.fileSystem + productFilter: existing.productFilter.union(node.productFilter) ) flattenedManifests[node.identity] = merged } else { @@ -122,7 +130,7 @@ extension PackageGraph { shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, testEntryPointPath: testEntryPointPath, createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false, - fileSystem: node.fileSystem, + fileSystem: fileSystem, observabilityScope: nodeObservabilityScope ) let package = try builder.construct() @@ -145,7 +153,13 @@ extension PackageGraph { rootManifests: root.manifests, unsafeAllowedPackages: unsafeAllowedPackages, platformRegistry: customPlatformsRegistry ?? .default, - xcTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets ?? MinimumDeploymentTarget.default.xcTestMinimumDeploymentTargets, + derivedXCTestPlatformProvider: { declared in + if let customXCTestMinimumDeploymentTargets { + return customXCTestMinimumDeploymentTargets[declared] + } else { + return MinimumDeploymentTarget.default.computeXCTestMinimumDeploymentTarget(for: declared) + } + }, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -226,7 +240,7 @@ private func createResolvedPackages( rootManifests: [PackageIdentity: Manifest], unsafeAllowedPackages: Set, platformRegistry: PlatformRegistry, - xcTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion], + derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion?, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> [ResolvedPackage] { @@ -272,7 +286,7 @@ private func createResolvedPackages( // Establish the manifest-declared package dependencies. package.manifest.dependenciesRequired(for: packageBuilder.productFilter).forEach { dependency in - let dependencyPackageRef = dependency.createPackageRef() + let dependencyPackageRef = dependency.packageRef // Otherwise, look it up by its identity. if let resolvedPackage = packagesByIdentity[dependency.identity] { @@ -349,16 +363,8 @@ private func createResolvedPackages( packageBuilder.platforms = computePlatforms( package: package, - usingXCTest: false, - platformRegistry: platformRegistry, - xcTestMinimumDeploymentTargets: xcTestMinimumDeploymentTargets - ) - - let testPlatforms = computePlatforms( - package: package, - usingXCTest: true, platformRegistry: platformRegistry, - xcTestMinimumDeploymentTargets: xcTestMinimumDeploymentTargets + derivedXCTestPlatformProvider: derivedXCTestPlatformProvider ) // Create target builders for each target in the package. @@ -380,7 +386,7 @@ private func createResolvedPackages( } } targetBuilder.defaultLocalization = packageBuilder.defaultLocalization - targetBuilder.platforms = targetBuilder.target.type == .test ? testPlatforms : packageBuilder.platforms + targetBuilder.platforms = packageBuilder.platforms } // Create product builders for each product in the package. A product can only contain a target present in the same package. @@ -402,7 +408,11 @@ private func createResolvedPackages( } } - let dupProductsChecker = DuplicateProductsChecker(packageBuilders: packageBuilders) + let dupProductsChecker = DuplicateProductsChecker( + packageBuilders: packageBuilders, + moduleAliasingUsed: moduleAliasingUsed, + observabilityScope: observabilityScope + ) try dupProductsChecker.run(lookupByProductIDs: moduleAliasingUsed, observabilityScope: observabilityScope) // The set of all target names. @@ -430,7 +440,8 @@ private func createResolvedPackages( return false }) - let lookupByProductIDs = packageBuilder.package.manifest.disambiguateByProductIDs || moduleAliasingUsed + let packageDoesNotSupportProductAliases = packageBuilder.package.doesNotSupportProductAliases + let lookupByProductIDs = !packageDoesNotSupportProductAliases && (packageBuilder.package.manifest.disambiguateByProductIDs || moduleAliasingUsed) // Get all the products from dependencies of this package. let productDependencies = packageBuilder.dependencies @@ -457,9 +468,11 @@ private func createResolvedPackages( productDependencies.map { ($0.product.name, $0) }, uniquingKeysWith: { lhs, _ in let duplicates = productDependencies.filter { $0.product.name == lhs.product.name } - throw PackageGraphError.duplicateProduct( - product: lhs.product.name, - packages: duplicates.map(\.packageBuilder.package) + throw emitDuplicateProductDiagnostic( + productName: lhs.product.name, + packages: duplicates.map(\.packageBuilder.package), + moduleAliasingUsed: moduleAliasingUsed, + observabilityScope: observabilityScope ) } ) @@ -490,13 +503,16 @@ private func createResolvedPackages( }.map {$0.targets}.flatMap{$0}.filter { t in t.name != productRef.name } - + + // Find a product name from the available product dependencies that is most similar to the required product name. + let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames)) let error = PackageGraphError.productDependencyNotFound( package: package.identity.description, targetName: targetBuilder.target.name, dependencyProductName: productRef.name, dependencyPackageName: productRef.package, - dependencyProductInDecl: !declProductsAsDependency.isEmpty + dependencyProductInDecl: !declProductsAsDependency.isEmpty, + similarProductName: bestMatchedProductName ) packageObservabilityScope.emit(error) } @@ -600,6 +616,31 @@ private func createResolvedPackages( return try packageBuilders.map{ try $0.construct() } } +private func emitDuplicateProductDiagnostic( + productName: String, + packages: [Package], + moduleAliasingUsed: Bool, + observabilityScope: ObservabilityScope +) -> PackageGraphError { + if moduleAliasingUsed { + packages.filter { $0.doesNotSupportProductAliases }.forEach { + // Emit an additional warning about product aliasing in case of older tools-versions. + observabilityScope.emit(warning: "product aliasing requires tools-version 5.2 or later, so it is not supported by '\($0.identity.description)'") + } + } + return PackageGraphError.duplicateProduct( + product: productName, + packages: packages + ) +} + +fileprivate extension Package { + var doesNotSupportProductAliases: Bool { + // We can never use the identity based lookup for older packages because they lack the necessary information. + return self.manifest.toolsVersion < .v5_2 + } +} + fileprivate struct Pair: Hashable { let package1: Package let package2: Package @@ -625,11 +666,16 @@ private class DuplicateProductsChecker { var packageIDToBuilder = [PackageIdentity: ResolvedPackageBuilder]() var checkedPkgIDs = [PackageIdentity]() - init(packageBuilders: [ResolvedPackageBuilder]) { + let moduleAliasingUsed: Bool + let observabilityScope: ObservabilityScope + + init(packageBuilders: [ResolvedPackageBuilder], moduleAliasingUsed: Bool, observabilityScope: ObservabilityScope) { for packageBuilder in packageBuilders { let pkgID = packageBuilder.package.identity self.packageIDToBuilder[pkgID] = packageBuilder } + self.moduleAliasingUsed = moduleAliasingUsed + self.observabilityScope = observabilityScope } func run(lookupByProductIDs: Bool = false, observabilityScope: ObservabilityScope) throws { @@ -657,9 +703,11 @@ private class DuplicateProductsChecker { } for (depIDOrName, depPkgs) in productToPkgMap.filter({Set($0.value).count > 1}) { let name = depIDOrName.components(separatedBy: "_").dropFirst().joined(separator: "_") - throw PackageGraphError.duplicateProduct( - product: name.isEmpty ? depIDOrName : name, - packages: depPkgs.compactMap{ packageIDToBuilder[$0]?.package } + throw emitDuplicateProductDiagnostic( + productName: name.isEmpty ? depIDOrName : name, + packages: depPkgs.compactMap{ packageIDToBuilder[$0]?.package }, + moduleAliasingUsed: self.moduleAliasingUsed, + observabilityScope: self.observabilityScope ) } } @@ -683,9 +731,11 @@ private class DuplicateProductsChecker { let duplicates = productToPkgMap.filter{ $0.value.count > 1 } for (productName, pkgs) in duplicates { - throw PackageGraphError.duplicateProduct( - product: productName, - packages: pkgs.compactMap{ packageIDToBuilder[$0]?.package } + throw emitDuplicateProductDiagnostic( + productName: productName, + packages: pkgs.compactMap{ packageIDToBuilder[$0]?.package }, + moduleAliasingUsed: self.moduleAliasingUsed, + observabilityScope: self.observabilityScope ) } } @@ -693,9 +743,8 @@ private class DuplicateProductsChecker { private func computePlatforms( package: Package, - usingXCTest: Bool, platformRegistry: PlatformRegistry, - xcTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion] + derivedXCTestPlatformProvider: @escaping (_ declared: PackageModel.Platform) -> PlatformVersion? ) -> SupportedPlatforms { // the supported platforms as declared in the manifest @@ -709,67 +758,9 @@ private func computePlatforms( ) } - // the derived platforms based on known minimum deployment target logic - var derivedPlatforms = [SupportedPlatform]() - - /// Add each declared platform to the supported platforms list. - for platform in package.manifest.platforms { - let declaredPlatform = platformRegistry.platformByName[platform.platformName] - ?? PackageModel.Platform.custom(name: platform.platformName, oldestSupportedVersion: platform.version) - var version = PlatformVersion(platform.version) - - if usingXCTest, let xcTestMinimumDeploymentTarget = xcTestMinimumDeploymentTargets[declaredPlatform], version < xcTestMinimumDeploymentTarget { - version = xcTestMinimumDeploymentTarget - } - - // If the declared version is smaller than the oldest supported one, we raise the derived version to that. - if version < declaredPlatform.oldestSupportedVersion { - version = declaredPlatform.oldestSupportedVersion - } - - let supportedPlatform = SupportedPlatform( - platform: declaredPlatform, - version: version, - options: platform.options - ) - - derivedPlatforms.append(supportedPlatform) - } - - // Find the undeclared platforms. - let remainingPlatforms = Set(platformRegistry.platformByName.keys).subtracting(derivedPlatforms.map({ $0.platform.name })) - - /// Start synthesizing for each undeclared platform. - for platformName in remainingPlatforms.sorted() { - let platform = platformRegistry.platformByName[platformName]! - - let minimumSupportedVersion: PlatformVersion - if usingXCTest, let xcTestMinimumDeploymentTarget = xcTestMinimumDeploymentTargets[platform], xcTestMinimumDeploymentTarget > platform.oldestSupportedVersion { - minimumSupportedVersion = xcTestMinimumDeploymentTarget - } else { - minimumSupportedVersion = platform.oldestSupportedVersion - } - - let oldestSupportedVersion: PlatformVersion - if platform == .macCatalyst, let iOS = derivedPlatforms.first(where: { $0.platform == .iOS }) { - // If there was no deployment target specified for Mac Catalyst, fall back to the iOS deployment target. - oldestSupportedVersion = max(minimumSupportedVersion, iOS.version) - } else { - oldestSupportedVersion = minimumSupportedVersion - } - - let supportedPlatform = SupportedPlatform( - platform: platform, - version: oldestSupportedVersion, - options: [] - ) - - derivedPlatforms.append(supportedPlatform) - } - return SupportedPlatforms( declared: declaredPlatforms.sorted(by: { $0.platform.name < $1.platform.name }), - derived: derivedPlatforms.sorted(by: { $0.platform.name < $1.platform.name }) + derivedXCTestPlatformProvider: derivedXCTestPlatformProvider ) } @@ -815,6 +806,10 @@ private func resolveModuleAliases(packageBuilders: [ResolvedPackageBuilder], observabilityScope: observabilityScope) } } + + // Emit diagnostics for any module aliases that did not end up being applied. + aliasTracker.diagnoseUnappliedAliases(observabilityScope: observabilityScope) + return true } @@ -893,7 +888,7 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { var defaultLocalization: String? = nil /// The platforms supported by this package. - var platforms: SupportedPlatforms = .init(declared: [], derived: []) + var platforms: SupportedPlatforms = .init(declared: [], derivedXCTestPlatformProvider: .none) init( target: Target, @@ -985,7 +980,7 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { var defaultLocalization: String? = nil /// The platforms supported by this package. - var platforms: SupportedPlatforms = .init(declared: [], derived: []) + var platforms: SupportedPlatforms = .init(declared: [], derivedXCTestPlatformProvider: .none) /// If the given package's source is a registry release, this provides additional metadata and signature information. var registryMetadata: RegistryReleaseMetadata? diff --git a/Sources/PackageGraph/PackageGraph.swift b/Sources/PackageGraph/PackageGraph.swift index 4f96a1ca7b9..1263d09b039 100644 --- a/Sources/PackageGraph/PackageGraph.swift +++ b/Sources/PackageGraph/PackageGraph.swift @@ -23,7 +23,7 @@ enum PackageGraphError: Swift.Error { case cycleDetected((path: [Manifest], cycle: [Manifest])) /// The product dependency not found. - case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool) + case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool, similarProductName: String?) /// The package dependency already satisfied by a different dependency package case dependencyAlreadySatisfiedByIdentifier(package: String, dependencyLocation: String, otherDependencyURL: String, identity: PackageIdentity) @@ -70,11 +70,11 @@ public struct PackageGraph { /// Returns all the products in the graph, regardless if they are reachable from the root targets or not. public let allProducts: Set - /// The set of package dependencies required for a fully resolved graph. + /// Package dependencies required for a fully resolved graph. /// - //// This set will also have references to packages that are currently present - /// in the graph due to loading errors. This set doesn't include the root packages. - public let requiredDependencies: Set + /// This will include a references to dependencies that are currently present + /// in the graph due to loading errors. This does not include the root packages. + public let requiredDependencies: [PackageReference] /// Returns true if a given target is present in root packages and is not excluded for the given build environment. public func isInRootPackages(_ target: ResolvedTarget, satisfying buildEnvironment: BuildEnvironment) -> Bool { @@ -123,7 +123,7 @@ public struct PackageGraph { public init( rootPackages: [ResolvedPackage], rootDependencies: [ResolvedPackage] = [], - dependencies requiredDependencies: Set, + dependencies requiredDependencies: [PackageReference], binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]] ) throws { self.rootPackages = rootPackages @@ -219,11 +219,15 @@ extension PackageGraphError: CustomStringConvertible { (cycle.path + cycle.cycle).map({ $0.displayName }).joined(separator: " -> ") + " -> " + cycle.cycle[0].displayName - case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl): + case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName): if dependencyProductInDecl { return "product '\(dependencyProductName)' is declared in the same package '\(package)' and can't be used as a dependency for target '\(targetName)'." } else { - return "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")." + var description = "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")." + if let similarProductName { + description += " Did you mean '\(similarProductName)'?" + } + return description } case .dependencyAlreadySatisfiedByIdentifier(let package, let dependencyURL, let otherDependencyURL, let identity): return "'\(package)' dependency on '\(dependencyURL)' conflicts with dependency on '\(otherDependencyURL)' which has the same identity '\(identity)'" diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index ad14f6ace55..88ecbf49f65 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -46,15 +46,45 @@ public struct PackageGraphRoot { return self.packages.values.map { $0.reference } } + private let _dependencies: [PackageDependency] + /// The top level dependencies. - public let dependencies: [PackageDependency] + public var dependencies: [PackageDependency] { + guard let dependencyMapper else { + return self._dependencies + } + + return self._dependencies.map { dependency in + do { + return try dependencyMapper.mappedDependency( + MappablePackageDependency( + dependency, + parentPackagePath: localFileSystem.currentWorkingDirectory ?? .root + ), + fileSystem: localFileSystem + ) + } catch { + observabilityScope.emit(warning: "could not map dependency \(dependency.identity): \(error.interpolationDescription)") + return dependency + } + } + } + + private let dependencyMapper: DependencyMapper? + private let observabilityScope: ObservabilityScope /// Create a package graph root. /// Note this quietly skip inputs for which manifests are not found. this could be because the manifest failed to load or for some other reasons // FIXME: This API behavior wrt to non-found manifests is fragile, but required by IDEs // it may lead to incorrect assumption in downstream code which may expect an error if a manifest was not found // we should refactor this API to more clearly return errors for inputs that do not have a corresponding manifest - public init(input: PackageGraphRootInput, manifests: [AbsolutePath: Manifest], explicitProduct: String? = nil) { + public init( + input: PackageGraphRootInput, + manifests: [AbsolutePath: Manifest], + explicitProduct: String? = nil, + dependencyMapper: DependencyMapper? = nil, + observabilityScope: ObservabilityScope + ) { self.packages = input.packages.reduce(into: .init(), { partial, inputPath in if let manifest = manifests[inputPath] { let packagePath = manifest.path.parentDirectory @@ -77,7 +107,9 @@ public struct PackageGraphRoot { } } - self.dependencies = adjustedDependencies + self._dependencies = adjustedDependencies + self.dependencyMapper = dependencyMapper + self.observabilityScope = observabilityScope } /// Returns the constraints imposed by root manifests + dependencies. @@ -88,7 +120,7 @@ public struct PackageGraphRoot { let depend = try dependencies.map{ PackageContainerConstraint( - package: $0.createPackageRef(), + package: $0.packageRef, requirement: try $0.toConstraintRequirement(), products: $0.productFilter ) diff --git a/Sources/PackageGraph/PackageModel+Extensions.swift b/Sources/PackageGraph/PackageModel+Extensions.swift index 23c1a800e82..df27f3e757c 100644 --- a/Sources/PackageGraph/PackageModel+Extensions.swift +++ b/Sources/PackageGraph/PackageModel+Extensions.swift @@ -14,7 +14,7 @@ import PackageModel extension PackageDependency { /// Create the package reference object for the dependency. - public func createPackageRef() -> PackageReference { + public var packageRef: PackageReference { let packageKind: PackageReference.Kind switch self { case .fileSystem(let settings): @@ -38,7 +38,7 @@ extension Manifest { public func dependencyConstraints(productFilter: ProductFilter) throws -> [PackageContainerConstraint] { return try self.dependenciesRequired(for: productFilter).map({ return PackageContainerConstraint( - package: $0.createPackageRef(), + package: $0.packageRef, requirement: try $0.toConstraintRequirement(), products: $0.productFilter) }) diff --git a/Sources/PackageGraph/DependencyResolutionNode.swift b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift similarity index 99% rename from Sources/PackageGraph/DependencyResolutionNode.swift rename to Sources/PackageGraph/Resolution/DependencyResolutionNode.swift index 9b643ef915f..ffadb471bbd 100644 --- a/Sources/PackageGraph/DependencyResolutionNode.swift +++ b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift @@ -18,7 +18,7 @@ import struct TSCUtility.Version /// /// See the documentation of each case for more detailed descriptions of each kind and how they interact. /// -/// - SeeAlso: `GraphLoadingNode` +/// - SeeAlso: ``GraphLoadingNode`` public enum DependencyResolutionNode { /// An empty package node. diff --git a/Sources/PackageGraph/Resolution/DependencyResolverBinding.swift b/Sources/PackageGraph/Resolution/DependencyResolverBinding.swift new file mode 100644 index 00000000000..64af5af37a7 --- /dev/null +++ b/Sources/PackageGraph/Resolution/DependencyResolverBinding.swift @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 enum PackageModel.ProductFilter +import struct PackageModel.PackageReference + +public struct DependencyResolverBinding { + public let package: PackageReference + public let boundVersion: BoundVersion + public let products: ProductFilter +} diff --git a/Sources/PackageGraph/DependencyResolver.swift b/Sources/PackageGraph/Resolution/DependencyResolverDelegate.swift similarity index 77% rename from Sources/PackageGraph/DependencyResolver.swift rename to Sources/PackageGraph/Resolution/DependencyResolverDelegate.swift index e44693b345b..d570deb2711 100644 --- a/Sources/PackageGraph/DependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/DependencyResolverDelegate.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2023 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 @@ -16,25 +16,6 @@ import PackageModel import struct TSCUtility.Version -public protocol DependencyResolver { - typealias Binding = (package: PackageReference, binding: BoundVersion, products: ProductFilter) - typealias Delegate = DependencyResolverDelegate -} - -public enum DependencyResolverError: Error, Equatable { - /// A revision-based dependency contains a local package dependency. - case revisionDependencyContainsLocalPackage(dependency: String, localPackage: String) -} - -extension DependencyResolverError: CustomStringConvertible { - public var description: String { - switch self { - case .revisionDependencyContainsLocalPackage(let dependency, let localPackage): - return "package '\(dependency)' is required using a revision-based requirement and it depends on local package '\(localPackage)', which is not supported" - } - } -} - public protocol DependencyResolverDelegate { func willResolve(term: Term) func didResolve(term: Term, version: Version, duration: DispatchTimeInterval) @@ -44,7 +25,7 @@ public protocol DependencyResolverDelegate { func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility) func partiallySatisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility, difference: Term) func failedToResolve(incompatibility: Incompatibility) - func solved(result: [DependencyResolver.Binding]) + func solved(result: [DependencyResolverBinding]) } public struct ObservabilityDependencyResolverDelegate: DependencyResolverDelegate { @@ -82,9 +63,9 @@ public struct ObservabilityDependencyResolverDelegate: DependencyResolverDelegat self.debug("\(term) is partially satisfied by '\(assignment)', which is caused by '\(assignment.cause?.description ?? "unknown cause")'. new incompatibility \(incompatibility)") } - public func solved(result: [DependencyResolver.Binding]) { - for (package, binding, _) in result { - self.debug("solved '\(package.identity)' (\(package.locationString)) at '\(binding)'") + public func solved(result: [DependencyResolverBinding]) { + for binding in result { + self.debug("solved '\(binding.package.identity)' (\(binding.package.locationString)) at '\(binding.boundVersion)'") } self.debug("dependency resolution complete!") } @@ -129,7 +110,7 @@ public struct MultiplexResolverDelegate: DependencyResolverDelegate { underlying.forEach { $0.failedToResolve(incompatibility: incompatibility) } } - public func solved(result: [(package: PackageReference, binding: BoundVersion, products: ProductFilter)]) { + public func solved(result: [DependencyResolverBinding]) { underlying.forEach { $0.solved(result: result) } } diff --git a/Sources/PackageGraph/Resolution/DependencyResolverError.swift b/Sources/PackageGraph/Resolution/DependencyResolverError.swift new file mode 100644 index 00000000000..153031c41d4 --- /dev/null +++ b/Sources/PackageGraph/Resolution/DependencyResolverError.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 + +public enum DependencyResolverError: Error, Equatable { + /// A revision-based dependency contains a local package dependency. + case revisionDependencyContainsLocalPackage(dependency: String, localPackage: String) +} + +extension DependencyResolverError: CustomStringConvertible { + public var description: String { + switch self { + case .revisionDependencyContainsLocalPackage(let dependency, let localPackage): + return "package '\(dependency)' is required using a revision-based requirement and it depends on local package '\(localPackage)', which is not supported" + } + } +} diff --git a/Sources/PackageGraph/PubGrub/Assignment.swift b/Sources/PackageGraph/Resolution/PubGrub/Assignment.swift similarity index 100% rename from Sources/PackageGraph/PubGrub/Assignment.swift rename to Sources/PackageGraph/Resolution/PubGrub/Assignment.swift diff --git a/Sources/PackageGraph/PubGrub/ContainerProvider.swift b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift similarity index 91% rename from Sources/PackageGraph/PubGrub/ContainerProvider.swift rename to Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift index 6094613b619..f952a066a99 100644 --- a/Sources/PackageGraph/PubGrub/ContainerProvider.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift @@ -62,14 +62,14 @@ final class ContainerProvider { completion: @escaping (Result) -> Void ) { // Return the cached container, if available. - if let container = self.containersCache[package], package.equalsIncludingLocation(container.package) { + if let container = self.containersCache[comparingLocation: package] { return completion(.success(container)) } if let prefetchSync = self.prefetches[package] { // If this container is already being prefetched, wait for that to complete prefetchSync.notify(queue: .sharedConcurrent) { - if let container = self.containersCache[package], package.equalsIncludingLocation(container.package) { + if let container = self.containersCache[comparingLocation: package] { // should be in the cache once prefetch completed return completion(.success(container)) } else { @@ -125,3 +125,12 @@ final class ContainerProvider { } } } + +extension ThreadSafeKeyValueStore where Key == PackageReference, Value == PubGrubPackageContainer { + subscript(comparingLocation package: PackageReference) -> PubGrubPackageContainer? { + if let container = self[package], container.package.equalsIncludingLocation(package) { + return container + } + return .none + } +} diff --git a/Sources/PackageGraph/PubGrub/DiagnosticReportBuilder.swift b/Sources/PackageGraph/Resolution/PubGrub/DiagnosticReportBuilder.swift similarity index 100% rename from Sources/PackageGraph/PubGrub/DiagnosticReportBuilder.swift rename to Sources/PackageGraph/Resolution/PubGrub/DiagnosticReportBuilder.swift diff --git a/Sources/PackageGraph/PubGrub/Incompatibility.swift b/Sources/PackageGraph/Resolution/PubGrub/Incompatibility.swift similarity index 100% rename from Sources/PackageGraph/PubGrub/Incompatibility.swift rename to Sources/PackageGraph/Resolution/PubGrub/Incompatibility.swift diff --git a/Sources/PackageGraph/PubGrub/PartialSolution.swift b/Sources/PackageGraph/Resolution/PubGrub/PartialSolution.swift similarity index 100% rename from Sources/PackageGraph/PubGrub/PartialSolution.swift rename to Sources/PackageGraph/Resolution/PubGrub/PartialSolution.swift diff --git a/Sources/PackageGraph/PubGrub/PubGrubDependencyResolver.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift similarity index 98% rename from Sources/PackageGraph/PubGrub/PubGrubDependencyResolver.swift rename to Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift index 5e154f30083..6c02e492d0e 100644 --- a/Sources/PackageGraph/PubGrub/PubGrubDependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift @@ -140,7 +140,7 @@ public struct PubGrubDependencyResolver { } /// Execute the resolution algorithm to find a valid assignment of versions. - public func solve(constraints: [Constraint]) -> Result<[DependencyResolver.Binding], Error> { + public func solve(constraints: [Constraint]) -> Result<[DependencyResolverBinding], Error> { // the graph resolution root let root: DependencyResolutionNode if constraints.count == 1, let constraint = constraints.first, constraint.package.kind.isRoot { @@ -180,7 +180,7 @@ public struct PubGrubDependencyResolver { /// Find a set of dependencies that fit the given constraints. If dependency /// resolution is unable to provide a result, an error is thrown. /// - Warning: It is expected that the root package reference has been set before this is called. - internal func solve(root: DependencyResolutionNode, constraints: [Constraint]) throws -> (bindings: [DependencyResolver.Binding], state: State) { + internal func solve(root: DependencyResolutionNode, constraints: [Constraint]) throws -> (bindings: [DependencyResolverBinding], state: State) { // first process inputs let inputs = try self.processInputs(root: root, with: constraints) @@ -241,10 +241,10 @@ public struct PubGrubDependencyResolver { flattenedAssignments[updatePackage] = (binding: boundVersion, products: products) } } - var finalAssignments: [DependencyResolver.Binding] + var finalAssignments: [DependencyResolverBinding] = flattenedAssignments.keys.sorted(by: { $0.deprecatedName < $1.deprecatedName }).map { package in let details = flattenedAssignments[package]! - return (package: package, binding: details.binding, products: details.products) + return .init(package: package, boundVersion: details.binding, products: details.products) } // Add overridden packages to the result. @@ -252,7 +252,11 @@ public struct PubGrubDependencyResolver { // TODO: replace with async/await when available let container = try temp_await { provider.getContainer(for: package, completion: $0) } let updatePackage = try container.underlying.loadPackageReference(at: override.version) - finalAssignments.append((updatePackage, override.version, override.products)) + finalAssignments.append(.init( + package: updatePackage, + boundVersion: override.version, + products: override.products + )) } self.delegate?.solved(result: finalAssignments) diff --git a/Sources/PackageGraph/PubGrub/PubGrubPackageContainer.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift similarity index 96% rename from Sources/PackageGraph/PubGrub/PubGrubPackageContainer.swift rename to Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift index e666c8a4025..53a33d8d843 100644 --- a/Sources/PackageGraph/PubGrub/PubGrubPackageContainer.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift @@ -76,16 +76,12 @@ internal final class PubGrubPackageContainer { /// Returns the best available version for a given term. func getBestAvailableVersion(for term: Term) throws -> Version? { assert(term.isPositive, "Expected term to be positive") - let versionSet = term.requirement + var versionSet = term.requirement // Restrict the selection to the pinned version if is allowed by the current requirements. if let pinnedVersion { if versionSet.contains(pinnedVersion) { - // Make sure the pinned version is still available - let version = try self.underlying.versionsDescending().first { pinnedVersion == $0 } - if version != nil { - return version - } + versionSet = .exact(pinnedVersion) } } diff --git a/Sources/PackageGraph/PubGrub/Term.swift b/Sources/PackageGraph/Resolution/PubGrub/Term.swift similarity index 100% rename from Sources/PackageGraph/PubGrub/Term.swift rename to Sources/PackageGraph/Resolution/PubGrub/Term.swift diff --git a/Sources/PackageGraph/ResolvedPackage.swift b/Sources/PackageGraph/Resolution/ResolvedPackage.swift similarity index 100% rename from Sources/PackageGraph/ResolvedPackage.swift rename to Sources/PackageGraph/Resolution/ResolvedPackage.swift diff --git a/Sources/PackageGraph/ResolvedProduct.swift b/Sources/PackageGraph/Resolution/ResolvedProduct.swift similarity index 85% rename from Sources/PackageGraph/ResolvedProduct.swift rename to Sources/PackageGraph/Resolution/ResolvedProduct.swift index 48c2c9b2da2..e27b13883e3 100644 --- a/Sources/PackageGraph/ResolvedProduct.swift +++ b/Sources/PackageGraph/Resolution/ResolvedProduct.swift @@ -59,6 +59,14 @@ public final class ResolvedProduct { self.underlyingProduct = product self.targets = targets + // defaultLocalization is currently shared across the entire package + // this may need to be enhanced if / when we support localization per target or product + let defaultLocalization = self.targets.first?.defaultLocalization + self.defaultLocalization = defaultLocalization + + let platforms = Self.computePlatforms(targets: targets) + self.platforms = platforms + self.testEntryPointTarget = underlyingProduct.testEntryPointPath.map { testEntryPointPath in // Create an executable resolved target with the entry point file, adding product's targets as dependencies. let dependencies: [Target.Dependency] = product.targets.map { .target($0, conditions: []) } @@ -69,16 +77,10 @@ public final class ResolvedProduct { return ResolvedTarget( target: swiftTarget, dependencies: targets.map { .target($0, conditions: []) }, - defaultLocalization: .none, // safe since this is a derived product - platforms: .init(declared: [], derived: []) // safe since this is a derived product + defaultLocalization: defaultLocalization ?? .none, // safe since this is a derived product + platforms: platforms ) } - - // defaultLocalization is currently shared across the entire package - // this may need to be enhanced if / when we support localization per target or product - self.defaultLocalization = self.targets.first?.defaultLocalization - - self.platforms = Self.computePlatforms(targets: targets) } /// True if this product contains Swift targets. @@ -118,16 +120,13 @@ public final class ResolvedProduct { merge(into: &partial, platforms: item.platforms.declared) } - let derived = targets.reduce(into: [SupportedPlatform]()) { partial, item in - merge(into: &partial, platforms: item.platforms.derived) - } - return SupportedPlatforms( - declared: declared.sorted(by: { $0.platform.name < $1.platform.name }), - derived: derived.sorted(by: { $0.platform.name < $1.platform.name }) - ) - - + declared: declared.sorted(by: { $0.platform.name < $1.platform.name })) { declared in + let platforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in + merge(into: &partial, platforms: [item.platforms.getDerived(for: declared, usingXCTest: item.type == .test)]) + } + return platforms.first!.version + } } } @@ -146,3 +145,10 @@ extension ResolvedProduct: CustomStringConvertible { return "" } } + +extension ResolvedProduct { + public var isLinkingXCTest: Bool { + // To retain existing behavior, we have to check both the product type, as well as the types of all of its targets. + return self.type == .test || self.targets.contains(where: { $0.type == .test }) + } +} diff --git a/Sources/PackageGraph/ResolvedTarget.swift b/Sources/PackageGraph/Resolution/ResolvedTarget.swift similarity index 100% rename from Sources/PackageGraph/ResolvedTarget.swift rename to Sources/PackageGraph/Resolution/ResolvedTarget.swift diff --git a/Sources/PackageLoading/ContextModel.swift b/Sources/PackageLoading/ContextModel.swift index 91440d5b351..90bd7b50765 100644 --- a/Sources/PackageLoading/ContextModel.swift +++ b/Sources/PackageLoading/ContextModel.swift @@ -10,7 +10,11 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import Foundation +#else +import Foundation +#endif struct ContextModel { let packageDirectory : String @@ -28,7 +32,7 @@ extension ContextModel : Codable { func encode() throws -> String { let encoder = JSONEncoder() let data = try encoder.encode(self) - return String(data: data, encoding: .utf8)! + return String(decoding: data, as: UTF8.self) } static func decode() throws -> ContextModel { @@ -36,9 +40,7 @@ extension ContextModel : Codable { while let arg = args.next() { if arg == "-context", let json = args.next() { let decoder = JSONDecoder() - guard let data = json.data(using: .utf8) else { - throw StringError(description: "Failed decoding context json as UTF8") - } + let data = Data(json.utf8) return try decoder.decode(ContextModel.self, from: data) } } diff --git a/Sources/PackageLoading/ManifestJSONParser.swift b/Sources/PackageLoading/ManifestJSONParser.swift index 2e5bc5af636..ad1322668ea 100644 --- a/Sources/PackageLoading/ManifestJSONParser.swift +++ b/Sources/PackageLoading/ManifestJSONParser.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@_implementationOnly import Foundation +import Foundation import PackageModel import struct Basics.AbsolutePath @@ -26,8 +26,6 @@ import struct TSCBasic.StringError import struct TSCUtility.Version enum ManifestJSONParser { - private static let filePrefix = "file://" - struct Input: Codable { let package: Serialization.Package let errors: [String] @@ -55,7 +53,9 @@ enum ManifestJSONParser { v4 jsonString: String, toolsVersion: ToolsVersion, packageKind: PackageReference.Kind, + packagePath: AbsolutePath, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem ) throws -> ManifestJSONParser.Result { let decoder = JSONDecoder.makeWithDefaults() @@ -78,12 +78,28 @@ enum ManifestJSONParser { throw ManifestParseError.runtimeManifestErrors(input.errors) } + var packagePath = packagePath + switch packageKind { + case .localSourceControl(let _packagePath): + // we have a more accurate path than the virtual one + packagePath = _packagePath + case .root(let _packagePath), .fileSystem(let _packagePath): + // we dont have a more accurate path, and they should be the same + // asserting (debug only) to make sure refactoring is correct 11/2023 + assert(packagePath == _packagePath, "expecting package path '\(packagePath)' to be the same as '\(_packagePath)'") + break + case .remoteSourceControl, .registry: + // we dont have a more accurate path + break + } + let dependencies = try input.package.dependencies.map { try Self.parseDependency( dependency: $0, toolsVersion: toolsVersion, - packageKind: packageKind, + parentPackagePath: packagePath, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem ) } @@ -146,182 +162,24 @@ enum ManifestJSONParser { private static func parseDependency( dependency: Serialization.PackageDependency, toolsVersion: ToolsVersion, - packageKind: PackageReference.Kind, - identityResolver: IdentityResolver, - fileSystem: FileSystem - ) throws -> PackageDependency { - switch dependency.kind { - case .registry(let identity, let requirement): - return try Self.parseRegistryDependency( - identity: .plain(identity), - requirement: .init(requirement), - identityResolver: identityResolver - ) - case .sourceControl(let name, let location, let requirement): - return try Self.parseSourceControlDependency( - packageKind: packageKind, - at: location, - name: name, - requirement: .init(requirement), - identityResolver: identityResolver, - fileSystem: fileSystem - ) - case .fileSystem(let name, let path): - return try Self.parseFileSystemDependency( - packageKind: packageKind, - at: path, - name: name, - identityResolver: identityResolver, - fileSystem: fileSystem - ) - } - } - - private static func parseFileSystemDependency( - packageKind: PackageReference.Kind, - at location: String, - name: String?, + parentPackagePath: AbsolutePath, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem ) throws -> PackageDependency { - let location = try sanitizeDependencyLocation(fileSystem: fileSystem, packageKind: packageKind, dependencyLocation: location) - let path: AbsolutePath do { - path = try AbsolutePath(validating: location) - } catch PathValidationError.invalidAbsolutePath(let path) { - throw ManifestParseError.invalidManifestFormat("'\(path)' is not a valid path for path-based dependencies; use relative or absolute path instead.", diagnosticFile: nil, compilerCommandLine: nil) - } - let identity = try identityResolver.resolveIdentity(for: path) - return .fileSystem(identity: identity, - nameForTargetDependencyResolutionOnly: name, - path: path, - productFilter: .everything) - } - - private static func parseSourceControlDependency( - packageKind: PackageReference.Kind, - at location: String, - name: String?, - requirement: PackageDependency.SourceControl.Requirement, - identityResolver: IdentityResolver, - fileSystem: FileSystem - ) throws -> PackageDependency { - // cleans up variants of path based location - var location = try sanitizeDependencyLocation(fileSystem: fileSystem, packageKind: packageKind, dependencyLocation: location) - // location mapping (aka mirrors) if any - location = identityResolver.mappedLocation(for: location) - if PackageIdentity.plain(location).isRegistry { - // re-mapped to registry - let identity = PackageIdentity.plain(location) - let registryRequirement: PackageDependency.Registry.Requirement - switch requirement { - case .branch, .revision: - throw StringError("invalid mapping of source control to registry, requirement information mismatch: cannot map branch or revision based dependencies to registry.") - case .exact(let value): - registryRequirement = .exact(value) - case .range(let value): - registryRequirement = .range(value) - } - return .registry( - identity: identity, - requirement: registryRequirement, - productFilter: .everything - ) - } else if let localPath = try? AbsolutePath(validating: location) { - // a package in a git location, may be a remote URL or on disk - // in the future this will check with the registries for the identity of the URL - let identity = try identityResolver.resolveIdentity(for: localPath) - return .localSourceControl( - identity: identity, - nameForTargetDependencyResolutionOnly: name, - path: localPath, - requirement: requirement, - productFilter: .everything - ) - } else { - let url = SourceControlURL(location) - // in the future this will check with the registries for the identity of the URL - let identity = try identityResolver.resolveIdentity(for: url) - return .remoteSourceControl( - identity: identity, - nameForTargetDependencyResolutionOnly: name, - url: url, - requirement: requirement, - productFilter: .everything - ) - } - } - - private static func parseRegistryDependency( - identity: PackageIdentity, - requirement: PackageDependency.Registry.Requirement, - identityResolver: IdentityResolver - ) throws -> PackageDependency { - // location mapping (aka mirrors) if any - let location = identityResolver.mappedLocation(for: identity.description) - if PackageIdentity.plain(location).isRegistry { - // re-mapped to registry - let identity = PackageIdentity.plain(location) - return .registry( - identity: identity, - requirement: requirement, - productFilter: .everything - ) - } else if let url = URL(string: location){ - let SourceControlURL = SourceControlURL(url) - // in the future this will check with the registries for the identity of the URL - let identity = try identityResolver.resolveIdentity(for: SourceControlURL) - let sourceControlRequirement: PackageDependency.SourceControl.Requirement - switch requirement { - case .exact(let value): - sourceControlRequirement = .exact(value) - case .range(let value): - sourceControlRequirement = .range(value) - } - return .remoteSourceControl( - identity: identity, - nameForTargetDependencyResolutionOnly: identity.description, - url: SourceControlURL, - requirement: sourceControlRequirement, - productFilter: .everything + return try dependencyMapper.mappedDependency( + MappablePackageDependency(dependency, parentPackagePath: parentPackagePath), + fileSystem: fileSystem ) - } else { - throw StringError("invalid location: \(location)") - } - } - - private static func sanitizeDependencyLocation(fileSystem: FileSystem, packageKind: PackageReference.Kind, dependencyLocation: String) throws -> String { - if dependencyLocation.hasPrefix("~/") { - // If the dependency URL starts with '~/', try to expand it. - return try AbsolutePath(validating: String(dependencyLocation.dropFirst(2)), relativeTo: fileSystem.homeDirectory).pathString - } else if dependencyLocation.hasPrefix(filePrefix) { - // FIXME: SwiftPM can't handle file locations with file:// scheme so we need to - // strip that. We need to design a Location data structure for SwiftPM. - let location = String(dependencyLocation.dropFirst(filePrefix.count)) - let hostnameComponent = location.prefix(while: { $0 != "/" }) - guard hostnameComponent.isEmpty else { - if hostnameComponent == ".." { - throw ManifestParseError.invalidManifestFormat( - "file:// URLs cannot be relative, did you mean to use '.package(path:)'?", diagnosticFile: nil, compilerCommandLine: nil - ) - } - throw ManifestParseError.invalidManifestFormat( - "file:// URLs with hostnames are not supported, are you missing a '/'?", diagnosticFile: nil, compilerCommandLine: nil - ) - } - return try AbsolutePath(validating: location).pathString - } else if parseScheme(dependencyLocation) == nil { - // If the URL has no scheme, we treat it as a path (either absolute or relative to the base URL). - switch packageKind { - case .root(let packagePath), .fileSystem(let packagePath), .localSourceControl(let packagePath): - return try AbsolutePath(validating: dependencyLocation, relativeTo: packagePath).pathString - case .remoteSourceControl, .registry: - // nothing to "fix" - return dependencyLocation + } catch let error as TSCBasic.PathValidationError { + if case .fileSystem(_, let path) = dependency.kind { + throw ManifestParseError.invalidManifestFormat("'\(path)' is not a valid path for path-based dependencies; use relative or absolute path instead.", diagnosticFile: nil, compilerCommandLine: nil) + } else { + throw error } - } else { - // nothing to "fix" - return dependencyLocation + } catch { + throw ManifestParseError.invalidManifestFormat("\(error.interpolationDescription)", diagnosticFile: nil, compilerCommandLine: nil) } } @@ -392,33 +250,6 @@ enum ManifestJSONParser { return settings } - /// Parses the URL type of a git repository - /// e.g. https://github.com/apple/swift returns "https" - /// e.g. git@github.com:apple/swift returns "git" - /// - /// This is *not* a generic URI scheme parser! - private static func parseScheme(_ location: String) -> String? { - func prefixOfSplitBy(_ delimiter: String) -> String? { - let (head, tail) = location.spm_split(around: delimiter) - if tail == nil { - //not found - return nil - } else { - //found, return head - //lowercase the "scheme", as specified by the URI RFC (just in case) - return head.lowercased() - } - } - - for delim in ["://", "@"] { - if let found = prefixOfSplitBy(delim), !found.contains("/") { - return found - } - } - - return nil - } - /// Looks for Xcode-style build setting macros "$()". fileprivate static let invalidValueRegex = try! RegEx(pattern: #"(\$\(.*?\))"#) } @@ -747,3 +578,28 @@ extension TargetBuildSettingDescription.Setting { ) } } + +extension MappablePackageDependency { + fileprivate init(_ seed: Serialization.PackageDependency, parentPackagePath: AbsolutePath) { + switch seed.kind { + case .fileSystem(let name, let path): + self.init( + parentPackagePath: parentPackagePath, + kind: .fileSystem(name: name, path: path), + productFilter: .everything + ) + case .sourceControl(let name, let location, let requirement): + self.init( + parentPackagePath: parentPackagePath, + kind: .sourceControl(name: name, location: location, requirement: .init(requirement)), + productFilter: .everything + ) + case .registry(let id, let requirement): + self.init( + parentPackagePath: parentPackagePath, + kind: .registry(id: id, requirement: .init(requirement)), + productFilter: .everything + ) + } + } +} diff --git a/Sources/PackageLoading/ManifestLoader+Validation.swift b/Sources/PackageLoading/ManifestLoader+Validation.swift index 304819cc7bb..62a0b235f23 100644 --- a/Sources/PackageLoading/ManifestLoader+Validation.swift +++ b/Sources/PackageLoading/ManifestLoader+Validation.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import PackageModel public struct ManifestValidator { @@ -217,29 +217,19 @@ public struct ManifestValidator { func validateSourceControlDependency(_ dependency: PackageDependency.SourceControl) -> [Basics.Diagnostic] { var diagnostics = [Basics.Diagnostic]() - - // validate source control ref - switch dependency.requirement { - case .branch(let name): - if !self.sourceControlValidator.isValidRefFormat(name) { - diagnostics.append(.invalidSourceControlBranchName(name)) - } - case .revision(let revision): - if !self.sourceControlValidator.isValidRefFormat(revision) { - diagnostics.append(.invalidSourceControlRevision(revision)) - } - default: - break - } // if a location is on file system, validate it is in fact a git repo // there is a case to be made to throw early (here) if the path does not exists // but many of our tests assume they can pass a non existent path if case .local(let localPath) = dependency.location, self.fileSystem.exists(localPath) { - if !self.sourceControlValidator.isValidDirectory(localPath) { - // Provides better feedback when mistakingly using url: for a dependency that - // is a local package. Still allows for using url with a local package that has - // also been initialized by git - diagnostics.append(.invalidSourceControlDirectory(localPath)) + do { + if try !self.sourceControlValidator.isValidDirectory(localPath) { + // Provides better feedback when mistakingly using url: for a dependency that + // is a local package. Still allows for using url with a local package that has + // also been initialized by git + diagnostics.append(.invalidSourceControlDirectory(localPath)) + } + } catch { + diagnostics.append(.invalidSourceControlDirectory(localPath, underlyingError: error)) } } return diagnostics @@ -247,8 +237,7 @@ public struct ManifestValidator { } public protocol ManifestSourceControlValidator { - func isValidRefFormat(_ revision: String) -> Bool - func isValidDirectory(_ path: AbsolutePath) -> Bool + func isValidDirectory(_ path: AbsolutePath) throws -> Bool } extension Basics.Diagnostic { @@ -304,16 +293,16 @@ extension Basics.Diagnostic { """) } - static func invalidSourceControlBranchName(_ name: String) -> Self { - .error("invalid branch name: '\(name)'") - } - - static func invalidSourceControlRevision(_ revision: String) -> Self { - .error("invalid revision: '\(revision)'") + static func errorSuffix(_ error: Error?) -> String { + if let error { + return ": \(error.interpolationDescription)" + } else { + return "" + } } - static func invalidSourceControlDirectory(_ path: AbsolutePath) -> Self { - .error("cannot clone from local directory \(path)\nPlease git init or use \"path:\" for \(path)") + static func invalidSourceControlDirectory(_ path: AbsolutePath, underlyingError: Error? = nil) -> Self { + .error("cannot clone from local directory \(path)\nPlease git init or use \"path:\" for \(path)\(errorSuffix(underlyingError))") } } diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index 623684a40e0..99bf6e161a2 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -10,9 +10,10 @@ // //===----------------------------------------------------------------------===// +import _Concurrency import Basics import Dispatch -@_implementationOnly import Foundation +import Foundation import PackageModel import class TSCBasic.BufferedOutputByteStream @@ -108,9 +109,9 @@ public protocol ManifestLoaderProtocol { /// - packageLocation: The location the package the manifest was loaded from. /// - packageVersion: Optional. The version and revision of the package. /// - identityResolver: A helper to resolve identities based on configuration + /// - dependencyMapper: A helper to map dependencies. /// - fileSystem: File system to load from. /// - observabilityScope: Observability scope to emit diagnostics. - /// - delegateQueue: The dispatch queue to call delegate handlers on. /// - callbackQueue: The dispatch queue to perform completion handler on. /// - completion: The completion handler . func load( @@ -121,6 +122,7 @@ public protocol ManifestLoaderProtocol { packageLocation: String, packageVersion: (version: Version?, revision: String?)?, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, @@ -136,8 +138,51 @@ public protocol ManifestLoaderProtocol { } public protocol ManifestLoaderDelegate { - func willLoad(manifest: AbsolutePath) - func willParse(manifest: AbsolutePath) + func willLoad( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath + ) + func didLoad( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath, + duration: DispatchTimeInterval + ) + + func willParse( + packageIdentity: PackageIdentity, + packageLocation: String + ) + func didParse( + packageIdentity: PackageIdentity, + packageLocation: String, + duration: DispatchTimeInterval + ) + + func willCompile( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath + ) + func didCompile( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath, + duration: DispatchTimeInterval + ) + + func willEvaluate( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath + ) + func didEvaluate( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath, + duration: DispatchTimeInterval + ) } // loads a manifest given a package root path @@ -152,6 +197,7 @@ extension ManifestLoaderProtocol { packageVersion: (version: Version?, revision: String?)?, currentToolsVersion: ToolsVersion, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, @@ -173,6 +219,7 @@ extension ManifestLoaderProtocol { packageLocation: packageLocation, packageVersion: packageVersion, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem, observabilityScope: observabilityScope, delegateQueue: delegateQueue, @@ -197,17 +244,23 @@ extension ManifestLoaderProtocol { /// serialized form of the manifest (as implemented by `PackageDescription`'s /// `atexit()` handler) which is then deserialized and loaded. public final class ManifestLoader: ManifestLoaderProtocol { + public typealias Delegate = ManifestLoaderDelegate + private let toolchain: UserToolchain private let serializedDiagnostics: Bool private let isManifestSandboxEnabled: Bool - private let delegate: ManifestLoaderDelegate? private let extraManifestFlags: [String] - private let restrictImports: (startingToolsVersion: ToolsVersion, allowedImports: [String])? + private let importRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])? - private let databaseCacheDir: AbsolutePath? + // not thread safe + public var delegate: Delegate? + private let databaseCacheDir: AbsolutePath? private let sdkRootCache = ThreadSafeBox() + private let useInMemoryCache: Bool + private let memoryCache = ThreadSafeKeyValueStore() + /// DispatchSemaphore to restrict concurrent manifest evaluations private let concurrencySemaphore: DispatchSemaphore /// OperationQueue to park pending lookups @@ -217,18 +270,21 @@ public final class ManifestLoader: ManifestLoaderProtocol { toolchain: UserToolchain, serializedDiagnostics: Bool = false, isManifestSandboxEnabled: Bool = true, - cacheDir: AbsolutePath? = nil, - delegate: ManifestLoaderDelegate? = nil, - extraManifestFlags: [String] = [], - restrictImports: (startingToolsVersion: ToolsVersion, allowedImports: [String])? = .none + useInMemoryCache: Bool = true, + cacheDir: AbsolutePath? = .none, + extraManifestFlags: [String]? = .none, + importRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])? = .none, + delegate: Delegate? = .none ) { self.toolchain = toolchain self.serializedDiagnostics = serializedDiagnostics self.isManifestSandboxEnabled = isManifestSandboxEnabled + self.extraManifestFlags = extraManifestFlags ?? [] + self.importRestrictions = importRestrictions + self.delegate = delegate - self.extraManifestFlags = extraManifestFlags - self.restrictImports = restrictImports + self.useInMemoryCache = useInMemoryCache self.databaseCacheDir = try? cacheDir.map(resolveSymlinks) // this queue and semaphore is used to limit the amount of concurrent manifest loading taking place @@ -237,7 +293,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { self.evaluationQueue.maxConcurrentOperationCount = Concurrency.maxOperations self.concurrencySemaphore = DispatchSemaphore(value: Concurrency.maxOperations) } - + public func load( manifestPath: AbsolutePath, manifestToolsVersion: ToolsVersion, @@ -246,6 +302,41 @@ public final class ManifestLoader: ManifestLoaderProtocol { packageLocation: String, packageVersion: (version: Version?, revision: String?)?, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + delegateQueue: DispatchQueue, + callbackQueue: DispatchQueue + ) async throws -> Manifest { + try await safe_async { + self.load( + manifestPath: manifestPath, + manifestToolsVersion: manifestToolsVersion, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: packageVersion, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + delegateQueue: delegateQueue, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + + @available(*, noasync, message: "Use the async alternative") + public func load( + manifestPath: AbsolutePath, + manifestToolsVersion: ToolsVersion, + packageIdentity: PackageIdentity, + packageKind: PackageReference.Kind, + packageLocation: String, + packageVersion: (version: Version?, revision: String?)?, + identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, @@ -253,8 +344,13 @@ public final class ManifestLoader: ManifestLoaderProtocol { completion: @escaping (Result) -> Void ) { // Inform the delegate. + let start = DispatchTime.now() delegateQueue.async { - self.delegate?.willLoad(manifest: manifestPath) + self.delegate?.willLoad( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath + ) } // Validate that the file exists. @@ -269,10 +365,13 @@ public final class ManifestLoader: ManifestLoaderProtocol { toolsVersion: manifestToolsVersion, packageIdentity: packageIdentity, packageKind: packageKind, + packageLocation: packageLocation, packageVersion: packageVersion?.version, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem, observabilityScope: observabilityScope, + delegate: delegate, delegateQueue: delegateQueue, callbackQueue: callbackQueue ) { parseResult in @@ -319,6 +418,17 @@ public final class ManifestLoader: ManifestLoaderProtocol { products: products, targets: targets ) + + // Inform the delegate. + delegateQueue.async { + self.delegate?.didLoad( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: start.distance(to: .now()) + ) + } + completion(.success(manifest)) } catch { callbackQueue.async { @@ -333,10 +443,16 @@ public final class ManifestLoader: ManifestLoaderProtocol { _ result: EvaluationResult, packageIdentity: PackageIdentity, packageKind: PackageReference.Kind, + packagePath: AbsolutePath, + packageLocation: String, toolsVersion: ToolsVersion, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, - observabilityScope: ObservabilityScope + emitCompilerOutput: Bool, + observabilityScope: ObservabilityScope, + delegate: Delegate?, + delegateQueue: DispatchQueue? ) throws -> ManifestJSONParser.Result { // Throw now if we weren't able to parse the manifest. guard let manifestJSON = result.manifestJSON, !manifestJSON.isEmpty else { @@ -351,7 +467,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { // We might have some non-fatal output (warnings/notes) from the compiler even when // we were able to parse the manifest successfully. - if let compilerOutput = result.compilerOutput { + if emitCompilerOutput, let compilerOutput = result.compilerOutput { let metadata = result.diagnosticFile.map { diagnosticFile -> ObservabilityMetadata in var metadata = ObservabilityMetadata() metadata.manifestLoadingDiagnosticFile = diagnosticFile @@ -360,13 +476,31 @@ public final class ManifestLoader: ManifestLoaderProtocol { observabilityScope.emit(warning: compilerOutput, metadata: metadata) } - return try ManifestJSONParser.parse( + let start = DispatchTime.now() + delegateQueue?.async { + delegate?.willParse( + packageIdentity: packageIdentity, + packageLocation: packageLocation + ) + } + + let result = try ManifestJSONParser.parse( v4: manifestJSON, toolsVersion: toolsVersion, packageKind: packageKind, + packagePath: packagePath, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem ) + delegateQueue?.async { + delegate?.didParse( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + duration: start.distance(to: .now()) + ) + } + return result } private func loadAndCacheManifest( @@ -374,15 +508,58 @@ public final class ManifestLoader: ManifestLoaderProtocol { toolsVersion: ToolsVersion, packageIdentity: PackageIdentity, packageKind: PackageReference.Kind, + packageLocation: String, packageVersion: Version?, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, + delegate: Delegate?, + delegateQueue: DispatchQueue?, callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void ) { - let cache = self.databaseCacheDir.map { cacheDir -> SQLiteBackedCache in + // put callback on right queue + var completion = completion + do { + let previousCompletion = completion + completion = { result in callbackQueue.async { previousCompletion(result) } } + } + + let key : CacheKey + do { + key = try CacheKey( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: path, + toolsVersion: toolsVersion, + env: ProcessEnv.cachableVars, + swiftpmVersion: SwiftVersion.current.displayString, + fileSystem: fileSystem + ) + } catch { + return completion(.failure(error)) + } + + // try from in-memory cache + if self.useInMemoryCache, let parsedManifest = self.memoryCache[key] { + observabilityScope.emit(debug: "loading manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown") from memory cache") + return completion(.success(parsedManifest)) + } + + // make sure callback record results in-memory + do { + let previousCompletion = completion + completion = { result in + if self.useInMemoryCache, case .success(let parsedManifest) = result { + self.memoryCache[key] = parsedManifest + } + previousCompletion(result) + } + } + + // initialize db cache + let dbCache = self.databaseCacheDir.map { cacheDir in let path = Self.manifestCacheDBPath(cacheDir) var configuration = SQLiteBackedCacheConfiguration() // FIXME: expose as user-facing configuration @@ -395,49 +572,42 @@ public final class ManifestLoader: ManifestLoaderProtocol { ) } - let closingCompletion = { (result: Result) in - do { - try cache?.close() - } catch { - observabilityScope.emit( - warning: "failed closing cache", - underlyingError: error - ) - } - - callbackQueue.async { - completion(result) - } - } - - let key : CacheKey + // make sure callback closes db cache do { - key = try CacheKey( - packageIdentity: packageIdentity, - manifestPath: path, - toolsVersion: toolsVersion, - env: ProcessEnv.vars, - swiftpmVersion: SwiftVersion.current.displayString, - fileSystem: fileSystem - ) - } catch { - return closingCompletion(.failure(error)) + let previousCompletion = completion + completion = { result in + do { + try dbCache?.close() + } catch { + observabilityScope.emit( + warning: "failed closing manifest db cache", + underlyingError: error + ) + } + previousCompletion(result) + } } do { // try to get it from the cache - if let result = try cache?.get(key: key.sha256Checksum), let manifestJSON = result.manifestJSON, !manifestJSON.isEmpty { - observabilityScope.emit(debug: "loading manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown") from cache") + if let evaluationResult = try dbCache?.get(key: key.sha256Checksum), let manifestJSON = evaluationResult.manifestJSON, !manifestJSON.isEmpty { + observabilityScope.emit(debug: "loading manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown") from db cache") let parsedManifest = try self.parseManifest( - result, + evaluationResult, packageIdentity: packageIdentity, packageKind: packageKind, + packagePath: path.parentDirectory, + packageLocation: packageLocation, toolsVersion: toolsVersion, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem, - observabilityScope: observabilityScope + emitCompilerOutput: false, + observabilityScope: observabilityScope, + delegate: delegate, + delegateQueue: delegateQueue ) - return closingCompletion(.success(parsedManifest)) + return completion(.success(parsedManifest)) } } catch { observabilityScope.emit( @@ -451,9 +621,12 @@ public final class ManifestLoader: ManifestLoaderProtocol { do { try self.evaluateManifest( packageIdentity: key.packageIdentity, + packageLocation: packageLocation, manifestPath: key.manifestPath, manifestContents: key.manifestContents, toolsVersion: key.toolsVersion, + observabilityScope: observabilityScope, + delegate: delegate, delegateQueue: delegateQueue, callbackQueue: callbackQueue ) { result in @@ -462,19 +635,25 @@ public final class ManifestLoader: ManifestLoaderProtocol { do { let evaluationResult = try result.get() // only cache successfully parsed manifests - let parseManifest = try self.parseManifest( + let parsedManifest = try self.parseManifest( evaluationResult, packageIdentity: packageIdentity, packageKind: packageKind, + packagePath: path.parentDirectory, + packageLocation: packageLocation, toolsVersion: toolsVersion, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem, - observabilityScope: observabilityScope + emitCompilerOutput: true, + observabilityScope: observabilityScope, + delegate: delegate, + delegateQueue: delegateQueue ) do { - // FIXME: (diagnostics) pass in observability scope when we have one - try cache?.put(key: key.sha256Checksum, value: evaluationResult) + self.memoryCache[key] = parsedManifest + try dbCache?.put(key: key.sha256Checksum, value: evaluationResult, observabilityScope: observabilityScope) } catch { observabilityScope.emit( warning: "failed storing manifest for '\(key.packageIdentity)' in cache", @@ -482,13 +661,13 @@ public final class ManifestLoader: ManifestLoaderProtocol { ) } - return closingCompletion(.success(parseManifest)) + return completion(.success(parsedManifest)) } catch { - return closingCompletion(.failure(error)) + return completion(.failure(error)) } } } catch { - return closingCompletion(.failure(error)) + return completion(.failure(error)) } } @@ -498,7 +677,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void) { // If there are no import restrictions, we do not need to validate. - guard let restrictImports = restrictImports, toolsVersion >= restrictImports.startingToolsVersion else { + guard let importRestrictions = self.importRestrictions, toolsVersion >= importRestrictions.startingToolsVersion else { return callbackQueue.async { completion(.success(())) } @@ -506,7 +685,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { // Allowed are the expected defaults, plus anything allowed by the configured restrictions. let allowedImports = ["PackageDescription", "Swift", - "SwiftOnoneSupport", "_SwiftConcurrencyShims"] + restrictImports.allowedImports + "SwiftOnoneSupport", "_SwiftConcurrencyShims"] + importRestrictions.allowedImports // wrap the completion to free concurrency control semaphore let completion: (Result) -> Void = { result in @@ -518,23 +697,21 @@ public final class ManifestLoader: ManifestLoaderProtocol { // we must not block the calling thread (for concurrency control) so nesting this in a queue self.evaluationQueue.addOperation { - do { - // park the evaluation thread based on the max concurrency allowed - self.concurrencySemaphore.wait() + // park the evaluation thread based on the max concurrency allowed + self.concurrencySemaphore.wait() - let importScanner = SwiftcImportScanner(swiftCompilerEnvironment: self.toolchain.swiftCompilerEnvironment, - swiftCompilerFlags: self.extraManifestFlags, - swiftCompilerPath: self.toolchain.swiftCompilerPathForManifests) + let importScanner = SwiftcImportScanner(swiftCompilerEnvironment: self.toolchain.swiftCompilerEnvironment, + swiftCompilerFlags: self.extraManifestFlags, + swiftCompilerPath: self.toolchain.swiftCompilerPathForManifests) - importScanner.scanImports(manifestPath, callbackQueue: callbackQueue) { result in - do { - let imports = try result.get().filter { !allowedImports.contains($0) } - guard imports.isEmpty else { - throw ManifestParseError.importsRestrictedModules(imports) - } - } catch { - completion(.failure(error)) + Task { + let result = try await importScanner.scanImports(manifestPath) + let imports = result.filter { !allowedImports.contains($0) } + guard imports.isEmpty else { + callbackQueue.async { + completion(.failure(ManifestParseError.importsRestrictedModules(imports))) } + return } } } @@ -543,10 +720,13 @@ public final class ManifestLoader: ManifestLoaderProtocol { /// Compiler the manifest at the given path and retrieve the JSON. fileprivate func evaluateManifest( packageIdentity: PackageIdentity, + packageLocation: String, manifestPath: AbsolutePath, manifestContents: [UInt8], toolsVersion: ToolsVersion, - delegateQueue: DispatchQueue, + observabilityScope: ObservabilityScope, + delegate: Delegate?, + delegateQueue: DispatchQueue?, callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void ) throws { @@ -564,11 +744,17 @@ public final class ManifestLoader: ManifestLoaderProtocol { let vfsOverlayTempFilePath = tempDir.appending("vfs.yaml") try VFSOverlay(roots: [ - VFSOverlay.File(name: manifestPath._normalized.replacingOccurrences(of: #"\"#, with: #"\\"#), - externalContents: manifestTempFilePath._nativePathString(escaped: true)) + VFSOverlay.File( + name: manifestPath._normalized.replacingOccurrences(of: #"\"#, with: #"\\"#), + externalContents: manifestTempFilePath._nativePathString(escaped: true) + ) ]).write(to: vfsOverlayTempFilePath, fileSystem: localFileSystem) - validateImports(manifestPath: manifestTempFilePath, toolsVersion: toolsVersion, callbackQueue: callbackQueue) { result in + validateImports( + manifestPath: manifestTempFilePath, + toolsVersion: toolsVersion, + callbackQueue: callbackQueue + ) { result in dispatchPrecondition(condition: .onQueue(callbackQueue)) do { @@ -578,7 +764,10 @@ public final class ManifestLoader: ManifestLoaderProtocol { at: manifestPath, vfsOverlayPath: vfsOverlayTempFilePath, packageIdentity: packageIdentity, + packageLocation: packageLocation, toolsVersion: toolsVersion, + observabilityScope: observabilityScope, + delegate: delegate, delegateQueue: delegateQueue, callbackQueue: callbackQueue ) { result in @@ -606,8 +795,11 @@ public final class ManifestLoader: ManifestLoaderProtocol { at manifestPath: AbsolutePath, vfsOverlayPath: AbsolutePath? = nil, packageIdentity: PackageIdentity, + packageLocation: String, toolsVersion: ToolsVersion, - delegateQueue: DispatchQueue, + observabilityScope: ObservabilityScope, + delegate: Delegate?, + delegateQueue: DispatchQueue?, callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void ) throws { @@ -621,10 +813,6 @@ public final class ManifestLoader: ManifestLoaderProtocol { var evaluationResult = EvaluationResult() - delegateQueue.async { - self.delegate?.willParse(manifest: manifestPath) - } - // For now, we load the manifest by having Swift interpret it directly. // Eventually, we should have two loading processes, one that loads only // the declarative package specification using the Swift compiler directly @@ -712,6 +900,14 @@ public final class ManifestLoader: ManifestLoaderProtocol { // park the evaluation thread based on the max concurrency allowed self.concurrencySemaphore.wait() // run the evaluation + let compileStart = DispatchTime.now() + delegateQueue?.async { + delegate?.willCompile( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath + ) + } try withTemporaryDirectory { tmpDir, cleanupTmpDir in // Set path to compiled manifest executable. #if os(Windows) @@ -781,7 +977,26 @@ public final class ManifestLoader: ManifestLoaderProtocol { } } + delegateQueue?.async { + delegate?.didCompile( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: compileStart.distance(to: .now()) + ) + } + // Run the compiled manifest. + + let evaluationStart = DispatchTime.now() + delegateQueue?.async { + delegate?.willEvaluate( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath + ) + } + var environment = ProcessEnv.vars #if os(Windows) let windowsPathComponent = runtimePath.pathString.replacingOccurrences(of: "/", with: "\\") @@ -814,6 +1029,15 @@ public final class ManifestLoader: ManifestLoaderProtocol { let jsonOutput: String = try localFileSystem.readFileContents(jsonOutputFile) evaluationResult.manifestJSON = jsonOutput + delegateQueue?.async { + delegate?.didEvaluate( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: evaluationStart.distance(to: .now()) + ) + } + completion(.success(evaluationResult)) } catch { completion(.failure(error)) @@ -882,7 +1106,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { /// reset internal cache public func resetCache(observabilityScope: ObservabilityScope) { - // nothing needed at this point + self.memoryCache.clear() } /// reset internal state and purge shared cache @@ -919,6 +1143,7 @@ extension ManifestLoader { let sha256Checksum: String init (packageIdentity: PackageIdentity, + packageLocation: String, manifestPath: AbsolutePath, toolsVersion: ToolsVersion, env: EnvironmentVariables, @@ -926,7 +1151,14 @@ extension ManifestLoader { fileSystem: FileSystem ) throws { let manifestContents = try fileSystem.readFileContents(manifestPath).contents - let sha256Checksum = try Self.computeSHA256Checksum(packageIdentity: packageIdentity, manifestContents: manifestContents, toolsVersion: toolsVersion, env: env, swiftpmVersion: swiftpmVersion) + let sha256Checksum = try Self.computeSHA256Checksum( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestContents: manifestContents, + toolsVersion: toolsVersion, + env: env, + swiftpmVersion: swiftpmVersion + ) self.packageIdentity = packageIdentity self.manifestPath = manifestPath @@ -943,6 +1175,7 @@ extension ManifestLoader { private static func computeSHA256Checksum( packageIdentity: PackageIdentity, + packageLocation: String, manifestContents: [UInt8], toolsVersion: ToolsVersion, env: EnvironmentVariables, @@ -950,6 +1183,7 @@ extension ManifestLoader { ) throws -> String { let stream = BufferedOutputByteStream() stream.send(packageIdentity) + stream.send(packageLocation) stream.send(manifestContents) stream.send(toolsVersion.description) for (key, value) in env.sorted(by: { $0.key > $1.key }) { @@ -1014,3 +1248,9 @@ extension ManifestLoader { } } } + +extension ProcessEnv { + fileprivate static var cachableVars: EnvironmentVariables { + Self.vars.cachable + } +} diff --git a/Sources/PackageLoading/ManifestSignatureParser.swift b/Sources/PackageLoading/ManifestSignatureParser.swift index 27e82cfe1d5..a72b65bbab1 100644 --- a/Sources/PackageLoading/ManifestSignatureParser.swift +++ b/Sources/PackageLoading/ManifestSignatureParser.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import struct Foundation.Data +import struct Foundation.Data public enum ManifestSignatureParser { public static func parse(manifestPath: AbsolutePath, fileSystem: FileSystem) throws -> ManifestSignature? { diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 3b40b16e3ea..d9619c6ae10 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import PackageModel /// Name of the module map file recognized by the Clang and Swift compilers. diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 45358897c4a..2cf6235b398 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -98,7 +98,7 @@ extension ModuleError: CustomStringConvertible { switch self { case .duplicateModule(let target, let packages): let packages = packages.map(\.description).sorted().joined(separator: "', '") - return "multiple targets named '\(target)' in: '\(packages)'; consider using the `moduleAliases` parameter in manifest to provide unique names" + return "multiple targets named '\(target)' in: '\(packages)'" case .moduleNotFound(let target, let type, let shouldSuggestRelaxedSourceDir): let folderName = (type == .test) ? "Tests" : (type == .plugin) ? "Plugins" : "Sources" var clauses = ["Source files for target \(target) should be located under '\(folderName)/\(target)'"] diff --git a/Sources/PackageLoading/PkgConfig.swift b/Sources/PackageLoading/PkgConfig.swift index 93adcc49b7c..cb2a5c539de 100644 --- a/Sources/PackageLoading/PkgConfig.swift +++ b/Sources/PackageLoading/PkgConfig.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import OrderedCollections import class TSCBasic.Process diff --git a/Sources/PackageLoading/Platform.swift b/Sources/PackageLoading/Platform.swift index 862ea340253..324f0593458 100644 --- a/Sources/PackageLoading/Platform.swift +++ b/Sources/PackageLoading/Platform.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import class TSCBasic.Process diff --git a/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift b/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift index 451e29bd286..cf60cd9a211 100644 --- a/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift +++ b/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import PackageModel public enum RegistryReleaseMetadataStorage { diff --git a/Sources/PackageLoading/Target+PkgConfig.swift b/Sources/PackageLoading/Target+PkgConfig.swift index 935c6b0f269..3d20fb863bd 100644 --- a/Sources/PackageLoading/Target+PkgConfig.swift +++ b/Sources/PackageLoading/Target+PkgConfig.swift @@ -14,6 +14,7 @@ import Basics import PackageModel import class TSCBasic.Process +import struct TSCBasic.RegEx import enum TSCUtility.Platform @@ -63,6 +64,7 @@ public struct PkgConfigResult { public func pkgConfigArgs( for target: SystemLibraryTarget, pkgConfigDirectories: [AbsolutePath], + sdkRootPath: AbsolutePath? = nil, brewPrefix: AbsolutePath? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope @@ -98,7 +100,14 @@ public func pkgConfigArgs( let filtered = try allowlist(pcFile: pkgConfigName, flags: (pkgConfig.cFlags, pkgConfig.libs)) // Remove any default flags which compiler adds automatically. - let (cFlags, libs) = try removeDefaultFlags(cFlags: filtered.cFlags, libs: filtered.libs) + var (cFlags, libs) = try removeDefaultFlags(cFlags: filtered.cFlags, libs: filtered.libs) + + // Patch any paths containing an SDK to the current SDK + // See https://github.com/apple/swift-package-manager/issues/6439 + if let sdkRootPath = sdkRootPath { + cFlags = try patchSDKPaths(in: cFlags, to: sdkRootPath) + libs = try patchSDKPaths(in: libs, to: sdkRootPath) + } // Set the error if there are any disallowed flags. var error: Swift.Error? @@ -261,43 +270,64 @@ public func allowlist( return (filteredCFlags.allowed, filteredLibs.allowed, filteredCFlags.disallowed + filteredLibs.disallowed) } +/// Maps values of the given flag with the given transform, removing those where the transform returns `nil`. +private func patch(flag: String, in flags: [String], transform: (String) -> String?) throws -> [String] { + var result = [String]() + var it = flags.makeIterator() + while let current = it.next() { + if current == flag { + // Handle style. + guard let value = it.next() else { + throw InternalError("Expected associated value") + } + if let newValue = transform(value) { + result.append(flag) + result.append(newValue) + } + } else if current.starts(with: flag) { + // Handle style + let value = String(current.dropFirst(flag.count)) + if let newValue = transform(value) { + result.append(flag + newValue) + } + } else { + // Leave other flags as-is + result.append(current) + } + } + return result +} + +/// Removes the given flag from the given flags. +private func remove(flag: String, with expectedValue: String, from flags: [String]) throws -> [String] { + try patch(flag: flag, in: flags) { value in value == expectedValue ? nil : value } +} + /// Remove the default flags which are already added by the compiler. /// /// This behavior is similar to pkg-config cli tool and helps avoid conflicts between /// sdk and default search paths in macOS. public func removeDefaultFlags(cFlags: [String], libs: [String]) throws -> ([String], [String]) { - /// removes a flag from given array of flags. - func remove(flag: (String, String), from flags: [String]) throws -> [String] { - var result = [String]() - var it = flags.makeIterator() - while let curr = it.next() { - switch curr { - case flag.0: - // Check for style. - guard let val = it.next() else { - throw InternalError("Expected associated value") - } - // If we found a match, don't add these flags and just skip. - if val == flag.1 { continue } - // Otherwise add both the flags. - result.append(curr) - result.append(val) - - case flag.0 + flag.1: - // Check for style. - continue + return ( + try remove(flag: "-I", with: "/usr/include", from: cFlags), + try remove(flag: "-L", with: "/usr/lib", from: libs) + ) +} - default: - // Otherwise just append this flag. - result.append(curr) +/// Replaces any path containing *.sdk with the current SDK to avoid conflicts. +/// +/// See https://github.com/apple/swift-package-manager/issues/6439 for details. +public func patchSDKPaths(in flags: [String], to sdkRootPath: AbsolutePath) throws -> [String] { + let sdkRegex = try! RegEx(pattern: #"^.*\.sdk(\/.*|$)"#) + + return try ["-I", "-L"].reduce(flags) { (flags, flag) in + try patch(flag: flag, in: flags) { value in + guard let groups = sdkRegex.matchGroups(in: value).first else { + return value } + return sdkRootPath.pathString + groups[0] } - return result } - return ( - try remove(flag: ("-I", "/usr/include"), from: cFlags), - try remove(flag: ("-L", "/usr/lib"), from: libs) - ) } extension ObservabilityMetadata { diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index cff3bafe7fb..124b4bf8f7f 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import PackageModel /// A utility to compute the source/resource files of a target. @@ -554,7 +554,7 @@ public struct FileRuleDescription { /// Indicates that the file should be treated as ignored, without causing an unhandled-file warning. case ignored - /// Sentinal to indicate that no rule was chosen for a given file. + /// Sentinel to indicate that no rule was chosen for a given file. case none } diff --git a/Sources/PackageLoading/ToolsVersionParser.swift b/Sources/PackageLoading/ToolsVersionParser.swift index 6c3cade990f..a739905a0bc 100644 --- a/Sources/PackageLoading/ToolsVersionParser.swift +++ b/Sources/PackageLoading/ToolsVersionParser.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -@_implementationOnly import Foundation +import Foundation import PackageModel import struct TSCBasic.ByteString @@ -583,8 +583,6 @@ extension ManifestLoader { } } - // Otherwise, check if there is a version-specific manifest that has - // a higher tools version than the main Package.swift file. let contents: [String] do { contents = try fileSystem.getDirectoryContents(packagePath) } catch { throw ToolsVersionParser.Error.inaccessiblePackage(path: packagePath, reason: String(describing: error)) @@ -607,39 +605,47 @@ extension ManifestLoader { let regularManifest = packagePath.appending(component: Manifest.filename) - // Find the newest version-specific manifest that is compatible with the the current tools version. - if let versionSpecificCandidate = versionSpecificManifests.keys.sorted(by: >).first(where: { $0 <= currentToolsVersion }) { + // Try to get the tools version of the regular manifest. As the comment marker is missing, we default to + // tools version 3.1.0 (as documented). + let regularManifestToolsVersion: ToolsVersion + do { + regularManifestToolsVersion = try ToolsVersionParser.parse(manifestPath: regularManifest, fileSystem: fileSystem) + } + catch let error as UnsupportedToolsVersion where error.packageToolsVersion == .v3 { + regularManifestToolsVersion = .v3 + } - let versionSpecificManifest = packagePath.appending(component: versionSpecificManifests[versionSpecificCandidate]!) + // Find the newest version-specific manifest that is compatible with the current tools version. + guard let versionSpecificCandidate = versionSpecificManifests.keys.sorted(by: >).first(where: { $0 <= currentToolsVersion }) else { + // Otherwise, return the regular manifest. + return regularManifest + } - // SwiftPM 4 introduced tools-version designations; earlier packages default to tools version 3.1.0. - // See https://swift.org/blog/swift-package-manager-manifest-api-redesign. - let versionSpecificManifestToolsVersion: ToolsVersion - if versionSpecificCandidate < .v4 { - versionSpecificManifestToolsVersion = .v3 - } - else { - versionSpecificManifestToolsVersion = try ToolsVersionParser.parse(manifestPath: versionSpecificManifest, fileSystem: fileSystem) - } + let versionSpecificManifest = packagePath.appending(component: versionSpecificManifests[versionSpecificCandidate]!) - // Try to get the tools version of the regular manifest. At the comment marker is missing, we default to - // tools version 3.1.0 (as documented). - let regularManifestToolsVersion: ToolsVersion - do { - regularManifestToolsVersion = try ToolsVersionParser.parse(manifestPath: regularManifest, fileSystem: fileSystem) - } - catch let error as UnsupportedToolsVersion where error.packageToolsVersion == .v3 { - regularManifestToolsVersion = .v3 - } + // SwiftPM 4 introduced tools-version designations; earlier packages default to tools version 3.1.0. + // See https://swift.org/blog/swift-package-manager-manifest-api-redesign. + let versionSpecificManifestToolsVersion: ToolsVersion + if versionSpecificCandidate < .v4 { + versionSpecificManifestToolsVersion = .v3 + } + else { + versionSpecificManifestToolsVersion = try ToolsVersionParser.parse(manifestPath: versionSpecificManifest, fileSystem: fileSystem) + } - // Compare the tools version of this manifest with the regular - // manifest and use the version-specific manifest if it has - // a greater tools version. - if versionSpecificManifestToolsVersion > regularManifestToolsVersion { + // Compare the tools version of this manifest with the regular + // manifest and use the version-specific manifest if it has + // a greater tools version. + if versionSpecificManifestToolsVersion > regularManifestToolsVersion { + return versionSpecificManifest + } else { + // If there's no primary candidate, validate the regular manifest. + if regularManifestToolsVersion.validateToolsVersion(currentToolsVersion) { + return regularManifest + } else { + // If that's incompatible, use the closest version-specific manifest we got. return versionSpecificManifest } } - - return regularManifest } } diff --git a/Sources/PackageMetadata/PackageMetadata.swift b/Sources/PackageMetadata/PackageMetadata.swift index 5435ecb47a3..ea2249a2623 100644 --- a/Sources/PackageMetadata/PackageMetadata.swift +++ b/Sources/PackageMetadata/PackageMetadata.swift @@ -253,7 +253,7 @@ public struct PackageSearchClient { to: tempPath, progressHandler: nil ) - if self.repositoryProvider.isValidDirectory(tempPath), + if try self.repositoryProvider.isValidDirectory(tempPath), let repository = try self.repositoryProvider.open( repository: repositorySpecifier, at: tempPath diff --git a/Sources/PackageModel/ArtifactsArchiveMetadata.swift b/Sources/PackageModel/ArtifactsArchiveMetadata.swift index 4c08b01fb55..6c10ac564aa 100644 --- a/Sources/PackageModel/ArtifactsArchiveMetadata.swift +++ b/Sources/PackageModel/ArtifactsArchiveMetadata.swift @@ -15,6 +15,8 @@ import Foundation import struct TSCUtility.Version +public let artifactBundleExtension = "artifactbundle" + public struct ArtifactsArchiveMetadata: Equatable { public let schemaVersion: String public let artifacts: [String: Artifact] diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index 7874c4a3794..4ca120f9161 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -12,8 +12,10 @@ add_library(PackageModel BuildEnvironment.swift BuildFlags.swift BuildSettings.swift + DependencyMapper.swift Diagnostics.swift IdentityResolver.swift + InstalledSwiftPMConfiguration.swift Manifest/Manifest.swift Manifest/PackageConditionDescription.swift Manifest/PackageDependencyDescription.swift @@ -41,10 +43,17 @@ add_library(PackageModel Sources.swift SupportedLanguageExtension.swift SwiftLanguageVersion.swift - SwiftSDK.swift - SwiftSDKConfigurationStore.swift - SwiftSDKBundle.swift - Target.swift + SwiftSDKs/SwiftSDK.swift + SwiftSDKs/SwiftSDKConfigurationStore.swift + SwiftSDKs/SwiftSDKBundle.swift + SwiftSDKs/SwiftSDKBundleStore.swift + Target/BinaryTarget.swift + Target/ClangTarget.swift + Target/MixedTarget.swift + Target/PluginTarget.swift + Target/SwiftTarget.swift + Target/SystemLibraryTarget.swift + Target/Target.swift Toolchain.swift ToolchainConfiguration.swift Toolset.swift diff --git a/Sources/PackageModel/DependencyMapper.swift b/Sources/PackageModel/DependencyMapper.swift new file mode 100644 index 00000000000..95837d9495e --- /dev/null +++ b/Sources/PackageModel/DependencyMapper.swift @@ -0,0 +1,346 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 Basics +import Foundation +import enum TSCBasic.PathValidationError +import struct TSCUtility.Version + +public protocol DependencyMapper { + func mappedDependency(_ dependency: MappablePackageDependency, fileSystem: FileSystem) throws -> PackageDependency +} + +/// a utility for applying mirrors base mapping +public struct DefaultDependencyMapper: DependencyMapper { + let identityResolver: IdentityResolver + + public init( + identityResolver: IdentityResolver + ) { + self.identityResolver = identityResolver + } + + public func mappedDependency(_ dependency: MappablePackageDependency, fileSystem: FileSystem) throws -> PackageDependency { + // clean up variants of path based dependencies + let dependencyLocationString = try self.normalizeDependencyLocation( + dependency: dependency, + parentPackagePath: dependency.parentPackagePath, + fileSystem: fileSystem + ) ?? dependency.locationString + + // location mapping (aka mirrors) if any + let mappedLocationString = self.identityResolver.mappedLocation(for: dependencyLocationString) + + if mappedLocationString == dependencyLocationString { + // no mapping done, return based on the cleaned up location string + return try .init(dependency, newLocationString: mappedLocationString) + } else if PackageIdentity.plain(mappedLocationString).isRegistry { + // mapped to registry + return .registry( + identity: .plain(mappedLocationString), + requirement: try dependency.registryRequirement(for: mappedLocationString), + productFilter: dependency.productFilter + ) + } else if parseScheme(mappedLocationString) != nil { + // mapped to a URL, we assume a remote SCM location + let url = SourceControlURL(mappedLocationString) + let identity = try self.identityResolver.resolveIdentity(for: url) + return .remoteSourceControl( + identity: identity, + nameForTargetDependencyResolutionOnly: dependency.nameForTargetDependencyResolutionOnly, + url: url, + requirement: try dependency.sourceControlRequirement(for: mappedLocationString), + productFilter: dependency.productFilter + ) + + } else { + // mapped to a path, we assume a local SCM location + let localPath = try AbsolutePath(validating: mappedLocationString) + let identity = try self.identityResolver.resolveIdentity(for: localPath) + return .localSourceControl( + identity: identity, + nameForTargetDependencyResolutionOnly: dependency.nameForTargetDependencyResolutionOnly, + path: localPath, + requirement: try dependency.sourceControlRequirement(for: mappedLocationString), + productFilter: dependency.productFilter + ) + } + } + + private static let filePrefix = "file://" + + private func normalizeDependencyLocation( + dependency: MappablePackageDependency, + parentPackagePath: AbsolutePath, + fileSystem: FileSystem + ) throws -> String? { + switch dependency.kind { + // nothing to normalize + case .registry: + return .none + // location may be a relative path so we need to normalize it + case .fileSystem, .sourceControl: + let dependencyLocation = dependency.locationString + switch parseScheme(dependencyLocation) { + // if the location has no scheme, we treat it as a path (either absolute or relative). + case .none: + // if the dependency URL starts with '~/', try to expand it. + if dependencyLocation.hasPrefix("~/") { + return try AbsolutePath(validating: String(dependencyLocation.dropFirst(2)), relativeTo: fileSystem.homeDirectory).pathString + } + + // check if already absolute path + if let path = try? AbsolutePath(validating: dependencyLocation) { + return path.pathString + } + + // otherwise treat as relative path to the parent package + return try AbsolutePath(validating: dependencyLocation, relativeTo: parentPackagePath).pathString + // SwiftPM can't handle file locations with file:// scheme so we need to + // strip that. We need to design a Location data structure for SwiftPM. + case .some("file"): + let location = String(dependencyLocation.dropFirst(Self.filePrefix.count)) + let hostnameComponent = location.prefix(while: { $0 != "/" }) + guard hostnameComponent.isEmpty else { + if hostnameComponent == ".." { + throw DependencyMappingError.invalidFileURL("file:// URLs cannot be relative, did you mean to use '.package(path:)'?") + } + throw DependencyMappingError.invalidFileURL("file:// URLs with hostnames are not supported, are you missing a '/'?") + } + return try AbsolutePath(validating: location).pathString + // if the location has a scheme, assume a URL and nothing to normalize + case .some(_): + return .none + } + } + } +} + +// trivial representation for mapping +public struct MappablePackageDependency { + public let parentPackagePath: AbsolutePath + public let kind: Kind + public let productFilter: ProductFilter + + public init( + parentPackagePath: AbsolutePath, + kind: Kind, + productFilter: ProductFilter + ) { + self.parentPackagePath = parentPackagePath + self.kind = kind + self.productFilter = productFilter + } + + public enum Kind { + case fileSystem(name: String?, path: String) + case sourceControl(name: String?, location: String, requirement: PackageDependency.SourceControl.Requirement) + case registry(id: String, requirement: PackageDependency.Registry.Requirement) + } + + public enum Requirement { + case exact(Version) + case range(Range) + case revision(String) + case branch(String) + } +} + +extension MappablePackageDependency { + public init(_ seed: PackageDependency, parentPackagePath: AbsolutePath) { + switch seed { + case .fileSystem(let settings): + self.init( + parentPackagePath: parentPackagePath, + kind: .fileSystem( + name: settings.nameForTargetDependencyResolutionOnly, + path: settings.path.pathString + ), + productFilter: settings.productFilter + ) + case .sourceControl(let settings): + let locationString: String + switch settings.location { + case .local(let path): + locationString = path.pathString + case .remote(let url): + locationString = url.absoluteString + } + self.init( + parentPackagePath: parentPackagePath, + kind: .sourceControl( + name: settings.nameForTargetDependencyResolutionOnly, + location: locationString, + requirement: settings.requirement + ), + productFilter: settings.productFilter + ) + case .registry(let settings): + self.init( + parentPackagePath: parentPackagePath, + kind: .registry( + id: settings.identity.description, + requirement: settings.requirement + ), + productFilter: settings.productFilter + ) + } + } +} + +extension MappablePackageDependency { + fileprivate var locationString: String { + switch self.kind { + case .fileSystem(_, let path): + return path + case .sourceControl(_, let location, _): + return location + case .registry(let id, _): + return id + } + } + + fileprivate var nameForTargetDependencyResolutionOnly: String? { + switch self.kind { + case .fileSystem(let name, _): + return name + case .sourceControl(let name, _, _): + return name + case .registry: + return .none + } + } + + fileprivate func sourceControlRequirement(for location: String) throws -> PackageDependency.SourceControl.Requirement { + switch self.kind { + case .fileSystem(_, let path): + throw DependencyMappingError.invalidMapping("mapping of file system dependency (\(path)) to source control (\(location)) is invalid") + case .sourceControl(_, _, let requirement): + return requirement + case .registry(_, let requirement): + return .init(requirement) + } + } + + fileprivate func registryRequirement(for identity: String) throws -> PackageDependency.Registry.Requirement { + switch self.kind { + case .fileSystem(_, let path): + throw DependencyMappingError.invalidMapping("mapping of file system dependency (\(path)) to registry (\(identity)) is invalid") + case .sourceControl(_, let location, let requirement): + return try .init(requirement, from: location, to: identity) + case .registry(_, let requirement): + return requirement + } + } +} + +fileprivate extension PackageDependency.Registry.Requirement { + init(_ requirement: PackageDependency.SourceControl.Requirement, from location: String, to identity: String) throws { + switch requirement { + case .branch, .revision: + throw DependencyMappingError.invalidMapping("mapping of source control (\(location)) to registry (\(identity)) is invalid due to requirement information mismatch: cannot map branch or revision based dependencies to registry.") + case .exact(let value): + self = .exact(value) + case .range(let value): + self = .range(value) + } + } +} + +fileprivate extension PackageDependency.SourceControl.Requirement { + init(_ requirement: PackageDependency.Registry.Requirement) { + switch requirement { + case .exact(let value): + self = .exact(value) + case .range(let value): + self = .range(value) + } + } +} + +extension PackageDependency { + init(_ seed: MappablePackageDependency, newLocationString: String) throws { + switch seed.kind { + case .fileSystem(let name, _): + let path = try AbsolutePath(validating: newLocationString) + self = .fileSystem( + identity: .init(path: path), + nameForTargetDependencyResolutionOnly: name, + path: path, + productFilter: seed.productFilter + ) + case .sourceControl(let name, _, let requirement): + let identity: PackageIdentity + let location: SourceControl.Location + if parseScheme(newLocationString) != nil { + identity = .init(urlString: newLocationString) + location = .remote(.init(newLocationString)) + } else { + let path = try AbsolutePath(validating: newLocationString) + identity = .init(path: path) + location = .local(path) + } + self = .sourceControl( + identity: identity, + nameForTargetDependencyResolutionOnly: name, + location: location, + requirement: requirement, + productFilter: seed.productFilter + ) + case .registry(let id, let requirement): + self = .registry( + identity: .plain(id), + requirement: requirement, + productFilter: seed.productFilter + ) + } + } +} + +private enum DependencyMappingError: Swift.Error, CustomStringConvertible { + case invalidFileURL(_ message: String) + case invalidMapping(_ message: String) + + var description: String { + switch self { + case .invalidFileURL(let message): return message + case .invalidMapping(let message): return message + } + } +} + +/// Parses the URL type of a git repository +/// e.g. https://github.com/apple/swift returns "https" +/// e.g. git@github.com:apple/swift returns "git" +/// +/// This is *not* a generic URI scheme parser! +private func parseScheme(_ location: String) -> String? { + func prefixOfSplitBy(_ delimiter: String) -> String? { + let (head, tail) = location.spm_split(around: delimiter) + if tail == nil { + //not found + return nil + } else { + //found, return head + //lowercase the "scheme", as specified by the URI RFC (just in case) + return head.lowercased() + } + } + + for delim in ["://", "@"] { + if let found = prefixOfSplitBy(delim), !found.contains("/") { + return found + } + } + + return nil +} diff --git a/Sources/PackageModel/InstalledSwiftPMConfiguration.swift b/Sources/PackageModel/InstalledSwiftPMConfiguration.swift new file mode 100644 index 00000000000..e89788cb2ad --- /dev/null +++ b/Sources/PackageModel/InstalledSwiftPMConfiguration.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +public struct InstalledSwiftPMConfiguration: Codable { + public struct Version: Codable, CustomStringConvertible { + let major: Int + let minor: Int + let patch: Int + let prereleaseIdentifier: String? + + public init(major: Int, minor: Int, patch: Int, prereleaseIdentifier: String? = nil) { + self.major = major + self.minor = minor + self.patch = patch + self.prereleaseIdentifier = prereleaseIdentifier + } + + public var description: String { + return "\(major).\(minor).\(patch)\(prereleaseIdentifier.map { "-\($0)" } ?? "")" + } + } + + let version: Int + public let swiftSyntaxVersionForMacroTemplate: Version + + public static var `default`: InstalledSwiftPMConfiguration { + return .init(version: 0, swiftSyntaxVersionForMacroTemplate: .init(major: 509, minor: 0, patch: 0)) + } +} diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 62f8896ce88..7556b79f7b9 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -153,14 +153,22 @@ public final class Manifest: Sendable { return self.targets case .specific(let productFilter): let products = self.products.filter { productFilter.contains($0.name) } - targets = targetsRequired(for: products) + targets = self.targetsRequired(for: products) } - _requiredTargets[productFilter] = targets + self._requiredTargets[productFilter] = targets return targets } #else - return packageKind.isRoot ? self.targets : targetsRequired(for: products) + // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false + if let targets = self._requiredTargets[.nothing] { + return targets + } else { + let targets = self.packageKind.isRoot ? self.targets : self.targetsRequired(for: self.products) + // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false + self._requiredTargets[.nothing] = targets + return targets + } #endif } @@ -177,26 +185,34 @@ public final class Manifest: Sendable { return dependencies } #else - guard toolsVersion >= .v5_2 && !packageKind.isRoot else { + guard self.toolsVersion >= .v5_2 && !self.packageKind.isRoot else { return self.dependencies } - var requiredDependencies: Set = [] - for targetTriple in self.targetsRequired(for: products) { - for targetDependency in targetTriple.dependencies { - if let dependency = self.packageDependency(referencedBy: targetDependency) { - requiredDependencies.insert(dependency.identity) + // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false + if let dependencies = self._requiredDependencies[.nothing] { + return dependencies + } else { + var requiredDependencies: Set = [] + for target in self.targetsRequired(for: self.products) { + for targetDependency in target.dependencies { + if let dependency = self.packageDependency(referencedBy: targetDependency) { + requiredDependencies.insert(dependency.identity) + } } - } - targetTriple.pluginUsages?.forEach { - if let dependency = self.packageDependency(referencedBy: $0) { - requiredDependencies.insert(dependency.identity) + target.pluginUsages?.forEach { + if let dependency = self.packageDependency(referencedBy: $0) { + requiredDependencies.insert(dependency.identity) + } } } - } - return self.dependencies.filter { requiredDependencies.contains($0.identity) } + let dependencies = self.dependencies.filter { requiredDependencies.contains($0.identity) } + // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false + self._requiredDependencies[.nothing] = dependencies + return dependencies + } #endif } @@ -248,20 +264,25 @@ public final class Manifest: Sendable { /// Returns the package dependencies required for building the provided targets. /// - /// The returned dependencies have their particular product filters registered. (To determine product filters without removing any dependencies from the list, specify `keepUnused: true`.) + /// The returned dependencies have their particular product filters registered. (To determine product filters + /// without removing any dependencies from the list, specify `keepUnused: true`.) private func dependenciesRequired( for targets: [TargetDescription], keepUnused: Bool = false ) -> [PackageDependency] { var registry: (known: [PackageIdentity: ProductFilter], unknown: Set) = ([:], []) - let availablePackages = Set(dependencies.lazy.map(\.identity)) + let availablePackages = Set(self.dependencies.lazy.map(\.identity)) for target in targets { for targetDependency in target.dependencies { - register(targetDependency: targetDependency, registry: ®istry, availablePackages: availablePackages) + self.register( + targetDependency: targetDependency, + registry: ®istry, + availablePackages: availablePackages + ) } for requiredPlugIn in target.pluginUsages ?? [] { - register(requiredPlugIn: requiredPlugIn, registry: ®istry, availablePackages: availablePackages) + self.register(requiredPlugIn: requiredPlugIn, registry: ®istry, availablePackages: availablePackages) } } @@ -275,7 +296,7 @@ public final class Manifest: Sendable { } } - return dependencies.compactMap { dependency in + return self.dependencies.compactMap { dependency in if let filter = associations[dependency.identity] { return dependency.filtered(by: filter) } else if keepUnused { @@ -304,7 +325,7 @@ public final class Manifest: Sendable { return nil } - return packageDependency(referencedBy: packageName) + return self.packageDependency(referencedBy: packageName) } /// Finds the package dependency referenced by the specified plugin usage. @@ -314,7 +335,7 @@ public final class Manifest: Sendable { ) -> PackageDependency? { switch pluginUsage { case .plugin(_, .some(let package)): - return packageDependency(referencedBy: package) + return self.packageDependency(referencedBy: package) default: return nil } @@ -335,7 +356,7 @@ public final class Manifest: Sendable { /// If none is found, it is assumed that the string is the package identity itself /// (although it may actually be a dangling reference diagnosed later). private func packageIdentity(referencedBy packageName: String) -> PackageIdentity { - packageDependency(referencedBy: packageName)?.identity + self.packageDependency(referencedBy: packageName)?.identity ?? .plain(packageName) } @@ -355,9 +376,9 @@ public final class Manifest: Sendable { break case .product(let product, let package, _, _): if let package { // ≥ 5.2 - if !register( + if !self.register( product: product, - inPackage: packageIdentity(referencedBy: package), + inPackage: self.packageIdentity(referencedBy: package), registry: ®istry.known, availablePackages: availablePackages ) { @@ -369,9 +390,9 @@ public final class Manifest: Sendable { registry.unknown.insert(product) } case .byName(let product, _): - if toolsVersion < .v5_2 { + if self.toolsVersion < .v5_2 { // A by‐name entry might be a product from anywhere. - if targets.contains(where: { $0.name == product }) { + if self.targets.contains(where: { $0.name == product }) { // Save the resolver some effort if it is known to only be a target anyway. break } else { @@ -379,14 +400,14 @@ public final class Manifest: Sendable { } } else { // ≥ 5.2 // If a by‐name entry is a product, it must be in a package of the same name. - if !register( + if !self.register( product: product, - inPackage: packageIdentity(referencedBy: product), + inPackage: self.packageIdentity(referencedBy: product), registry: ®istry.known, availablePackages: availablePackages ) { // If it doesn’t match a package, it should be a target, not a product. - if targets.contains(where: { $0.name == product }) { + if self.targets.contains(where: { $0.name == product }) { break } else { // But in case the user is trying to reference a product, @@ -414,9 +435,9 @@ public final class Manifest: Sendable { switch requiredPlugIn { case .plugin(let name, let package): if let package { - if !register( + if !self.register( product: name, - inPackage: packageIdentity(referencedBy: package), + inPackage: self.packageIdentity(referencedBy: package), registry: ®istry.known, availablePackages: availablePackages ) { @@ -438,7 +459,8 @@ public final class Manifest: Sendable { /// - registry: The registry in which to record the association. /// - availablePackages: The set of available packages. /// - /// - Returns: `true` if the particular dependency was found and the product was registered; `false` if no matching dependency was found and the product has not yet been handled. + /// - Returns: `true` if the particular dependency was found and the product was registered; `false` if no matching + /// dependency was found and the product has not yet been handled. private func register( product: String, inPackage package: PackageIdentity, @@ -460,20 +482,20 @@ public final class Manifest: Sendable { public func targetsWithCommonSourceRoot(type: TargetDescription.TargetType) -> [TargetDescription] { switch type { case .test: - return targets.filter { $0.type == .test } + return self.targets.filter { $0.type == .test } case .plugin: - return targets.filter { $0.type == .plugin } + return self.targets.filter { $0.type == .plugin } default: - return targets.filter { $0.type != .test && $0.type != .plugin } + return self.targets.filter { $0.type != .test && $0.type != .plugin } } } /// Returns true if the tools version is >= 5.9 and the number of targets with a common source root is 1. public func shouldSuggestRelaxedSourceDir(type: TargetDescription.TargetType) -> Bool { - guard toolsVersion >= .v5_9 else { + guard self.toolsVersion >= .v5_9 else { return false } - return targetsWithCommonSourceRoot(type: type).count == 1 + return self.targetsWithCommonSourceRoot(type: type).count == 1 } } diff --git a/Sources/PackageModel/MinimumDeploymentTarget.swift b/Sources/PackageModel/MinimumDeploymentTarget.swift index 5d0e5a63372..735a2b64c4f 100644 --- a/Sources/PackageModel/MinimumDeploymentTarget.swift +++ b/Sources/PackageModel/MinimumDeploymentTarget.swift @@ -17,15 +17,16 @@ import struct TSCBasic.ProcessResult public struct MinimumDeploymentTarget { - public let xcTestMinimumDeploymentTargets: [PackageModel.Platform:PlatformVersion] + public let xcTestMinimumDeploymentTargets = ThreadSafeKeyValueStore() public static let `default`: MinimumDeploymentTarget = .init() - public init() { - xcTestMinimumDeploymentTargets = PlatformRegistry.default.knownPlatforms.reduce([PackageModel.Platform:PlatformVersion]()) { - var dict = $0 - dict[$1] = Self.computeXCTestMinimumDeploymentTarget(for: $1) - return dict + private init() { + } + + public func computeXCTestMinimumDeploymentTarget(for platform: PackageModel.Platform) -> PlatformVersion { + self.xcTestMinimumDeploymentTargets.memoize(platform) { + return Self.computeXCTestMinimumDeploymentTarget(for: platform) } } diff --git a/Sources/PackageModel/PackageIdentity.swift b/Sources/PackageModel/PackageIdentity.swift index 327681b3df2..b0c329cd251 100644 --- a/Sources/PackageModel/PackageIdentity.swift +++ b/Sources/PackageModel/PackageIdentity.swift @@ -419,50 +419,71 @@ public struct CanonicalPackageLocation: Equatable, CustomStringConvertible { /// Instantiates an instance of the conforming type from a string representation. public init(_ string: String) { - var description = string.precomposedStringWithCanonicalMapping.lowercased() + self.description = computeCanonicalLocation(string).description + } +} - // Remove the scheme component, if present. - let detectedScheme = description.dropSchemeComponentPrefixIfPresent() +/// Similar to `CanonicalPackageLocation` but differentiates based on the scheme. +public struct CanonicalPackageURL: Equatable, CustomStringConvertible { + public let description: String + public let scheme: String? - // Remove the userinfo subcomponent (user / password), if present. - if case (let user, _)? = description.dropUserinfoSubcomponentPrefixIfPresent() { - // If a user was provided, perform tilde expansion, if applicable. - description.replaceFirstOccurenceIfPresent(of: "/~/", with: "/~\(user)/") - } + public init(_ string: String) { + let location = computeCanonicalLocation(string) + self.description = location.description + self.scheme = location.scheme + } +} - // Remove the port subcomponent, if present. - description.removePortComponentIfPresent() +private func computeCanonicalLocation(_ string: String) -> (description: String, scheme: String?) { + var description = string.precomposedStringWithCanonicalMapping.lowercased() - // Remove the fragment component, if present. - description.removeFragmentComponentIfPresent() + // Remove the scheme component, if present. + let detectedScheme = description.dropSchemeComponentPrefixIfPresent() + var scheme = detectedScheme - // Remove the query component, if present. - description.removeQueryComponentIfPresent() + // Remove the userinfo subcomponent (user / password), if present. + if case (let user, _)? = description.dropUserinfoSubcomponentPrefixIfPresent() { + // If a user was provided, perform tilde expansion, if applicable. + description.replaceFirstOccurenceIfPresent(of: "/~/", with: "/~\(user)/") - // Accomodate "`scp`-style" SSH URLs - if detectedScheme == nil || detectedScheme == "ssh" { - description.replaceFirstOccurenceIfPresent(of: ":", before: description.firstIndex(of: "/"), with: "/") + if user == "git", scheme == nil { + scheme = "ssh" } + } - // Split the remaining string into path components, - // filtering out empty path components and removing valid percent encodings. - var components = description.split(omittingEmptySubsequences: true, whereSeparator: isSeparator) - .compactMap { $0.removingPercentEncoding ?? String($0) } + // Remove the port subcomponent, if present. + description.removePortComponentIfPresent() - // Remove the `.git` suffix from the last path component. - var lastPathComponent = components.popLast() ?? "" - lastPathComponent.removeSuffixIfPresent(".git") - components.append(lastPathComponent) + // Remove the fragment component, if present. + description.removeFragmentComponentIfPresent() - description = components.joined(separator: "/") + // Remove the query component, if present. + description.removeQueryComponentIfPresent() - // Prepend a leading slash for file URLs and paths - if detectedScheme == "file" || string.first.flatMap(isSeparator) ?? false { - description.insert("/", at: description.startIndex) - } + // Accomodate "`scp`-style" SSH URLs + if detectedScheme == nil || detectedScheme == "ssh" { + description.replaceFirstOccurenceIfPresent(of: ":", before: description.firstIndex(of: "/"), with: "/") + } + + // Split the remaining string into path components, + // filtering out empty path components and removing valid percent encodings. + var components = description.split(omittingEmptySubsequences: true, whereSeparator: isSeparator) + .compactMap { $0.removingPercentEncoding ?? String($0) } - self.description = description + // Remove the `.git` suffix from the last path component. + var lastPathComponent = components.popLast() ?? "" + lastPathComponent.removeSuffixIfPresent(".git") + components.append(lastPathComponent) + + description = components.joined(separator: "/") + + // Prepend a leading slash for file URLs and paths + if detectedScheme == "file" || string.first.flatMap(isSeparator) ?? false { + description.insert("/", at: description.startIndex) } + + return (description, scheme) } #if os(Windows) diff --git a/Sources/PackageModel/PackageReference.swift b/Sources/PackageModel/PackageReference.swift index fa2f4049a46..7b62bfe511d 100644 --- a/Sources/PackageModel/PackageReference.swift +++ b/Sources/PackageModel/PackageReference.swift @@ -161,7 +161,24 @@ extension PackageReference: Equatable { // TODO: consider rolling into Equatable public func equalsIncludingLocation(_ other: PackageReference) -> Bool { - return self.identity == other.identity && self.canonicalLocation == other.canonicalLocation + if self.identity != other.identity { + return false + } + if self.canonicalLocation != other.canonicalLocation { + return false + } + switch (self.kind, other.kind) { + case (.remoteSourceControl(let lurl), .remoteSourceControl(let rurl)): + return lurl.canonicalURL == rurl.canonicalURL + default: + return true + } + } +} + +extension SourceControlURL { + var canonicalURL: CanonicalPackageURL { + CanonicalPackageURL(self.absoluteString) } } diff --git a/Sources/PackageModel/Platform.swift b/Sources/PackageModel/Platform.swift index 101d5b96969..b74d94b93f9 100644 --- a/Sources/PackageModel/Platform.swift +++ b/Sources/PackageModel/Platform.swift @@ -39,8 +39,8 @@ public struct Platform: Equatable, Hashable, Codable { public static let macOS: Platform = Platform(name: "macos", oldestSupportedVersion: "10.13") public static let macCatalyst: Platform = Platform(name: "maccatalyst", oldestSupportedVersion: "13.0") - public static let iOS: Platform = Platform(name: "ios", oldestSupportedVersion: "11.0") - public static let tvOS: Platform = Platform(name: "tvos", oldestSupportedVersion: "11.0") + public static let iOS: Platform = Platform(name: "ios", oldestSupportedVersion: "12.0") + public static let tvOS: Platform = Platform(name: "tvos", oldestSupportedVersion: "12.0") public static let watchOS: Platform = Platform(name: "watchos", oldestSupportedVersion: "4.0") public static let visionOS: Platform = Platform(name: "visionos", oldestSupportedVersion: "1.0") public static let driverKit: Platform = Platform(name: "driverkit", oldestSupportedVersion: "19.0") @@ -54,16 +54,56 @@ public struct Platform: Equatable, Hashable, Codable { public struct SupportedPlatforms { public let declared: [SupportedPlatform] - public let derived: [SupportedPlatform] + private let derivedXCTestPlatformProvider: ((Platform) -> PlatformVersion?)? - public init(declared: [SupportedPlatform], derived: [SupportedPlatform]) { + public init(declared: [SupportedPlatform], derivedXCTestPlatformProvider: ((_ declared: Platform) -> PlatformVersion?)?) { self.declared = declared - self.derived = derived + self.derivedXCTestPlatformProvider = derivedXCTestPlatformProvider } /// Returns the supported platform instance for the given platform. - public func getDerived(for platform: Platform) -> SupportedPlatform? { - return self.derived.first(where: { $0.platform == platform }) + public func getDerived(for platform: Platform, usingXCTest: Bool) -> SupportedPlatform { + // derived platform based on known minimum deployment target logic + if let declaredPlatform = self.declared.first(where: { $0.platform == platform }) { + var version = declaredPlatform.version + + if usingXCTest, let xcTestMinimumDeploymentTarget = derivedXCTestPlatformProvider?(platform), version < xcTestMinimumDeploymentTarget { + version = xcTestMinimumDeploymentTarget + } + + // If the declared version is smaller than the oldest supported one, we raise the derived version to that. + if version < platform.oldestSupportedVersion { + version = platform.oldestSupportedVersion + } + + return SupportedPlatform( + platform: declaredPlatform.platform, + version: version, + options: declaredPlatform.options + ) + } else { + let minimumSupportedVersion: PlatformVersion + if usingXCTest, let xcTestMinimumDeploymentTarget = derivedXCTestPlatformProvider?(platform), xcTestMinimumDeploymentTarget > platform.oldestSupportedVersion { + minimumSupportedVersion = xcTestMinimumDeploymentTarget + } else { + minimumSupportedVersion = platform.oldestSupportedVersion + } + + let oldestSupportedVersion: PlatformVersion + if platform == .macCatalyst { + let iOS = getDerived(for: .iOS, usingXCTest: usingXCTest) + // If there was no deployment target specified for Mac Catalyst, fall back to the iOS deployment target. + oldestSupportedVersion = max(minimumSupportedVersion, iOS.version) + } else { + oldestSupportedVersion = minimumSupportedVersion + } + + return SupportedPlatform( + platform: platform, + version: oldestSupportedVersion, + options: [] + ) + } } } diff --git a/Sources/PackageModel/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKBundle.swift deleted file mode 100644 index 1aa3c237240..00000000000 --- a/Sources/PackageModel/SwiftSDKBundle.swift +++ /dev/null @@ -1,487 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2022-2023 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 Basics - -import struct Foundation.URL -import protocol TSCBasic.FileSystem -import struct TSCBasic.RegEx - -/// Represents an `.artifactbundle` on the filesystem that contains a Swift SDK. -public struct SwiftSDKBundle { - public struct Variant: Equatable { - let metadata: ArtifactsArchiveMetadata.Variant - let swiftSDKs: [SwiftSDK] - } - - // Path to the bundle root directory. - public let path: AbsolutePath - - /// Mapping of artifact IDs to variants available for a corresponding artifact. - public fileprivate(set) var artifacts = [String: [Variant]]() - - /// Name of the destination bundle that can be used to distinguish it from other bundles. - public var name: String { path.basename } - - /// Lists all valid cross-compilation destination bundles in a given directory. - /// - Parameters: - /// - swiftSDKsDirectory: the directory to scan for destination bundles. - /// - fileSystem: the filesystem the directory is located on. - /// - observabilityScope: observability scope to report bundle validation errors. - /// - Returns: an array of valid destination bundles. - public static func getAllValidBundles( - swiftSDKsDirectory: AbsolutePath, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) throws -> [Self] { - // Get absolute paths to available destination bundles. - try fileSystem.getDirectoryContents(swiftSDKsDirectory).filter { - $0.hasSuffix(BinaryTarget.Kind.artifactsArchive.fileExtension) - }.map { - swiftSDKsDirectory.appending(components: [$0]) - }.compactMap { - do { - // Enumerate available bundles and parse manifests for each of them, then validate supplied - // destinations. - return try Self.parseAndValidate( - bundlePath: $0, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - } catch { - observabilityScope.emit( - warning: "Couldn't parse `info.json` manifest of a destination bundle at \($0)", - underlyingError: error - ) - return nil - } - } - } - - /// Select destinations matching a given query and host triple from all destinations available in a directory. - /// - Parameters: - /// - destinationsDirectory: the directory to scan for destination bundles. - /// - fileSystem: the filesystem the directory is located on. - /// - query: either an artifact ID or target triple to filter with. - /// - hostTriple: triple of the host building with these destinations. - /// - observabilityScope: observability scope to log warnings about multiple matches. - /// - Returns: `Destination` value matching `query` either by artifact ID or target triple, `nil` if none found. - public static func selectBundle( - fromBundlesAt destinationsDirectory: AbsolutePath?, - fileSystem: FileSystem, - matching selector: String, - hostTriple: Triple, - observabilityScope: ObservabilityScope - ) throws -> SwiftSDK { - guard let destinationsDirectory else { - throw StringError( - """ - No directory found for installed Swift SDKs, specify one - with `--experimental-swift-sdks-path` option. - """ - ) - } - - let validBundles = try SwiftSDKBundle.getAllValidBundles( - swiftSDKsDirectory: destinationsDirectory, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - - guard !validBundles.isEmpty else { - throw StringError( - "No valid Swift SDK bundles found at \(destinationsDirectory)." - ) - } - - guard var selectedDestination = validBundles.selectDestination( - matching: selector, - hostTriple: hostTriple, - observabilityScope: observabilityScope - ) else { - throw StringError( - """ - No Swift SDK found matching query `\(selector)` and host triple - `\(hostTriple.tripleString)`. Use `swift experimental-sdk list` command to see - available destinations. - """ - ) - } - - selectedDestination.applyPathCLIOptions() - - return selectedDestination - } - - /// Installs a destination bundle from a given path or URL to a destinations installation directory. - /// - Parameters: - /// - bundlePathOrURL: A string passed on the command line, which is either an absolute or relative to a current - /// working directory path, or a URL to a destination artifact bundle. - /// - destinationsDirectory: A directory where the destination artifact bundle should be installed. - /// - fileSystem: File system on which all of the file operations should run. - /// - observabilityScope: Observability scope for reporting warnings and errors. - public static func install( - bundlePathOrURL: String, - swiftSDKsDirectory: AbsolutePath, - _ fileSystem: some FileSystem, - _ archiver: some Archiver, - _ observabilityScope: ObservabilityScope - ) async throws { - _ = try await withTemporaryDirectory( - removeTreeOnDeinit: true - ) { temporaryDirectory in - let bundlePath: AbsolutePath - - if - let bundleURL = URL(string: bundlePathOrURL), - let scheme = bundleURL.scheme, - scheme == "http" || scheme == "https" - { - let bundleName = bundleURL.lastPathComponent - let downloadedBundlePath = temporaryDirectory.appending(component: bundleName) - - let client = HTTPClient() - var request = HTTPClientRequest.download( - url: bundleURL, - fileSystem: fileSystem, - destination: downloadedBundlePath - ) - request.options.validResponseCodes = [200] - _ = try await client.execute( - request, - observabilityScope: observabilityScope, - progress: nil - ) - - bundlePath = downloadedBundlePath - - print("Swift SDK bundle successfully downloaded from `\(bundleURL)`.") - } else if - let cwd: AbsolutePath = fileSystem.currentWorkingDirectory, - let originalBundlePath = try? AbsolutePath(validating: bundlePathOrURL, relativeTo: cwd) - { - bundlePath = originalBundlePath - } else { - throw SwiftSDKError.invalidPathOrURL(bundlePathOrURL) - } - - try await installIfValid( - bundlePath: bundlePath, - destinationsDirectory: swiftSDKsDirectory, - temporaryDirectory: temporaryDirectory, - fileSystem, - archiver, - observabilityScope - ) - } - - print("Swift SDK bundle at `\(bundlePathOrURL)` successfully installed.") - } - - /// Unpacks a destination bundle if it has an archive extension in its filename. - /// - Parameters: - /// - bundlePath: Absolute path to a destination bundle to unpack if needed. - /// - temporaryDirectory: Absolute path to a temporary directory in which the bundle can be unpacked if needed. - /// - fileSystem: A file system to operate on that contains the given paths. - /// - archiver: Archiver to use for unpacking. - /// - Returns: Path to an unpacked destination bundle if unpacking is needed, value of `bundlePath` is returned - /// otherwise. - private static func unpackIfNeeded( - bundlePath: AbsolutePath, - destinationsDirectory: AbsolutePath, - temporaryDirectory: AbsolutePath, - _ fileSystem: some FileSystem, - _ archiver: some Archiver - ) async throws -> AbsolutePath { - let regex = try RegEx(pattern: "(.+\\.artifactbundle).*") - - guard let bundleName = bundlePath.components.last else { - throw SwiftSDKError.invalidPathOrURL(bundlePath.pathString) - } - - guard let unpackedBundleName = regex.matchGroups(in: bundleName).first?.first else { - throw SwiftSDKError.invalidBundleName(bundleName) - } - - let installedBundlePath = destinationsDirectory.appending(component: unpackedBundleName) - guard !fileSystem.exists(installedBundlePath) else { - throw SwiftSDKError.swiftSDKBundleAlreadyInstalled(bundleName: unpackedBundleName) - } - - // If there's no archive extension on the bundle name, assuming it's not archived and returning the same path. - guard unpackedBundleName != bundleName else { - return bundlePath - } - - print("\(bundleName) is assumed to be an archive, unpacking...") - - try await archiver.extract(from: bundlePath, to: temporaryDirectory) - - return temporaryDirectory.appending(component: unpackedBundleName) - } - - /// Installs an unpacked destination bundle to a destinations installation directory. - /// - Parameters: - /// - bundlePath: absolute path to an unpacked destination bundle directory. - /// - destinationsDirectory: a directory where the destination artifact bundle should be installed. - /// - fileSystem: file system on which all of the file operations should run. - /// - observabilityScope: observability scope for reporting warnings and errors. - private static func installIfValid( - bundlePath: AbsolutePath, - destinationsDirectory: AbsolutePath, - temporaryDirectory: AbsolutePath, - _ fileSystem: some FileSystem, - _ archiver: some Archiver, - _ observabilityScope: ObservabilityScope - ) async throws { - #if os(macOS) - // Check the quarantine attribute on bundles downloaded manually in the browser. - guard !fileSystem.hasAttribute(.quarantine, bundlePath) else { - throw SwiftSDKError.quarantineAttributePresent(bundlePath: bundlePath) - } - #endif - - let unpackedBundlePath = try await unpackIfNeeded( - bundlePath: bundlePath, - destinationsDirectory: destinationsDirectory, - temporaryDirectory: temporaryDirectory, - fileSystem, - archiver - ) - - guard - fileSystem.isDirectory(unpackedBundlePath), - let bundleName = unpackedBundlePath.components.last - else { - throw SwiftSDKError.pathIsNotDirectory(bundlePath) - } - - let installedBundlePath = destinationsDirectory.appending(component: bundleName) - - let validatedBundle = try Self.parseAndValidate( - bundlePath: unpackedBundlePath, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - let newArtifactIDs = validatedBundle.artifacts.keys - - let installedBundles = try Self.getAllValidBundles( - swiftSDKsDirectory: destinationsDirectory, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - - for installedBundle in installedBundles { - for artifactID in installedBundle.artifacts.keys { - guard !newArtifactIDs.contains(artifactID) else { - throw SwiftSDKError.swiftSDKArtifactAlreadyInstalled( - installedBundleName: installedBundle.name, - newBundleName: validatedBundle.name, - artifactID: artifactID - ) - } - } - } - - try fileSystem.copy(from: unpackedBundlePath, to: installedBundlePath) - } - - /// Parses metadata of an `.artifactbundle` and validates it as a bundle containing - /// cross-compilation Swift SDKs. - /// - Parameters: - /// - bundlePath: path to the bundle root directory. - /// - fileSystem: filesystem containing the bundle. - /// - observabilityScope: observability scope to log validation warnings. - /// - Returns: Validated `SwiftSDKBundle` containing validated `Destination` values for - /// each artifact and its variants. - private static func parseAndValidate( - bundlePath: AbsolutePath, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) throws -> Self { - let parsedManifest = try ArtifactsArchiveMetadata.parse( - fileSystem: fileSystem, - rootPath: bundlePath - ) - - return try parsedManifest.validateSwiftSDKBundle( - bundlePath: bundlePath, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - } -} - -extension ArtifactsArchiveMetadata { - fileprivate func validateSwiftSDKBundle( - bundlePath: AbsolutePath, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) throws -> SwiftSDKBundle { - var result = SwiftSDKBundle(path: bundlePath) - - for (artifactID, artifactMetadata) in artifacts { - if artifactMetadata.type == .crossCompilationDestination { - observabilityScope.emit( - warning: """ - `crossCompilationDestination` bundle metadata value used for `\(artifactID)` is deprecated, \ - use `swiftSDK` instead. - """ - ) - } else { - guard artifactMetadata.type == .swiftSDK else { continue } - } - - var variants = [SwiftSDKBundle.Variant]() - - for variantMetadata in artifactMetadata.variants { - let variantConfigurationPath = bundlePath - .appending(variantMetadata.path) - .appending("swift-sdk.json") - - guard fileSystem.exists(variantConfigurationPath) else { - observabilityScope.emit( - .warning( - """ - Swift SDK metadata file not found at \( - variantConfigurationPath - ) for a variant of artifact \(artifactID) - """ - ) - ) - - continue - } - - do { - let swiftSDKs = try SwiftSDK.decode( - fromFile: variantConfigurationPath, fileSystem: fileSystem, - observabilityScope: observabilityScope - ) - - variants.append(.init(metadata: variantMetadata, swiftSDKs: swiftSDKs)) - } catch { - observabilityScope.emit( - warning: "Couldn't parse Swift SDK artifact metadata at \(variantConfigurationPath)", - underlyingError: error - ) - } - } - - result.artifacts[artifactID] = variants - } - - return result - } -} - -extension [SwiftSDKBundle] { - /// Select a destination with a given artifact ID from a `self` array of available destinations. - /// - Parameters: - /// - id: artifact ID of the destination to look up. - /// - hostTriple: triple of the machine on which the destination is building. - /// - targetTriple: triple of the machine for which the destination is building. - /// - Returns: `Destination` value with a given artifact ID, `nil` if none found. - public func selectSwiftSDK(id: String, hostTriple: Triple, targetTriple: Triple) -> SwiftSDK? { - for bundle in self { - for (artifactID, variants) in bundle.artifacts { - guard artifactID == id else { - continue - } - - for variant in variants { - guard variant.metadata.supportedTriples.contains(hostTriple) else { - continue - } - - return variant.swiftSDKs.first { $0.targetTriple == targetTriple } - } - } - } - - return nil - } - - /// Select destinations matching a given selector and host triple from a `self` array of available destinations. - /// - Parameters: - /// - selector: either an artifact ID or target triple to filter with. - /// - hostTriple: triple of the host building with these destinations. - /// - observabilityScope: observability scope to log warnings about multiple matches. - /// - Returns: `Destination` value matching `query` either by artifact ID or target triple, `nil` if none found. - public func selectDestination( - matching selector: String, - hostTriple: Triple, - observabilityScope: ObservabilityScope - ) -> SwiftSDK? { - var matchedByID: (path: AbsolutePath, variant: SwiftSDKBundle.Variant, destination: SwiftSDK)? - var matchedByTriple: (path: AbsolutePath, variant: SwiftSDKBundle.Variant, destination: SwiftSDK)? - - for bundle in self { - for (artifactID, variants) in bundle.artifacts { - for variant in variants { - guard variant.metadata.supportedTriples.contains(hostTriple) else { - continue - } - - for destination in variant.swiftSDKs { - if artifactID == selector { - if let matchedByID { - observabilityScope.emit( - warning: - """ - multiple destinations match ID `\(artifactID)` and host triple \( - hostTriple.tripleString - ), selected one at \( - matchedByID.path.appending(matchedByID.variant.metadata.path) - ) - """ - ) - } else { - matchedByID = (bundle.path, variant, destination) - } - } - - if destination.targetTriple?.tripleString == selector { - if let matchedByTriple { - observabilityScope.emit( - warning: - """ - multiple Swift SDKs match target triple `\(selector)` and host triple \( - hostTriple.tripleString - ), selected one at \( - matchedByTriple.path.appending(matchedByTriple.variant.metadata.path) - ) - """ - ) - } else { - matchedByTriple = (bundle.path, variant, destination) - } - } - } - } - } - } - - if let matchedByID, let matchedByTriple, matchedByID != matchedByTriple { - observabilityScope.emit( - warning: - """ - multiple Swift SDKs match the query `\(selector)` and host triple \( - hostTriple.tripleString - ), selected one at \(matchedByID.path.appending(matchedByID.variant.metadata.path)) - """ - ) - } - - return matchedByID?.destination ?? matchedByTriple?.destination - } -} diff --git a/Sources/PackageModel/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift similarity index 87% rename from Sources/PackageModel/SwiftSDK.swift rename to Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index 657b4e18d55..e8b0c5b2e3c 100644 --- a/Sources/PackageModel/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -20,6 +20,9 @@ import struct TSCUtility.Version /// Errors related to Swift SDKs. public enum SwiftSDKError: Swift.Error { + /// A bundle archive should contain at least one directory with the `.artifactbundle` extension. + case invalidBundleArchive(AbsolutePath) + /// A passed argument is neither a valid file system path nor a URL. case invalidPathOrURL(String) @@ -62,6 +65,11 @@ public enum SwiftSDKError: Swift.Error { extension SwiftSDKError: CustomStringConvertible { public var description: String { switch self { + case .invalidBundleArchive(let archivePath): + return """ + Swift SDK archive at `\(archivePath)` does not contain at least one directory with the \ + `.artifactbundle` extension. + """ case .invalidPathOrURL(let argument): return "`\(argument)` is neither a valid filesystem path nor a URL." case .invalidSchemaVersion: @@ -138,6 +146,9 @@ public struct SwiftSDK: Equatable { /// The architectures to build for. We build for host architecture if this is empty. public var architectures: [String]? = nil + /// Whether or not the receiver supports testing. + public let supportsTesting: Bool + /// Root directory path of the SDK used to compile for the target triple. @available(*, deprecated, message: "use `pathsConfiguration.sdkRootPath` instead") public var sdk: AbsolutePath? { @@ -244,29 +255,29 @@ public struct SwiftSDK: Equatable { /// Initialize paths configuration from values deserialized using v3 schema. /// - Parameters: - /// - properties: properties of the destination for the given triple. - /// - destinationDirectory: directory used for converting relative paths in `properties` to absolute paths. + /// - properties: properties of the Swift SDK for the given triple. + /// - swiftSDKDirectory: directory used for converting relative paths in `properties` to absolute paths. fileprivate init( _ properties: SerializedDestinationV3.TripleProperties, - destinationDirectory: AbsolutePath? = nil + swiftSDKDirectory: AbsolutePath? = nil ) throws { - if let destinationDirectory { + if let swiftSDKDirectory { self.init( - sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: destinationDirectory), + sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: swiftSDKDirectory), swiftResourcesPath: try properties.swiftResourcesPath.map { - try AbsolutePath(validating: $0, relativeTo: destinationDirectory) + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) }, swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { - try AbsolutePath(validating: $0, relativeTo: destinationDirectory) + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) }, includeSearchPaths: try properties.includeSearchPaths?.map { - try AbsolutePath(validating: $0, relativeTo: destinationDirectory) + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) }, librarySearchPaths: try properties.librarySearchPaths?.map { - try AbsolutePath(validating: $0, relativeTo: destinationDirectory) + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) }, toolsetPaths: try properties.toolsetPaths?.map { - try AbsolutePath(validating: $0, relativeTo: destinationDirectory) + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) } ) } else { @@ -364,10 +375,10 @@ public struct SwiftSDK: Equatable { } } - /// Configuration of file system paths used by this destination when building. + /// Configuration of file system paths used by this Swift SDK when building. public var pathsConfiguration: PathsConfiguration - /// Creates a compilation destination with the specified properties. + /// Creates a Swift SDK with the specified properties. @available(*, deprecated, message: "use `init(targetTriple:sdkRootDir:toolset:)` instead") public init( target: Triple? = nil, @@ -389,7 +400,7 @@ public struct SwiftSDK: Equatable { ) } - /// Creates a compilation destination with the specified properties. + /// Creates a Swift SDK with the specified properties. @available(*, deprecated, message: "use `init(hostTriple:targetTriple:toolset:pathsConfiguration:)` instead") public init( hostTriple: Triple? = nil, @@ -406,17 +417,19 @@ public struct SwiftSDK: Equatable { ) } - /// Creates a compilation destination with the specified properties. + /// Creates a Swift SDK with the specified properties. public init( hostTriple: Triple? = nil, targetTriple: Triple? = nil, toolset: Toolset, - pathsConfiguration: PathsConfiguration + pathsConfiguration: PathsConfiguration, + supportsTesting: Bool = true ) { self.hostTriple = hostTriple self.targetTriple = targetTriple self.toolset = toolset self.pathsConfiguration = pathsConfiguration + self.supportsTesting = supportsTesting } /// Returns the bin directory for the host. @@ -433,8 +446,7 @@ public struct SwiftSDK: Equatable { return try AbsolutePath(validating: CommandLine.arguments[0], relativeTo: cwd).parentDirectory } - /// The destination describing the host OS. - + /// The Swift SDK describing the host platform. @available(*, deprecated, renamed: "hostSwiftSDK") public static func hostDestination( _ binDir: AbsolutePath? = nil, @@ -444,11 +456,12 @@ public struct SwiftSDK: Equatable { try self.hostSwiftSDK(binDir, originalWorkingDirectory: originalWorkingDirectory, environment: environment) } - /// The Swift SDK for the host OS. + /// The Swift SDK for the host platform. public static func hostSwiftSDK( _ binDir: AbsolutePath? = nil, originalWorkingDirectory: AbsolutePath? = nil, - environment: [String: String] = ProcessEnv.vars + environment: [String: String] = ProcessEnv.vars, + observabilityScope: ObservabilityScope? = nil ) throws -> SwiftSDK { let originalWorkingDirectory = originalWorkingDirectory ?? localFileSystem.currentWorkingDirectory // Select the correct binDir. @@ -483,14 +496,23 @@ public struct SwiftSDK: Equatable { #endif // Compute common arguments for clang and swift. + let supportsTesting: Bool var extraCCFlags: [String] = [] var extraSwiftCFlags: [String] = [] #if os(macOS) - let sdkPaths = try SwiftSDK.sdkPlatformFrameworkPaths(environment: environment) - extraCCFlags += ["-F", sdkPaths.fwk.pathString] - extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString] - extraSwiftCFlags += ["-I", sdkPaths.lib.pathString] - extraSwiftCFlags += ["-L", sdkPaths.lib.pathString] + do { + let sdkPaths = try SwiftSDK.sdkPlatformFrameworkPaths(environment: environment) + extraCCFlags += ["-F", sdkPaths.fwk.pathString] + extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString] + extraSwiftCFlags += ["-I", sdkPaths.lib.pathString] + extraSwiftCFlags += ["-L", sdkPaths.lib.pathString] + supportsTesting = true + } catch { + supportsTesting = false + observabilityScope?.emit(warning: "could not determine XCTest paths: \(error)") + } + #else + supportsTesting = true #endif #if !os(Windows) @@ -505,7 +527,8 @@ public struct SwiftSDK: Equatable { ], rootPaths: [binDir] ), - pathsConfiguration: .init(sdkRootPath: sdkPath) + pathsConfiguration: .init(sdkRootPath: sdkPath), + supportsTesting: supportsTesting ) } @@ -544,7 +567,7 @@ public struct SwiftSDK: Equatable { /// Cache storage for sdk platform path. private static var _sdkPlatformFrameworkPath: (fwk: AbsolutePath, lib: AbsolutePath)? = nil - /// Returns a default destination of a given target environment + /// Returns a default Swift SDK for a given target environment @available(*, deprecated, renamed: "defaultSwiftSDK") public static func defaultDestination(for triple: Triple, host: SwiftSDK) -> SwiftSDK? { if triple.isWASI() { @@ -575,7 +598,7 @@ public struct SwiftSDK: Equatable { return nil } - /// Propagates toolchain and SDK paths known to the destination to `swiftc` CLI options. + /// Propagates toolchain and SDK paths known to the Swift SDK to `swiftc` CLI options. public mutating func applyPathCLIOptions() { var properties = self.toolset.knownTools[.swiftCompiler] ?? .init(extraCLIOptions: []) properties.extraCLIOptions.append(contentsOf: self.toolset.rootPaths.flatMap { ["-tools-directory", $0.pathString] }) @@ -588,7 +611,7 @@ public struct SwiftSDK: Equatable { } /// Appends a path to the array of toolset root paths. - /// - Parameter toolsetRootPath: new path to add to the destination's toolset. + /// - Parameter toolsetRootPath: new path to add to Swift SDK's toolset. public mutating func add(toolsetRootPath: AbsolutePath) { self.toolset.rootPaths.append(toolsetRootPath) } @@ -627,17 +650,17 @@ extension SwiftSDK { ) throws -> [SwiftSDK] { switch semanticVersion.schemaVersion { case Version(3, 0, 0): - let destinations = try decoder.decode(path: path, fileSystem: fileSystem, as: SerializedDestinationV3.self) - let destinationDirectory = path.parentDirectory + let swiftSDKs = try decoder.decode(path: path, fileSystem: fileSystem, as: SerializedDestinationV3.self) + let swiftSDKDirectory = path.parentDirectory - return try destinations.runTimeTriples.map { triple, properties in + return try swiftSDKs.runTimeTriples.map { triple, properties in let triple = try Triple(triple) let pathStrings = properties.toolsetPaths ?? [] let toolset = try pathStrings.reduce(into: Toolset(knownTools: [:], rootPaths: [])) { try $0.merge( with: Toolset( - from: .init(validating: $1, relativeTo: destinationDirectory), + from: .init(validating: $1, relativeTo: swiftSDKDirectory), at: fileSystem, observabilityScope ) @@ -645,10 +668,10 @@ extension SwiftSDK { } return try SwiftSDK( - runTimeTriple: triple, + targetTriple: triple, properties: properties, toolset: toolset, - destinationDirectory: destinationDirectory + swiftSDKDirectory: swiftSDKDirectory ) } @@ -701,22 +724,22 @@ extension SwiftSDK { ) } - /// Initialize new Swift SDK from values deserialized using v3 schema. + /// Initialize new Swift SDK from values deserialized using the v3 schema. /// - Parameters: - /// - runTimeTriple: triple of the machine running code built with this destination. + /// - targetTriple: triple of the machine running code built with this Swift SDK. /// - properties: properties of the destination for the given triple. /// - toolset: combined toolset used by this destination. - /// - destinationDirectory: directory used for converting relative paths in `properties` to absolute paths. + /// - swiftSDKDirectory: directory used for converting relative paths in `properties` to absolute paths. private init( - runTimeTriple: Triple, + targetTriple: Triple, properties: SerializedDestinationV3.TripleProperties, toolset: Toolset = .init(), - destinationDirectory: AbsolutePath? = nil + swiftSDKDirectory: AbsolutePath? = nil ) throws { self.init( - targetTriple: runTimeTriple, + targetTriple: targetTriple, toolset: toolset, - pathsConfiguration: try .init(properties, destinationDirectory: destinationDirectory) + pathsConfiguration: try .init(properties, swiftSDKDirectory: swiftSDKDirectory) ) } @@ -730,40 +753,44 @@ extension SwiftSDK { // Check schema version. switch version.version { case 1: - let destination = try decoder.decode(path: path, fileSystem: fileSystem, as: SerializedDestinationV1.self) + let serializedMetadata = try decoder.decode( + path: path, + fileSystem: fileSystem, + as: SerializedDestinationV1.self + ) try self.init( - targetTriple: destination.target.map { try Triple($0) }, + targetTriple: serializedMetadata.target.map { try Triple($0) }, toolset: .init( - toolchainBinDir: destination.binDir, + toolchainBinDir: serializedMetadata.binDir, buildFlags: .init( - cCompilerFlags: destination.extraCCFlags, - cxxCompilerFlags: destination.extraCPPFlags, - swiftCompilerFlags: destination.extraSwiftCFlags + cCompilerFlags: serializedMetadata.extraCCFlags, + cxxCompilerFlags: serializedMetadata.extraCPPFlags, + swiftCompilerFlags: serializedMetadata.extraSwiftCFlags ) ), - pathsConfiguration: .init(sdkRootPath: destination.sdk) + pathsConfiguration: .init(sdkRootPath: serializedMetadata.sdk) ) case 2: - let destination = try decoder.decode(path: path, fileSystem: fileSystem, as: SerializedDestinationV2.self) - let destinationDirectory = path.parentDirectory + let serializedMetadata = try decoder.decode(path: path, fileSystem: fileSystem, as: SerializedDestinationV2.self) + let swiftSDKDirectory = path.parentDirectory try self.init( - hostTriple: destination.hostTriples.map(Triple.init).first, - targetTriple: destination.targetTriples.map(Triple.init).first, + hostTriple: serializedMetadata.hostTriples.map(Triple.init).first, + targetTriple: serializedMetadata.targetTriples.map(Triple.init).first, toolset: .init( toolchainBinDir: AbsolutePath( - validating: destination.toolchainBinDir, - relativeTo: destinationDirectory + validating: serializedMetadata.toolchainBinDir, + relativeTo: swiftSDKDirectory ), buildFlags: .init( - cCompilerFlags: destination.extraCCFlags, - cxxCompilerFlags: destination.extraCXXFlags, - swiftCompilerFlags: destination.extraSwiftCFlags, - linkerFlags: destination.extraLinkerFlags + cCompilerFlags: serializedMetadata.extraCCFlags, + cxxCompilerFlags: serializedMetadata.extraCXXFlags, + swiftCompilerFlags: serializedMetadata.extraSwiftCFlags, + linkerFlags: serializedMetadata.extraLinkerFlags ) ), pathsConfiguration: .init( - sdkRootPath: AbsolutePath(validating: destination.sdkRootDir, relativeTo: destinationDirectory) + sdkRootPath: AbsolutePath(validating: serializedMetadata.sdkRootDir, relativeTo: swiftSDKDirectory) ) ) default: @@ -771,9 +798,9 @@ extension SwiftSDK { } } - /// Encodes a destination into its serialized form, which is a pair of its run time triple and paths configuration. + /// Encodes a Swift SDK into its serialized form, which is a pair of its run time triple and paths configuration. /// Returns a pair that can be used to reconstruct a `SerializedDestinationV3` value for storage. `nil` if - /// required configuration properties aren't available on `self`, which can happen if `Destination` was decoded + /// required configuration properties aren't available on `self`, which can happen if `Swift SDK` was decoded /// from different schema versions or constructed manually without providing valid values for such properties. var serialized: (Triple, SerializedDestinationV3.TripleProperties) { get throws { diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift new file mode 100644 index 00000000000..8b743666244 --- /dev/null +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift @@ -0,0 +1,142 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2022-2023 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 Basics + +import struct Foundation.URL +import protocol TSCBasic.FileSystem +import struct TSCBasic.RegEx + +/// Represents an `.artifactbundle` on the filesystem that contains a Swift SDK. +public struct SwiftSDKBundle { + public struct Variant: Equatable { + let metadata: ArtifactsArchiveMetadata.Variant + let swiftSDKs: [SwiftSDK] + } + + // Path to the bundle root directory. + public let path: AbsolutePath + + /// Mapping of artifact IDs to variants available for a corresponding artifact. + public internal(set) var artifacts = [String: [Variant]]() + + /// Name of the Swift SDK bundle that can be used to distinguish it from other bundles. + public var name: String { path.basename } +} + +extension [SwiftSDKBundle] { + /// Select a Swift SDK with a given artifact ID from a `self` array of available Swift SDKs. + /// - Parameters: + /// - id: artifact ID of the Swift SDK to look up. + /// - hostTriple: triple of the machine on which the Swift SDK is building. + /// - targetTriple: triple of the machine for which the Swift SDK is building. + /// - Returns: ``SwiftSDK`` value with a given artifact ID, `nil` if none found. + public func selectSwiftSDK(id: String, hostTriple: Triple, targetTriple: Triple) -> SwiftSDK? { + for bundle in self { + for (artifactID, variants) in bundle.artifacts { + guard artifactID == id else { + continue + } + + for variant in variants { + guard variant.metadata.supportedTriples.contains(hostTriple) else { + continue + } + + return variant.swiftSDKs.first { $0.targetTriple == targetTriple } + } + } + } + + return nil + } + + /// Select Swift SDKs matching a given selector and host triple from a `self` array of available Swift SDKs. + /// - Parameters: + /// - selector: either an artifact ID or target triple to filter with. + /// - hostTriple: triple of the host building with these Swift SDKs. + /// - observabilityScope: observability scope to log warnings about multiple matches. + /// - Returns: ``SwiftSDK`` value matching `query` either by artifact ID or target triple, `nil` if none found. + func selectSwiftSDK( + matching selector: String, + hostTriple: Triple, + observabilityScope: ObservabilityScope + ) -> SwiftSDK? { + var matchedByID: (path: AbsolutePath, variant: SwiftSDKBundle.Variant, swiftSDK: SwiftSDK)? + var matchedByTriple: (path: AbsolutePath, variant: SwiftSDKBundle.Variant, swiftSDK: SwiftSDK)? + + for bundle in self { + for (artifactID, variants) in bundle.artifacts { + for variant in variants { + guard variant.metadata.supportedTriples.contains(where: { variantTriple in + hostTriple.isRuntimeCompatible(with: variantTriple) + }) else { + continue + } + + for swiftSDK in variant.swiftSDKs { + if artifactID == selector { + if let matchedByID { + observabilityScope.emit( + warning: + """ + multiple Swift SDKs match ID `\(artifactID)` and host triple \( + hostTriple.tripleString + ), selected one at \( + matchedByID.path.appending(matchedByID.variant.metadata.path) + ) + """ + ) + } else { + matchedByID = (bundle.path, variant, swiftSDK) + } + } + + if swiftSDK.targetTriple?.tripleString == selector { + if let matchedByTriple { + observabilityScope.emit( + warning: + """ + multiple Swift SDKs match target triple `\(selector)` and host triple \( + hostTriple.tripleString + ), selected one at \( + matchedByTriple.path.appending(matchedByTriple.variant.metadata.path) + ) + """ + ) + } else { + matchedByTriple = (bundle.path, variant, swiftSDK) + } + } + } + } + } + } + + if let matchedByID, let matchedByTriple, matchedByID != matchedByTriple { + observabilityScope.emit( + warning: + """ + multiple Swift SDKs match the query `\(selector)` and host triple \( + hostTriple.tripleString + ), selected one at \(matchedByID.path.appending(matchedByID.variant.metadata.path)) + """ + ) + } + + return matchedByID?.swiftSDK ?? matchedByTriple?.swiftSDK + } + + public var sortedArtifactIDs: [String] { + self.flatMap(\.artifacts.keys).sorted() + } +} diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift new file mode 100644 index 00000000000..ae2d8ec2bbb --- /dev/null +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift @@ -0,0 +1,371 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2022-2023 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 +// +//===----------------------------------------------------------------------===// + +// FIXME: can't write `import actor Basics.HTTPClient`, importing the whole module because of that :( +import Basics +import struct Foundation.URL +import protocol TSCBasic.FileSystem +import struct TSCBasic.RegEx + +public final class SwiftSDKBundleStore { + public enum Output: Equatable, CustomStringConvertible { + case downloadStarted(URL) + case downloadFinishedSuccessfully(URL) + case unpackingArchive(bundlePathOrURL: String) + case installationSuccessful(bundlePathOrURL: String, bundleName: String) + + public var description: String { + switch self { + case let .downloadStarted(url): + return "Downloading a Swift SDK bundle archive from `\(url)`..." + case let .downloadFinishedSuccessfully(url): + return "Swift SDK bundle archive successfully downloaded from `\(url)`." + case let .installationSuccessful(bundlePathOrURL, bundleName): + return "Swift SDK bundle at `\(bundlePathOrURL)` successfully installed as \(bundleName)." + case let .unpackingArchive(bundlePathOrURL): + return "Swift SDK bundle at `\(bundlePathOrURL)` is assumed to be an archive, unpacking..." + } + } + } + + enum Error: Swift.Error, CustomStringConvertible { + case noMatchingSwiftSDK(selector: String, hostTriple: Triple) + + var description: String { + switch self { + case let .noMatchingSwiftSDK(selector, hostTriple): + return """ + No Swift SDK found matching query `\(selector)` and host triple \ + `\(hostTriple.tripleString)`. Use `swift experimental-sdk list` command to see \ + available Swift SDKs. + """ + } + } + } + + /// Directory in which Swift SDKs bundles are stored. + let swiftSDKsDirectory: AbsolutePath + + /// File system instance used for reading from and writing to SDK bundles stored on it. + let fileSystem: any FileSystem + + /// Observability scope used for logging. + private let observabilityScope: ObservabilityScope + + /// Closure invoked for output produced by this store during its operation. + private let outputHandler: (Output) -> Void + + public init( + swiftSDKsDirectory: AbsolutePath, + fileSystem: any FileSystem, + observabilityScope: ObservabilityScope, + outputHandler: @escaping (Output) -> Void + ) { + self.swiftSDKsDirectory = swiftSDKsDirectory + self.fileSystem = fileSystem + self.observabilityScope = observabilityScope + self.outputHandler = outputHandler + } + + /// An array of valid Swift SDK bundles stored in ``SwiftSDKBundleStore//swiftSDKsDirectory``. + public var allValidBundles: [SwiftSDKBundle] { + get throws { + // Get absolute paths to available Swift SDK bundles. + try self.fileSystem.getDirectoryContents(swiftSDKsDirectory).filter { + $0.hasSuffix(BinaryTarget.Kind.artifactsArchive.fileExtension) + }.map { + self.swiftSDKsDirectory.appending(components: [$0]) + }.compactMap { + do { + // Enumerate available bundles and parse manifests for each of them, then validate supplied + // Swift SDKs. + return try self.parseAndValidate(bundlePath: $0) + } catch { + observabilityScope.emit( + warning: "Couldn't parse `info.json` manifest of a Swift SDK bundle at \($0)", + underlyingError: error + ) + return nil + } + } + } + } + + /// Select a Swift SDK matching a given query and host triple from all Swift SDKs available in + /// ``SwiftSDKBundleStore//swiftSDKsDirectory``. + /// - Parameters: + /// - query: either an artifact ID or target triple to filter with. + /// - hostTriple: triple of the host building with these Swift SDKs. + /// - Returns: ``SwiftSDK`` value matching `query` either by artifact ID or target triple, `nil` if none found. + public func selectBundle( + matching selector: String, + hostTriple: Triple + ) throws -> SwiftSDK { + let validBundles = try self.allValidBundles + + guard !validBundles.isEmpty else { + throw StringError( + "No valid Swift SDK bundles found at \(self.swiftSDKsDirectory)." + ) + } + + guard var selectedSwiftSDKs = validBundles.selectSwiftSDK( + matching: selector, + hostTriple: hostTriple, + observabilityScope: self.observabilityScope + ) else { + throw Error.noMatchingSwiftSDK(selector: selector, hostTriple: hostTriple) + } + + selectedSwiftSDKs.applyPathCLIOptions() + + return selectedSwiftSDKs + } + + /// Installs a Swift SDK bundle from a given path or URL to ``SwiftSDKBundleStore//swiftSDKsDirectory``. + /// - Parameters: + /// - bundlePathOrURL: A string passed on the command line, which is either an absolute or relative to a current + /// working directory path, or a URL to a Swift SDK artifact bundle. + /// - archiver: Archiver instance to use for extracting bundle archives. + public func install( + bundlePathOrURL: String, + _ archiver: any Archiver, + _ httpClient: HTTPClient = .init() + ) async throws { + let bundleName = try await withTemporaryDirectory(fileSystem: self.fileSystem, removeTreeOnDeinit: true) { temporaryDirectory in + let bundlePath: AbsolutePath + + if + let bundleURL = URL(string: bundlePathOrURL), + let scheme = bundleURL.scheme, + scheme == "http" || scheme == "https" + { + let bundleName: String + let fileNameComponent = bundleURL.lastPathComponent + if fileNameComponent.hasSuffix(".tar.gz") { + bundleName = fileNameComponent + } else { + bundleName = "bundle.tar.gz" + } + let downloadedBundlePath = temporaryDirectory.appending(component: bundleName) + + var request = HTTPClientRequest.download( + url: bundleURL, + fileSystem: self.fileSystem, + destination: downloadedBundlePath + ) + request.options.validResponseCodes = [200] + + self.outputHandler(.downloadStarted(bundleURL)) + + _ = try await httpClient.execute( + request, + observabilityScope: self.observabilityScope, + progress: nil + ) + + bundlePath = downloadedBundlePath + + self.outputHandler(.downloadFinishedSuccessfully(bundleURL)) + } else if + let cwd: AbsolutePath = self.fileSystem.currentWorkingDirectory, + let originalBundlePath = try? AbsolutePath(validating: bundlePathOrURL, relativeTo: cwd) + { + bundlePath = originalBundlePath + } else { + throw SwiftSDKError.invalidPathOrURL(bundlePathOrURL) + } + + return try await self.installIfValid( + bundlePathOrURL: bundlePathOrURL, + validatedBundlePath: bundlePath, + temporaryDirectory: temporaryDirectory, + archiver: archiver + ) + }.value + + self.outputHandler(.installationSuccessful(bundlePathOrURL: bundlePathOrURL, bundleName: bundleName)) + } + + /// Unpacks a Swift SDK bundle if it has an archive extension in its filename. + /// - Parameters: + /// - bundlePath: Absolute path to a Swift SDK bundle to unpack if needed. + /// - temporaryDirectory: Absolute path to a temporary directory in which the bundle can be unpacked if needed. + /// - archiver: Archiver instance to use for extracting bundle archives. + /// - Returns: Path to an unpacked Swift SDK bundle if unpacking is needed, value of `bundlePath` is returned + /// otherwise. + private func unpackIfNeeded( + bundlePathOrURL: String, + validatedBundlePath bundlePath: AbsolutePath, + temporaryDirectory: AbsolutePath, + _ archiver: any Archiver + ) async throws -> AbsolutePath { + // If there's no archive extension on the bundle name, assuming it's not archived and returning the same path. + guard !bundlePath.pathString.hasSuffix(".\(artifactBundleExtension)") else { + return bundlePath + } + + self.outputHandler(.unpackingArchive(bundlePathOrURL: bundlePathOrURL)) + let extractionResultsDirectory = temporaryDirectory.appending("extraction-results") + try self.fileSystem.createDirectory(extractionResultsDirectory) + + try await archiver.extract(from: bundlePath, to: extractionResultsDirectory) + + guard let bundleName = try fileSystem.getDirectoryContents(extractionResultsDirectory).first, + bundleName.hasSuffix(".\(artifactBundleExtension)") + else { + throw SwiftSDKError.invalidBundleArchive(bundlePath) + } + + let installedBundlePath = swiftSDKsDirectory.appending(component: bundleName) + guard !self.fileSystem.exists(installedBundlePath) else { + throw SwiftSDKError.swiftSDKBundleAlreadyInstalled(bundleName: bundleName) + } + + return extractionResultsDirectory.appending(component: bundleName) + } + + /// Installs an unpacked Swift SDK bundle to a Swift SDK installation directory. + /// - Parameters: + /// - bundlePath: absolute path to an unpacked Swift SDK bundle directory. + /// - temporaryDirectory: Temporary directory to use if the bundle is an archive that needs extracting. + /// - archiver: Archiver instance to use for extracting bundle archives. + /// - Returns: Name of the bundle installed. + private func installIfValid( + bundlePathOrURL: String, + validatedBundlePath: AbsolutePath, + temporaryDirectory: AbsolutePath, + archiver: any Archiver + ) async throws -> String { + #if os(macOS) + // Check the quarantine attribute on bundles downloaded manually in the browser. + guard !self.fileSystem.hasAttribute(.quarantine, validatedBundlePath) else { + throw SwiftSDKError.quarantineAttributePresent(bundlePath: validatedBundlePath) + } + #endif + + let unpackedBundlePath = try await self.unpackIfNeeded( + bundlePathOrURL: bundlePathOrURL, + validatedBundlePath: validatedBundlePath, + temporaryDirectory: temporaryDirectory, + archiver + ) + + guard + self.fileSystem.isDirectory(unpackedBundlePath), + let bundleName = unpackedBundlePath.components.last + else { + throw SwiftSDKError.pathIsNotDirectory(validatedBundlePath) + } + + let installedBundlePath = self.swiftSDKsDirectory.appending(component: bundleName) + + let validatedBundle = try self.parseAndValidate(bundlePath: unpackedBundlePath) + let newArtifactIDs = validatedBundle.artifacts.keys + + let installedBundles = try self.allValidBundles + + for installedBundle in installedBundles { + for artifactID in installedBundle.artifacts.keys { + guard !newArtifactIDs.contains(artifactID) else { + throw SwiftSDKError.swiftSDKArtifactAlreadyInstalled( + installedBundleName: installedBundle.name, + newBundleName: validatedBundle.name, + artifactID: artifactID + ) + } + } + } + + try self.fileSystem.copy(from: unpackedBundlePath, to: installedBundlePath) + + return bundleName + } + + /// Parses metadata of an `.artifactbundle` and validates it as a bundle containing + /// cross-compilation Swift SDKs. + /// - Parameters: + /// - bundlePath: path to the bundle root directory. + /// - Returns: Validated ``SwiftSDKBundle`` containing validated ``SwiftSDK`` values for + /// each artifact and its variants. + private func parseAndValidate(bundlePath: AbsolutePath) throws -> SwiftSDKBundle { + let parsedManifest = try ArtifactsArchiveMetadata.parse( + fileSystem: self.fileSystem, + rootPath: bundlePath + ) + + return try self.validateSwiftSDKBundle( + bundlePath: bundlePath, + bundleManifest: parsedManifest + ) + } + + private func validateSwiftSDKBundle( + bundlePath: AbsolutePath, + bundleManifest: ArtifactsArchiveMetadata + ) throws -> SwiftSDKBundle { + var result = SwiftSDKBundle(path: bundlePath) + + for (artifactID, artifactMetadata) in bundleManifest.artifacts { + if artifactMetadata.type == .crossCompilationDestination { + self.observabilityScope.emit( + warning: """ + `crossCompilationDestination` bundle metadata value used for `\(artifactID)` is deprecated, \ + use `swiftSDK` instead. + """ + ) + } else { + guard artifactMetadata.type == .swiftSDK else { continue } + } + + var variants = [SwiftSDKBundle.Variant]() + + for variantMetadata in artifactMetadata.variants { + let variantConfigurationPath = bundlePath + .appending(variantMetadata.path) + .appending("swift-sdk.json") + + guard self.fileSystem.exists(variantConfigurationPath) else { + self.observabilityScope.emit( + .warning( + """ + Swift SDK metadata file not found at \( + variantConfigurationPath + ) for a variant of artifact \(artifactID) + """ + ) + ) + + continue + } + + do { + let swiftSDKs = try SwiftSDK.decode( + fromFile: variantConfigurationPath, fileSystem: fileSystem, + observabilityScope: observabilityScope + ) + + variants.append(.init(metadata: variantMetadata, swiftSDKs: swiftSDKs)) + } catch { + observabilityScope.emit( + warning: "Couldn't parse Swift SDK artifact metadata at \(variantConfigurationPath)", + underlyingError: error + ) + } + } + + result.artifacts[artifactID] = variants + } + + return result + } +} diff --git a/Sources/PackageModel/SwiftSDKConfigurationStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift similarity index 89% rename from Sources/PackageModel/SwiftSDKConfigurationStore.swift rename to Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift index f9642645fe4..5002ce264dd 100644 --- a/Sources/PackageModel/SwiftSDKConfigurationStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift @@ -32,7 +32,7 @@ public final class SwiftSDKConfigurationStore { private let fileSystem: FileSystem // An observability scope on which warnings can be reported if any appear. - private let observabilityScope: ObservabilityScope + private let swiftSDKBundleStore: SwiftSDKBundleStore /// Encoder used for encoding updated configuration to be written to ``SwiftSDKConfigurationStore//fileSystem``. private let encoder: JSONEncoder @@ -50,12 +50,11 @@ public final class SwiftSDKConfigurationStore { /// - observabilityScope: an observability scope on which warnings can be reported if any appear. public init( hostTimeTriple: Triple, - swiftSDKsDirectoryPath: AbsolutePath, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope + swiftSDKBundleStore: SwiftSDKBundleStore ) throws { - let configurationDirectoryPath = swiftSDKsDirectoryPath.appending(component: "configuration") + let configurationDirectoryPath = swiftSDKBundleStore.swiftSDKsDirectory.appending(component: "configuration") + let fileSystem = swiftSDKBundleStore.fileSystem if fileSystem.exists(configurationDirectoryPath) { guard fileSystem.isDirectory(configurationDirectoryPath) else { throw SwiftSDKError.pathIsNotDirectory(configurationDirectoryPath) @@ -65,10 +64,10 @@ public final class SwiftSDKConfigurationStore { } self.hostTriple = hostTimeTriple - self.swiftSDKsDirectoryPath = swiftSDKsDirectoryPath + self.swiftSDKsDirectoryPath = swiftSDKBundleStore.swiftSDKsDirectory self.configurationDirectoryPath = configurationDirectoryPath self.fileSystem = fileSystem - self.observabilityScope = observabilityScope + self.swiftSDKBundleStore = swiftSDKBundleStore self.encoder = JSONEncoder.makeWithDefaults(prettified: true) self.decoder = JSONDecoder.makeWithDefaults() } @@ -94,11 +93,7 @@ public final class SwiftSDKConfigurationStore { component: "\(sdkID)_\(targetTriple.tripleString).json" ) - let swiftSDKs = try SwiftSDKBundle.getAllValidBundles( - swiftSDKsDirectory: swiftSDKsDirectoryPath, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) + let swiftSDKs = try self.swiftSDKBundleStore.allValidBundles guard var swiftSDK = swiftSDKs.selectSwiftSDK( id: sdkID, diff --git a/Sources/PackageModel/Target/ClangTarget.swift b/Sources/PackageModel/Target/ClangTarget.swift index 44e1ebf438c..d38f559a40d 100644 --- a/Sources/PackageModel/Target/ClangTarget.swift +++ b/Sources/PackageModel/Target/ClangTarget.swift @@ -14,7 +14,6 @@ import struct Basics.AbsolutePath import struct Basics.StringError public final class ClangTarget: Target { - /// The default public include directory component. public static let defaultPublicHeadersComponent = "include" diff --git a/Sources/PackageModel/Target/MixedTarget.swift b/Sources/PackageModel/Target/MixedTarget.swift index df5f6470d6a..0f6c07f6ea4 100644 --- a/Sources/PackageModel/Target/MixedTarget.swift +++ b/Sources/PackageModel/Target/MixedTarget.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +import struct Basics.AbsolutePath +import struct Basics.StringError + public final class MixedTarget: Target { // The Clang target for the mixed target's Clang sources. diff --git a/Sources/PackageModel/Target/PluginTarget.swift b/Sources/PackageModel/Target/PluginTarget.swift index 1576c617f7d..df195bcc799 100644 --- a/Sources/PackageModel/Target/PluginTarget.swift +++ b/Sources/PackageModel/Target/PluginTarget.swift @@ -173,18 +173,3 @@ public enum PluginPermission: Hashable, Codable { } } } - -public extension Sequence where Iterator.Element == Target { - var executables: [Target] { - return filter { - switch $0.type { - case .binary: - return ($0 as? BinaryTarget)?.containsExecutable == true - case .executable, .snippet, .macro: - return true - default: - return false - } - } - } -} diff --git a/Sources/PackageModel/Target/SwiftTarget.swift b/Sources/PackageModel/Target/SwiftTarget.swift index 227d98b79c3..f23f79db6fe 100644 --- a/Sources/PackageModel/Target/SwiftTarget.swift +++ b/Sources/PackageModel/Target/SwiftTarget.swift @@ -14,7 +14,6 @@ import struct Basics.AbsolutePath import struct Basics.SwiftVersion public final class SwiftTarget: Target { - /// The default name for the test entry point file located in a package. public static let defaultTestEntryPointName = "XCTMain.swift" diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift index 83f1b66303f..d21ca08e8e6 100644 --- a/Sources/PackageModel/Target/Target.swift +++ b/Sources/PackageModel/Target/Target.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2023 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 @@ -241,7 +241,7 @@ public class Target: PolymorphicCodableProtocol { /// Whether or not this target uses any custom unsafe flags. public let usesUnsafeFlags: Bool - fileprivate init( + init( name: String, potentialBundleName: String? = nil, type: Kind, @@ -334,3 +334,18 @@ extension Target: CustomStringConvertible { return "<\(Swift.type(of: self)): \(name)>" } } + +public extension Sequence where Iterator.Element == Target { + var executables: [Target] { + return filter { + switch $0.type { + case .binary: + return ($0 as? BinaryTarget)?.containsExecutable == true + case .executable, .snippet, .macro: + return true + default: + return false + } + } + } +} diff --git a/Sources/PackageModel/Toolchain.swift b/Sources/PackageModel/Toolchain.swift index 4ffa22ce09d..63fe3a4efac 100644 --- a/Sources/PackageModel/Toolchain.swift +++ b/Sources/PackageModel/Toolchain.swift @@ -19,11 +19,17 @@ public protocol Toolchain { /// Path of the `swiftc` compiler. var swiftCompilerPath: AbsolutePath { get } + /// Path to `lib/swift` + var swiftResourcesPath: AbsolutePath? { get } + + /// Path to `lib/swift_static` + var swiftStaticResourcesPath: AbsolutePath? { get } + /// Whether the used compiler is from a open source development toolchain. var isSwiftDevelopmentToolchain: Bool { get } /// Path to the Swift plugin server utility. - var swiftPluginServerPath: AbsolutePath? { get } + var swiftPluginServerPath: AbsolutePath? { get throws } /// Path containing the macOS Swift stdlib. var macosSwiftStdlib: AbsolutePath { get throws } @@ -34,6 +40,12 @@ public protocol Toolchain { /// An array of paths to search for libraries at link time. var librarySearchPaths: [AbsolutePath] { get } + /// Configuration from the used toolchain. + var installedSwiftPMConfiguration: InstalledSwiftPMConfiguration { get } + + /// The root path to the Swift SDK used by this toolchain. + var sdkRootPath: AbsolutePath? { get } + /// Path of the `clang` compiler. func getClangCompiler() throws -> AbsolutePath @@ -81,7 +93,15 @@ extension Toolchain { return try AbsolutePath(validating: "../../lib", relativeTo: resolveSymlinks(swiftCompilerPath)) } } - + + /// Returns the appropriate Swift resources directory path. + /// + /// - Parameter static: Controls whether to use the static or dynamic + /// resources directory. + public func swiftResourcesPath(isStatic: Bool) -> AbsolutePath? { + isStatic ? swiftStaticResourcesPath : swiftResourcesPath + } + public var extraCCFlags: [String] { extraFlags.cCompilerFlags } diff --git a/Sources/PackageModel/ToolchainConfiguration.swift b/Sources/PackageModel/ToolchainConfiguration.swift index f0e8f3fbce5..53ef2e7f631 100644 --- a/Sources/PackageModel/ToolchainConfiguration.swift +++ b/Sources/PackageModel/ToolchainConfiguration.swift @@ -42,9 +42,6 @@ public struct ToolchainConfiguration { /// This is optional for example on macOS w/o Xcode. public var xctestPath: AbsolutePath? - /// Path to the Swift plugin server utility. - public var swiftPluginServerPath: AbsolutePath? - /// Creates the set of manifest resources associated with a `swiftc` executable. /// /// - Parameters: @@ -55,7 +52,6 @@ public struct ToolchainConfiguration { /// - swiftPMLibrariesRootPath: Custom path for SwiftPM libraries. Computed based on the compiler path by default. /// - sdkRootPath: Optional path to SDK root. /// - xctestPath: Optional path to XCTest. - /// - swiftPluginServerPath: Optional path to the Swift plugin server executable. public init( librarianPath: AbsolutePath, swiftCompilerPath: AbsolutePath, @@ -63,8 +59,7 @@ public struct ToolchainConfiguration { swiftCompilerEnvironment: EnvironmentVariables = .process(), swiftPMLibrariesLocation: SwiftPMLibrariesLocation? = nil, sdkRootPath: AbsolutePath? = nil, - xctestPath: AbsolutePath? = nil, - swiftPluginServerPath: AbsolutePath? = nil + xctestPath: AbsolutePath? = nil ) { let swiftPMLibrariesLocation = swiftPMLibrariesLocation ?? { return .init(swiftCompilerPath: swiftCompilerPath) @@ -77,7 +72,6 @@ public struct ToolchainConfiguration { self.swiftPMLibrariesLocation = swiftPMLibrariesLocation self.sdkRootPath = sdkRootPath self.xctestPath = xctestPath - self.swiftPluginServerPath = swiftPluginServerPath } } diff --git a/Sources/PackageModel/ToolsVersion.swift b/Sources/PackageModel/ToolsVersion.swift index 258243e22ab..427399d0255 100644 --- a/Sources/PackageModel/ToolsVersion.swift +++ b/Sources/PackageModel/ToolsVersion.swift @@ -30,6 +30,7 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable { public static let v5_7 = ToolsVersion(version: "5.7.0") public static let v5_8 = ToolsVersion(version: "5.8.0") public static let v5_9 = ToolsVersion(version: "5.9.0") + public static let v5_10 = ToolsVersion(version: "5.10.0") public static let vNext = ToolsVersion(version: "999.0.0") /// The current tools version in use. @@ -105,30 +106,53 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable { _version = version } + private enum ValidationResult { + case valid + case unsupportedToolsVersion + case requireNewerTools + } + + private func _validateToolsVersion(_ currentToolsVersion: ToolsVersion) -> ValidationResult { + // We don't want to throw any error when using the special vNext version. + if SwiftVersion.current.isDevelopment && self == .vNext { + return .valid + } + + // Make sure the package has the right minimum tools version. + guard self >= .minimumRequired else { + return .unsupportedToolsVersion + } + + // Make sure the package isn't newer than the current tools version. + guard currentToolsVersion >= self else { + return .requireNewerTools + } + + return .valid + } + /// Returns true if the tools version is valid and can be used by this /// version of the package manager. + public func validateToolsVersion(_ currentToolsVersion: ToolsVersion) -> Bool { + return self._validateToolsVersion(currentToolsVersion) == .valid + } + public func validateToolsVersion( _ currentToolsVersion: ToolsVersion, packageIdentity: PackageIdentity, packageVersion: String? = .none ) throws { - // We don't want to throw any error when using the special vNext version. - if SwiftVersion.current.isDevelopment && self == .vNext { - return - } - - // Make sure the package has the right minimum tools version. - guard self >= .minimumRequired else { + switch self._validateToolsVersion(currentToolsVersion) { + case .valid: + break + case .unsupportedToolsVersion: throw UnsupportedToolsVersion( packageIdentity: packageIdentity, packageVersion: packageVersion, currentToolsVersion: currentToolsVersion, packageToolsVersion: self ) - } - - // Make sure the package isn't newer than the current tools version. - guard currentToolsVersion >= self else { + case .requireNewerTools: throw RequireNewerTools( packageIdentity: packageIdentity, packageVersion: packageVersion, diff --git a/Sources/PackageModel/Toolset.swift b/Sources/PackageModel/Toolset.swift index 8853e00c480..f389977e3cf 100644 --- a/Sources/PackageModel/Toolset.swift +++ b/Sources/PackageModel/Toolset.swift @@ -157,7 +157,11 @@ extension Toolset { } } - init(toolchainBinDir: AbsolutePath, buildFlags: BuildFlags) { + /// Initialize a new ad-hoc toolset that wasn't previously serialized, but created in memory. + /// - Parameters: + /// - toolchainBinDir: absolute path to the toolchain binaries directory, which are used in this toolset. + /// - buildFlags: flags provided to each tool as CLI options. + public init(toolchainBinDir: AbsolutePath, buildFlags: BuildFlags = .init()) { self.rootPaths = [toolchainBinDir] self.knownTools = [ .cCompiler: .init(extraCLIOptions: buildFlags.cCompilerFlags), diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index 7c264f82e39..392313c401f 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -40,6 +40,16 @@ public final class UserToolchain: Toolchain { /// An array of paths to search for libraries at link time. public let librarySearchPaths: [AbsolutePath] + /// Path containing Swift resources for dynamic linking. + public var swiftResourcesPath: AbsolutePath? { + swiftSDK.pathsConfiguration.swiftResourcesPath + } + + /// Path containing Swift resources for static linking. + public var swiftStaticResourcesPath: AbsolutePath? { + swiftSDK.pathsConfiguration.swiftStaticResourcesPath + } + /// Additional flags to be passed to the build tools. public var extraFlags: BuildFlags @@ -76,6 +86,8 @@ public final class UserToolchain: Toolchain { public let isSwiftDevelopmentToolchain: Bool + public let installedSwiftPMConfiguration: InstalledSwiftPMConfiguration + /// Returns the runtime library for the given sanitizer. public func runtimeLibrary(for sanitizer: Sanitizer) throws -> AbsolutePath { // FIXME: This is only for SwiftPM development time support. It is OK @@ -172,9 +184,7 @@ public final class UserToolchain: Toolchain { } return "link" } - // TODO(compnerd) consider defaulting to `llvm-ar` universally with - // a fallback to `ar`. - return triple.isAndroid() ? "llvm-ar" : "ar" + return "llvm-ar" }() if let librarian = UserToolchain.lookup( @@ -190,7 +200,18 @@ public final class UserToolchain: Toolchain { if let librarian = try? UserToolchain.getTool(tool, binDirectories: binDirectories) { return librarian } - return try UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: useXcrun) + if triple.isApple() || triple.isWindows() { + return try UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: useXcrun) + } else { + if let librarian = try? UserToolchain.findTool(tool, envSearchPaths: searchPaths, useXcrun: false) { + return librarian + } + // Fall back to looking for binutils `ar` if `llvm-ar` can't be found. + if let librarian = try? UserToolchain.getTool("ar", binDirectories: binDirectories) { + return librarian + } + return try UserToolchain.findTool("ar", envSearchPaths: searchPaths, useXcrun: false) + } } /// Determines the Swift compiler paths for compilation and manifest parsing. @@ -328,7 +349,11 @@ public final class UserToolchain: Toolchain { swiftSDK: SwiftSDK, environment: EnvironmentVariables ) throws -> [String] { - let swiftCompilerFlags = swiftSDK.toolset.knownTools[.swiftCompiler]?.extraCLIOptions ?? [] + var swiftCompilerFlags = swiftSDK.toolset.knownTools[.swiftCompiler]?.extraCLIOptions ?? [] + + if let linker = swiftSDK.toolset.knownTools[.linker]?.path { + swiftCompilerFlags += ["-ld-path=\(linker)"] + } guard let sdkDir = swiftSDK.pathsConfiguration.sdkRootPath else { if triple.isWindows() { @@ -456,7 +481,8 @@ public final class UserToolchain: Toolchain { swiftSDK: SwiftSDK, environment: EnvironmentVariables = .process(), searchStrategy: SearchStrategy = .default, - customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil + customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil, + customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil ) throws { self.swiftSDK = swiftSDK self.environment = environment @@ -499,7 +525,19 @@ public final class UserToolchain: Toolchain { self.isSwiftDevelopmentToolchain = false #endif - // Use the triple from destination or compute the host triple using swiftc. + if let customInstalledSwiftPMConfiguration { + self.installedSwiftPMConfiguration = customInstalledSwiftPMConfiguration + } else { + let path = self.swiftCompilerPath.parentDirectory.parentDirectory.appending(components: ["share", "pm", "config.json"]) + if localFileSystem.exists(path) { + self.installedSwiftPMConfiguration = try JSONDecoder.makeWithDefaults().decode(path: path, fileSystem: localFileSystem, as: InstalledSwiftPMConfiguration.self) + } else { + // We *could* eventually make this an error, but not for a few releases. + self.installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default + } + } + + // Use the triple from Swift SDK or compute the host triple using swiftc. var triple = try swiftSDK.targetTriple ?? Triple.getHostTriple(usingSwiftCompiler: swiftCompilers.compile) // Change the triple to the specified arch if there's exactly one of them. @@ -582,13 +620,10 @@ public final class UserToolchain: Toolchain { environment: environment ) - let swiftPluginServerPath: AbsolutePath? let xctestPath: AbsolutePath? if case .custom(_, let useXcrun) = searchStrategy, !useXcrun { - swiftPluginServerPath = nil xctestPath = nil } else { - swiftPluginServerPath = try Self.derivePluginServerPath(triple: triple) xctestPath = try Self.deriveXCTestPath( swiftSDK: self.swiftSDK, triple: triple, @@ -603,8 +638,7 @@ public final class UserToolchain: Toolchain { swiftCompilerEnvironment: environment, swiftPMLibrariesLocation: swiftPMLibrariesLocation, sdkRootPath: self.swiftSDK.pathsConfiguration.sdkRootPath, - xctestPath: xctestPath, - swiftPluginServerPath: swiftPluginServerPath + xctestPath: xctestPath ) } @@ -678,8 +712,8 @@ public final class UserToolchain: Toolchain { private static func derivePluginServerPath(triple: Triple) throws -> AbsolutePath? { if triple.isDarwin() { - let xctestFindArgs = ["/usr/bin/xcrun", "--find", "swift-plugin-server"] - if let path = try? TSCBasic.Process.checkNonZeroExit(arguments: xctestFindArgs, environment: [:]) + let pluginServerPathFindArgs = ["/usr/bin/xcrun", "--find", "swift-plugin-server"] + if let path = try? TSCBasic.Process.checkNonZeroExit(arguments: pluginServerPathFindArgs, environment: [:]) .spm_chomp() { return try AbsolutePath(validating: path) } @@ -810,7 +844,13 @@ public final class UserToolchain: Toolchain { configuration.xctestPath } + private let _swiftPluginServerPath = ThreadSafeBox() + public var swiftPluginServerPath: AbsolutePath? { - configuration.swiftPluginServerPath + get throws { + try _swiftPluginServerPath.memoize { + return try Self.derivePluginServerPath(triple: self.targetTriple) + } + } } } diff --git a/Sources/PackagePlugin/Command.swift b/Sources/PackagePlugin/Command.swift index 93de164027a..088dfc4cb70 100644 --- a/Sources/PackagePlugin/Command.swift +++ b/Sources/PackagePlugin/Command.swift @@ -11,10 +11,10 @@ //===----------------------------------------------------------------------===// /// A command to run during the build, including executable, command lines, -/// environment variables, initial working directory, etc. All paths should -/// be based on the ones passed to the plugin in the target build context. +/// environment variables, initial working directory, etc. All paths should be +/// based on the ones passed to the plugin in the target build context. public enum Command { - + case _buildCommand( displayName: String?, executable: Path, @@ -23,7 +23,7 @@ public enum Command { workingDirectory: Path? = nil, inputFiles: [Path] = [], outputFiles: [Path] = []) - + case _prebuildCommand( displayName: String?, executable: Path, @@ -34,32 +34,33 @@ public enum Command { } public extension Command { - - /// Creates a command to run during the build. The executable should be a - /// tool returned by `PluginContext.tool(named:)`, and any paths in the - /// arguments list as well as in the input and output lists should be - /// based on the paths provided in the target build context structure. + + /// Returns a command that runs when any of its ouput files are needed by + /// the build, but out-of-date. /// - /// The build command will run whenever its outputs are missing or if its - /// inputs have changed since the command was last run. In other words, - /// it is incorporated into the build command graph. + /// An output file is out-of-date if it doesn't exist, or if any input files + /// have changed since the command was last run. /// - /// This is the preferred kind of command to create when the outputs that - /// will be generated are known ahead of time. + /// - Note: the paths in the list of output files may depend on the list of + /// input file paths, but **must not** depend on reading the contents of + /// any input files. Such cases must be handled using a `prebuildCommand`. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The executable to be invoked; should be a tool looked - /// up using `tool(named:)`, which may reference either a tool provided - /// by a binary target or build from source. - /// - arguments: Arguments to be passed to the tool. Any paths should be - /// based on the paths provided in the target build context. - /// - environment: Any custom environment assignments for the subprocess. - /// - inputFiles: Input files to the build command. Any changes to the - /// input files cause the command to be rerun. - /// - outputFiles: Output files that should be processed further according - /// to the rules defined by the build system. + /// - executable: The absolute path to the executable to be invoked. + /// - arguments: Command-line arguments to be passed to the executable. + /// - environment: Environment variable assignments visible to the + /// executable. + /// - inputFiles: Files on which the contents of output files may depend. + /// Any paths passed as `arguments` should typically be passed here as + /// well. + /// - outputFiles: Files to be generated or updated by the executable. + /// Any files recognizable by their extension as source files + /// (e.g. `.swift`) are compiled into the target for which this command + /// was generated as if in its source directory; other files are treated + /// as resources as if explicitly listed in `Package.swift` using + /// `.process(...)`. static func buildCommand( displayName: String?, executable: Path, @@ -78,32 +79,32 @@ public extension Command { outputFiles: outputFiles) } - /// Creates a command to run during the build. The executable should be a - /// tool returned by `PluginContext.tool(named:)`, and any paths in the - /// arguments list as well as in the input and output lists should be - /// based on the paths provided in the target build context structure. + /// Returns a command that runs when any of its ouput files are needed + /// by the build, but out-of-date. /// - /// The build command will run whenever its outputs are missing or if its - /// inputs have changed since the command was last run. In other words, - /// it is incorporated into the build command graph. + /// An output file is out-of-date if it doesn't exist, or if any input + /// files have changed since the command was last run. /// - /// This is the preferred kind of command to create when the outputs that - /// will be generated are known ahead of time. + /// - Note: the paths in the list of output files may depend on the list + /// of input file paths, but **must not** depend on reading the contents + /// of any input files. Such cases must be handled using a `prebuildCommand`. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The executable to be invoked; should be a tool looked - /// up using `tool(named:)`, which may reference either a tool provided - /// by a binary target or build from source. - /// - arguments: Arguments to be passed to the tool. Any paths should be - /// based on the paths provided in the target build context. - /// - environment: Any custom environment assignments for the subprocess. - /// - workingDirectory: Optional initial working directory of the command. - /// - inputFiles: Input files to the build command. Any changes to the - /// input files cause the command to be rerun. - /// - outputFiles: Output files that should be processed further according - /// to the rules defined by the build system. + /// - executable: The absolute path to the executable to be invoked. + /// - arguments: Command-line arguments to be passed to the executable. + /// - environment: Environment variable assignments visible to the executable. + /// - workingDirectory: Optional initial working directory when the executable + /// runs. + /// - inputFiles: Files on which the contents of output files may depend. + /// Any paths passed as `arguments` should typically be passed here as well. + /// - outputFiles: Files to be generated or updated by the executable. + /// Any files recognizable by their extension as source files + /// (e.g. `.swift`) are compiled into the target for which this command + /// was generated as if in its source directory; other files are treated + /// as resources as if explicitly listed in `Package.swift` using + /// `.process(...)`. @available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported") static func buildCommand( displayName: String?, @@ -124,35 +125,31 @@ public extension Command { outputFiles: outputFiles) } - /// Creates a command to run before the build. The executable should be a - /// tool returned by `PluginContext.tool(named:)`, and any paths in the - /// arguments list and in the output files directory should be based on - /// the paths provided in the target build context structure. - /// - /// The build command will run before the build starts, and is allowed to - /// create an arbitrary set of output files based on the contents of the - /// inputs. + /// Returns a command that runs unconditionally before every build. /// - /// Because prebuild commands are run on every build, they can have a - /// significant performance impact and should only be used when there is - /// no way to know the names of the outputs before the command is run. - /// - /// The `outputFilesDirectory` parameter is the path of a directory into - /// which the command will write its output files. Any files that are in - /// that directory after the prebuild command finishes will be interpreted - /// according to the same build rules as for sources. + /// Prebuild commands can have a significant performance impact + /// and should only be used when there would be no way to know the + /// list of output file paths without first reading the contents + /// of one or more input files. Typically there is no way to + /// determine this list without first running the command, so + /// instead of encoding that list, the caller supplies an + /// `outputFilesDirectory` parameter, and all files in that + /// directory after the command runs are treated as output files. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The executable to be invoked; should be a tool looked - /// up using `tool(named:)`, which may reference either a tool provided - /// by a binary target or build from source. - /// - arguments: Arguments to be passed to the tool. Any paths should be - /// based on the paths provided in the target build context. - /// - environment: Any custom environment assignments for the subprocess. - /// - outputFilesDirectory: A directory into which the command can write - /// output files that should be processed further. + /// - executable: The absolute path to the executable to be invoked. + /// - arguments: Command-line arguments to be passed to the executable. + /// - environment: Environment variable assignments visible to the executable. + /// - workingDirectory: Optional initial working directory when the executable + /// runs. + /// - outputFilesDirectory: A directory into which the command writes its + /// output files. Any files there recognizable by their extension as + /// source files (e.g. `.swift`) are compiled into the target for which + /// this command was generated as if in its source directory; other + /// files are treated as resources as if explicitly listed in + /// `Package.swift` using `.process(...)`. static func prebuildCommand( displayName: String?, executable: Path, @@ -169,36 +166,31 @@ public extension Command { outputFilesDirectory: outputFilesDirectory) } - /// Creates a command to run before the build. The executable should be a - /// tool returned by `PluginContext.tool(named:)`, and any paths in the - /// arguments list and in the output files directory should be based on - /// the paths provided in the target build context structure. - /// - /// The build command will run before the build starts, and is allowed to - /// create an arbitrary set of output files based on the contents of the - /// inputs. + /// Returns a command that runs unconditionally before every build. /// /// Because prebuild commands are run on every build, they can have a - /// significant performance impact and should only be used when there is - /// no way to know the names of the outputs before the command is run. - /// - /// The `outputFilesDirectory` parameter is the path of a directory into - /// which the command will write its output files. Any files that are in - /// that directory after the prebuild command finishes will be interpreted - /// according to the same build rules as for sources. + /// significant performance impact and should only be used when there + /// would be no way to know the list of output file paths without first + /// reading the contents of one or more input files. Typically there is + /// no way to determine this list without first running the command, so + /// instead of encoding that list, the caller supplies an + /// `outputFilesDirectory` parameter, and all files in that directory + /// after the command runs are treated as output files. /// /// - parameters: /// - displayName: An optional string to show in build logs and other /// status areas. - /// - executable: The executable to be invoked; should be a tool looked - /// up using `tool(named:)`, which may reference either a tool provided - /// by a binary target or build from source. - /// - arguments: Arguments to be passed to the tool. Any paths should be - /// based on the paths provided in the target build context. - /// - environment: Any custom environment assignments for the subprocess. - /// - workingDirectory: Optional initial working directory of the command. - /// - outputFilesDirectory: A directory into which the command can write - /// output files that should be processed further. + /// - executable: The absolute path to the executable to be invoked. + /// - arguments: Command-line arguments to be passed to the executable. + /// - environment: Environment variable assignments visible to the executable. + /// - workingDirectory: Optional initial working directory when the executable + /// runs. + /// - outputFilesDirectory: A directory into which the command writes its + /// output files. Any files there recognizable by their extension as + /// source files (e.g. `.swift`) are compiled into the target for which + /// this command was generated as if in its source directory; other + /// files are treated as resources as if explicitly listed in + /// `Package.swift` using `.process(...)`. @available(*, unavailable, message: "specifying the initial working directory for a command is not yet supported") static func prebuildCommand( displayName: String?, diff --git a/Sources/PackagePlugin/PackageModel.swift b/Sources/PackagePlugin/PackageModel.swift index aadd06033f9..27c9dc8e8e5 100644 --- a/Sources/PackagePlugin/PackageModel.swift +++ b/Sources/PackagePlugin/PackageModel.swift @@ -482,6 +482,13 @@ extension FileList: Sequence { } } +@available(_PackageDescription, introduced: 5.10) +extension FileList: RandomAccessCollection { + public var startIndex: Int { 0 } + public var endIndex: Int { files.endIndex } + public subscript(i: Int) -> File { files[i] } +} + /// Provides information about a single file in a FileList. public struct File { /// The path of the file. diff --git a/Sources/PackagePlugin/Plugin.swift b/Sources/PackagePlugin/Plugin.swift index a6147cfb455..e9f68d5629f 100644 --- a/Sources/PackagePlugin/Plugin.swift +++ b/Sources/PackagePlugin/Plugin.swift @@ -26,6 +26,16 @@ internal func close(_ fd: CInt) -> CInt { internal func fileno(_ fh: UnsafeMutablePointer?) -> CInt { return _fileno(fh) } + +internal func strerror(_ errno: CInt) -> String? { + // MSDN indicates that the returned string can have a maximum of 94 + // characters, so allocate 95 characters. + return withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: 95) { + let result = _wcserror_s($0.baseAddress, $0.count, errno) + guard result == 0, let baseAddress = $0.baseAddress else { return nil } + return String(decodingCString: baseAddress, as: UTF16.self) + } +} #endif // @@ -267,8 +277,12 @@ extension Plugin { // Private function to construct an error message from an `errno` code. fileprivate static func describe(errno: Int32) -> String { +#if os(Windows) + return strerror(errno) ?? String(errno) +#else if let cStr = strerror(errno) { return String(cString: cStr) } return String(describing: errno) +#endif } } @@ -303,7 +317,7 @@ internal struct MessageConnection where TX: Encodable, RX: Decodable { } // Decode the count. - let count = header.withUnsafeBytes{ $0.load(as: UInt64.self).littleEndian } + let count = header.withUnsafeBytes{ $0.loadUnaligned(as: UInt64.self).littleEndian } guard count >= 2 else { throw PluginMessageError.invalidPayloadSize } diff --git a/Sources/PackageRegistry/ChecksumTOFU.swift b/Sources/PackageRegistry/ChecksumTOFU.swift index 9d114183493..38cf7141449 100644 --- a/Sources/PackageRegistry/ChecksumTOFU.swift +++ b/Sources/PackageRegistry/ChecksumTOFU.swift @@ -36,7 +36,30 @@ struct PackageVersionChecksumTOFU { } // MARK: - source archive - + func validateSourceArchive( + registry: Registry, + package: PackageIdentity.RegistryIdentity, + version: Version, + checksum: String, + timeout: DispatchTimeInterval?, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.validateSourceArchive( + registry: registry, + package: package, + version: version, + checksum: checksum, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + + @available(*, noasync, message: "Use the async alternative") func validateSourceArchive( registry: Registry, package: PackageIdentity.RegistryIdentity, @@ -135,7 +158,31 @@ struct PackageVersionChecksumTOFU { } // MARK: - manifests - + func validateManifest( + registry: Registry, + package: PackageIdentity.RegistryIdentity, + version: Version, + toolsVersion: ToolsVersion?, + checksum: String, + timeout: DispatchTimeInterval?, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.validateManifest( + registry: registry, + package: package, + version: version, + toolsVersion: toolsVersion, + checksum: checksum, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") func validateManifest( registry: Registry, package: PackageIdentity.RegistryIdentity, diff --git a/Sources/PackageRegistry/RegistryClient.swift b/Sources/PackageRegistry/RegistryClient.swift index a267ddf75c8..ff8ec74b5c6 100644 --- a/Sources/PackageRegistry/RegistryClient.swift +++ b/Sources/PackageRegistry/RegistryClient.swift @@ -28,7 +28,7 @@ public protocol RegistryClientDelegate { } /// Package registry client. -/// API specification: https://github.com/apple/swift-package-manager/blob/main/Documentation/Registry.md +/// API specification: https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md public final class RegistryClient: Cancellable { public typealias Delegate = RegistryClientDelegate @@ -86,9 +86,7 @@ public final class RegistryClient: Cancellable { switch registryAuthentication.type { case .basic: let authorizationString = "\(user):\(password)" - guard let authorizationData = authorizationString.data(using: .utf8) else { - return nil - } + let authorizationData = Data(authorizationString.utf8) return "Basic \(authorizationData.base64EncodedString())" case .token: // `user` value is irrelevant in this case return "Bearer \(password)" @@ -149,7 +147,25 @@ public final class RegistryClient: Cancellable { callback: completion ) } + + public func getPackageMetadata( + package: PackageIdentity, + timeout: DispatchTimeInterval? = .none, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> PackageMetadata { + try await safe_async { + self.getPackageMetadata( + package: package, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func getPackageMetadata( package: PackageIdentity, timeout: DispatchTimeInterval? = .none, @@ -262,7 +278,29 @@ public final class RegistryClient: Cancellable { ) } } + + public func getPackageVersionMetadata( + package: PackageIdentity, + version: Version, + timeout: DispatchTimeInterval? = .none, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> PackageVersionMetadata { + try await safe_async { + self.getPackageVersionMetadata( + package: package, + version: version, + timeout: timeout, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func getPackageVersionMetadata( package: PackageIdentity, version: Version, @@ -464,6 +502,26 @@ public final class RegistryClient: Cancellable { } } + public func getAvailableManifests( + package: PackageIdentity, + version: Version, + timeout: DispatchTimeInterval? = .none, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> [String: (toolsVersion: ToolsVersion, content: String?)]{ + try await safe_async { + self.getAvailableManifests( + package: package, + version: version, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + + @available(*, noasync, message: "Use the async alternative") public func getAvailableManifests( package: PackageIdentity, version: Version, @@ -592,9 +650,7 @@ public final class RegistryClient: Cancellable { guard let data = response.body else { throw RegistryError.invalidResponse } - guard let manifestContent = String(data: data, encoding: .utf8) else { - throw RegistryError.invalidResponse - } + let manifestContent = String(decoding: data, as: UTF8.self) signatureValidation.validate( registry: registry, @@ -693,7 +749,28 @@ public final class RegistryClient: Cancellable { } } } - + public func getManifestContent( + package: PackageIdentity, + version: Version, + customToolsVersion: ToolsVersion?, + timeout: DispatchTimeInterval? = .none, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> String { + try await safe_async { + self.getManifestContent( + package: package, + version: version, + customToolsVersion: customToolsVersion, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + + @available(*, noasync, message: "Use the async alternative") public func getManifestContent( package: PackageIdentity, version: Version, @@ -830,9 +907,7 @@ public final class RegistryClient: Cancellable { guard let data = response.body else { throw RegistryError.invalidResponse } - guard let manifestContent = String(data: data, encoding: .utf8) else { - throw RegistryError.invalidResponse - } + let manifestContent = String(decoding: data, as: UTF8.self) signatureValidation.validate( registry: registry, @@ -904,7 +979,32 @@ public final class RegistryClient: Cancellable { } } } + public func downloadSourceArchive( + package: PackageIdentity, + version: Version, + destinationPath: AbsolutePath, + progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + timeout: DispatchTimeInterval? = .none, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.downloadSourceArchive( + package: package, + version: version, + destinationPath: destinationPath, + progressHandler: progressHandler, + timeout: timeout, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func downloadSourceArchive( package: PackageIdentity, version: Version, @@ -1195,7 +1295,25 @@ public final class RegistryClient: Cancellable { } } } + + public func lookupIdentities( + scmURL: SourceControlURL, + timeout: DispatchTimeInterval? = .none, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> Set { + try await safe_async { + self.lookupIdentities( + scmURL: scmURL, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func lookupIdentities( scmURL: SourceControlURL, timeout: DispatchTimeInterval? = .none, @@ -1300,7 +1418,25 @@ public final class RegistryClient: Cancellable { ) } } + + public func login( + loginURL: URL, + timeout: DispatchTimeInterval? = .none, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.login( + loginURL: loginURL, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func login( loginURL: URL, timeout: DispatchTimeInterval? = .none, @@ -1337,7 +1473,41 @@ public final class RegistryClient: Cancellable { } } } + + public func publish( + registryURL: URL, + packageIdentity: PackageIdentity, + packageVersion: Version, + packageArchive: AbsolutePath, + packageMetadata: AbsolutePath?, + signature: [UInt8]?, + metadataSignature: [UInt8]?, + signatureFormat: SignatureFormat?, + timeout: DispatchTimeInterval? = .none, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> PublishResult { + try await safe_async { + self.publish( + registryURL: registryURL, + packageIdentity: packageIdentity, + packageVersion: packageVersion, + packageArchive: packageArchive, + packageMetadata: packageMetadata, + signature: signature, + metadataSignature: metadataSignature, + signatureFormat: signatureFormat, + timeout: timeout, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func publish( registryURL: URL, packageIdentity: PackageIdentity, @@ -1505,8 +1675,26 @@ public final class RegistryClient: Cancellable { ) } } + + func checkAvailability( + registry: Registry, + timeout: DispatchTimeInterval? = .none, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> AvailabilityStatus { + try await safe_async { + self.checkAvailability( + registry: registry, + timeout: timeout, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } // marked internal for testing + @available(*, noasync, message: "Use the async alternative") func checkAvailability( registry: Registry, timeout: DispatchTimeInterval? = .none, @@ -1614,14 +1802,14 @@ public final class RegistryClient: Cancellable { case 400...499: return RegistryError.clientError( code: response.statusCode, - details: response.body.flatMap { String(data: $0, encoding: .utf8) } ?? "" + details: response.body.map { String(decoding: $0, as: UTF8.self) } ?? "" ) case 501: return RegistryError.authenticationMethodNotSupported case 500...599: return RegistryError.serverError( code: response.statusCode, - details: response.body.flatMap { String(data: $0, encoding: .utf8) } ?? "" + details: response.body.map { String(decoding: $0, as: UTF8.self) } ?? "" ) default: return RegistryError.invalidResponseStatus(expected: expectedStatus, actual: response.statusCode) diff --git a/Sources/PackageRegistry/RegistryDownloadsManager.swift b/Sources/PackageRegistry/RegistryDownloadsManager.swift index dcddf1c3a7f..6504df5f4c8 100644 --- a/Sources/PackageRegistry/RegistryDownloadsManager.swift +++ b/Sources/PackageRegistry/RegistryDownloadsManager.swift @@ -43,7 +43,27 @@ public class RegistryDownloadsManager: Cancellable { self.registryClient = registryClient self.delegate = delegate } + + public func lookup( + package: PackageIdentity, + version: Version, + observabilityScope: ObservabilityScope, + delegateQueue: DispatchQueue, + callbackQueue: DispatchQueue + ) async throws -> AbsolutePath { + try await safe_async { + self.lookup( + package: package, + version: version, + observabilityScope: observabilityScope, + delegateQueue: delegateQueue, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func lookup( package: PackageIdentity, version: Version, diff --git a/Sources/PackageRegistry/SignatureValidation.swift b/Sources/PackageRegistry/SignatureValidation.swift index 87d46367d8c..3e5fccd64ae 100644 --- a/Sources/PackageRegistry/SignatureValidation.swift +++ b/Sources/PackageRegistry/SignatureValidation.swift @@ -53,7 +53,34 @@ struct SignatureValidation { } // MARK: - source archive - + func validate( + registry: Registry, + package: PackageIdentity.RegistryIdentity, + version: Version, + content: Data, + configuration: RegistryConfiguration.Security.Signing, + timeout: DispatchTimeInterval?, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> SigningEntity? { + try await safe_async { + self.validate( + registry: registry, + package: package, + version: version, + content: content, + configuration: configuration, + timeout: timeout, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + + @available(*, noasync, message: "Use the async alternative") func validate( registry: Registry, package: PackageIdentity.RegistryIdentity, @@ -285,7 +312,36 @@ struct SignatureValidation { } // MARK: - manifests + func validate( + registry: Registry, + package: PackageIdentity.RegistryIdentity, + version: Version, + toolsVersion: ToolsVersion?, + manifestContent: String, + configuration: RegistryConfiguration.Security.Signing, + timeout: DispatchTimeInterval?, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> SigningEntity? { + try await safe_async { + self.validate( + registry: registry, + package: package, + version: version, + toolsVersion: toolsVersion, + manifestContent: manifestContent, + configuration: configuration, + timeout: timeout, + fileSystem:fileSystem, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") func validate( registry: Registry, package: PackageIdentity.RegistryIdentity, diff --git a/Sources/PackageRegistry/SigningEntityTOFU.swift b/Sources/PackageRegistry/SigningEntityTOFU.swift index 00c9ff509d6..efd8884b8d2 100644 --- a/Sources/PackageRegistry/SigningEntityTOFU.swift +++ b/Sources/PackageRegistry/SigningEntityTOFU.swift @@ -29,7 +29,29 @@ struct PackageSigningEntityTOFU { self.signingEntityStorage = signingEntityStorage self.signingEntityCheckingMode = signingEntityCheckingMode } + + func validate( + registry: Registry, + package: PackageIdentity.RegistryIdentity, + version: Version, + signingEntity: SigningEntity?, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") func validate( registry: Registry, package: PackageIdentity.RegistryIdentity, diff --git a/Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift b/Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift index 3f9ffce7a48..94a71141df8 100644 --- a/Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift +++ b/Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift @@ -19,7 +19,12 @@ import PackageModel import PackageRegistry import PackageSigning import Workspace + +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import X509 // FIXME: need this import or else SwiftSigningIdentity initializer fails +#else +import X509 +#endif import struct TSCBasic.ByteString import struct TSCBasic.RegEx diff --git a/Sources/PackageRegistryTool/PackageRegistryTool.swift b/Sources/PackageRegistryTool/PackageRegistryTool.swift index a1d4da85a09..b27402e9d67 100644 --- a/Sources/PackageRegistryTool/PackageRegistryTool.swift +++ b/Sources/PackageRegistryTool/PackageRegistryTool.swift @@ -141,13 +141,14 @@ public struct SwiftPackageRegistryTool: ParsableCommand { static func getRegistriesConfig(_ swiftTool: SwiftTool, global: Bool) throws -> Workspace.Configuration.Registries { if global { + let sharedRegistriesFile = Workspace.DefaultLocations.registriesConfigurationFile( + at: swiftTool.sharedConfigurationDirectory + ) // Workspace not needed when working with user-level registries config return try .init( fileSystem: swiftTool.fileSystem, localRegistriesFile: .none, - sharedRegistriesFile: swiftTool.sharedConfigurationDirectory.map { - Workspace.DefaultLocations.registriesConfigurationFile(at: $0) - } + sharedRegistriesFile: sharedRegistriesFile ) } else { let workspace = try swiftTool.getActiveWorkspace() diff --git a/Sources/PackageSigning/CertificateStores.swift b/Sources/PackageSigning/CertificateStores.swift index 06ff9316bef..112ace90587 100644 --- a/Sources/PackageSigning/CertificateStores.swift +++ b/Sources/PackageSigning/CertificateStores.swift @@ -10,7 +10,11 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import X509 +#else +import X509 +#endif enum Certificates { static let appleRootsRaw = [ diff --git a/Sources/PackageSigning/SignatureProvider.swift b/Sources/PackageSigning/SignatureProvider.swift index 1605b14550d..24ac4a986ab 100644 --- a/Sources/PackageSigning/SignatureProvider.swift +++ b/Sources/PackageSigning/SignatureProvider.swift @@ -13,13 +13,23 @@ import struct Foundation.Data import struct Foundation.Date +#if USE_IMPL_ONLY_IMPORTS #if canImport(Security) @_implementationOnly import Security #endif -import Basics @_implementationOnly import SwiftASN1 @_implementationOnly @_spi(CMS) import X509 +#else +#if canImport(Security) +import Security +#endif + +import SwiftASN1 +@_spi(CMS) import X509 +#endif + +import Basics // MARK: - Public signature API diff --git a/Sources/PackageSigning/SigningEntity/PackageSigningEntityStorage.swift b/Sources/PackageSigning/SigningEntity/PackageSigningEntityStorage.swift index f53d4da36f7..7e66147e33d 100644 --- a/Sources/PackageSigning/SigningEntity/PackageSigningEntityStorage.swift +++ b/Sources/PackageSigning/SigningEntity/PackageSigningEntityStorage.swift @@ -20,6 +20,7 @@ import struct TSCUtility.Version public protocol PackageSigningEntityStorage { /// For a given package, return the signing entities and the package versions that each of them signed. + @available(*, noasync, message: "Use the async alternative") func get( package: PackageIdentity, observabilityScope: ObservabilityScope, @@ -31,6 +32,7 @@ public protocol PackageSigningEntityStorage { /// /// This throws `PackageSigningEntityStorageError.conflict` if `signingEntity` /// of the package version is different from that in storage. + @available(*, noasync, message: "Use the async alternative") func put( package: PackageIdentity, version: Version, @@ -46,6 +48,7 @@ public protocol PackageSigningEntityStorage { /// If the package version already has other `SigningEntity`s in storage, this /// API **adds** `signingEntity` to the package version's signers rather than /// throwing an error. + @available(*, noasync, message: "Use the async alternative") func add( package: PackageIdentity, version: Version, @@ -57,6 +60,7 @@ public protocol PackageSigningEntityStorage { ) /// Make `signingEntity` the package's expected signer starting from the given version. + @available(*, noasync, message: "Use the async alternative") func changeSigningEntityFromVersion( package: PackageIdentity, version: Version, @@ -71,6 +75,7 @@ public protocol PackageSigningEntityStorage { /// /// This API deletes all other existing signers from storage, therefore making /// `signingEntity` the package's sole signer. + @available(*, noasync, message: "Use the async alternative") func changeSigningEntityForAllVersions( package: PackageIdentity, version: Version, @@ -82,6 +87,107 @@ public protocol PackageSigningEntityStorage { ) } +public extension PackageSigningEntityStorage { + func get( + package: PackageIdentity, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> PackageSigners { + try await safe_async { + self.get( + package: package, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + callback: $0 + ) + } + } + + func put( + package: PackageIdentity, + version: Version, + signingEntity: SigningEntity, + origin: SigningEntity.Origin, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.put( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + callback: $0 + ) + } + } + + func add( + package: PackageIdentity, + version: Version, + signingEntity: SigningEntity, + origin: SigningEntity.Origin, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.add( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + callback: $0 + ) + } + } + + func changeSigningEntityFromVersion( + package: PackageIdentity, + version: Version, + signingEntity: SigningEntity, + origin: SigningEntity.Origin, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.changeSigningEntityFromVersion( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + callback: $0 + ) + } + } + + func changeSigningEntityForAllVersions( + package: PackageIdentity, + version: Version, + signingEntity: SigningEntity, + origin: SigningEntity.Origin, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws { + try await safe_async { + self.changeSigningEntityForAllVersions( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + callback: $0 + ) + } + } +} + // MARK: - Models extension SigningEntity { diff --git a/Sources/PackageSigning/SigningEntity/SigningEntity.swift b/Sources/PackageSigning/SigningEntity/SigningEntity.swift index 19ac9b565c1..3fb9a444c03 100644 --- a/Sources/PackageSigning/SigningEntity/SigningEntity.swift +++ b/Sources/PackageSigning/SigningEntity/SigningEntity.swift @@ -10,8 +10,13 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SwiftASN1 @_implementationOnly import X509 +#else +import SwiftASN1 +import X509 +#endif // MARK: - SigningEntity is the entity that generated the signature diff --git a/Sources/PackageSigning/SigningIdentity.swift b/Sources/PackageSigning/SigningIdentity.swift index f8bd59b512e..084bc67d70d 100644 --- a/Sources/PackageSigning/SigningIdentity.swift +++ b/Sources/PackageSigning/SigningIdentity.swift @@ -10,13 +10,23 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS #if canImport(Security) @_implementationOnly import Security #endif -import Basics @_implementationOnly import Crypto @_implementationOnly import X509 +#else +#if canImport(Security) +import Security +#endif + +import Crypto +import X509 +#endif + +import Basics public protocol SigningIdentity {} diff --git a/Sources/PackageSigning/VerifierPolicies.swift b/Sources/PackageSigning/VerifierPolicies.swift index a962be9df47..dc82439c876 100644 --- a/Sources/PackageSigning/VerifierPolicies.swift +++ b/Sources/PackageSigning/VerifierPolicies.swift @@ -16,15 +16,21 @@ import struct Foundation.Date import struct Foundation.URL import Basics + +#if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SwiftASN1 @_implementationOnly @_spi(DisableValidityCheck) import X509 +#else +import SwiftASN1 +@_spi(DisableValidityCheck) import X509 +#endif extension SignatureProviderProtocol { @PolicyBuilder func buildPolicySet(configuration: VerifierConfiguration, httpClient: HTTPClient) -> some VerifierPolicy { _CodeSigningPolicy() _ADPCertificatePolicy() - + let now = Date() switch (configuration.certificateExpiration, configuration.certificateRevocation) { case (.enabled(let expiryValidationTime), .strict(let revocationValidationTime)): @@ -158,27 +164,31 @@ struct _OCSPVerifierPolicy: VerifierPolicy { private struct _OCSPRequester: OCSPRequester { let httpClient: HTTPClient - func query(request: [UInt8], uri: String) async throws -> [UInt8] { + func query(request: [UInt8], uri: String) async -> OCSPRequesterQueryResult { guard let url = URL(string: uri), let host = url.host else { - throw SwiftOCSPRequesterError.invalidURL(uri) + return .terminalError(SwiftOCSPRequesterError.invalidURL(uri)) } - let response = try await self.httpClient.post( - url, - body: Data(request), - headers: [ - "Content-Type": "application/ocsp-request", - "Host": host, - ] - ) + do { + let response = try await self.httpClient.post( + url, + body: Data(request), + headers: [ + "Content-Type": "application/ocsp-request", + "Host": host, + ] + ) - guard response.statusCode == 200 else { - throw SwiftOCSPRequesterError.invalidResponse(statusCode: response.statusCode) - } - guard let responseBody = response.body else { - throw SwiftOCSPRequesterError.emptyResponse + guard response.statusCode == 200 else { + throw SwiftOCSPRequesterError.invalidResponse(statusCode: response.statusCode) + } + guard let responseBody = response.body else { + throw SwiftOCSPRequesterError.emptyResponse + } + return .response(Array(responseBody)) + } catch { + return .nonTerminalError(error) } - return Array(responseBody) } } diff --git a/Sources/PackageSigning/X509Extensions.swift b/Sources/PackageSigning/X509Extensions.swift index 8c1603138fa..7045a22ac81 100644 --- a/Sources/PackageSigning/X509Extensions.swift +++ b/Sources/PackageSigning/X509Extensions.swift @@ -12,13 +12,23 @@ import struct Foundation.Data +#if USE_IMPL_ONLY_IMPORTS #if canImport(Security) @_implementationOnly import Security #endif -import Basics @_implementationOnly import SwiftASN1 @_implementationOnly import X509 +#else +#if canImport(Security) +import Security +#endif + +import SwiftASN1 +import X509 +#endif + +import Basics #if canImport(Security) extension Certificate { @@ -30,7 +40,7 @@ extension Certificate { init(secIdentity: SecIdentity) throws { var secCertificate: SecCertificate? let status = SecIdentityCopyCertificate(secIdentity, &secCertificate) - guard status == errSecSuccess, let secCertificate = secCertificate else { + guard status == errSecSuccess, let secCertificate else { throw StringError("failed to get certificate from SecIdentity: status \(status)") } self = try Certificate(secCertificate: secCertificate) @@ -60,33 +70,13 @@ extension DistinguishedName { private func stringAttribute(oid: ASN1ObjectIdentifier) -> String? { for relativeDistinguishedName in self { for attribute in relativeDistinguishedName where attribute.type == oid { - if let stringValue = attribute.stringValue { - return stringValue - } + return attribute.value.description } } return nil } } -extension RelativeDistinguishedName.Attribute { - fileprivate var stringValue: String? { - let asn1StringBytes: ArraySlice? - do { - asn1StringBytes = try ASN1PrintableString(asn1Any: self.value).bytes - } catch { - asn1StringBytes = try? ASN1UTF8String(asn1Any: self.value).bytes - } - - guard let asn1StringBytes, - let stringValue = String(bytes: asn1StringBytes, encoding: .utf8) - else { - return nil - } - return stringValue - } -} - // MARK: - Certificate cache extension Certificate { diff --git a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift index 4e530f25298..278c3b0167e 100644 --- a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift +++ b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift @@ -41,10 +41,10 @@ extension BinaryTarget { // At the moment we return at most a single library. let metadata = try XCFrameworkMetadata.parse(fileSystem: fileSystem, rootPath: self.artifactPath) // Filter the libraries that are relevant to the triple. - // FIXME: this filter needs to become more sophisticated guard let library = metadata.libraries.first(where: { $0.platform == triple.os?.asXCFrameworkPlatformString && - $0.architectures.contains(triple.archName) + $0.variant == triple.environment?.asXCFrameworkPlatformVariantString && + $0.architectures.contains(triple.archName) }) else { return [] } @@ -100,8 +100,27 @@ extension Triple.OS { return nil // XCFrameworks do not support any of these platforms today. case .macosx: return "macos" + case .ios: + return "ios" + case .tvos: + return "tvos" + case .watchos: + return "watchos" default: return nil // XCFrameworks do not support any of these platforms today. } } } + +extension Triple.Environment { + fileprivate var asXCFrameworkPlatformVariantString: String? { + switch self { + case .simulator: + return "simulator" + case .macabi: + return "maccatalyst" + default: + return nil // XCFrameworks do not support any of these platform variants today. + } + } +} diff --git a/Sources/SPMBuildCore/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters.swift deleted file mode 100644 index 88a47ba1b31..00000000000 --- a/Sources/SPMBuildCore/BuildParameters.swift +++ /dev/null @@ -1,622 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2020 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 Basics -import class Foundation.ProcessInfo -import PackageModel -import PackageGraph - -public struct BuildParameters: Encodable { - /// Mode for the indexing-while-building feature. - public enum IndexStoreMode: String, Encodable { - /// Index store should be enabled. - case on - /// Index store should be disabled. - case off - /// Index store should be enabled in debug configuration. - case auto - } - - /// An optional intermodule optimization to run at link time. - /// - /// When using Link Time Optimization (LTO for short) the swift and clang - /// compilers produce objects containing containing a higher level - /// representation of the program bitcode instead of machine code. The - /// linker combines these objects together performing additional - /// optimizations with visibility into each module/object, resulting in a - /// further optimized version of the executable. - /// - /// Using LTO can have significant impact on compile times, however can be - /// used to dramatically reduce code-size in some cases. - /// - /// Note: Bitcode objects and machine code objects can be linked together. - public enum LinkTimeOptimizationMode: String, Encodable { - /// The "standard" LTO mode designed to produce minimal code sign. - /// - /// Full LTO can lead to large link times. Consider using thin LTO if - /// build time is more important than minimizing binary size. - case full - /// An LTO mode designed to scale better with input size. - /// - /// Thin LTO typically results in faster link times than traditional LTO. - /// However, thin LTO may not result in binary as small as full LTO. - case thin - } - - /// Represents the debug information format. - /// - /// The debug information format controls the format of the debug information - /// that the compiler generates. Some platforms support debug information - // formats other than DWARF. - public enum DebugInfoFormat: String, Encodable { - /// DWARF debug information format, the default format used by Swift. - case dwarf - /// CodeView debug information format, used on Windows. - case codeview - /// No debug information to be emitted. - case none - } - - /// Represents the debugging strategy. - /// - /// Swift binaries requires the swiftmodule files in order for lldb to work. - /// On Darwin, linker can directly take the swiftmodule file path using the - /// -add_ast_path flag. On other platforms, we convert the swiftmodule into - /// an object file using Swift's modulewrap tool. - public enum DebuggingStrategy { - case swiftAST - case modulewrap - } - - /// Represents the test product style. - public enum TestProductStyle: Encodable { - /// Test product is a loadable bundle. This style is used on Darwin platforms and, for XCTest tests, relies on the Objective-C - /// runtime to automatically discover all tests. - case loadableBundle - - /// Test product is an executable which serves as the testing entry point. This style is used on non-Darwin platforms and, - /// for XCTests, relies on the testing entry point file to indicate which tests to run. By default, the test entry point file is - /// synthesized automatically, and uses indexer data to locate all tests and run them. But the entry point may be customized - /// in one of two ways: if a path to a test entry point file was explicitly passed via the - /// `--experimental-test-entry-point-path ` option, that file is used, otherwise if an `XCTMain.swift` - /// (formerly `LinuxMain.swift`) file is located in the package, it is used. - /// - /// - Parameter explicitlyEnabledDiscovery: Whether test discovery generation was forced by passing - /// `--enable-test-discovery`, overriding any custom test entry point file specified via other CLI options or located in - /// the package. - /// - Parameter explicitlySpecifiedPath: The path to the test entry point file, if one was specified explicitly via - /// `--experimental-test-entry-point-path `. - case entryPointExecutable( - explicitlyEnabledDiscovery: Bool, - explicitlySpecifiedPath: AbsolutePath? - ) - - /// Whether this test product style requires additional, derived test targets, i.e. there must be additional test targets, beyond those - /// listed explicitly in the package manifest, created in order to add additional behavior (such as entry point logic). - public var requiresAdditionalDerivedTestTargets: Bool { - switch self { - case .loadableBundle: - return false - case .entryPointExecutable: - return true - } - } - - /// The explicitly-specified entry point file path, if this style of test product supports it and a path was specified. - public var explicitlySpecifiedEntryPointPath: AbsolutePath? { - switch self { - case .loadableBundle: - return nil - case .entryPointExecutable(explicitlyEnabledDiscovery: _, explicitlySpecifiedPath: let entryPointPath): - return entryPointPath - } - } - - public enum DiscriminatorKeys: String, Codable { - case loadableBundle - case entryPointExecutable - } - - public enum CodingKeys: CodingKey { - case _case - case explicitlyEnabledDiscovery - case explicitlySpecifiedPath - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .loadableBundle: - try container.encode(DiscriminatorKeys.loadableBundle, forKey: ._case) - case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath): - try container.encode(DiscriminatorKeys.entryPointExecutable, forKey: ._case) - try container.encode(explicitlyEnabledDiscovery, forKey: .explicitlyEnabledDiscovery) - try container.encode(explicitlySpecifiedPath, forKey: .explicitlySpecifiedPath) - } - } - } - - /// A mode for explicit import checking - public enum TargetDependencyImportCheckingMode : Codable { - case none - case warn - case error - } - - /// The path to the data directory. - public var dataPath: AbsolutePath - - /// The build configuration. - public var configuration: BuildConfiguration - - /// The toolchain. - public var toolchain: Toolchain { _toolchain.toolchain } - private let _toolchain: _Toolchain - - /// Host triple. - public var hostTriple: Triple - - /// Target triple. - @available(*, deprecated, renamed: "targetTriple") - public var triple: Triple { - get { targetTriple } - set { targetTriple = newValue } - } - - /// Target triple. - public var targetTriple: Triple - - /// Extra build flags. - public var flags: BuildFlags - - /// An array of paths to search for pkg-config `.pc` files. - public var pkgConfigDirectories: [AbsolutePath] - - /// The architectures to build for. - public var architectures: [String]? - - /// How many jobs should llbuild and the Swift compiler spawn - public var workers: UInt32 - - /// If should link the Swift stdlib statically. - public var shouldLinkStaticSwiftStdlib: Bool - - /// Which compiler sanitizers should be enabled - public var sanitizers: EnabledSanitizers - - /// If should enable llbuild manifest caching. - public var shouldEnableManifestCaching: Bool - - /// The mode to use for indexing-while-building feature. - public var indexStoreMode: IndexStoreMode - - /// Whether to enable code coverage. - public var enableCodeCoverage: Bool - - /// Whether to enable generation of `.swiftinterface` files alongside. - /// `.swiftmodule`s. - public var enableParseableModuleInterfaces: Bool - - /// Whether to use the integrated Swift driver rather than shelling out - /// to a separate process. - public var useIntegratedSwiftDriver: Bool - - /// Whether to use the explicit module build flow (with the integrated driver). - public var useExplicitModuleBuild: Bool - - /// A flag that indicates this build should check whether targets only import. - /// their explicitly-declared dependencies - public var explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode - - /// Whether to create dylibs for dynamic library products. - public var shouldCreateDylibForDynamicProducts: Bool - - /// Whether to enable the entry-point-function-name feature. - public var canRenameEntrypointFunctionName: Bool - - /// The current build environment. - public var buildEnvironment: BuildEnvironment { - BuildEnvironment(platform: currentPlatform, configuration: configuration) - } - - /// The current platform we're building for. - var currentPlatform: PackageModel.Platform { - if self.targetTriple.isDarwin() { - return .macOS - } else if self.targetTriple.isAndroid() { - return .android - } else if self.targetTriple.isWASI() { - return .wasi - } else if self.targetTriple.isWindows() { - return .windows - } else if self.targetTriple.isOpenBSD() { - return .openbsd - } else { - return .linux - } - } - - /// Whether the Xcode build system is used. - public var isXcodeBuildSystemEnabled: Bool - - // Whether building for testability is enabled. - public var enableTestability: Bool - - /// The style of test product to produce. - public var testProductStyle: TestProductStyle - - /// Whether to disable dead code stripping by the linker - public var linkerDeadStrip: Bool - - public var colorizedOutput: Bool - - public var verboseOutput: Bool - - public var linkTimeOptimizationMode: LinkTimeOptimizationMode? - - public var debugInfoFormat: DebugInfoFormat - - public var shouldSkipBuilding: Bool - - @available(*, deprecated, message: "use `init` overload with `targetTriple` parameter name instead") - @_disfavoredOverload - public init( - dataPath: AbsolutePath, - configuration: BuildConfiguration, - toolchain: Toolchain, - hostTriple: Triple? = nil, - destinationTriple: Triple? = nil, - flags: BuildFlags, - pkgConfigDirectories: [AbsolutePath] = [], - architectures: [String]? = nil, - workers: UInt32 = UInt32(ProcessInfo.processInfo.activeProcessorCount), - shouldLinkStaticSwiftStdlib: Bool = false, - shouldEnableManifestCaching: Bool = false, - canRenameEntrypointFunctionName: Bool = false, - shouldCreateDylibForDynamicProducts: Bool = true, - sanitizers: EnabledSanitizers = EnabledSanitizers(), - enableCodeCoverage: Bool = false, - indexStoreMode: IndexStoreMode = .auto, - enableParseableModuleInterfaces: Bool = false, - useIntegratedSwiftDriver: Bool = false, - useExplicitModuleBuild: Bool = false, - isXcodeBuildSystemEnabled: Bool = false, - enableTestability: Bool? = nil, - forceTestDiscovery: Bool = false, - testEntryPointPath: AbsolutePath? = nil, - explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode = .none, - linkerDeadStrip: Bool = true, - colorizedOutput: Bool = false, - verboseOutput: Bool = false, - linkTimeOptimizationMode: LinkTimeOptimizationMode? = nil, - debugInfoFormat: DebugInfoFormat = .dwarf, - shouldSkipBuilding: Bool = false - ) throws { - try self.init( - dataPath: dataPath, - configuration: configuration, - toolchain: toolchain, - hostTriple: hostTriple, - targetTriple: destinationTriple, - flags: flags, - pkgConfigDirectories: pkgConfigDirectories, - architectures: architectures, - workers: workers, - shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib, - shouldEnableManifestCaching: shouldEnableManifestCaching, - canRenameEntrypointFunctionName: canRenameEntrypointFunctionName, - shouldCreateDylibForDynamicProducts: shouldCreateDylibForDynamicProducts, - sanitizers: sanitizers, - enableCodeCoverage: enableCodeCoverage, - indexStoreMode: indexStoreMode, - enableParseableModuleInterfaces: enableParseableModuleInterfaces, - useIntegratedSwiftDriver: useIntegratedSwiftDriver, - useExplicitModuleBuild: useExplicitModuleBuild, - isXcodeBuildSystemEnabled: isXcodeBuildSystemEnabled, - enableTestability: enableTestability, - forceTestDiscovery: forceTestDiscovery, - testEntryPointPath: testEntryPointPath, - explicitTargetDependencyImportCheckingMode: explicitTargetDependencyImportCheckingMode, - linkerDeadStrip: linkerDeadStrip, - colorizedOutput: colorizedOutput, - verboseOutput: verboseOutput, - linkTimeOptimizationMode: linkTimeOptimizationMode, - debugInfoFormat: debugInfoFormat, - shouldSkipBuilding: shouldSkipBuilding - ) - } - - public init( - dataPath: AbsolutePath, - configuration: BuildConfiguration, - toolchain: Toolchain, - hostTriple: Triple? = nil, - targetTriple: Triple? = nil, - flags: BuildFlags, - pkgConfigDirectories: [AbsolutePath] = [], - architectures: [String]? = nil, - workers: UInt32 = UInt32(ProcessInfo.processInfo.activeProcessorCount), - shouldLinkStaticSwiftStdlib: Bool = false, - shouldEnableManifestCaching: Bool = false, - canRenameEntrypointFunctionName: Bool = false, - shouldCreateDylibForDynamicProducts: Bool = true, - sanitizers: EnabledSanitizers = EnabledSanitizers(), - enableCodeCoverage: Bool = false, - indexStoreMode: IndexStoreMode = .auto, - enableParseableModuleInterfaces: Bool = false, - useIntegratedSwiftDriver: Bool = false, - useExplicitModuleBuild: Bool = false, - isXcodeBuildSystemEnabled: Bool = false, - enableTestability: Bool? = nil, - forceTestDiscovery: Bool = false, - testEntryPointPath: AbsolutePath? = nil, - explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode = .none, - linkerDeadStrip: Bool = true, - colorizedOutput: Bool = false, - verboseOutput: Bool = false, - linkTimeOptimizationMode: LinkTimeOptimizationMode? = nil, - debugInfoFormat: DebugInfoFormat = .dwarf, - shouldSkipBuilding: Bool = false - ) throws { - let targetTriple = try targetTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompilerPath) - - self.dataPath = dataPath - self.configuration = configuration - self._toolchain = _Toolchain(toolchain: toolchain) - self.hostTriple = try hostTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompilerPath) - self.targetTriple = targetTriple - switch debugInfoFormat { - case .dwarf: - var flags = flags - // DWARF requires lld as link.exe expects CodeView debug info. - self.flags = flags.merging(targetTriple.isWindows() ? BuildFlags( - cCompilerFlags: ["-gdwarf"], - cxxCompilerFlags: ["-gdwarf"], - swiftCompilerFlags: ["-g", "-use-ld=lld"], - linkerFlags: ["-debug:dwarf"] - ) : BuildFlags(cCompilerFlags: ["-g"], cxxCompilerFlags: ["-g"], swiftCompilerFlags: ["-g"])) - case .codeview: - if !targetTriple.isWindows() { - throw StringError("CodeView debug information is currently not supported on \(targetTriple.osName)") - } - var flags = flags - self.flags = flags.merging(BuildFlags( - cCompilerFlags: ["-g"], - cxxCompilerFlags: ["-g"], - swiftCompilerFlags: ["-g", "-debug-info-format=codeview"], - linkerFlags: ["-debug"] - )) - case .none: - var flags = flags - self.flags = flags.merging(BuildFlags( - cCompilerFlags: ["-g0"], - cxxCompilerFlags: ["-g0"], - swiftCompilerFlags: ["-gnone"] - )) - } - self.pkgConfigDirectories = pkgConfigDirectories - self.architectures = architectures - self.workers = workers - self.shouldLinkStaticSwiftStdlib = shouldLinkStaticSwiftStdlib - self.shouldEnableManifestCaching = shouldEnableManifestCaching - self.shouldCreateDylibForDynamicProducts = shouldCreateDylibForDynamicProducts - self.canRenameEntrypointFunctionName = canRenameEntrypointFunctionName - self.sanitizers = sanitizers - self.enableCodeCoverage = enableCodeCoverage - self.indexStoreMode = indexStoreMode - self.enableParseableModuleInterfaces = enableParseableModuleInterfaces - self.useIntegratedSwiftDriver = useIntegratedSwiftDriver - self.useExplicitModuleBuild = useExplicitModuleBuild - self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled - // decide on testability based on debug/release config - // the goals of this being based on the build configuration is - // that `swift build` followed by a `swift test` will need to do minimal rebuilding - // given that the default configuration for `swift build` is debug - // and that `swift test` normally requires building with testable enabled. - // when building and testing in release mode, one can use the '--disable-testable-imports' flag - // to disable testability in `swift test`, but that requires that the tests do not use the testable imports feature - self.enableTestability = enableTestability ?? (.debug == configuration) - self.testProductStyle = targetTriple.isDarwin() ? .loadableBundle : .entryPointExecutable( - explicitlyEnabledDiscovery: forceTestDiscovery, - explicitlySpecifiedPath: testEntryPointPath - ) - self.explicitTargetDependencyImportCheckingMode = explicitTargetDependencyImportCheckingMode - self.linkerDeadStrip = linkerDeadStrip - self.colorizedOutput = colorizedOutput - self.verboseOutput = verboseOutput - self.linkTimeOptimizationMode = linkTimeOptimizationMode - self.debugInfoFormat = debugInfoFormat - self.shouldSkipBuilding = shouldSkipBuilding - } - - @available(*, deprecated, renamed: "forTriple()") - public func withDestination(_ destinationTriple: Triple) throws -> BuildParameters { - try self.forTriple(destinationTriple) - } - - public func forTriple(_ targetTriple: Triple) throws -> BuildParameters { - let forceTestDiscovery: Bool - let testEntryPointPath: AbsolutePath? - switch self.testProductStyle { - case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath): - forceTestDiscovery = explicitlyEnabledDiscovery - testEntryPointPath = explicitlySpecifiedPath - case .loadableBundle: - forceTestDiscovery = false - testEntryPointPath = nil - } - - return try .init( - dataPath: self.dataPath.parentDirectory.appending(components: ["plugins", "tools"]), - configuration: self.configuration, - toolchain: try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK()), - hostTriple: self.hostTriple, - targetTriple: targetTriple, - flags: BuildFlags(), - pkgConfigDirectories: self.pkgConfigDirectories, - architectures: nil, - workers: self.workers, - shouldLinkStaticSwiftStdlib: self.shouldLinkStaticSwiftStdlib, - shouldEnableManifestCaching: self.shouldEnableManifestCaching, - canRenameEntrypointFunctionName: self.canRenameEntrypointFunctionName, - shouldCreateDylibForDynamicProducts: self.shouldCreateDylibForDynamicProducts, - sanitizers: self.sanitizers, - enableCodeCoverage: self.enableCodeCoverage, - indexStoreMode: self.indexStoreMode, - enableParseableModuleInterfaces: self.enableParseableModuleInterfaces, - useIntegratedSwiftDriver: self.useIntegratedSwiftDriver, - useExplicitModuleBuild: self.useExplicitModuleBuild, - isXcodeBuildSystemEnabled: self.isXcodeBuildSystemEnabled, - enableTestability: self.enableTestability, - forceTestDiscovery: forceTestDiscovery, - testEntryPointPath: testEntryPointPath, - explicitTargetDependencyImportCheckingMode: self.explicitTargetDependencyImportCheckingMode, - linkerDeadStrip: self.linkerDeadStrip, - colorizedOutput: self.colorizedOutput, - verboseOutput: self.verboseOutput, - linkTimeOptimizationMode: self.linkTimeOptimizationMode, - shouldSkipBuilding: self.shouldSkipBuilding - ) - } - - /// The path to the build directory (inside the data directory). - public var buildPath: AbsolutePath { - if isXcodeBuildSystemEnabled { - return dataPath.appending(components: "Products", configuration.dirname.capitalized) - } else { - return dataPath.appending(component: configuration.dirname) - } - } - - /// The path to the index store directory. - public var indexStore: AbsolutePath { - assert(indexStoreMode != .off, "index store is disabled") - return buildPath.appending(components: "index", "store") - } - - /// The path to the code coverage directory. - public var codeCovPath: AbsolutePath { - return buildPath.appending("codecov") - } - - /// The path to the code coverage profdata file. - public var codeCovDataFile: AbsolutePath { - return codeCovPath.appending("default.profdata") - } - - public var llbuildManifest: AbsolutePath { - return dataPath.appending(components: "..", configuration.dirname + ".yaml") - } - - public var pifManifest: AbsolutePath { - return dataPath.appending(components: "..", "manifest.pif") - } - - public var buildDescriptionPath: AbsolutePath { - return buildPath.appending(components: "description.json") - } - - /// The debugging strategy according to the current build parameters. - public var debuggingStrategy: DebuggingStrategy? { - guard configuration == .debug else { - return nil - } - - if targetTriple.isApple() { - return .swiftAST - } - return .modulewrap - } - - /// Returns the path to the binary of a product for the current build parameters. - public func binaryPath(for product: ResolvedProduct) throws -> AbsolutePath { - return try buildPath.appending(binaryRelativePath(for: product)) - } - - /// Returns the path to the dynamic library of a product for the current build parameters. - func potentialDynamicLibraryPath(for product: ResolvedProduct) throws -> RelativePath { - try RelativePath(validating: "\(targetTriple.dynamicLibraryPrefix)\(product.name)\(targetTriple.dynamicLibraryExtension)") - } - - /// Returns the path to the binary of a product for the current build parameters, relative to the build directory. - public func binaryRelativePath(for product: ResolvedProduct) throws -> RelativePath { - let potentialExecutablePath = try RelativePath(validating: "\(product.name)\(targetTriple.executableExtension)") - - switch product.type { - case .executable, .snippet: - return potentialExecutablePath - case .library(.static): - return try RelativePath(validating: "lib\(product.name)\(targetTriple.staticLibraryExtension)") - case .library(.dynamic): - return try potentialDynamicLibraryPath(for: product) - case .library(.automatic), .plugin: - fatalError() - case .test: - guard !targetTriple.isWASI() else { - return try RelativePath(validating: "\(product.name).wasm") - } - - let base = "\(product.name).xctest" - if targetTriple.isDarwin() { - return try RelativePath(validating: "\(base)/Contents/MacOS/\(product.name)") - } else { - return try RelativePath(validating: base) - } - case .macro: - #if BUILD_MACROS_AS_DYLIBS - return try potentialDynamicLibraryPath(for: product) - #else - return potentialExecutablePath - #endif - } - } -} - -/// A shim struct for toolchain so we can encode it without having to write encode(to:) for -/// entire BuildParameters by hand. -private struct _Toolchain: Encodable { - let toolchain: Toolchain - - enum CodingKeys: String, CodingKey { - case swiftCompiler - case clangCompiler - case extraCCFlags - case extraSwiftCFlags - case extraCPPFlags - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(toolchain.swiftCompilerPath, forKey: .swiftCompiler) - try container.encode(toolchain.getClangCompiler(), forKey: .clangCompiler) - - try container.encode(toolchain.extraFlags.cCompilerFlags, forKey: .extraCCFlags) - // Maintaining `extraCPPFlags` key for compatibility with older encoding. - try container.encode(toolchain.extraFlags.cxxCompilerFlags, forKey: .extraCPPFlags) - try container.encode(toolchain.extraFlags.swiftCompilerFlags, forKey: .extraSwiftCFlags) - try container.encode(toolchain.swiftCompilerPath, forKey: .swiftCompiler) - } -} - -extension BuildParameters { - /// Whether to build Swift code with whole module optimization (WMO) - /// enabled. - public var useWholeModuleOptimization: Bool { - switch configuration { - case .debug: - return false - - case .release: - return true - } - } -} diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Debugging.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Debugging.swift new file mode 100644 index 00000000000..5c0950d6aea --- /dev/null +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Debugging.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 struct Basics.Triple +import enum PackageModel.BuildConfiguration + +extension BuildParameters { + public struct Debugging: Encodable { + public init( + debugInfoFormat: DebugInfoFormat = .dwarf, + targetTriple: Triple, + shouldEnableDebuggingEntitlement: Bool, + omitFramePointers: Bool? + ) { + self.debugInfoFormat = debugInfoFormat + + // Per rdar://112065568 for backtraces to work on macOS a special entitlement needs to be granted on the final + // executable. + self.shouldEnableDebuggingEntitlement = targetTriple.isMacOSX && shouldEnableDebuggingEntitlement + // rdar://117578677: frame-pointer to support backtraces + // this can be removed once the backtracer uses DWARF instead of frame pointers + if let omitFramePointers { + // if set, we respect user's preference + self.omitFramePointers = omitFramePointers + } else if targetTriple.isLinux() { + // on Linux we preserve frame pointers by default + self.omitFramePointers = false + } else { + // otherwise, use the platform default + self.omitFramePointers = nil + } + } + + public var debugInfoFormat: DebugInfoFormat + + /// Whether the produced executable should be codesigned with the debugging entitlement, enabling enhanced + /// backtraces on macOS. + public var shouldEnableDebuggingEntitlement: Bool + + /// Whether to omit frame pointers + public var omitFramePointers: Bool? + } + + /// Represents the debugging strategy. + /// + /// Swift binaries requires the swiftmodule files in order for lldb to work. + /// On Darwin, linker can directly take the swiftmodule file path using the + /// -add_ast_path flag. On other platforms, we convert the swiftmodule into + /// an object file using Swift's modulewrap tool. + public enum DebuggingStrategy { + case swiftAST + case modulewrap + } + + /// The debugging strategy according to the current build parameters. + public var debuggingStrategy: DebuggingStrategy? { + guard configuration == .debug else { + return nil + } + + if targetTriple.isApple() { + return .swiftAST + } + return .modulewrap + } + + /// Represents the debug information format. + /// + /// The debug information format controls the format of the debug information + /// that the compiler generates. Some platforms support debug information + // formats other than DWARF. + public enum DebugInfoFormat: String, Encodable { + /// DWARF debug information format, the default format used by Swift. + case dwarf + /// CodeView debug information format, used on Windows. + case codeview + /// No debug information to be emitted. + case none + } + +} diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift new file mode 100644 index 00000000000..6007d1d6046 --- /dev/null +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 +// +//===----------------------------------------------------------------------===// + +extension BuildParameters { + /// A mode for explicit import checking + public enum TargetDependencyImportCheckingMode : Codable { + case none + case warn + case error + } + + /// Build parameters related to Swift Driver grouped in a single type to aggregate those in one place. + public struct Driver: Encodable { + public init( + canRenameEntrypointFunctionName: Bool = false, + enableParseableModuleInterfaces: Bool = false, + explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode = .none, + useIntegratedSwiftDriver: Bool = false, + useExplicitModuleBuild: Bool = false + ) { + self.canRenameEntrypointFunctionName = canRenameEntrypointFunctionName + self.enableParseableModuleInterfaces = enableParseableModuleInterfaces + self.explicitTargetDependencyImportCheckingMode = explicitTargetDependencyImportCheckingMode + self.useIntegratedSwiftDriver = useIntegratedSwiftDriver + self.useExplicitModuleBuild = useExplicitModuleBuild + } + + /// Whether to enable the entry-point-function-name feature. + public var canRenameEntrypointFunctionName: Bool + + /// A flag that indicates this build should check whether targets only import. + /// their explicitly-declared dependencies + public var explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode + + /// Whether to enable generation of `.swiftinterface` files alongside. + /// `.swiftmodule`s. + public var enableParseableModuleInterfaces: Bool + + /// Whether to use the integrated Swift driver rather than shelling out + /// to a separate process. + public var useIntegratedSwiftDriver: Bool + + /// Whether to use the explicit module build flow (with the integrated driver). + public var useExplicitModuleBuild: Bool + } +} diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Linking.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Linking.swift new file mode 100644 index 00000000000..94f9a9df526 --- /dev/null +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Linking.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 +// +//===----------------------------------------------------------------------===// + +extension BuildParameters { + /// An optional intermodule optimization to run at link time. + /// + /// When using Link Time Optimization (LTO for short) the swift and clang + /// compilers produce objects containing containing a higher level + /// representation of the program bitcode instead of machine code. The + /// linker combines these objects together performing additional + /// optimizations with visibility into each module/object, resulting in a + /// further optimized version of the executable. + /// + /// Using LTO can have significant impact on compile times, however can be + /// used to dramatically reduce code-size in some cases. + /// + /// Note: Bitcode objects and machine code objects can be linked together. + public enum LinkTimeOptimizationMode: String, Encodable { + /// The "standard" LTO mode designed to produce minimal code sign. + /// + /// Full LTO can lead to large link times. Consider using thin LTO if + /// build time is more important than minimizing binary size. + case full + /// An LTO mode designed to scale better with input size. + /// + /// Thin LTO typically results in faster link times than traditional LTO. + /// However, thin LTO may not result in binary as small as full LTO. + case thin + } + + /// Build parameters related to linking grouped in a single type to aggregate those in one place. + public struct Linking: Encodable { + /// Whether to disable dead code stripping by the linker + public var linkerDeadStrip: Bool + + public var linkTimeOptimizationMode: LinkTimeOptimizationMode? + + /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying + public var shouldDisableLocalRpath: Bool + + /// If should link the Swift stdlib statically. + public var shouldLinkStaticSwiftStdlib: Bool + + public init( + linkerDeadStrip: Bool = true, + linkTimeOptimizationMode: LinkTimeOptimizationMode? = nil, + shouldDisableLocalRpath: Bool = false, + shouldLinkStaticSwiftStdlib: Bool = false + ) { + self.linkerDeadStrip = linkerDeadStrip + self.linkTimeOptimizationMode = linkTimeOptimizationMode + self.shouldDisableLocalRpath = shouldDisableLocalRpath + self.shouldLinkStaticSwiftStdlib = shouldLinkStaticSwiftStdlib + } + } +} diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Output.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Output.swift new file mode 100644 index 00000000000..3f4ec706a16 --- /dev/null +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Output.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 +// +//===----------------------------------------------------------------------===// + +extension BuildParameters { + /// Build parameters related to output and logging grouped in a single type to aggregate those in one place. + public struct Output: Encodable { + public init( + isColorized: Bool = false, + isVerbose: Bool = false + ) { + self.isColorized = isColorized + self.isVerbose = isVerbose + } + + public var isColorized: Bool + + public var isVerbose: Bool + } +} diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Testing.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Testing.swift new file mode 100644 index 00000000000..36b0c5660e2 --- /dev/null +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Testing.swift @@ -0,0 +1,142 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 struct Basics.AbsolutePath +import struct Basics.Triple +import enum PackageModel.BuildConfiguration + +extension BuildParameters { + /// Represents the test product style. + public enum TestProductStyle: Encodable { + /// Test product is a loadable bundle. This style is used on Darwin platforms and, for XCTest tests, relies on the Objective-C + /// runtime to automatically discover all tests. + case loadableBundle + + /// Test product is an executable which serves as the testing entry point. This style is used on non-Darwin platforms and, + /// for XCTests, relies on the testing entry point file to indicate which tests to run. By default, the test entry point file is + /// synthesized automatically, and uses indexer data to locate all tests and run them. But the entry point may be customized + /// in one of two ways: if a path to a test entry point file was explicitly passed via the + /// `--experimental-test-entry-point-path ` option, that file is used, otherwise if an `XCTMain.swift` + /// (formerly `LinuxMain.swift`) file is located in the package, it is used. + /// + /// - Parameter explicitlyEnabledDiscovery: Whether test discovery generation was forced by passing + /// `--enable-test-discovery`, overriding any custom test entry point file specified via other CLI options or located in + /// the package. + /// - Parameter explicitlySpecifiedPath: The path to the test entry point file, if one was specified explicitly via + /// `--experimental-test-entry-point-path `. + case entryPointExecutable( + explicitlyEnabledDiscovery: Bool, + explicitlySpecifiedPath: AbsolutePath? + ) + + /// Whether this test product style requires additional, derived test targets, i.e. there must be additional test targets, beyond those + /// listed explicitly in the package manifest, created in order to add additional behavior (such as entry point logic). + public var requiresAdditionalDerivedTestTargets: Bool { + switch self { + case .loadableBundle: + return false + case .entryPointExecutable: + return true + } + } + + /// The explicitly-specified entry point file path, if this style of test product supports it and a path was specified. + public var explicitlySpecifiedEntryPointPath: AbsolutePath? { + switch self { + case .loadableBundle: + return nil + case .entryPointExecutable(explicitlyEnabledDiscovery: _, explicitlySpecifiedPath: let entryPointPath): + return entryPointPath + } + } + + public enum DiscriminatorKeys: String, Codable { + case loadableBundle + case entryPointExecutable + } + + public enum CodingKeys: CodingKey { + case _case + case explicitlyEnabledDiscovery + case explicitlySpecifiedPath + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .loadableBundle: + try container.encode(DiscriminatorKeys.loadableBundle, forKey: ._case) + case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath): + try container.encode(DiscriminatorKeys.entryPointExecutable, forKey: ._case) + try container.encode(explicitlyEnabledDiscovery, forKey: .explicitlyEnabledDiscovery) + try container.encode(explicitlySpecifiedPath, forKey: .explicitlySpecifiedPath) + } + } + } + + /// Build parameters related to testing grouped in a single type to aggregate those in one place. + public struct Testing: Encodable { + /// Whether to enable code coverage. + public var enableCodeCoverage: Bool + + /// Whether building for testability is enabled. + public var enableTestability: Bool + + /// Whether or not to enable the experimental test output mode. + public var experimentalTestOutput: Bool + + /// The style of test product to produce. + public var testProductStyle: TestProductStyle + + /// The testing libraries supported by the package manager. + public enum Library: String, Codable { + /// The XCTest library. + /// + /// This case represents both the open-source swift-corelibs-xctest + /// package and Apple's XCTest framework that ships with Xcode. + case xctest = "XCTest" + + /// The swift-testing library. + case swiftTesting = "swift-testing" + } + + /// Which testing library to use for this build. + public var library: Library + + public init( + configuration: BuildConfiguration, + targetTriple: Triple, + enableCodeCoverage: Bool = false, + enableTestability: Bool? = nil, + experimentalTestOutput: Bool = false, + forceTestDiscovery: Bool = false, + testEntryPointPath: AbsolutePath? = nil, + library: Library = .xctest + ) { + self.enableCodeCoverage = enableCodeCoverage + self.experimentalTestOutput = experimentalTestOutput + // decide on testability based on debug/release config + // the goals of this being based on the build configuration is + // that `swift build` followed by a `swift test` will need to do minimal rebuilding + // given that the default configuration for `swift build` is debug + // and that `swift test` normally requires building with testable enabled. + // when building and testing in release mode, one can use the '--disable-testable-imports' flag + // to disable testability in `swift test`, but that requires that the tests do not use the testable imports feature + self.enableTestability = enableTestability ?? (.debug == configuration) + self.testProductStyle = (targetTriple.isDarwin() && library == .xctest) ? .loadableBundle : .entryPointExecutable( + explicitlyEnabledDiscovery: forceTestDiscovery, + explicitlySpecifiedPath: testEntryPointPath + ) + self.library = library + } + } +} diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift new file mode 100644 index 00000000000..156c9b4a7d1 --- /dev/null +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -0,0 +1,350 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 Basics +import class Foundation.ProcessInfo +import PackageModel +import PackageGraph + +public struct BuildParameters: Encodable { + /// Mode for the indexing-while-building feature. + public enum IndexStoreMode: String, Encodable { + /// Index store should be enabled. + case on + /// Index store should be disabled. + case off + /// Index store should be enabled in debug configuration. + case auto + } + + /// The path to the data directory. + public var dataPath: AbsolutePath + + /// The build configuration. + public var configuration: BuildConfiguration + + /// The toolchain. + public var toolchain: Toolchain { _toolchain.toolchain } + private let _toolchain: _Toolchain + + /// Host triple. + public var hostTriple: Triple + + /// Target triple. + public var targetTriple: Triple + + /// Extra build flags. + public var flags: BuildFlags + + /// An array of paths to search for pkg-config `.pc` files. + public var pkgConfigDirectories: [AbsolutePath] + + /// The architectures to build for. + // FIXME: this may be inconsistent with `targetTriple`. + public var architectures: [String]? + + /// How many jobs should llbuild and the Swift compiler spawn + public var workers: UInt32 + + /// Which compiler sanitizers should be enabled + public var sanitizers: EnabledSanitizers + + /// The mode to use for indexing-while-building feature. + public var indexStoreMode: IndexStoreMode + + /// Whether to create dylibs for dynamic library products. + public var shouldCreateDylibForDynamicProducts: Bool + + /// The current build environment. + public var buildEnvironment: BuildEnvironment { + BuildEnvironment(platform: currentPlatform, configuration: configuration) + } + + /// The current platform we're building for. + var currentPlatform: PackageModel.Platform { + if self.targetTriple.isDarwin() { + switch self.targetTriple.darwinPlatform { + case .iOS(.catalyst): + return .macCatalyst + case .iOS(.device), .iOS(.simulator): + return .iOS + case .tvOS: + return .tvOS + case .watchOS: + return .watchOS + case .macOS, nil: + return .macOS + } + } else if self.targetTriple.isAndroid() { + return .android + } else if self.targetTriple.isWASI() { + return .wasi + } else if self.targetTriple.isWindows() { + return .windows + } else if self.targetTriple.isOpenBSD() { + return .openbsd + } else { + return .linux + } + } + + /// Whether the Xcode build system is used. + public var isXcodeBuildSystemEnabled: Bool + + public var shouldSkipBuilding: Bool + + /// Build parameters related to debugging. + public var debuggingParameters: Debugging + + /// Build parameters related to Swift Driver. + public var driverParameters: Driver + + /// Build parameters related to linking. + public var linkingParameters: Linking + + /// Build parameters related to output and logging. + public var outputParameters: Output + + /// Build parameters related to testing. + public var testingParameters: Testing + + public init( + dataPath: AbsolutePath, + configuration: BuildConfiguration, + toolchain: Toolchain, + hostTriple: Triple? = nil, + targetTriple: Triple? = nil, + flags: BuildFlags, + pkgConfigDirectories: [AbsolutePath] = [], + architectures: [String]? = nil, + workers: UInt32 = UInt32(ProcessInfo.processInfo.activeProcessorCount), + shouldCreateDylibForDynamicProducts: Bool = true, + sanitizers: EnabledSanitizers = EnabledSanitizers(), + indexStoreMode: IndexStoreMode = .auto, + isXcodeBuildSystemEnabled: Bool = false, + shouldSkipBuilding: Bool = false, + debuggingParameters: Debugging? = nil, + driverParameters: Driver = .init(), + linkingParameters: Linking = .init(), + outputParameters: Output = .init(), + testingParameters: Testing? = nil + ) throws { + let targetTriple = try targetTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompilerPath) + self.debuggingParameters = debuggingParameters ?? .init( + targetTriple: targetTriple, + shouldEnableDebuggingEntitlement: configuration == .debug, + omitFramePointers: nil + ) + + self.dataPath = dataPath + self.configuration = configuration + self._toolchain = _Toolchain(toolchain: toolchain) + self.hostTriple = try hostTriple ?? .getHostTriple(usingSwiftCompiler: toolchain.swiftCompilerPath) + self.targetTriple = targetTriple + switch self.debuggingParameters.debugInfoFormat { + case .dwarf: + var flags = flags + // DWARF requires lld as link.exe expects CodeView debug info. + self.flags = flags.merging(targetTriple.isWindows() ? BuildFlags( + cCompilerFlags: ["-gdwarf"], + cxxCompilerFlags: ["-gdwarf"], + swiftCompilerFlags: ["-g", "-use-ld=lld"], + linkerFlags: ["-debug:dwarf"] + ) : BuildFlags(cCompilerFlags: ["-g"], cxxCompilerFlags: ["-g"], swiftCompilerFlags: ["-g"])) + case .codeview: + if !targetTriple.isWindows() { + throw StringError("CodeView debug information is currently not supported on \(targetTriple.osName)") + } + var flags = flags + self.flags = flags.merging(BuildFlags( + cCompilerFlags: ["-g"], + cxxCompilerFlags: ["-g"], + swiftCompilerFlags: ["-g", "-debug-info-format=codeview"], + linkerFlags: ["-debug"] + )) + case .none: + var flags = flags + self.flags = flags.merging(BuildFlags( + cCompilerFlags: ["-g0"], + cxxCompilerFlags: ["-g0"], + swiftCompilerFlags: ["-gnone"] + )) + } + self.pkgConfigDirectories = pkgConfigDirectories + self.architectures = architectures + self.workers = workers + self.shouldCreateDylibForDynamicProducts = shouldCreateDylibForDynamicProducts + self.sanitizers = sanitizers + self.indexStoreMode = indexStoreMode + self.isXcodeBuildSystemEnabled = isXcodeBuildSystemEnabled + self.shouldSkipBuilding = shouldSkipBuilding + self.driverParameters = driverParameters + self.linkingParameters = linkingParameters + self.outputParameters = outputParameters + self.testingParameters = testingParameters ?? .init(configuration: configuration, targetTriple: targetTriple) + } + + public func forTriple(_ targetTriple: Triple) throws -> BuildParameters { + var hostSDK = try SwiftSDK.hostSwiftSDK() + hostSDK.targetTriple = targetTriple + + return try .init( + dataPath: self.dataPath.parentDirectory.appending(components: ["plugins", "tools"]), + configuration: self.configuration, + toolchain: try UserToolchain(swiftSDK: hostSDK), + hostTriple: self.hostTriple, + targetTriple: targetTriple, + flags: BuildFlags(), + pkgConfigDirectories: self.pkgConfigDirectories, + architectures: nil, + workers: self.workers, + shouldCreateDylibForDynamicProducts: self.shouldCreateDylibForDynamicProducts, + sanitizers: self.sanitizers, + indexStoreMode: self.indexStoreMode, + isXcodeBuildSystemEnabled: self.isXcodeBuildSystemEnabled, + shouldSkipBuilding: self.shouldSkipBuilding, + driverParameters: self.driverParameters, + linkingParameters: self.linkingParameters, + outputParameters: self.outputParameters, + testingParameters: self.testingParameters + ) + } + + /// The path to the build directory (inside the data directory). + public var buildPath: AbsolutePath { + if isXcodeBuildSystemEnabled { + return dataPath.appending(components: "Products", configuration.dirname.capitalized) + } else { + return dataPath.appending(component: configuration.dirname) + } + } + + /// The path to the index store directory. + public var indexStore: AbsolutePath { + assert(indexStoreMode != .off, "index store is disabled") + return buildPath.appending(components: "index", "store") + } + + /// The path to the code coverage directory. + public var codeCovPath: AbsolutePath { + return buildPath.appending("codecov") + } + + /// The path to the code coverage profdata file. + public var codeCovDataFile: AbsolutePath { + return codeCovPath.appending("default.profdata") + } + + public var llbuildManifest: AbsolutePath { + return dataPath.appending(components: "..", configuration.dirname + ".yaml") + } + + public var pifManifest: AbsolutePath { + return dataPath.appending(components: "..", "manifest.pif") + } + + public var buildDescriptionPath: AbsolutePath { + return buildPath.appending(components: "description.json") + } + + public var testOutputPath: AbsolutePath { + return buildPath.appending(component: "testOutput.txt") + } + /// Returns the path to the binary of a product for the current build parameters. + public func binaryPath(for product: ResolvedProduct) throws -> AbsolutePath { + return try buildPath.appending(binaryRelativePath(for: product)) + } + + /// Returns the path to the dynamic library of a product for the current build parameters. + func potentialDynamicLibraryPath(for product: ResolvedProduct) throws -> RelativePath { + try RelativePath(validating: "\(targetTriple.dynamicLibraryPrefix)\(product.name)\(targetTriple.dynamicLibraryExtension)") + } + + /// Returns the path to the binary of a product for the current build parameters, relative to the build directory. + public func binaryRelativePath(for product: ResolvedProduct) throws -> RelativePath { + let potentialExecutablePath = try RelativePath(validating: "\(product.name)\(targetTriple.executableExtension)") + + switch product.type { + case .executable, .snippet: + return potentialExecutablePath + case .library(.static): + return try RelativePath(validating: "lib\(product.name)\(targetTriple.staticLibraryExtension)") + case .library(.dynamic): + return try potentialDynamicLibraryPath(for: product) + case .library(.automatic), .plugin: + fatalError() + case .test: + guard !targetTriple.isWASI() else { + return try RelativePath(validating: "\(product.name).wasm") + } + + let base = "\(product.name).xctest" + if targetTriple.isDarwin() { + return try RelativePath(validating: "\(base)/Contents/MacOS/\(product.name)") + } else { + return try RelativePath(validating: base) + } + case .macro: + #if BUILD_MACROS_AS_DYLIBS + return try potentialDynamicLibraryPath(for: product) + #else + return potentialExecutablePath + #endif + } + } +} + +/// A shim struct for toolchain so we can encode it without having to write encode(to:) for +/// entire BuildParameters by hand. +private struct _Toolchain: Encodable { + let toolchain: Toolchain + + enum CodingKeys: String, CodingKey { + case swiftCompiler + case clangCompiler + case extraCCFlags + case extraSwiftCFlags + case extraCPPFlags + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(toolchain.swiftCompilerPath, forKey: .swiftCompiler) + try container.encode(toolchain.getClangCompiler(), forKey: .clangCompiler) + + try container.encode(toolchain.extraFlags.cCompilerFlags, forKey: .extraCCFlags) + // Maintaining `extraCPPFlags` key for compatibility with older encoding. + try container.encode(toolchain.extraFlags.cxxCompilerFlags, forKey: .extraCPPFlags) + try container.encode(toolchain.extraFlags.swiftCompilerFlags, forKey: .extraSwiftCFlags) + try container.encode(toolchain.swiftCompilerPath, forKey: .swiftCompiler) + } +} + +extension BuildParameters { + /// Whether to build Swift code with whole module optimization (WMO) + /// enabled. + public var useWholeModuleOptimization: Bool { + switch configuration { + case .debug: + return false + + case .release: + return true + } + } +} + +extension Triple { + public var supportsTestSummary: Bool { + return !self.isWindows() + } +} diff --git a/Sources/SPMBuildCore/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift similarity index 79% rename from Sources/SPMBuildCore/BuildSystem.swift rename to Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index 0d0278d5f2f..b9216ca5048 100644 --- a/Sources/SPMBuildCore/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -87,6 +87,18 @@ public protocol BuildPlan { func createREPLArguments() throws -> [String] } +public protocol BuildSystemFactory { + func makeBuildSystem( + explicitProduct: String?, + cacheBuildManifest: Bool, + customBuildParameters: BuildParameters?, + customPackageGraphLoader: (() throws -> PackageGraph)?, + customOutputStream: OutputByteStream?, + customLogLevel: Diagnostic.Severity?, + customObservabilityScope: ObservabilityScope? + ) throws -> any BuildSystem +} + public struct BuildSystemProvider { // TODO: In the future, we may want this to be about specific capabilities of a build system rather than choosing a concrete one. public enum Kind: String, CaseIterable { @@ -94,19 +106,9 @@ public struct BuildSystemProvider { case xcode } - public typealias Provider = ( - _ explicitProduct: String?, - _ cacheBuildManifest: Bool, - _ customBuildParameters: BuildParameters?, - _ customPackageGraphLoader: (() throws -> PackageGraph)?, - _ customOutputStream: OutputByteStream?, - _ customLogLevel: Diagnostic.Severity?, - _ customObservabilityScope: ObservabilityScope? - ) throws -> BuildSystem - - public let providers: [Kind:Provider] + public let providers: [Kind: any BuildSystemFactory] - public init(providers: [Kind:Provider]) { + public init(providers: [Kind: any BuildSystemFactory]) { self.providers = providers } @@ -119,11 +121,19 @@ public struct BuildSystemProvider { customOutputStream: OutputByteStream? = .none, customLogLevel: Diagnostic.Severity? = .none, customObservabilityScope: ObservabilityScope? = .none - ) throws -> BuildSystem { - guard let provider = self.providers[kind] else { + ) throws -> any BuildSystem { + guard let buildSystemFactory = self.providers[kind] else { throw Errors.buildSystemProviderNotRegistered(kind: kind) } - return try provider(explicitProduct, cacheBuildManifest, customBuildParameters, customPackageGraphLoader, customOutputStream, customLogLevel, customObservabilityScope) + return try buildSystemFactory.makeBuildSystem( + explicitProduct: explicitProduct, + cacheBuildManifest: cacheBuildManifest, + customBuildParameters: customBuildParameters, + customPackageGraphLoader: customPackageGraphLoader, + customOutputStream: customOutputStream, + customLogLevel: customLogLevel, + customObservabilityScope: customObservabilityScope + ) } } diff --git a/Sources/SPMBuildCore/BuildSystemCommand.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift similarity index 100% rename from Sources/SPMBuildCore/BuildSystemCommand.swift rename to Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift diff --git a/Sources/SPMBuildCore/BuildSystemDelegate.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift similarity index 100% rename from Sources/SPMBuildCore/BuildSystemDelegate.swift rename to Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift diff --git a/Sources/SPMBuildCore/BuiltTestProduct.swift b/Sources/SPMBuildCore/BuiltTestProduct.swift index bdea7300b55..3b0bdb3ce0e 100644 --- a/Sources/SPMBuildCore/BuiltTestProduct.swift +++ b/Sources/SPMBuildCore/BuiltTestProduct.swift @@ -20,6 +20,9 @@ public struct BuiltTestProduct: Codable { /// The path of the test binary. public let binaryPath: AbsolutePath + /// The path to the package this product was declared in. + public let packagePath: AbsolutePath + /// The path of the test bundle. public var bundlePath: AbsolutePath { // Go up the folder hierarchy until we find the .xctest bundle. @@ -35,8 +38,10 @@ public struct BuiltTestProduct: Codable { /// - Parameters: /// - productName: The test product name. /// - binaryPath: The path of the test binary. - public init(productName: String, binaryPath: AbsolutePath) { + /// - packagePath: The path to the package this product was declared in. + public init(productName: String, binaryPath: AbsolutePath, packagePath: AbsolutePath) { self.productName = productName self.binaryPath = binaryPath + self.packagePath = packagePath } } diff --git a/Sources/SPMBuildCore/CMakeLists.txt b/Sources/SPMBuildCore/CMakeLists.txt index 19865beb280..6fcfd2f3df6 100644 --- a/Sources/SPMBuildCore/CMakeLists.txt +++ b/Sources/SPMBuildCore/CMakeLists.txt @@ -8,15 +8,20 @@ add_library(SPMBuildCore BinaryTarget+Extensions.swift - BuildParameters.swift - BuildSystem.swift - BuildSystemCommand.swift - BuildSystemDelegate.swift + BuildParameters/BuildParameters.swift + BuildParameters/BuildParameters+Debugging.swift + BuildParameters/BuildParameters+Driver.swift + BuildParameters/BuildParameters+Linking.swift + BuildParameters/BuildParameters+Output.swift + BuildParameters/BuildParameters+Testing.swift + BuildSystem/BuildSystem.swift + BuildSystem/BuildSystemCommand.swift + BuildSystem/BuildSystemDelegate.swift BuiltTestProduct.swift - PluginContextSerializer.swift - PluginInvocation.swift - PluginMessages.swift - PluginScriptRunner.swift + Plugins/PluginContextSerializer.swift + Plugins/PluginInvocation.swift + Plugins/PluginMessages.swift + Plugins/PluginScriptRunner.swift PrebuildCommandResult.swift Triple+Extensions.swift XCFrameworkMetadata.swift) diff --git a/Sources/SPMBuildCore/PluginMessages.swift b/Sources/SPMBuildCore/PluginMessages.swift deleted file mode 120000 index ef556b4fa80..00000000000 --- a/Sources/SPMBuildCore/PluginMessages.swift +++ /dev/null @@ -1 +0,0 @@ -../PackagePlugin/PluginMessages.swift \ No newline at end of file diff --git a/Sources/SPMBuildCore/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift similarity index 99% rename from Sources/SPMBuildCore/PluginContextSerializer.swift rename to Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index f7834943942..e7a205726e3 100644 --- a/Sources/SPMBuildCore/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -24,6 +24,7 @@ internal struct PluginContextSerializer { let fileSystem: FileSystem let buildEnvironment: BuildEnvironment let pkgConfigDirectories: [AbsolutePath] + let sdkRootPath: AbsolutePath? var paths: [WireInput.Path] = [] var pathsToIds: [AbsolutePath: WireInput.Path.Id] = [:] var targets: [WireInput.Target] = [] @@ -120,6 +121,7 @@ internal struct PluginContextSerializer { for result in try pkgConfigArgs( for: target, pkgConfigDirectories: pkgConfigDirectories, + sdkRootPath: sdkRootPath, fileSystem: fileSystem, observabilityScope: observabilityScope ) { diff --git a/Sources/SPMBuildCore/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift similarity index 99% rename from Sources/SPMBuildCore/PluginInvocation.swift rename to Sources/SPMBuildCore/Plugins/PluginInvocation.swift index 370ea9cd2ad..2b0829a36ae 100644 --- a/Sources/SPMBuildCore/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -59,6 +59,7 @@ extension PluginTarget { readOnlyDirectories: [AbsolutePath], allowNetworkConnections: [SandboxNetworkPermission], pkgConfigDirectories: [AbsolutePath], + sdkRootPath: AbsolutePath?, fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, @@ -79,7 +80,8 @@ extension PluginTarget { var serializer = PluginContextSerializer( fileSystem: fileSystem, buildEnvironment: buildEnvironment, - pkgConfigDirectories: pkgConfigDirectories + pkgConfigDirectories: pkgConfigDirectories, + sdkRootPath: sdkRootPath ) let pluginWorkDirId = try serializer.serialize(path: outputDirectory) let toolSearchDirIds = try toolSearchDirectories.map{ try serializer.serialize(path: $0) } @@ -332,13 +334,14 @@ extension PackageGraph { buildEnvironment: BuildEnvironment, toolSearchDirectories: [AbsolutePath], pkgConfigDirectories: [AbsolutePath], + sdkRootPath: AbsolutePath?, pluginScriptRunner: PluginScriptRunner, observabilityScope: ObservabilityScope, fileSystem: FileSystem, builtToolHandler: (_ name: String, _ path: RelativePath) throws -> AbsolutePath? = { _, _ in return nil } ) throws -> [ResolvedTarget: [BuildToolPluginInvocationResult]] { var pluginResultsByTarget: [ResolvedTarget: [BuildToolPluginInvocationResult]] = [:] - for target in self.reachableTargets.sorted(by: { $0.name < $1.name }) { + for target in self.allTargets.sorted(by: { $0.name < $1.name }) { // Infer plugins from the declared dependencies, and collect them as well as any regular dependencies. Although usage of build tool plugins is declared separately from dependencies in the manifest, in the internal model we currently consider both to be dependencies. var pluginTargets: [PluginTarget] = [] var dependencyTargets: [Target] = [] @@ -479,6 +482,7 @@ extension PackageGraph { readOnlyDirectories: readOnlyDirectories, allowNetworkConnections: [], pkgConfigDirectories: pkgConfigDirectories, + sdkRootPath: sdkRootPath, fileSystem: fileSystem, observabilityScope: observabilityScope, callbackQueue: delegateQueue, diff --git a/Sources/SPMBuildCore/Plugins/PluginMessages.swift b/Sources/SPMBuildCore/Plugins/PluginMessages.swift new file mode 120000 index 00000000000..c5846bb658f --- /dev/null +++ b/Sources/SPMBuildCore/Plugins/PluginMessages.swift @@ -0,0 +1 @@ +../../PackagePlugin/PluginMessages.swift \ No newline at end of file diff --git a/Sources/SPMBuildCore/PluginScriptRunner.swift b/Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift similarity index 94% rename from Sources/SPMBuildCore/PluginScriptRunner.swift rename to Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift index e0a74a2a07c..b833f7d10ae 100644 --- a/Sources/SPMBuildCore/PluginScriptRunner.swift +++ b/Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift @@ -19,7 +19,7 @@ import PackageGraph /// Implements the mechanics of running and communicating with a plugin (implemented as a set of Swift source files). In most environments this is done by compiling the code to an executable, invoking it as a sandboxed subprocess, and communicating with it using pipes. Specific implementations are free to implement things differently, however. public protocol PluginScriptRunner { - /// Public protocol function that starts compiling the plugin script to an exectutable. The name is used as the basename for the executable and auxiliary files. The tools version controls the availability of APIs in PackagePlugin, and should be set to the tools version of the package that defines the plugin (not of the target to which it is being applied). This function returns immediately and then calls the completion handler on the callbackq queue when compilation ends. + /// Public protocol function that starts compiling the plugin script to an executable. The name is used as the basename for the executable and auxiliary files. The tools version controls the availability of APIs in PackagePlugin, and should be set to the tools version of the package that defines the plugin (not of the target to which it is being applied). This function returns immediately and then calls the completion handler on the callback queue when compilation ends. func compilePluginScript( sourceFiles: [AbsolutePath], pluginName: String, diff --git a/Sources/SPMBuildCore/Triple+Extensions.swift b/Sources/SPMBuildCore/Triple+Extensions.swift index 1d17f77fc40..bf9a1216c3d 100644 --- a/Sources/SPMBuildCore/Triple+Extensions.swift +++ b/Sources/SPMBuildCore/Triple+Extensions.swift @@ -13,12 +13,12 @@ import struct Basics.Triple extension Triple { - public func platformBuildPathComponent() -> String { + public var platformBuildPathComponent: String { if isDarwin() { - return tripleString(forPlatformVersion: "") + return self.tripleString(forPlatformVersion: "") } - return tripleString + return self.tripleString } } @@ -27,6 +27,6 @@ extension Triple { // Use "apple" as the subdirectory because in theory Xcode build system // can be used to build for any Apple platform and it has its own // conventions for build subpaths based on platforms. - buildSystem == .xcode ? "apple" : self.platformBuildPathComponent() + buildSystem == .xcode ? "apple" : self.platformBuildPathComponent } } diff --git a/Sources/SPMBuildCore/XCFrameworkMetadata.swift b/Sources/SPMBuildCore/XCFrameworkMetadata.swift index 1b90a46bf29..8ebc79701b0 100644 --- a/Sources/SPMBuildCore/XCFrameworkMetadata.swift +++ b/Sources/SPMBuildCore/XCFrameworkMetadata.swift @@ -25,19 +25,22 @@ public struct XCFrameworkMetadata: Equatable { public let headersPath: String? public let platform: String public let architectures: [String] + public let variant: String? public init( libraryIdentifier: String, libraryPath: String, headersPath: String?, platform: String, - architectures: [String] + architectures: [String], + variant: String? ) { self.libraryIdentifier = libraryIdentifier self.libraryPath = libraryPath self.headersPath = headersPath self.platform = platform self.architectures = architectures + self.variant = variant } } @@ -78,6 +81,7 @@ extension XCFrameworkMetadata.Library: Decodable { case headersPath = "HeadersPath" case platform = "SupportedPlatform" case architectures = "SupportedArchitectures" + case variant = "SupportedPlatformVariant" } } diff --git a/Sources/SPMTestSupport/InMemoryGitRepository.swift b/Sources/SPMTestSupport/InMemoryGitRepository.swift index f688907b64c..f462e4eaa25 100644 --- a/Sources/SPMTestSupport/InMemoryGitRepository.swift +++ b/Sources/SPMTestSupport/InMemoryGitRepository.swift @@ -383,7 +383,7 @@ extension InMemoryGitRepository: WorkingCheckout { } } - public func isAlternateObjectStoreValid() -> Bool { + public func isAlternateObjectStoreValid(expected: AbsolutePath) -> Bool { return true } @@ -451,10 +451,10 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider { } public func open(repository: RepositorySpecifier, at path: AbsolutePath) throws -> Repository { - guard let repo = fetchedMap[path] else { - throw InternalError("unknown repo at \(path)") + guard let repository = self.fetchedMap[path] else { + throw InternalError("unknown repository at \(path)") } - return repo + return repository } public func createWorkingCopy( @@ -482,11 +482,11 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider { return checkout } - public func isValidDirectory(_ directory: AbsolutePath) -> Bool { + public func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { return true } - public func isValidRefFormat(_ ref: String) -> Bool { + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { return true } diff --git a/Sources/SPMTestSupport/MockManifestLoader.swift b/Sources/SPMTestSupport/MockManifestLoader.swift index 6cb5881eef4..f1b0ebfd56a 100644 --- a/Sources/SPMTestSupport/MockManifestLoader.swift +++ b/Sources/SPMTestSupport/MockManifestLoader.swift @@ -58,6 +58,7 @@ public final class MockManifestLoader: ManifestLoaderProtocol { packageLocation: String, packageVersion: (version: Version?, revision: String?)?, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, @@ -84,6 +85,7 @@ extension ManifestLoader { packageKind: PackageReference.Kind, toolsVersion manifestToolsVersion: ToolsVersion, identityResolver: IdentityResolver = DefaultIdentityResolver(), + dependencyMapper: DependencyMapper? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> Manifest{ @@ -116,6 +118,7 @@ extension ManifestLoader { packageLocation: packageLocation, packageVersion: nil, identityResolver: identityResolver, + dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), fileSystem: fileSystem, observabilityScope: observabilityScope, delegateQueue: .sharedConcurrent, @@ -133,6 +136,7 @@ extension ManifestLoader { packageKind: PackageReference.Kind, currentToolsVersion: ToolsVersion, identityResolver: IdentityResolver = DefaultIdentityResolver(), + dependencyMapper: DependencyMapper? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> Manifest{ @@ -165,6 +169,7 @@ extension ManifestLoader { packageVersion: nil, currentToolsVersion: currentToolsVersion, identityResolver: identityResolver, + dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), fileSystem: fileSystem, observabilityScope: observabilityScope, delegateQueue: .sharedConcurrent, diff --git a/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift b/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift index a0132d7b753..c53aff4014a 100644 --- a/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift +++ b/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift @@ -25,7 +25,23 @@ public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { public init(_ packageSigners: [PackageIdentity: PackageSigners] = [:]) { self.packageSigners = packageSigners } + + public func get( + package: PackageIdentity, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue + ) async throws -> PackageSigners { + try await safe_async { + self.get( + package: package, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + callback: $0 + ) + } + } + @available(*, noasync, message: "Use the async alternative") public func get( package: PackageIdentity, observabilityScope: ObservabilityScope, diff --git a/Sources/SPMTestSupport/MockWorkspace.swift b/Sources/SPMTestSupport/MockWorkspace.swift index f01be48ea7b..7070e6c441e 100644 --- a/Sources/SPMTestSupport/MockWorkspace.swift +++ b/Sources/SPMTestSupport/MockWorkspace.swift @@ -68,7 +68,7 @@ public final class MockWorkspace { self.packages = packages self.fingerprints = customFingerprints ?? MockPackageFingerprintStorage() self.signingEntities = customSigningEntities ?? MockPackageSigningEntityStorage() - self.mirrors = customMirrors ?? DependencyMirrors() + self.mirrors = try customMirrors ?? DependencyMirrors() self.identityResolver = DefaultIdentityResolver( locationMapper: self.mirrors.effective(for:), identityMapper: self.mirrors.effectiveIdentity(for:) @@ -295,7 +295,7 @@ public final class MockWorkspace { skipSignatureValidation: false, sourceControlToRegistryDependencyTransformation: self.sourceControlToRegistryDependencyTransformation, defaultRegistry: self.defaultRegistry, - restrictImports: .none + manifestImportRestrictions: .none ), customFingerprints: self.fingerprints, customMirrors: self.mirrors, @@ -517,7 +517,7 @@ public final class MockWorkspace { let rootInput = PackageGraphRootInput(packages: try rootPaths(for: roots.map { $0.name }), dependencies: []) let rootManifests = try temp_await { workspace.loadRootManifests(packages: rootInput.packages, observabilityScope: observability.topScope, completion: $0) } - let root = PackageGraphRoot(input: rootInput, manifests: rootManifests) + let root = PackageGraphRoot(input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope) let dependencyManifests = try workspace.loadDependencyManifests(root: root, observabilityScope: observability.topScope) @@ -733,7 +733,7 @@ public final class MockWorkspace { packages: try rootPaths(for: roots), dependencies: dependencies ) let rootManifests = try temp_await { workspace.loadRootManifests(packages: rootInput.packages, observabilityScope: observability.topScope, completion: $0) } - let graphRoot = PackageGraphRoot(input: rootInput, manifests: rootManifests) + let graphRoot = PackageGraphRoot(input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope) let manifests = try workspace.loadDependencyManifests(root: graphRoot, observabilityScope: observability.topScope) result(manifests, observability.diagnostics) } @@ -893,11 +893,11 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate { // noop } - public func willDownloadBinaryArtifact(from url: String) { + public func willDownloadBinaryArtifact(from url: String, fromCache: Bool) { self.append("downloading binary artifact package: \(url)") } - public func didDownloadBinaryArtifact(from url: String, result: Result, duration: DispatchTimeInterval) { + public func didDownloadBinaryArtifact(from url: String, result: Result<(path: AbsolutePath, fromCache: Bool), Error>, duration: DispatchTimeInterval) { self.append("finished downloading binary artifact package: \(url)") } @@ -933,6 +933,22 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate { // noop } + public func willCompileManifest(packageIdentity: PackageIdentity, packageLocation: String) { + // noop + } + + public func didCompileManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + // noop + } + + public func willEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String) { + // noop + } + + public func didEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + // noop + } + private func append(_ event: String) { self.lock.withLock { self._events.append(event) diff --git a/Sources/SPMTestSupport/Observability.swift b/Sources/SPMTestSupport/Observability.swift index 198e5b898f2..54ad29e53bb 100644 --- a/Sources/SPMTestSupport/Observability.swift +++ b/Sources/SPMTestSupport/Observability.swift @@ -72,6 +72,14 @@ public struct TestingObservability { // TODO: do something useful with scope func handleDiagnostic(scope: ObservabilityScope, diagnostic: Basics.Diagnostic) { + // Filter superfluous diagnostics. + guard !diagnostic.message.hasPrefix(":0: warning: annotation implies no releases") else { + return + } + guard !diagnostic.message.hasPrefix(":0: note: add explicit") else { + return + } + if self.verbose { print(diagnostic.description) } diff --git a/Sources/SPMTestSupport/PackageGraphTester.swift b/Sources/SPMTestSupport/PackageGraphTester.swift index 580b157f880..981f7fc91e0 100644 --- a/Sources/SPMTestSupport/PackageGraphTester.swift +++ b/Sources/SPMTestSupport/PackageGraphTester.swift @@ -179,13 +179,21 @@ public final class ResolvedTargetResult { } public func checkDerivedPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { - let targetPlatforms = Dictionary(uniqueKeysWithValues: target.platforms.derived.map({ ($0.platform.name, $0.version.versionString) })) + let derived = platforms.map { + let platform = PlatformRegistry.default.platformByName[$0.key] ?? PackageModel.Platform + .custom(name: $0.key, oldestSupportedVersion: $0.value) + return self.target.platforms.getDerived(for: platform, usingXCTest: self.target.type == .test) + } + let targetPlatforms = Dictionary( + uniqueKeysWithValues: derived + .map { ($0.platform.name, $0.version.versionString) } + ) XCTAssertEqual(platforms, targetPlatforms, file: file, line: line) } public func checkDerivedPlatformOptions(_ platform: PackageModel.Platform, options: [String], file: StaticString = #file, line: UInt = #line) { - let platform = target.platforms.getDerived(for: platform) - XCTAssertEqual(platform?.options, options, file: file, line: line) + let platform = target.platforms.getDerived(for: platform, usingXCTest: target.type == .test) + XCTAssertEqual(platform.options, options, file: file, line: line) } } @@ -230,13 +238,17 @@ public final class ResolvedProductResult { } public func checkDerivedPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { - let targetPlatforms = Dictionary(uniqueKeysWithValues: product.platforms.derived.map({ ($0.platform.name, $0.version.versionString) })) + let derived = platforms.map { + let platform = PlatformRegistry.default.platformByName[$0.key] ?? PackageModel.Platform.custom(name: $0.key, oldestSupportedVersion: $0.value) + return product.platforms.getDerived(for: platform, usingXCTest: product.isLinkingXCTest) + } + let targetPlatforms = Dictionary(uniqueKeysWithValues: derived.map({ ($0.platform.name, $0.version.versionString) })) XCTAssertEqual(platforms, targetPlatforms, file: file, line: line) } public func checkDerivedPlatformOptions(_ platform: PackageModel.Platform, options: [String], file: StaticString = #file, line: UInt = #line) { - let platform = product.platforms.getDerived(for: platform) - XCTAssertEqual(platform?.options, options, file: file, line: line) + let platform = product.platforms.getDerived(for: platform, usingXCTest: product.isLinkingXCTest) + XCTAssertEqual(platform.options, options, file: file, line: line) } } diff --git a/Sources/SPMTestSupport/SwiftPMProduct.swift b/Sources/SPMTestSupport/SwiftPMProduct.swift index 9cc4e25292b..a3bb5b845b0 100644 --- a/Sources/SPMTestSupport/SwiftPMProduct.swift +++ b/Sources/SPMTestSupport/SwiftPMProduct.swift @@ -46,14 +46,18 @@ extension SwiftPM { /// Path to currently built binary. public var path: AbsolutePath { + return Self.testBinaryPath(for: self.executableName) + } + + public static func testBinaryPath(for executableName: RelativePath) -> AbsolutePath { #if canImport(Darwin) for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { - return try! AbsolutePath(AbsolutePath(validating: bundle.bundlePath).parentDirectory, self.executableName) + return try! AbsolutePath(AbsolutePath(validating: bundle.bundlePath).parentDirectory, executableName) } fatalError() #else return try! AbsolutePath(validating: CommandLine.arguments.first!, relativeTo: localFileSystem.currentWorkingDirectory!) - .parentDirectory.appending(self.executableName) + .parentDirectory.appending(executableName) #endif } } diff --git a/Sources/SPMTestSupport/Toolchain.swift b/Sources/SPMTestSupport/Toolchain.swift index ebadbd417e1..92c13d77401 100644 --- a/Sources/SPMTestSupport/Toolchain.swift +++ b/Sources/SPMTestSupport/Toolchain.swift @@ -110,4 +110,48 @@ extension UserToolchain { return false } } + + public func supportsSuppressWarnings() -> Bool { + do { + try testWithTemporaryDirectory { tmpPath in + let inputPath = tmpPath.appending("best.swift") + try localFileSystem.writeFileContents(inputPath, string: "let foo: String? = \"bar\"\nprint(foo)\n") + let outputPath = tmpPath.appending("foo") + let serializedDiagnosticsPath = tmpPath.appending("out.dia") + let toolchainPath = self.swiftCompilerPath.parentDirectory.parentDirectory + try Process.checkNonZeroExit(arguments: ["/usr/bin/xcrun", "--toolchain", toolchainPath.pathString, "swiftc", inputPath.pathString, "-Xfrontend", "-serialize-diagnostics-path", "-Xfrontend", serializedDiagnosticsPath.pathString, "-o", outputPath.pathString, "-suppress-warnings"]) + + let diaFileContents = try localFileSystem.readFileContents(serializedDiagnosticsPath) + let diagnosticsSet = try SerializedDiagnostics(bytes: diaFileContents) + + if diagnosticsSet.diagnostics.contains(where: { $0.text.contains("warning") }) { + throw StringError("does not support suppressing warnings") + } + } + return true + } catch { + return false + } + } + + // This builds a trivial program with `-warnings-as-errors`, if it fails, the compiler in use generates warnings by default and is not suitable for testing warnings as errors behaviors. + public func supportsWarningsAsErrors() -> Bool { + do { + try testWithTemporaryDirectory { tmpPath in + let inputPath = tmpPath.appending("best.swift") + try localFileSystem.writeFileContents(inputPath, string: "print(\"hello\")") + let outputPath = tmpPath.appending("foo") + let toolchainPath = self.swiftCompilerPath.parentDirectory.parentDirectory + try Process.checkNonZeroExit(arguments: ["/usr/bin/xcrun", "--toolchain", toolchainPath.pathString, "swiftc", inputPath.pathString, "-o", outputPath.pathString, "-warnings-as-errors"]) + } + return true + } catch { + return false + } + } + + /// Helper function to determine whether we should run SDK-dependent tests. + public func supportsSDKDependentTests() -> Bool { + return ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] == nil + } } diff --git a/Sources/SPMTestSupport/XCTAssertHelpers.swift b/Sources/SPMTestSupport/XCTAssertHelpers.swift index 6d7198eee4f..ffd110aada3 100644 --- a/Sources/SPMTestSupport/XCTAssertHelpers.swift +++ b/Sources/SPMTestSupport/XCTAssertHelpers.swift @@ -49,6 +49,23 @@ public func XCTSkipIfCI(file: StaticString = #filePath, line: UInt = #line) thro } } +/// An `async`-friendly replacement for `XCTAssertThrowsError`. +public func XCTAssertAsyncThrowsError( + _ expression: @autoclosure () async throws -> T, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line, + _ errorHandler: (_ error: any Error) -> Void = { _ in } +) async { + do { + _ = try await expression() + XCTFail(message(), file: file, line: line) + } catch { + errorHandler(error) + } +} + + public func XCTAssertBuilds( _ path: AbsolutePath, configurations: Set = [.Debug, .Release], @@ -134,6 +151,26 @@ public func XCTAssertEqual( XCTAssertEqual(actual, expected, file: file, line: line) } +public func XCTAssertAsyncTrue( + _ expression: @autoclosure () async throws -> Bool, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) async rethrows { + let result = try await expression() + XCTAssertTrue(result, message(), file: file, line: line) +} + +public func XCTAssertAsyncFalse( + _ expression: @autoclosure () async throws -> Bool, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) async rethrows { + let result = try await expression() + XCTAssertFalse(result, message(), file: file, line: line) +} + public func XCTAssertThrowsCommandExecutionError( _ expression: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", diff --git a/Sources/SPMTestSupport/misc.swift b/Sources/SPMTestSupport/misc.swift index a01197a7899..8164a923b6c 100644 --- a/Sources/SPMTestSupport/misc.swift +++ b/Sources/SPMTestSupport/misc.swift @@ -20,6 +20,7 @@ import PackageGraph import PackageLoading import PackageModel import SourceControl +import struct SPMBuildCore.BuildParameters import TSCTestSupport import Workspace import func XCTest.XCTFail @@ -32,6 +33,7 @@ import enum TSCUtility.Git @_exported import func TSCTestSupport.systemQuietly @_exported import enum TSCTestSupport.StringPattern +/// Test helper utility for executing a block with a temporary directory. public func testWithTemporaryDirectory( function: StaticString = #function, body: (AbsolutePath) throws -> Void @@ -39,32 +41,52 @@ public func testWithTemporaryDirectory( let body2 = { (path: TSCAbsolutePath) in try body(AbsolutePath(path)) } + try TSCTestSupport.testWithTemporaryDirectory( function: function, body: body2 ) } +public func testWithTemporaryDirectory( + function: StaticString = #function, + body: (AbsolutePath) async throws -> Void +) async throws { + let cleanedFunction = function.description + .replacingOccurrences(of: "(", with: "") + .replacingOccurrences(of: ")", with: "") + .replacingOccurrences(of: ".", with: "") + .replacingOccurrences(of: ":", with: "_") + try await withTemporaryDirectory(prefix: "spm-tests-\(cleanedFunction)") { tmpDirPath in + defer { + // Unblock and remove the tmp dir on deinit. + try? localFileSystem.chmod(.userWritable, path: tmpDirPath, options: [.recursive]) + try? localFileSystem.removeFileTree(tmpDirPath) + } + try await body(tmpDirPath) + } +} + /// Test-helper function that runs a block of code on a copy of a test fixture /// package. The copy is made into a temporary directory, and the block is /// given a path to that directory. The block is permitted to modify the copy. /// The temporary copy is deleted after the block returns. The fixture name may /// contain `/` characters, which are treated as path separators, exactly as if /// the name were a relative path. -public func fixture( +@discardableResult public func fixture( name: String, createGitRepo: Bool = true, file: StaticString = #file, line: UInt = #line, - body: (AbsolutePath) throws -> Void -) throws { + body: (AbsolutePath) throws -> T +) throws -> T { do { // Make a suitable test directory name from the fixture subpath. let fixtureSubpath = try RelativePath(validating: name) let copyName = fixtureSubpath.components.joined(separator: "_") // Create a temporary directory for the duration of the block. - try withTemporaryDirectory(prefix: copyName) { tmpDirPath in + return try withTemporaryDirectory(prefix: copyName) { tmpDirPath in defer { // Unblock and remove the tmp dir on deinit. @@ -72,48 +94,52 @@ public func fixture( try? localFileSystem.removeFileTree(tmpDirPath) } - // Construct the expected path of the fixture. - // FIXME: This seems quite hacky; we should provide some control over where fixtures are found. - let fixtureDir = AbsolutePath("../../../Fixtures", relativeTo: #file) - .appending(fixtureSubpath) - - // Check that the fixture is really there. - guard localFileSystem.isDirectory(fixtureDir) else { - XCTFail("No such fixture: \(fixtureDir)", file: file, line: line) - return - } + let fixtureDir = try verifyFixtureExists(at: fixtureSubpath, file: file, line: line) + let preparedFixture = try setup( + fixtureDir: fixtureDir, + in: tmpDirPath, + copyName: copyName, + createGitRepo:createGitRepo + ) + return try body(preparedFixture) + } + } catch SwiftPMError.executionFailure(let error, let output, let stderr) { + print("**** FAILURE EXECUTING SUBPROCESS ****") + print("output:", output) + print("stderr:", stderr) + throw error + } +} - // The fixture contains either a checkout or just a Git directory. - if localFileSystem.isFile(fixtureDir.appending("Package.swift")) { - // It's a single package, so copy the whole directory as-is. - let dstDir = tmpDirPath.appending(component: copyName) -#if os(Windows) - try localFileSystem.copy(from: fixtureDir, to: dstDir) -#else - try systemQuietly("cp", "-R", "-H", fixtureDir.pathString, dstDir.pathString) -#endif +@discardableResult public func fixture( + name: String, + createGitRepo: Bool = true, + file: StaticString = #file, + line: UInt = #line, + body: (AbsolutePath) async throws -> T +) async throws -> T { + do { + // Make a suitable test directory name from the fixture subpath. + let fixtureSubpath = try RelativePath(validating: name) + let copyName = fixtureSubpath.components.joined(separator: "_") - // Invoke the block, passing it the path of the copied fixture. - try body(dstDir) - } else { - // Copy each of the package directories and construct a git repo in it. - for fileName in try localFileSystem.getDirectoryContents(fixtureDir).sorted() { - let srcDir = fixtureDir.appending(component: fileName) - guard localFileSystem.isDirectory(srcDir) else { continue } - let dstDir = tmpDirPath.appending(component: fileName) -#if os(Windows) - try localFileSystem.copy(from: srcDir, to: dstDir) -#else - try systemQuietly("cp", "-R", "-H", srcDir.pathString, dstDir.pathString) -#endif - if createGitRepo { - initGitRepo(dstDir, tag: "1.2.3", addFile: false) - } - } + // Create a temporary directory for the duration of the block. + return try await withTemporaryDirectory(prefix: copyName) { tmpDirPath in - // Invoke the block, passing it the path of the copied fixture. - try body(tmpDirPath) + defer { + // Unblock and remove the tmp dir on deinit. + try? localFileSystem.chmod(.userWritable, path: tmpDirPath, options: [.recursive]) + try? localFileSystem.removeFileTree(tmpDirPath) } + + let fixtureDir = try verifyFixtureExists(at: fixtureSubpath, file: file, line: line) + let preparedFixture = try setup( + fixtureDir: fixtureDir, + in: tmpDirPath, + copyName: copyName, + createGitRepo:createGitRepo + ) + return try await body(preparedFixture) } } catch SwiftPMError.executionFailure(let error, let output, let stderr) { print("**** FAILURE EXECUTING SUBPROCESS ****") @@ -123,6 +149,50 @@ public func fixture( } } +fileprivate func verifyFixtureExists(at fixtureSubpath: RelativePath, file: StaticString = #file, line: UInt = #line) throws -> AbsolutePath { + let fixtureDir = AbsolutePath("../../../Fixtures", relativeTo: #file) + .appending(fixtureSubpath) + + // Check that the fixture is really there. + guard localFileSystem.isDirectory(fixtureDir) else { + XCTFail("No such fixture: \(fixtureDir)", file: file, line: line) + throw SwiftPMError.packagePathNotFound + } + + return fixtureDir +} + +fileprivate func setup(fixtureDir: AbsolutePath, in tmpDirPath: AbsolutePath, copyName: String, createGitRepo: Bool = true) throws -> AbsolutePath { + func copy(from srcDir: AbsolutePath, to dstDir: AbsolutePath) throws { +#if os(Windows) + try localFileSystem.copy(from: srcDir, to: dstDir) +#else + try systemQuietly("cp", "-R", "-H", srcDir.pathString, dstDir.pathString) +#endif + } + + // The fixture contains either a checkout or just a Git directory. + if localFileSystem.isFile(fixtureDir.appending("Package.swift")) { + // It's a single package, so copy the whole directory as-is. + let dstDir = tmpDirPath.appending(component: copyName) + try copy(from: fixtureDir, to: dstDir) + // Invoke the block, passing it the path of the copied fixture. + return dstDir + } + // Copy each of the package directories and construct a git repo in it. + for fileName in try localFileSystem.getDirectoryContents(fixtureDir).sorted() { + let srcDir = fixtureDir.appending(component: fileName) + guard localFileSystem.isDirectory(srcDir) else { continue } + let dstDir = tmpDirPath.appending(component: fileName) + + try copy(from: srcDir, to: dstDir) + if createGitRepo { + initGitRepo(dstDir, tag: "1.2.3", addFile: false) + } + } + return tmpDirPath +} + /// Test-helper function that creates a new Git repository in a directory. The new repository will contain /// exactly one empty file unless `addFile` is `false`, and if a tag name is provided, a tag with that name will be /// created. @@ -268,7 +338,12 @@ public func loadPackageGraph( let packages = Array(rootManifests.keys) let input = PackageGraphRootInput(packages: packages) - let graphRoot = PackageGraphRoot(input: input, manifests: rootManifests, explicitProduct: explicitProduct) + let graphRoot = PackageGraphRoot( + input: input, + manifests: rootManifests, + explicitProduct: explicitProduct, + observabilityScope: observabilityScope + ) return try PackageGraph.load( root: graphRoot, @@ -364,3 +439,20 @@ extension RelativePath: ExpressibleByStringInterpolation { try! self.init(validating: value) } } + +extension InitPackage { + public convenience init( + name: String, + packageType: PackageType, + destinationPath: AbsolutePath, + fileSystem: FileSystem + ) throws { + try self.init( + name: name, + options: InitPackageOptions(packageType: packageType), + destinationPath: destinationPath, + installedSwiftPMConfiguration: .default, + fileSystem: fileSystem + ) + } +} diff --git a/Sources/SourceControl/GitRepository.swift b/Sources/SourceControl/GitRepository.swift index 6fb5e0037d1..2781413db10 100644 --- a/Sources/SourceControl/GitRepository.swift +++ b/Sources/SourceControl/GitRepository.swift @@ -85,6 +85,8 @@ public struct GitRepositoryProvider: RepositoryProvider, Cancellable { private let cancellator: Cancellator private let git: GitShellHelper + private var repositoryCache = ThreadSafeKeyValueStore() + public init() { // helper to cancel outstanding processes self.cancellator = Cancellator(observabilityScope: .none) @@ -200,29 +202,17 @@ public struct GitRepositoryProvider: RepositoryProvider, Cancellable { } public func repositoryExists(at directory: Basics.AbsolutePath) -> Bool { - if !localFileSystem.isDirectory(directory) { - return false - } - return self.isValidDirectory(directory) + return localFileSystem.isDirectory(directory) } - public func isValidDirectory(_ directory: Basics.AbsolutePath) -> Bool { - do { - let result = try self.git.run(["-C", directory.pathString, "rev-parse", "--git-dir"]) - return result == ".git" || result == "." || result == directory.pathString - } catch { - return false - } + public func isValidDirectory(_ directory: Basics.AbsolutePath) throws -> Bool { + let result = try self.git.run(["-C", directory.pathString, "rev-parse", "--git-dir"]) + return result == ".git" || result == "." || result == directory.pathString } - /// Returns true if the git reference name is well formed. - public func isValidRefFormat(_ ref: String) -> Bool { - do { - _ = try self.git.run(["check-ref-format", "--allow-onelevel", ref]) - return true - } catch { - return false - } + public func isValidDirectory(_ directory: Basics.AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + let remoteURL = try self.git.run(["-C", directory.pathString, "config", "--get", "remote.origin.url"]) + return remoteURL == repository.url } public func copy(from sourcePath: Basics.AbsolutePath, to destinationPath: Basics.AbsolutePath) throws { @@ -230,7 +220,10 @@ public struct GitRepositoryProvider: RepositoryProvider, Cancellable { } public func open(repository: RepositorySpecifier, at path: Basics.AbsolutePath) -> Repository { - GitRepository(git: self.git, path: path, isWorkingRepo: false) + let key = "\(repository)@\(path)" + return self.repositoryCache.memoize(key) { + GitRepository(git: self.git, path: path, isWorkingRepo: false) + } } public func createWorkingCopy( @@ -421,6 +414,8 @@ public final class GitRepository: Repository, WorkingCheckout { private var cachedTrees = ThreadSafeKeyValueStore() private var cachedTags = ThreadSafeBox<[String]>() private var cachedBranches = ThreadSafeBox<[String]>() + private var cachedIsBareRepo = ThreadSafeBox() + private var cachedHasSubmodules = ThreadSafeBox() public convenience init(path: AbsolutePath, isWorkingRepo: Bool = true, cancellator: Cancellator? = .none) { // used in one-off operations on git repo, as such the terminator is not ver important @@ -639,7 +634,7 @@ public final class GitRepository: Repository, WorkingCheckout { tag, failureMessage: "Couldn’t check out tag ‘\(tag)’" ) - try self.updateSubmoduleAndCleanNotOnQueue() + try self.updateSubmoduleAndCleanIfNecessary() } } @@ -654,33 +649,30 @@ public final class GitRepository: Repository, WorkingCheckout { revision.identifier, failureMessage: "Couldn’t check out revision ‘\(revision.identifier)’" ) - try self.updateSubmoduleAndCleanNotOnQueue() + try self.updateSubmoduleAndCleanIfNecessary() } } internal func isBare() throws -> Bool { - do { + return try self.cachedIsBareRepo.memoize(body: { let output = try callGit( "rev-parse", "--is-bare-repository", failureMessage: "Couldn’t test for bare repository" ) + return output == "true" - } + }) } internal func checkoutExists() throws -> Bool { - self.lock.withLock { - do { - let output = try callGit( - "rev-parse", - "--is-bare-repository", - failureMessage: "Couldn’t test if check-out exists" - ) - return output == "false" - } catch { - return false - } + return try !self.isBare() + } + + private func updateSubmoduleAndCleanIfNecessary() throws { + if self.cachedHasSubmodules.get(default: false) || localFileSystem.exists(self.path.appending(".gitmodules")) { + self.cachedHasSubmodules.put(true) + try self.updateSubmoduleAndCleanNotOnQueue() } } @@ -702,10 +694,8 @@ public final class GitRepository: Repository, WorkingCheckout { /// Returns true if a revision exists. public func exists(revision: Revision) -> Bool { - self.lock.withLock { - let output = try? callGit("rev-parse", "--verify", "\(revision.identifier)^{commit}") - return output != nil - } + let output = try? callGit("rev-parse", "--verify", "\(revision.identifier)^{commit}") + return output != nil } public func checkout(newBranch: String) throws { @@ -744,7 +734,7 @@ public final class GitRepository: Repository, WorkingCheckout { } /// Returns true if there is an alternative object store in the repository and it is valid. - public func isAlternateObjectStoreValid() -> Bool { + public func isAlternateObjectStoreValid(expected: AbsolutePath) -> Bool { let objectStoreFile = self.path.appending(components: ".git", "objects", "info", "alternates") guard let bytes = try? localFileSystem.readFileContents(objectStoreFile) else { return false @@ -753,7 +743,11 @@ public final class GitRepository: Repository, WorkingCheckout { guard let firstLine = ByteString(split[0]).validDescription else { return false } - return (try? localFileSystem.isDirectory(AbsolutePath(validating: firstLine))) == true + guard let objectsPath = try? AbsolutePath(validating: firstLine), localFileSystem.isDirectory(objectsPath) else { + return false + } + let repositoryPath = objectsPath.parentDirectory + return expected == repositoryPath } /// Returns true if the file at `path` is ignored by `git` @@ -925,6 +919,14 @@ public final class GitRepository: Repository, WorkingCheckout { } } } + + /// Read a symbolic link. + func readLink(hash: Hash) throws -> String { + return try callGit( + "cat-file", "-p", String(describing: hash.bytes), + failureMessage: "Couldn't read '\(String(describing: hash.bytes))'" + ) + } } // MARK: - GitFileSystemView @@ -1085,13 +1087,18 @@ private class GitFileSystemView: FileSystem { guard entry.type != .tree else { throw FileSystemError(.isDirectory, path) } - guard entry.type != .symlink else { - throw InternalError("symlinks not supported") - } guard case .hash(let hash) = entry.location else { throw InternalError("only hash locations supported") } - return try self.repository.readBlob(hash: hash) + switch entry.type { + case .symlink: + let path = try repository.readLink(hash: hash) + return try readFileContents(AbsolutePath(validating: path)) + case .blob: + return try self.repository.readBlob(hash: hash) + default: + throw InternalError("unsupported git entry type \(entry.type) at path \(path)") + } } // MARK: Unsupported methods. diff --git a/Sources/SourceControl/Repository.swift b/Sources/SourceControl/Repository.swift index 9fdf6a9309c..79a41a78ea1 100644 --- a/Sources/SourceControl/Repository.swift +++ b/Sources/SourceControl/Repository.swift @@ -143,10 +143,10 @@ public protocol RepositoryProvider: Cancellable { func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws /// Returns true if the directory is valid git location. - func isValidDirectory(_ directory: AbsolutePath) -> Bool + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool - /// Returns true if the git reference name is well formed. - func isValidRefFormat(_ ref: String) -> Bool + /// Returns true if the directory is valid git location for the specified repository + func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool } /// Abstract repository operations. @@ -272,7 +272,7 @@ public protocol WorkingCheckout { func checkout(newBranch: String) throws /// Returns true if there is an alternative store in the checkout and it is valid. - func isAlternateObjectStoreValid() -> Bool + func isAlternateObjectStoreValid(expected: AbsolutePath) -> Bool /// Returns true if the file at `path` is ignored by `git` func areIgnored(_ paths: [AbsolutePath]) throws -> [Bool] diff --git a/Sources/SourceControl/RepositoryManager.swift b/Sources/SourceControl/RepositoryManager.swift index f6e8abd2842..4d31db93c9e 100644 --- a/Sources/SourceControl/RepositoryManager.swift +++ b/Sources/SourceControl/RepositoryManager.swift @@ -85,7 +85,7 @@ public class RepositoryManager: Cancellable { self.delegate = delegate // this queue and semaphore is used to limit the amount of concurrent git operations taking place - let maxConcurrentOperations = min(maxConcurrentOperations ?? 3, Concurrency.maxOperations) + let maxConcurrentOperations = max(1, maxConcurrentOperations ?? 3*Concurrency.maxOperations/4) self.lookupQueue = OperationQueue() self.lookupQueue.name = "org.swift.swiftpm.repository-manager" self.lookupQueue.maxConcurrentOperationCount = maxConcurrentOperations @@ -188,20 +188,26 @@ public class RepositoryManager: Cancellable { // sync func and roll the logic into the async version above private func lookup( package: PackageIdentity, - repository: RepositorySpecifier, + repository repositorySpecifier: RepositorySpecifier, updateStrategy: RepositoryUpdateStrategy, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue ) throws -> RepositoryHandle { - let relativePath = try repository.storagePath() + let relativePath = try repositorySpecifier.storagePath() let repositoryPath = self.path.appending(relativePath) - let handle = RepositoryHandle(manager: self, repository: repository, subpath: relativePath) + let handle = RepositoryHandle(manager: self, repository: repositorySpecifier, subpath: relativePath) // check if a repository already exists // errors when trying to check if a repository already exists are legitimate // and recoverable, and as such can be ignored - if (try? self.provider.repositoryExists(at: repositoryPath)) ?? false { + quick: if (try? self.provider.repositoryExists(at: repositoryPath)) ?? false { let repository = try handle.open() + + guard ((try? self.provider.isValidDirectory(repositoryPath, for: repositorySpecifier)) ?? false) else { + observabilityScope.emit(warning: "\(repositoryPath) is not valid git repository for '\(repositorySpecifier.location)', will fetch again.") + break quick + } + // Update the repository if needed if self.fetchRequired(repository: repository, updateStrategy: updateStrategy) { let start = DispatchTime.now() @@ -216,6 +222,7 @@ public class RepositoryManager: Cancellable { self.delegate?.didUpdate(package: package, repository: handle.repository, duration: duration) } } + return handle } @@ -332,7 +339,7 @@ public class RepositoryManager: Cancellable { } } catch { // If we are offline and have a valid cached repository, use the cache anyway. - if isOffline(error) && self.provider.isValidDirectory(cachedRepositoryPath) { + if try isOffline(error) && self.provider.isValidDirectory(cachedRepositoryPath, for: handle.repository) { // For the first offline use in the lifetime of this repository manager, emit a warning. if self.emitNoConnectivityWarning.get(default: false) { self.emitNoConnectivityWarning.put(false) @@ -379,10 +386,18 @@ public class RepositoryManager: Cancellable { } } + /// Open a working copy checkout at a path public func openWorkingCopy(at path: AbsolutePath) throws -> WorkingCheckout { try self.provider.openWorkingCopy(at: path) } + /// Validate a working copy check is aligned with its repository setup + public func isValidWorkingCopy(_ workingCopy: WorkingCheckout, for repository: RepositorySpecifier) throws -> Bool { + let relativePath = try repository.storagePath() + let repositoryPath = self.path.appending(relativePath) + return workingCopy.isAlternateObjectStoreValid(expected: repositoryPath) + } + /// Open a repository from a handle. private func open(_ handle: RepositoryHandle) throws -> Repository { try self.provider.open( @@ -412,13 +427,13 @@ public class RepositoryManager: Cancellable { } /// Returns true if the directory is valid git location. - public func isValidDirectory(_ directory: AbsolutePath) -> Bool { - self.provider.isValidDirectory(directory) + public func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { + try self.provider.isValidDirectory(directory) } - /// Returns true if the git reference name is well formed. - public func isValidRefFormat(_ ref: String) -> Bool { - self.provider.isValidRefFormat(ref) + /// Returns true if the directory is valid git location for the specified repository + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + try self.provider.isValidDirectory(directory, for: repository) } /// Reset the repository manager. @@ -436,7 +451,7 @@ public class RepositoryManager: Cancellable { } /// Sets up the cache directories if they don't already exist. - public func initializeCacheIfNeeded(cachePath: AbsolutePath) throws { + private func initializeCacheIfNeeded(cachePath: AbsolutePath) throws { // Create the supplied cache directory. if !self.fileSystem.exists(cachePath) { try self.fileSystem.createDirectory(cachePath, recursive: true) @@ -577,8 +592,9 @@ extension RepositorySpecifier { } extension RepositorySpecifier { - fileprivate var canonicalLocation: CanonicalPackageLocation { - .init(self.location.description) + fileprivate var canonicalLocation: String { + let canonicalPackageLocation: CanonicalPackageURL = .init(self.location.description) + return "\(canonicalPackageLocation.description)_\(canonicalPackageLocation.scheme ?? "")" } } diff --git a/Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift b/Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift index d4984b5c21d..3c8e6b4a71e 100644 --- a/Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift +++ b/Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift @@ -18,15 +18,15 @@ protocol ConfigurationSubcommand: SwiftSDKSubcommand { /// An identifier of an already installed Swift SDK. var sdkID: String { get } - /// A target triple of the destination. + /// A target triple of the Swift SDK. var targetTriple: String { get } - /// Run a command related to configuration of cross-compilation destinations, passing it required configuration + /// Run a command related to configuration of Swift SDKs, passing it required configuration /// values. /// - Parameters: /// - hostTriple: triple of the machine this command is running on. /// - targetTriple: triple of the machine on which cross-compiled code will run on. - /// - destination: destination configuration fetched that matches currently set `destinationID` and + /// - swiftSDK: Swift SDK configuration fetched that matches currently set `sdkID` and /// `targetTriple`. /// - configurationStore: storage for configuration properties that this command operates on. /// - swiftSDKsDirectory: directory containing Swift SDK artifact bundles and their configuration. @@ -34,7 +34,7 @@ protocol ConfigurationSubcommand: SwiftSDKSubcommand { func run( hostTriple: Triple, targetTriple: Triple, - _ destination: SwiftSDK, + _ swiftSDK: SwiftSDK, _ configurationStore: SwiftSDKConfigurationStore, _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope @@ -47,15 +47,19 @@ extension ConfigurationSubcommand { _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { + let bundleStore = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope, + outputHandler: { print($0) } + ) let configurationStore = try SwiftSDKConfigurationStore( hostTimeTriple: hostTriple, - swiftSDKsDirectoryPath: swiftSDKsDirectory, - fileSystem: fileSystem, - observabilityScope: observabilityScope + swiftSDKBundleStore: bundleStore ) let targetTriple = try Triple(self.targetTriple) - guard let destination = try configurationStore.readConfiguration( + guard let swiftSDK = try configurationStore.readConfiguration( sdkID: sdkID, targetTriple: targetTriple ) else { @@ -69,7 +73,7 @@ extension ConfigurationSubcommand { try run( hostTriple: hostTriple, targetTriple: targetTriple, - destination, + swiftSDK, configurationStore, swiftSDKsDirectory, observabilityScope diff --git a/Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift b/Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift index 9351ba076d3..fdf0bd24151 100644 --- a/Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift +++ b/Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift @@ -16,7 +16,7 @@ public struct ConfigureSwiftSDK: ParsableCommand { public static let configuration = CommandConfiguration( commandName: "configuration", abstract: """ - Manages configuration options for installed cross-compilation destinations. + Manages configuration options for installed Swift SDKs. """, subcommands: [ ResetConfiguration.self, diff --git a/Sources/SwiftSDKTool/Configuration/ResetConfiguration.swift b/Sources/SwiftSDKTool/Configuration/ResetConfiguration.swift index 1db853ff0b6..ebb1aec7c43 100644 --- a/Sources/SwiftSDKTool/Configuration/ResetConfiguration.swift +++ b/Sources/SwiftSDKTool/Configuration/ResetConfiguration.swift @@ -19,8 +19,8 @@ struct ResetConfiguration: ConfigurationSubcommand { static let configuration = CommandConfiguration( commandName: "reset", abstract: """ - Resets configuration properties currently applied to a given destination and run-time triple. If no specific \ - property is specified, all of them are reset for the destination. + Resets configuration properties currently applied to a given Swift SDK and target triple. If no specific \ + property is specified, all of them are reset for the Swift SDK. """ ) @@ -47,24 +47,24 @@ struct ResetConfiguration: ConfigurationSubcommand { @Argument( help: """ - An identifier of an already installed destination. Use the `list` subcommand to see all available \ + An identifier of an already installed Swift SDK. Use the `list` subcommand to see all available \ identifiers. """ ) var sdkID: String - @Argument(help: "A run-time triple of the destination specified by `destination-id` identifier string.") + @Argument(help: "A target triple of the Swift SDK specified by `sdk-id` identifier string.") var targetTriple: String func run( hostTriple: Triple, targetTriple: Triple, - _ destination: SwiftSDK, + _ swiftSDK: SwiftSDK, _ configurationStore: SwiftSDKConfigurationStore, - _ destinationsDirectory: AbsolutePath, + _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { - var configuration = destination.pathsConfiguration + var configuration = swiftSDK.pathsConfiguration var shouldResetAll = true var resetProperties = [String]() @@ -107,24 +107,24 @@ struct ResetConfiguration: ConfigurationSubcommand { if shouldResetAll { if try !configurationStore.resetConfiguration(sdkID: sdkID, targetTriple: targetTriple) { observabilityScope.emit( - warning: "No configuration for destination \(sdkID)" + warning: "No configuration for Swift SDK `\(sdkID)`" ) } else { observabilityScope.emit( info: """ - All configuration properties of destination `\(sdkID) for run-time triple \ + All configuration properties of Swift SDK `\(sdkID)` for target triple \ `\(targetTriple)` were successfully reset. """ ) } } else { - var destination = destination - destination.pathsConfiguration = configuration - try configurationStore.updateConfiguration(sdkID: sdkID, swiftSDK: destination) + var swiftSDK = swiftSDK + swiftSDK.pathsConfiguration = configuration + try configurationStore.updateConfiguration(sdkID: sdkID, swiftSDK: swiftSDK) observabilityScope.emit( info: """ - These properties of destination `\(sdkID) for run-time triple \ + These properties of Swift SDK `\(sdkID)` for target triple \ `\(targetTriple)` were successfully reset: \(resetProperties.joined(separator: ", ")). """ ) diff --git a/Sources/SwiftSDKTool/Configuration/SetConfiguration.swift b/Sources/SwiftSDKTool/Configuration/SetConfiguration.swift index c7a95c4c473..1467b2b9e57 100644 --- a/Sources/SwiftSDKTool/Configuration/SetConfiguration.swift +++ b/Sources/SwiftSDKTool/Configuration/SetConfiguration.swift @@ -19,7 +19,7 @@ struct SetConfiguration: ConfigurationSubcommand { static let configuration = CommandConfiguration( commandName: "set", abstract: """ - Sets configuration options for installed cross-compilation destinations. + Sets configuration options for installed Swift SDKs. """ ) @@ -63,24 +63,24 @@ struct SetConfiguration: ConfigurationSubcommand { @Argument( help: """ - An identifier of an already installed destination. Use the `list` subcommand to see all available \ + An identifier of an already installed Swift SDK. Use the `list` subcommand to see all available \ identifiers. """ ) var sdkID: String - @Argument(help: "The run-time triple of the destination to configure.") + @Argument(help: "The target triple of the Swift SDK to configure.") var targetTriple: String func run( hostTriple: Triple, targetTriple: Triple, - _ destination: SwiftSDK, + _ swiftSDK: SwiftSDK, _ configurationStore: SwiftSDKConfigurationStore, - _ destinationsDirectory: AbsolutePath, + _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { - var configuration = destination.pathsConfiguration + var configuration = swiftSDK.pathsConfiguration var updatedProperties = [String]() let currentWorkingDirectory: AbsolutePath? = fileSystem.currentWorkingDirectory @@ -123,20 +123,20 @@ struct SetConfiguration: ConfigurationSubcommand { guard !updatedProperties.isEmpty else { observabilityScope.emit( error: """ - No properties of destination `\(sdkID) for run-time triple `\(targetTriple)` were updated \ + No properties of Swift SDK `\(sdkID)` for target triple `\(targetTriple)` were updated \ since none were specified. Pass `--help` flag to see the list of all available properties. """ ) return } - var destination = destination - destination.pathsConfiguration = configuration - try configurationStore.updateConfiguration(sdkID: sdkID, swiftSDK: destination) + var swiftSDK = swiftSDK + swiftSDK.pathsConfiguration = configuration + try configurationStore.updateConfiguration(sdkID: sdkID, swiftSDK: swiftSDK) observabilityScope.emit( info: """ - These properties of destination `\(sdkID) for run-time triple \ + These properties of Swift SDK `\(sdkID)` for target triple \ `\(targetTriple)` were successfully updated: \(updatedProperties.joined(separator: ", ")). """ ) diff --git a/Sources/SwiftSDKTool/Configuration/ShowConfiguration.swift b/Sources/SwiftSDKTool/Configuration/ShowConfiguration.swift index c9cda18d7b9..ac901a1e36b 100644 --- a/Sources/SwiftSDKTool/Configuration/ShowConfiguration.swift +++ b/Sources/SwiftSDKTool/Configuration/ShowConfiguration.swift @@ -19,7 +19,7 @@ struct ShowConfiguration: ConfigurationSubcommand { static let configuration = CommandConfiguration( commandName: "show", abstract: """ - Prints all configuration properties currently applied to a given destination and run-time triple. + Prints all configuration properties currently applied to a given Swift SDK and target triple. """ ) @@ -28,24 +28,24 @@ struct ShowConfiguration: ConfigurationSubcommand { @Argument( help: """ - An identifier of an already installed destination. Use the `list` subcommand to see all available \ + An identifier of an already installed Swift SDK. Use the `list` subcommand to see all available \ identifiers. """ ) var sdkID: String - @Argument(help: "The run-time triple of the destination to configure.") + @Argument(help: "The target triple of the Swift SDK to configure.") var targetTriple: String func run( hostTriple: Triple, targetTriple: Triple, - _ destination: SwiftSDK, + _ swiftSDK: SwiftSDK, _ configurationStore: SwiftSDKConfigurationStore, - _ destinationsDirectory: AbsolutePath, + _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { - print(destination.pathsConfiguration) + print(swiftSDK.pathsConfiguration) } } diff --git a/Sources/SwiftSDKTool/InstallSwiftSDK.swift b/Sources/SwiftSDKTool/InstallSwiftSDK.swift index 20213e999db..f034ddaef1a 100644 --- a/Sources/SwiftSDKTool/InstallSwiftSDK.swift +++ b/Sources/SwiftSDKTool/InstallSwiftSDK.swift @@ -37,17 +37,22 @@ public struct InstallSwiftSDK: SwiftSDKSubcommand { func run( hostTriple: Triple, - _ destinationsDirectory: AbsolutePath, + _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) async throws { let cancellator = Cancellator(observabilityScope: observabilityScope) cancellator.installSignalHandlers() - try await SwiftSDKBundle.install( + + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope, + outputHandler: { print($0.description) } + ) + try await store.install( bundlePathOrURL: bundlePathOrURL, - swiftSDKsDirectory: destinationsDirectory, - self.fileSystem, UniversalArchiver(self.fileSystem, cancellator), - observabilityScope + HTTPClient() ) } } diff --git a/Sources/SwiftSDKTool/ListSwiftSDKs.swift b/Sources/SwiftSDKTool/ListSwiftSDKs.swift index 0bf0fb1fd27..23e221788fb 100644 --- a/Sources/SwiftSDKTool/ListSwiftSDKs.swift +++ b/Sources/SwiftSDKTool/ListSwiftSDKs.swift @@ -21,7 +21,7 @@ public struct ListSwiftSDKs: SwiftSDKSubcommand { commandName: "list", abstract: """ - Print a list of IDs of available cross-compilation destinations available on the filesystem. + Print a list of IDs of available Swift SDKs available on the filesystem. """ ) @@ -32,22 +32,24 @@ public struct ListSwiftSDKs: SwiftSDKSubcommand { func run( hostTriple: Triple, - _ destinationsDirectory: AbsolutePath, + _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { - let validBundles = try SwiftSDKBundle.getAllValidBundles( - swiftSDKsDirectory: destinationsDirectory, + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, fileSystem: fileSystem, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + outputHandler: { print($0.description) } ) + let validBundles = try store.allValidBundles guard !validBundles.isEmpty else { - print("No cross-compilation destinations are currently installed.") + print("No Swift SDKs are currently installed.") return } - for bundle in validBundles { - bundle.artifacts.keys.forEach { print($0) } + for artifactID in validBundles.sortedArtifactIDs { + print(artifactID) } } } diff --git a/Sources/SwiftSDKTool/README.md b/Sources/SwiftSDKTool/README.md index 23b65da0d16..15fb0924c16 100644 --- a/Sources/SwiftSDKTool/README.md +++ b/Sources/SwiftSDKTool/README.md @@ -1,4 +1,4 @@ -# Cross-Compilation Destinations Tool +# Swift SDKs Tool -This module implements `swift experimental destination` command and its subcommands, which allow managing artifact -bundles used as cross-compilation destinations. +This module implements `swift experimental-sdk` command and its subcommands, which allow managing artifact +bundles used as Swift SDKs, as specified in [SE-0387](https://github.com/apple/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md). diff --git a/Sources/SwiftSDKTool/RemoveSwiftSDK.swift b/Sources/SwiftSDKTool/RemoveSwiftSDK.swift index f2169cd773f..2c0a735c257 100644 --- a/Sources/SwiftSDKTool/RemoveSwiftSDK.swift +++ b/Sources/SwiftSDKTool/RemoveSwiftSDK.swift @@ -33,11 +33,10 @@ public struct RemoveSwiftSDK: SwiftSDKSubcommand { func run( hostTriple: Triple, - _ destinationsDirectory: AbsolutePath, + _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) async throws { - let destinationsDirectory = try self.getOrCreateDestinationsDirectory() - let artifactBundleDirectory = destinationsDirectory.appending(component: self.sdkIDOrBundleName) + let artifactBundleDirectory = swiftSDKsDirectory.appending(component: self.sdkIDOrBundleName) let removedBundleDirectory: AbsolutePath if fileSystem.exists(artifactBundleDirectory) { @@ -45,12 +44,15 @@ public struct RemoveSwiftSDK: SwiftSDKSubcommand { removedBundleDirectory = artifactBundleDirectory } else { - let bundles = try SwiftSDKBundle.getAllValidBundles( - swiftSDKsDirectory: destinationsDirectory, - fileSystem: fileSystem, - observabilityScope: observabilityScope + let bundleStore = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope, + outputHandler: { print($0) } ) + let bundles = try bundleStore.allValidBundles + let matchingBundles = bundles.compactMap { bundle in bundle.artifacts[sdkIDOrBundleName] != nil ? bundle : nil } diff --git a/Sources/SwiftSDKTool/SwiftSDKSubcommand.swift b/Sources/SwiftSDKTool/SwiftSDKSubcommand.swift index 26b2b111505..c721e918de9 100644 --- a/Sources/SwiftSDKTool/SwiftSDKSubcommand.swift +++ b/Sources/SwiftSDKTool/SwiftSDKSubcommand.swift @@ -18,12 +18,12 @@ import PackageModel import var TSCBasic.stdoutStream -/// A protocol for functions and properties common to all destination subcommands. +/// A protocol for functions and properties common to all Swift SDK subcommands. protocol SwiftSDKSubcommand: AsyncParsableCommand { /// Common locations options provided by ArgumentParser. var locations: LocationOptions { get } - /// Run a command operating on cross-compilation destinations, passing it required configuration values. + /// Run a command operating on Swift SDKs, passing it required configuration values. /// - Parameters: /// - hostTriple: triple of the machine this command is running on. /// - swiftSDKsDirectory: directory containing Swift SDK artifact bundles and their configuration. @@ -39,38 +39,33 @@ extension SwiftSDKSubcommand { /// The file system used by default by this command. var fileSystem: FileSystem { localFileSystem } - /// Parses destinations directory option if provided or uses the default path for cross-compilation destinations + /// Parses Swift SDKs directory option if provided or uses the default path for Swift SDKs /// on the file system. A new directory at this path is created if one doesn't exist already. /// - Returns: existing or a newly created directory at the computed location. - func getOrCreateDestinationsDirectory() throws -> AbsolutePath { - guard var destinationsDirectory = try fileSystem.getSharedSwiftSDKsDirectory( + func getOrCreateSwiftSDKsDirectory() throws -> AbsolutePath { + var swiftSDKsDirectory = try fileSystem.getSharedSwiftSDKsDirectory( explicitDirectory: locations.swiftSDKsDirectory - ) else { - let expectedPath = try fileSystem.swiftSDKsDirectory - throw StringError( - "Couldn't find or create a directory where cross-compilation destinations are stored: `\(expectedPath)`" - ) - } + ) - if !self.fileSystem.exists(destinationsDirectory) { - destinationsDirectory = try self.fileSystem.getOrCreateSwiftPMSwiftSDKsDirectory() + if !self.fileSystem.exists(swiftSDKsDirectory) { + swiftSDKsDirectory = try self.fileSystem.getOrCreateSwiftPMSwiftSDKsDirectory() } - return destinationsDirectory + return swiftSDKsDirectory } public func run() async throws { let observabilityHandler = SwiftToolObservabilityHandler(outputStream: stdoutStream, logLevel: .info) let observabilitySystem = ObservabilitySystem(observabilityHandler) let observabilityScope = observabilitySystem.topScope - let destinationsDirectory = try self.getOrCreateDestinationsDirectory() + let swiftSDKsDirectory = try self.getOrCreateSwiftSDKsDirectory() let hostToolchain = try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK()) let triple = try Triple.getHostTriple(usingSwiftCompiler: hostToolchain.swiftCompilerPath) var commandError: Error? = nil do { - try await self.run(hostTriple: triple, destinationsDirectory, observabilityScope) + try await self.run(hostTriple: triple, swiftSDKsDirectory, observabilityScope) if observabilityScope.errorsReported { throw ExitCode.failure } diff --git a/Sources/Workspace/CMakeLists.txt b/Sources/Workspace/CMakeLists.txt index 31c13b95ecb..d6e656f146a 100644 --- a/Sources/Workspace/CMakeLists.txt +++ b/Sources/Workspace/CMakeLists.txt @@ -10,18 +10,28 @@ add_library(Workspace CheckoutState.swift DefaultPluginScriptRunner.swift Diagnostics.swift - FileSystemPackageContainer.swift InitPackage.swift + LoadableResult.swift ManagedArtifact.swift ManagedDependency.swift - RegistryPackageContainer.swift + PackageContainer/FileSystemPackageContainer.swift + PackageContainer/RegistryPackageContainer.swift + PackageContainer/SourceControlPackageContainer.swift ResolvedFileWatcher.swift ResolverPrecomputationProvider.swift - SourceControlPackageContainer.swift ToolsVersionSpecificationRewriter.swift Workspace.swift Workspace+BinaryArtifacts.swift Workspace+Configuration.swift + Workspace+Delegation.swift + Workspace+Dependencies.swift + Workspace+Editing.swift + Workspace+Manifests.swift + Workspace+PackageContainer.swift + Workspace+Pinning.swift + Workspace+Registry.swift + Workspace+Signing.swift + Workspace+SourceControl.swift Workspace+State.swift) target_link_libraries(Workspace PUBLIC TSCBasic diff --git a/Sources/Workspace/DefaultPluginScriptRunner.swift b/Sources/Workspace/DefaultPluginScriptRunner.swift index c7763e23f07..e5279b0c82e 100644 --- a/Sources/Workspace/DefaultPluginScriptRunner.swift +++ b/Sources/Workspace/DefaultPluginScriptRunner.swift @@ -270,7 +270,7 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable { /// Persisted information about the last time the compiler was invoked. struct PersistedCompilationState: Codable { var commandLine: [String] - var environment: [String:String] + var environment: EnvironmentVariables var inputHash: String? var output: String var result: Result @@ -364,7 +364,7 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable { // Save the persisted compilation state for possible reuse next time. let compilationState = PersistedCompilationState( commandLine: commandLine, - environment: toolchain.swiftCompilerEnvironment, + environment: toolchain.swiftCompilerEnvironment.cachable, inputHash: compilerInputHash, output: compilerOutput, result: .init(process.exitStatus)) @@ -664,7 +664,7 @@ fileprivate extension FileHandle { guard header.count == 8 else { throw PluginMessageError.truncatedHeader } - let length = header.withUnsafeBytes{ $0.load(as: UInt64.self).littleEndian } + let length = header.withUnsafeBytes{ $0.loadUnaligned(as: UInt64.self).littleEndian } guard length >= 2 else { throw PluginMessageError.invalidPayloadSize } diff --git a/Sources/Workspace/Diagnostics.swift b/Sources/Workspace/Diagnostics.swift index 4165aed82f2..09ffc9d5d3e 100644 --- a/Sources/Workspace/Diagnostics.swift +++ b/Sources/Workspace/Diagnostics.swift @@ -28,12 +28,11 @@ public struct ManifestParseDiagnostic: CustomStringConvertible { } public var description: String { - "manifest parse error(s):\n" + errors.joined(separator: "\n") + "manifest parse error(s):\n" + self.errors.joined(separator: "\n") } } public enum WorkspaceDiagnostics { - // MARK: - Errors /// The diagnostic triggered when an operation fails because its completion @@ -43,7 +42,7 @@ public enum WorkspaceDiagnostics { public let repositoryPath: AbsolutePath public var description: String { - return "repository '\(repositoryPath)' has uncommited changes" + "repository '\(self.repositoryPath)' has uncommited changes" } } @@ -54,7 +53,7 @@ public enum WorkspaceDiagnostics { public let repositoryPath: AbsolutePath public var description: String { - return "repository '\(repositoryPath)' has unpushed changes" + "repository '\(self.repositoryPath)' has unpushed changes" } } @@ -65,7 +64,7 @@ public enum WorkspaceDiagnostics { public let dependencyName: String public var description: String { - return "dependency '\(dependencyName)' not in edit mode" + "dependency '\(self.dependencyName)' not in edit mode" } } @@ -76,7 +75,7 @@ public enum WorkspaceDiagnostics { public let branch: String public var description: String { - return "branch '\(branch)' already exists" + "branch '\(self.branch)' already exists" } } @@ -87,7 +86,7 @@ public enum WorkspaceDiagnostics { public let revision: String public var description: String { - return "revision '\(revision)' does not exist" + "revision '\(self.revision)' does not exist" } } } @@ -98,11 +97,15 @@ extension Basics.Diagnostic { } static func editBranchNotCheckedOut(packageName: String, branchName: String) -> Self { - .warning("dependency '\(packageName)' already exists at the edit destination; not checking-out branch '\(branchName)'") + .warning( + "dependency '\(packageName)' already exists at the edit destination; not checking-out branch '\(branchName)'" + ) } static func editRevisionNotUsed(packageName: String, revisionIdentifier: String) -> Self { - .warning("dependency '\(packageName)' already exists at the edit destination; not using revision '\(revisionIdentifier)'") + .warning( + "dependency '\(packageName)' already exists at the edit destination; not using revision '\(revisionIdentifier)'" + ) } static func editedDependencyMissing(packageName: String) -> Self { @@ -122,27 +125,39 @@ extension Basics.Diagnostic { } static func artifactInvalidArchive(artifactURL: URL, targetName: String) -> Self { - .error("invalid archive returned from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)'") + .error( + "invalid archive returned from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)'" + ) } static func artifactChecksumChanged(targetName: String) -> Self { - .error("artifact of binary target '\(targetName)' has changed checksum; this is a potential security risk so the new artifact won't be downloaded") + .error( + "artifact of binary target '\(targetName)' has changed checksum; this is a potential security risk so the new artifact won't be downloaded" + ) } static func artifactInvalidChecksum(targetName: String, expectedChecksum: String, actualChecksum: String?) -> Self { - .error("checksum of downloaded artifact of binary target '\(targetName)' (\(actualChecksum ?? "none")) does not match checksum specified by the manifest (\(expectedChecksum))") + .error( + "checksum of downloaded artifact of binary target '\(targetName)' (\(actualChecksum ?? "none")) does not match checksum specified by the manifest (\(expectedChecksum))" + ) } static func artifactFailedDownload(artifactURL: URL, targetName: String, reason: String) -> Self { - .error("failed downloading '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)") + .error( + "failed downloading '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)" + ) } static func artifactFailedValidation(artifactURL: URL, targetName: String, reason: String) -> Self { - .error("failed validating archive from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)") + .error( + "failed validating archive from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)" + ) } static func remoteArtifactFailedExtraction(artifactURL: URL, targetName: String, reason: String) -> Self { - .error("failed extracting '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)") + .error( + "failed extracting '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)" + ) } static func localArtifactFailedExtraction(artifactPath: AbsolutePath, targetName: String, reason: String) -> Self { @@ -150,7 +165,9 @@ extension Basics.Diagnostic { } static func remoteArtifactNotFound(artifactURL: URL, targetName: String) -> Self { - .error("downloaded archive of binary target '\(targetName)' from '\(artifactURL.absoluteString)' does not contain a binary artifact.") + .error( + "downloaded archive of binary target '\(targetName)' from '\(artifactURL.absoluteString)' does not contain a binary artifact." + ) } static func localArchivedArtifactNotFound(archivePath: AbsolutePath, targetName: String) -> Self { @@ -160,11 +177,25 @@ extension Basics.Diagnostic { static func localArtifactNotFound(artifactPath: AbsolutePath, targetName: String) -> Self { .error("local binary target '\(targetName)' at '\(artifactPath)' does not contain a binary artifact.") } -} + static func exhaustedAttempts(missing: [PackageReference]) -> Self { + let missing = missing.sorted(by: { $0.identity < $1.identity }).map { + switch $0.kind { + case .registry(let identity): + return "'\(identity.description)'" + case .remoteSourceControl(let url): + return "'\($0.identity)' from \(url)" + case .localSourceControl(let path), .fileSystem(let path), .root(let path): + return "'\($0.identity)' at \(path)" + } + } + return .error( + "exhausted attempts to resolve the dependencies graph, with the following dependencies unresolved:\n* \(missing.joined(separator: "\n* "))" + ) + } +} extension FileSystemError: CustomStringConvertible { - public var description: String { guard let path else { switch self.kind { diff --git a/Sources/Workspace/InitPackage.swift b/Sources/Workspace/InitPackage.swift index ec8df371578..1f24c2ac11c 100644 --- a/Sources/Workspace/InitPackage.swift +++ b/Sources/Workspace/InitPackage.swift @@ -69,6 +69,9 @@ public final class InitPackage { /// The options for package to create. let options: InitPackageOptions + /// Configuration from the used toolchain. + let installedSwiftPMConfiguration: InstalledSwiftPMConfiguration + /// The name of the package to create. let pkgname: String @@ -85,12 +88,14 @@ public final class InitPackage { name: String, packageType: PackageType, destinationPath: AbsolutePath, + installedSwiftPMConfiguration: InstalledSwiftPMConfiguration, fileSystem: FileSystem ) throws { try self.init( name: name, options: InitPackageOptions(packageType: packageType), destinationPath: destinationPath, + installedSwiftPMConfiguration: installedSwiftPMConfiguration, fileSystem: fileSystem ) } @@ -100,12 +105,14 @@ public final class InitPackage { name: String, options: InitPackageOptions, destinationPath: AbsolutePath, + installedSwiftPMConfiguration: InstalledSwiftPMConfiguration, fileSystem: FileSystem ) throws { self.options = options self.pkgname = name self.moduleName = name.spm_mangledToC99ExtendedIdentifier() self.destinationPath = destinationPath + self.installedSwiftPMConfiguration = installedSwiftPMConfiguration self.fileSystem = fileSystem } @@ -259,8 +266,7 @@ public final class InitPackage { } else if packageType == .macro { pkgParams.append(""" dependencies: [ - // Depend on the Swift 5.9 release of SwiftSyntax - .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"), + .package(url: "https://github.com/apple/swift-syntax.git", from: "\(self.installedSwiftPMConfiguration.swiftSyntaxVersionForMacroTemplate.description)"), ] """) } @@ -567,6 +573,7 @@ public final class InitPackage { print("Hello, world!") } } + """ case .macro: content = """ @@ -581,6 +588,7 @@ public final class InitPackage { /// produces a tuple `(x + y, "x + y")`. @freestanding(expression) public macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "\(moduleName)Macros", type: "StringifyMacro") + """ case .empty, .buildToolPlugin, .commandPlugin: diff --git a/Sources/Workspace/LoadableResult.swift b/Sources/Workspace/LoadableResult.swift new file mode 100644 index 00000000000..65ae8b2df3a --- /dev/null +++ b/Sources/Workspace/LoadableResult.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 +// +//===----------------------------------------------------------------------===// + +/// A result which can be loaded. +/// +/// It is useful for objects that hold a state on disk and need to be +/// loaded frequently. +public final class LoadableResult { + /// The constructor closure for the value. + private let construct: () throws -> Value + + /// Create a loadable result. + public init(_ construct: @escaping () throws -> Value) { + self.construct = construct + } + + /// Load and return the result. + public func loadResult() -> Result { + Result(catching: { + try self.construct() + }) + } + + /// Load and return the value. + public func load() throws -> Value { + try self.loadResult().get() + } +} diff --git a/Sources/Workspace/ManagedDependency.swift b/Sources/Workspace/ManagedDependency.swift index 0fbf78c85a8..b771f37221c 100644 --- a/Sources/Workspace/ManagedDependency.swift +++ b/Sources/Workspace/ManagedDependency.swift @@ -196,7 +196,7 @@ extension Workspace { // When loading manifests in Workspace, there are cases where we must also compare the location // as it may attempt to load manifests for dependencies that have the same identity but from a different location - // (e.g. dependency is changed to a fork with the same identity) + // (e.g. dependency is changed to a fork with the same identity) public subscript(comparingLocation package: PackageReference) -> ManagedDependency? { if let dependency = self.dependencies[package.identity], dependency.packageRef.equalsIncludingLocation(package) { return dependency diff --git a/Sources/Workspace/FileSystemPackageContainer.swift b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift similarity index 96% rename from Sources/Workspace/FileSystemPackageContainer.swift rename to Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift index 8033394f268..9c5ffe11aba 100644 --- a/Sources/Workspace/FileSystemPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift @@ -27,6 +27,7 @@ import struct TSCUtility.Version public struct FileSystemPackageContainer: PackageContainer { public let package: PackageReference private let identityResolver: IdentityResolver + private let dependencyMapper: DependencyMapper private let manifestLoader: ManifestLoaderProtocol private let currentToolsVersion: ToolsVersion @@ -42,6 +43,7 @@ public struct FileSystemPackageContainer: PackageContainer { public init( package: PackageReference, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, manifestLoader: ManifestLoaderProtocol, currentToolsVersion: ToolsVersion, fileSystem: FileSystem, @@ -55,6 +57,7 @@ public struct FileSystemPackageContainer: PackageContainer { } self.package = package self.identityResolver = identityResolver + self.dependencyMapper = dependencyMapper self.manifestLoader = manifestLoader self.currentToolsVersion = currentToolsVersion self.fileSystem = fileSystem @@ -84,6 +87,7 @@ public struct FileSystemPackageContainer: PackageContainer { packageVersion: nil, currentToolsVersion: self.currentToolsVersion, identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, fileSystem: self.fileSystem, observabilityScope: self.observabilityScope, delegateQueue: .sharedConcurrent, diff --git a/Sources/Workspace/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift similarity index 98% rename from Sources/Workspace/RegistryPackageContainer.swift rename to Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 82c5bb756fc..fc5d451c8c4 100644 --- a/Sources/Workspace/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -26,6 +26,7 @@ public class RegistryPackageContainer: PackageContainer { private let registryClient: RegistryClient private let identityResolver: IdentityResolver + private let dependencyMapper: DependencyMapper private let manifestLoader: ManifestLoaderProtocol private let currentToolsVersion: ToolsVersion private let observabilityScope: ObservabilityScope @@ -39,6 +40,7 @@ public class RegistryPackageContainer: PackageContainer { public init( package: PackageReference, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, registryClient: RegistryClient, manifestLoader: ManifestLoaderProtocol, currentToolsVersion: ToolsVersion, @@ -46,6 +48,7 @@ public class RegistryPackageContainer: PackageContainer { ) { self.package = package self.identityResolver = identityResolver + self.dependencyMapper = dependencyMapper self.registryClient = registryClient self.manifestLoader = manifestLoader self.currentToolsVersion = currentToolsVersion @@ -154,6 +157,7 @@ public class RegistryPackageContainer: PackageContainer { packageVersion: (version: version, revision: nil), currentToolsVersion: self.currentToolsVersion, identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, fileSystem: result.fileSystem, observabilityScope: self.observabilityScope, delegateQueue: .sharedConcurrent, diff --git a/Sources/Workspace/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift similarity index 99% rename from Sources/Workspace/SourceControlPackageContainer.swift rename to Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index 06b73a7970d..6e634df9fd4 100644 --- a/Sources/Workspace/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -57,6 +57,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri private let repositorySpecifier: RepositorySpecifier private let repository: Repository private let identityResolver: IdentityResolver + private let dependencyMapper: DependencyMapper private let manifestLoader: ManifestLoaderProtocol private let currentToolsVersion: ToolsVersion private let fingerprintStorage: PackageFingerprintStorage? @@ -78,6 +79,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri init( package: PackageReference, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, repositorySpecifier: RepositorySpecifier, repository: Repository, manifestLoader: ManifestLoaderProtocol, @@ -88,6 +90,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri ) throws { self.package = package self.identityResolver = identityResolver + self.dependencyMapper = dependencyMapper self.repositorySpecifier = repositorySpecifier self.repository = repository self.manifestLoader = manifestLoader @@ -409,6 +412,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri packageVersion: (version: version, revision: revision), currentToolsVersion: self.currentToolsVersion, identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, fileSystem: fileSystem, observabilityScope: self.observabilityScope, delegateQueue: .sharedConcurrent, diff --git a/Sources/Workspace/Workspace+BinaryArtifacts.swift b/Sources/Workspace/Workspace+BinaryArtifacts.swift index e3bd82d2d32..d0f464b4c6b 100644 --- a/Sources/Workspace/Workspace+BinaryArtifacts.swift +++ b/Sources/Workspace/Workspace+BinaryArtifacts.swift @@ -12,9 +12,9 @@ import Basics import Foundation +import PackageLoading import PackageModel import SPMBuildCore -import PackageLoading import struct TSCBasic.ByteString import protocol TSCBasic.HashAlgorithm @@ -26,10 +26,16 @@ extension Workspace { public struct CustomBinaryArtifactsManager { let httpClient: LegacyHTTPClient? let archiver: Archiver? + let useCache: Bool? - public init(httpClient: LegacyHTTPClient? = .none, archiver: Archiver? = .none) { + public init( + httpClient: LegacyHTTPClient? = .none, + archiver: Archiver? = .none, + useCache: Bool? = .none + ) { self.httpClient = httpClient self.archiver = archiver + self.useCache = useCache } } @@ -43,6 +49,7 @@ extension Workspace { private let httpClient: LegacyHTTPClient private let archiver: Archiver private let checksumAlgorithm: HashAlgorithm + private let cachePath: AbsolutePath? private let delegate: Delegate? public init( @@ -50,6 +57,7 @@ extension Workspace { authorizationProvider: AuthorizationProvider?, hostToolchain: UserToolchain, checksumAlgorithm: HashAlgorithm, + cachePath: AbsolutePath?, customHTTPClient: LegacyHTTPClient?, customArchiver: Archiver?, delegate: Delegate? @@ -60,6 +68,7 @@ extension Workspace { self.checksumAlgorithm = checksumAlgorithm self.httpClient = customHTTPClient ?? LegacyHTTPClient() self.archiver = customArchiver ?? ZipArchiver(fileSystem: fileSystem) + self.cachePath = cachePath self.delegate = delegate } @@ -126,7 +135,7 @@ extension Workspace { return (local: localArtifacts, remote: remoteArtifacts) } - func download( + func fetch( _ artifacts: [RemoteArtifact], artifactsDirectory: AbsolutePath, observabilityScope: ObservabilityScope @@ -229,23 +238,11 @@ extension Workspace { } group.enter() - var headers = HTTPClientHeaders() - headers.add(name: "Accept", value: "application/octet-stream") - var request = LegacyHTTPClient.Request.download( - url: artifact.url, - headers: headers, - fileSystem: self.fileSystem, - destination: archivePath - ) - request.options.authorizationProvider = self.authorizationProvider?.httpAuthorizationHeader(for:) - request.options.retryStrategy = .exponentialBackoff(maxAttempts: 3, baseDelay: .milliseconds(50)) - request.options.validResponseCodes = [200] - - let downloadStart: DispatchTime = .now() - self.delegate?.willDownloadBinaryArtifact(from: artifact.url.absoluteString) - observabilityScope.emit(debug: "downloading \(artifact.url) to \(archivePath)") - self.httpClient.execute( - request, + let fetchStart: DispatchTime = .now() + self.fetch( + artifact: artifact, + destination: archivePath, + observabilityScope: observabilityScope, progress: { bytesDownloaded, totalBytesToDownload in self.delegate?.downloadingBinaryArtifact( from: artifact.url.absoluteString, @@ -253,13 +250,12 @@ extension Workspace { totalBytesToDownload: totalBytesToDownload ) }, - completion: { downloadResult in + completion: { fetchResult in defer { group.leave() } - // TODO: Use the same extraction logic for both remote and local archived artifacts. - switch downloadResult { - case .success: - + switch fetchResult { + case .success(let cached): + // TODO: Use the same extraction logic for both remote and local archived artifacts. group.enter() observabilityScope.emit(debug: "validating \(archivePath)") self.archiver.validate(path: archivePath, completion: { validationResult in @@ -381,8 +377,8 @@ extension Workspace { ) self.delegate?.didDownloadBinaryArtifact( from: artifact.url.absoluteString, - result: .success(artifactPath), - duration: downloadStart.distance(to: .now()) + result: .success((path: artifactPath, fromCache: cached)), + duration: fetchStart.distance(to: .now()) ) case .failure(let error): observabilityScope.emit(.remoteArtifactFailedExtraction( @@ -393,7 +389,7 @@ extension Workspace { self.delegate?.didDownloadBinaryArtifact( from: artifact.url.absoluteString, result: .failure(error), - duration: downloadStart.distance(to: .now()) + duration: fetchStart.distance(to: .now()) ) } @@ -409,7 +405,7 @@ extension Workspace { self.delegate?.didDownloadBinaryArtifact( from: artifact.url.absoluteString, result: .failure(error), - duration: downloadStart.distance(to: .now()) + duration: fetchStart.distance(to: .now()) ) } }) @@ -423,7 +419,7 @@ extension Workspace { self.delegate?.didDownloadBinaryArtifact( from: artifact.url.absoluteString, result: .failure(error), - duration: downloadStart.distance(to: .now()) + duration: fetchStart.distance(to: .now()) ) } } @@ -563,17 +559,116 @@ extension Workspace { try cancellableArchiver.cancel(deadline: deadline) } } + + private func fetch( + artifact: RemoteArtifact, + destination: AbsolutePath, + observabilityScope: ObservabilityScope, + progress: @escaping (Int64, Optional) -> Void, + completion: @escaping (Result) -> Void + ) { + // not using cache, download directly + guard let cachePath = self.cachePath else { + self.delegate?.willDownloadBinaryArtifact(from: artifact.url.absoluteString, fromCache: false) + return self.download( + artifact: artifact, + destination: destination, + observabilityScope: observabilityScope, + progress: progress, + completion: { result in + // not fetched from cache + completion(result.map{ _ in false }) + } + ) + } + + // initialize cache if necessary + do { + if !self.fileSystem.exists(cachePath) { + try self.fileSystem.createDirectory(cachePath, recursive: true) + } + } catch { + return completion(.failure(error)) + } + + + // try to fetch from cache, or download and cache + // / FIXME: use better escaping of URL + let cacheKey = artifact.url.absoluteString.spm_mangledToC99ExtendedIdentifier() + let cachedArtifactPath = cachePath.appending(cacheKey) + + if self.fileSystem.exists(cachedArtifactPath) { + observabilityScope.emit(debug: "copying cached binary artifact for \(artifact.url) from \(cachedArtifactPath)") + self.delegate?.willDownloadBinaryArtifact(from: artifact.url.absoluteString, fromCache: true) + return completion( + Result.init(catching: { + // copy from cache to destination + try self.fileSystem.copy(from: cachedArtifactPath, to: destination) + return true // fetched from cache + }) + ) + } + + // download to the cache + observabilityScope.emit(debug: "downloading binary artifact for \(artifact.url) to cached at \(cachedArtifactPath)") + self.download( + artifact: artifact, + destination: cachedArtifactPath, + observabilityScope: observabilityScope, + progress: progress, + completion: { result in + self.delegate?.willDownloadBinaryArtifact(from: artifact.url.absoluteString, fromCache: false) + completion(result.flatMap { + Result.init(catching: { + // copy from cache to destination + try self.fileSystem.copy(from: cachedArtifactPath, to: destination) + return false // not fetched from cache + }) + }) + } + ) + } + + private func download( + artifact: RemoteArtifact, + destination: AbsolutePath, + observabilityScope: ObservabilityScope, + progress: @escaping (Int64, Optional) -> Void, + completion: @escaping (Result) -> Void + ) { + observabilityScope.emit(debug: "downloading \(artifact.url) to \(destination)") + + var headers = HTTPClientHeaders() + headers.add(name: "Accept", value: "application/octet-stream") + var request = LegacyHTTPClient.Request.download( + url: artifact.url, + headers: headers, + fileSystem: self.fileSystem, + destination: destination + ) + request.options.authorizationProvider = self.authorizationProvider?.httpAuthorizationHeader(for:) + request.options.retryStrategy = .exponentialBackoff(maxAttempts: 3, baseDelay: .milliseconds(50)) + request.options.validResponseCodes = [200] + + self.httpClient.execute( + request, + progress: progress, + completion: { result in + completion(result.map{ _ in Void() }) + } + ) + } } } /// Delegate to notify clients about actions being performed by BinaryArtifactsDownloadsManage. public protocol BinaryArtifactsManagerDelegate { /// The workspace has started downloading a binary artifact. - func willDownloadBinaryArtifact(from url: String) + func willDownloadBinaryArtifact(from url: String, fromCache: Bool) /// The workspace has finished downloading a binary artifact. func didDownloadBinaryArtifact( from url: String, - result: Result, + result: Result<(path: AbsolutePath, fromCache: Bool), Error>, duration: DispatchTimeInterval ) /// The workspace is downloading a binary artifact. @@ -696,7 +791,8 @@ extension Workspace.BinaryArtifactsManager { _ = try decoder.decode(XCFrameworkMetadata.self, from: fileSystem.readFileContents(infoPlist)) return .xcframework } catch { - observabilityScope.emit(debug: "info.plist found in '\(path)' but failed to parse: \(error.interpolationDescription)") + observabilityScope + .emit(debug: "info.plist found in '\(path)' but failed to parse: \(error.interpolationDescription)") } } @@ -716,6 +812,156 @@ extension Workspace.BinaryArtifactsManager { } } +extension Workspace { + func updateBinaryArtifacts( + manifests: DependencyManifests, + addedOrUpdatedPackages: [PackageReference], + observabilityScope: ObservabilityScope + ) throws { + let manifestArtifacts = try self.binaryArtifactsManager.parseArtifacts( + from: manifests, + observabilityScope: observabilityScope + ) + + var artifactsToRemove: [ManagedArtifact] = [] + var artifactsToAdd: [ManagedArtifact] = [] + var artifactsToDownload: [BinaryArtifactsManager.RemoteArtifact] = [] + var artifactsToExtract: [ManagedArtifact] = [] + + for artifact in state.artifacts { + if !manifestArtifacts.local + .contains(where: { $0.packageRef == artifact.packageRef && $0.targetName == artifact.targetName }) && + !manifestArtifacts.remote + .contains(where: { $0.packageRef == artifact.packageRef && $0.targetName == artifact.targetName }) + { + artifactsToRemove.append(artifact) + } + } + + for artifact in manifestArtifacts.local { + let existingArtifact = self.state.artifacts[ + packageIdentity: artifact.packageRef.identity, + targetName: artifact.targetName + ] + + if artifact.path.extension?.lowercased() == "zip" { + // If we already have an artifact that was extracted from an archive with the same checksum, + // we don't need to extract the artifact again. + if case .local(let existingChecksum) = existingArtifact?.source, + try existingChecksum == (self.binaryArtifactsManager.checksum(forBinaryArtifactAt: artifact.path)) + { + continue + } + + artifactsToExtract.append(artifact) + } else { + guard let _ = try BinaryArtifactsManager.deriveBinaryArtifact( + fileSystem: self.fileSystem, + path: artifact.path, + observabilityScope: observabilityScope + ) else { + observabilityScope.emit(.localArtifactNotFound( + artifactPath: artifact.path, + targetName: artifact.targetName + )) + continue + } + artifactsToAdd.append(artifact) + } + + if let existingArtifact, isAtArtifactsDirectory(existingArtifact) { + // Remove the old extracted artifact, be it local archived or remote one. + artifactsToRemove.append(existingArtifact) + } + } + + for artifact in manifestArtifacts.remote { + let existingArtifact = self.state.artifacts[ + packageIdentity: artifact.packageRef.identity, + targetName: artifact.targetName + ] + + if let existingArtifact { + if case .remote(let existingURL, let existingChecksum) = existingArtifact.source { + // If we already have an artifact with the same checksum, we don't need to download it again. + if artifact.checksum == existingChecksum { + continue + } + + let urlChanged = artifact.url != URL(string: existingURL) + // If the checksum is different but the package wasn't updated, this is a security risk. + if !urlChanged && !addedOrUpdatedPackages.contains(artifact.packageRef) { + observabilityScope.emit(.artifactChecksumChanged(targetName: artifact.targetName)) + continue + } + } + + if isAtArtifactsDirectory(existingArtifact) { + // Remove the old extracted artifact, be it local archived or remote one. + artifactsToRemove.append(existingArtifact) + } + } + + artifactsToDownload.append(artifact) + } + + // Remove the artifacts and directories which are not needed anymore. + observabilityScope.trap { + for artifact in artifactsToRemove { + state.artifacts.remove(packageIdentity: artifact.packageRef.identity, targetName: artifact.targetName) + + if isAtArtifactsDirectory(artifact) { + try fileSystem.removeFileTree(artifact.path) + } + } + + for directory in try fileSystem.getDirectoryContents(self.location.artifactsDirectory) { + let directoryPath = self.location.artifactsDirectory.appending(component: directory) + if try fileSystem.isDirectory(directoryPath) && fileSystem.getDirectoryContents(directoryPath).isEmpty { + try fileSystem.removeFileTree(directoryPath) + } + } + } + + guard !observabilityScope.errorsReported else { + throw Diagnostics.fatalError + } + + // Download the artifacts + let downloadedArtifacts = try self.binaryArtifactsManager.fetch( + artifactsToDownload, + artifactsDirectory: self.location.artifactsDirectory, + observabilityScope: observabilityScope + ) + artifactsToAdd.append(contentsOf: downloadedArtifacts) + + // Extract the local archived artifacts + let extractedLocalArtifacts = try self.binaryArtifactsManager.extract( + artifactsToExtract, + artifactsDirectory: self.location.artifactsDirectory, + observabilityScope: observabilityScope + ) + artifactsToAdd.append(contentsOf: extractedLocalArtifacts) + + // Add the new artifacts + for artifact in artifactsToAdd { + self.state.artifacts.add(artifact) + } + + guard !observabilityScope.errorsReported else { + throw Diagnostics.fatalError + } + + observabilityScope.trap { + try self.state.save() + } + + func isAtArtifactsDirectory(_ artifact: ManagedArtifact) -> Bool { + artifact.path.isDescendant(of: self.location.artifactsDirectory) + } + } +} + extension FileSystem { // helper to decide if an archive directory would benefit from stripping first level fileprivate func shouldStripFirstLevel( diff --git a/Sources/Workspace/Workspace+Configuration.swift b/Sources/Workspace/Workspace+Configuration.swift index 2843339326e..103a0f13aeb 100644 --- a/Sources/Workspace/Workspace+Configuration.swift +++ b/Sources/Workspace/Workspace+Configuration.swift @@ -145,6 +145,11 @@ extension Workspace { self.sharedCacheDirectory.map { $0.appending(components: "registry", "downloads") } } + /// Path to the shared repositories cache. + public var sharedBinaryArtifactsCacheDirectory: AbsolutePath? { + self.sharedCacheDirectory.map { $0.appending("artifacts") } + } + /// Create a new workspace location. /// /// - Parameters: @@ -179,14 +184,14 @@ extension Workspace { /// - Parameters: /// - rootPath: Path to the root of the package, from which other locations can be derived. public init(forRootPackage rootPath: AbsolutePath, fileSystem: FileSystem) throws { - self.init( + try self.init( scratchDirectory: DefaultLocations.scratchDirectory(forRootPackage: rootPath), editsDirectory: DefaultLocations.editsDirectory(forRootPackage: rootPath), resolvedVersionsFile: DefaultLocations.resolvedVersionsFile(forRootPackage: rootPath), localConfigurationDirectory: DefaultLocations.configurationDirectory(forRootPackage: rootPath), - sharedConfigurationDirectory: try fileSystem.swiftPMConfigurationDirectory, - sharedSecurityDirectory: try fileSystem.swiftPMSecurityDirectory, - sharedCacheDirectory: try fileSystem.swiftPMCacheDirectory + sharedConfigurationDirectory: fileSystem.swiftPMConfigurationDirectory, + sharedSecurityDirectory: fileSystem.swiftPMSecurityDirectory, + sharedCacheDirectory: fileSystem.swiftPMCacheDirectory ) } } @@ -208,7 +213,7 @@ extension Workspace { } public static func resolvedVersionsFile(forRootPackage rootPath: AbsolutePath) -> AbsolutePath { - rootPath.appending(Self.resolvedFileName) + rootPath.appending(self.resolvedFileName) } public static func configurationDirectory(forRootPackage rootPath: AbsolutePath) -> AbsolutePath { @@ -300,7 +305,7 @@ extension Workspace.Configuration { guard fileSystem.exists(path) else { throw StringError("Did not find netrc file at \(path).") } - providers.append(try NetrcAuthorizationProvider(path: path, fileSystem: fileSystem)) + try providers.append(NetrcAuthorizationProvider(path: path, fileSystem: fileSystem)) case .user: // user .netrc file (most typical) let userHomePath = try fileSystem.homeDirectory.appending(".netrc") @@ -360,7 +365,7 @@ extension Workspace.Configuration { guard fileSystem.exists(path) else { throw StringError("did not find netrc file at \(path)") } - providers.append(try NetrcAuthorizationProvider(path: path, fileSystem: fileSystem)) + try providers.append(NetrcAuthorizationProvider(path: path, fileSystem: fileSystem)) case .user: let userHomePath = try fileSystem.homeDirectory.appending(".netrc") // Add user .netrc file unless we don't have access @@ -462,7 +467,7 @@ extension Workspace.Configuration { .map { .init(path: $0, fileSystem: fileSystem, deleteWhenEmpty: false) } self.fileSystem = fileSystem // computes the initial mirrors - self._mirrors = DependencyMirrors() + self._mirrors = try DependencyMirrors() try self.computeMirrors() } @@ -492,13 +497,13 @@ extension Workspace.Configuration { // prefer local mirrors to shared ones let local = try self.localMirrors.get() if !local.isEmpty { - self._mirrors.append(contentsOf: local) + try self._mirrors.append(contentsOf: local) return } // use shared if local was not found or empty if let shared = try self.sharedMirrors?.get(), !shared.isEmpty { - self._mirrors.append(contentsOf: shared) + try self._mirrors.append(contentsOf: shared) } } } @@ -520,10 +525,10 @@ extension Workspace.Configuration { /// The mirrors in this configuration public func get() throws -> DependencyMirrors { guard self.fileSystem.exists(self.path) else { - return DependencyMirrors() + return try DependencyMirrors() } return try self.fileSystem.withLock(on: self.path.parentDirectory, type: .shared) { - return DependencyMirrors(try Self.load(self.path, fileSystem: self.fileSystem)) + try DependencyMirrors(Self.load(self.path, fileSystem: self.fileSystem)) } } @@ -534,8 +539,8 @@ extension Workspace.Configuration { try self.fileSystem.createDirectory(self.path.parentDirectory, recursive: true) } return try self.fileSystem.withLock(on: self.path.parentDirectory, type: .exclusive) { - let mirrors = DependencyMirrors(try Self.load(self.path, fileSystem: self.fileSystem)) - var updatedMirrors = DependencyMirrors(mirrors.mapping) + let mirrors = try DependencyMirrors(Self.load(self.path, fileSystem: self.fileSystem)) + var updatedMirrors = try DependencyMirrors(mirrors.mapping) try handler(&updatedMirrors) if updatedMirrors != mirrors { try Self.save( @@ -773,7 +778,7 @@ public struct WorkspaceConfiguration { public var createREPLProduct: Bool /// Whether or not there should be import restrictions applied when loading manifests - public var restrictImports: (startingToolsVersion: ToolsVersion, allowedImports: [String])? + public var manifestImportRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])? public init( skipDependenciesUpdates: Bool, @@ -787,7 +792,7 @@ public struct WorkspaceConfiguration { skipSignatureValidation: Bool, sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation, defaultRegistry: Registry?, - restrictImports: (startingToolsVersion: ToolsVersion, allowedImports: [String])? + manifestImportRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])? ) { self.skipDependenciesUpdates = skipDependenciesUpdates self.prefetchBasedOnResolvedFile = prefetchBasedOnResolvedFile @@ -800,7 +805,7 @@ public struct WorkspaceConfiguration { self.skipSignatureValidation = skipSignatureValidation self.sourceControlToRegistryDependencyTransformation = sourceControlToRegistryDependencyTransformation self.defaultRegistry = defaultRegistry - self.restrictImports = restrictImports + self.manifestImportRestrictions = manifestImportRestrictions } /// Default instance of WorkspaceConfiguration @@ -817,7 +822,7 @@ public struct WorkspaceConfiguration { skipSignatureValidation: false, sourceControlToRegistryDependencyTransformation: .disabled, defaultRegistry: .none, - restrictImports: .none + manifestImportRestrictions: .none ) } diff --git a/Sources/Workspace/Workspace+Delegation.swift b/Sources/Workspace/Workspace+Delegation.swift new file mode 100644 index 00000000000..dac189887fd --- /dev/null +++ b/Sources/Workspace/Workspace+Delegation.swift @@ -0,0 +1,425 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.Diagnostic +import enum Dispatch.DispatchTimeInterval +import struct Foundation.URL +import class PackageLoading.ManifestLoader +import class PackageModel.Manifest +import struct PackageModel.PackageIdentity +import struct PackageModel.PackageReference +import struct PackageModel.Registry +import class PackageRegistry.RegistryClient +import class PackageRegistry.RegistryDownloadsManager +import class SourceControl.RepositoryManager +import struct SourceControl.RepositorySpecifier +import struct TSCUtility.Version + +/// The delegate interface used by the workspace to report status information. +public protocol WorkspaceDelegate: AnyObject { + /// The workspace is about to load a package manifest (which might be in the cache, or might need to be parsed). + /// Note that this does not include speculative loading of manifests that may occur during + /// dependency resolution; rather, it includes only the final manifest loading that happens after a particular + /// package version has been checked out into a working directory. + func willLoadManifest( + packageIdentity: PackageIdentity, + packagePath: AbsolutePath, + url: String, + version: Version?, + packageKind: PackageReference.Kind + ) + /// The workspace has loaded a package manifest, either successfully or not. The manifest is nil if an error occurs, + /// in which case there will also be at least one error in the list of diagnostics (there may be warnings even if a + /// manifest is loaded successfully). + func didLoadManifest( + packageIdentity: PackageIdentity, + packagePath: AbsolutePath, + url: String, + version: Version?, + packageKind: PackageReference.Kind, + manifest: Manifest?, + diagnostics: [Diagnostic], + duration: DispatchTimeInterval + ) + + /// The workspace is about to compile a package manifest, as reported by the assigned manifest loader. this happens + /// for non-cached manifests + func willCompileManifest(packageIdentity: PackageIdentity, packageLocation: String) + /// The workspace successfully compiled a package manifest, as reported by the assigned manifest loader. this + /// happens for non-cached manifests + func didCompileManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) + + /// The workspace is about to evaluate (execute) a compiled package manifest, as reported by the assigned manifest + /// loader. this happens for non-cached manifests + func willEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String) + /// The workspace successfully evaluated (executed) a compiled package manifest, as reported by the assigned + /// manifest loader. this happens for non-cached manifests + func didEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) + + /// The workspace has started fetching this package. + func willFetchPackage(package: PackageIdentity, packageLocation: String?, fetchDetails: PackageFetchDetails) + /// The workspace has finished fetching this package. + func didFetchPackage( + package: PackageIdentity, + packageLocation: String?, + result: Result, + duration: DispatchTimeInterval + ) + /// Called every time the progress of the package fetch operation updates. + func fetchingPackage(package: PackageIdentity, packageLocation: String?, progress: Int64, total: Int64?) + + /// The workspace has started updating this repository. + func willUpdateRepository(package: PackageIdentity, repository url: String) + /// The workspace has finished updating this repository. + func didUpdateRepository(package: PackageIdentity, repository url: String, duration: DispatchTimeInterval) + + /// The workspace has finished updating and all the dependencies are already up-to-date. + func dependenciesUpToDate() + + /// The workspace is about to clone a repository from the local cache to a working directory. + func willCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath) + /// The workspace has cloned a repository from the local cache to a working directory. The error indicates whether + /// the operation failed or succeeded. + func didCreateWorkingCopy( + package: PackageIdentity, + repository url: String, + at path: AbsolutePath, + duration: DispatchTimeInterval + ) + + /// The workspace is about to check out a particular revision of a working directory. + func willCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath) + /// The workspace has checked out a particular revision of a working directory. The error indicates whether the + /// operation failed or succeeded. + func didCheckOut( + package: PackageIdentity, + repository url: String, + revision: String, + at path: AbsolutePath, + duration: DispatchTimeInterval + ) + + /// The workspace is removing this repository because it is no longer needed. + func removing(package: PackageIdentity, packageLocation: String?) + + /// Called when the resolver is about to be run. + func willResolveDependencies(reason: WorkspaceResolveReason) + + /// Called when the resolver begins to be compute the version for the repository. + func willComputeVersion(package: PackageIdentity, location: String) + /// Called when the resolver finished computing the version for the repository. + func didComputeVersion(package: PackageIdentity, location: String, version: String, duration: DispatchTimeInterval) + + /// Called when the Package.resolved file is changed *outside* of libSwiftPM operations. + /// + /// This is only fired when activated using Workspace's watchResolvedFile() method. + func resolvedFileChanged() + + /// The workspace has started downloading a binary artifact. + func willDownloadBinaryArtifact(from url: String, fromCache: Bool) + /// The workspace has finished downloading a binary artifact. + func didDownloadBinaryArtifact( + from url: String, + result: Result<(path: AbsolutePath, fromCache: Bool), Error>, + duration: DispatchTimeInterval + ) + /// The workspace is downloading a binary artifact. + func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) + /// The workspace finished downloading all binary artifacts. + func didDownloadAllBinaryArtifacts() + + // handlers for unsigned and untrusted registry based dependencies + func onUnsignedRegistryPackage( + registryURL: URL, + package: PackageModel.PackageIdentity, + version: TSCUtility.Version, + completion: (Bool) -> Void + ) + func onUntrustedRegistryPackage( + registryURL: URL, + package: PackageModel.PackageIdentity, + version: TSCUtility.Version, + completion: (Bool) -> Void + ) + + /// The workspace has started updating dependencies + func willUpdateDependencies() + /// The workspace has finished updating dependencies + func didUpdateDependencies(duration: DispatchTimeInterval) + + /// The workspace has started resolving dependencies + func willResolveDependencies() + /// The workspace has finished resolving dependencies + func didResolveDependencies(duration: DispatchTimeInterval) + + /// The workspace has started loading the graph to memory + func willLoadGraph() + /// The workspace has finished loading the graph to memory + func didLoadGraph(duration: DispatchTimeInterval) +} + +// FIXME: default implementation until the feature is stable, at which point we should remove this and force the clients to implement +extension WorkspaceDelegate { + public func onUnsignedRegistryPackage( + registryURL: URL, + package: PackageModel.PackageIdentity, + version: TSCUtility.Version, + completion: (Bool) -> Void + ) { + // true == continue resolution + // false == stop dependency resolution + completion(true) + } + + public func onUntrustedRegistryPackage( + registryURL: URL, + package: PackageModel.PackageIdentity, + version: TSCUtility.Version, + completion: (Bool) -> Void + ) { + // true == continue resolution + // false == stop dependency resolution + completion(true) + } +} + +struct WorkspaceManifestLoaderDelegate: ManifestLoader.Delegate { + private weak var workspaceDelegate: Workspace.Delegate? + + init(workspaceDelegate: Workspace.Delegate) { + self.workspaceDelegate = workspaceDelegate + } + + func willLoad(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // handled by workspace directly + } + + func didLoad( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath, + duration: DispatchTimeInterval + ) { + // handled by workspace directly + } + + func willParse(packageIdentity: PackageIdentity, packageLocation: String) { + // noop + } + + func didParse(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + // noop + } + + func willCompile(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + self.workspaceDelegate?.willCompileManifest(packageIdentity: packageIdentity, packageLocation: packageLocation) + } + + func didCompile( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath, + duration: DispatchTimeInterval + ) { + self.workspaceDelegate?.didCompileManifest( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + duration: duration + ) + } + + func willEvaluate(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + self.workspaceDelegate?.willCompileManifest(packageIdentity: packageIdentity, packageLocation: packageLocation) + } + + func didEvaluate( + packageIdentity: PackageIdentity, + packageLocation: String, + manifestPath: AbsolutePath, + duration: DispatchTimeInterval + ) { + self.workspaceDelegate?.didEvaluateManifest( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + duration: duration + ) + } +} + +struct WorkspaceRepositoryManagerDelegate: RepositoryManager.Delegate { + private weak var workspaceDelegate: Workspace.Delegate? + + init(workspaceDelegate: Workspace.Delegate) { + self.workspaceDelegate = workspaceDelegate + } + + func willFetch(package: PackageIdentity, repository: RepositorySpecifier, details: RepositoryManager.FetchDetails) { + self.workspaceDelegate?.willFetchPackage( + package: package, + packageLocation: repository.location.description, + fetchDetails: PackageFetchDetails(fromCache: details.fromCache, updatedCache: details.updatedCache) + ) + } + + func fetching( + package: PackageIdentity, + repository: RepositorySpecifier, + objectsFetched: Int, + totalObjectsToFetch: Int + ) { + self.workspaceDelegate?.fetchingPackage( + package: package, + packageLocation: repository.location.description, + progress: Int64(objectsFetched), + total: Int64(totalObjectsToFetch) + ) + } + + func didFetch( + package: PackageIdentity, + repository: RepositorySpecifier, + result: Result, + duration: DispatchTimeInterval + ) { + self.workspaceDelegate?.didFetchPackage( + package: package, + packageLocation: repository.location.description, + result: result.map { PackageFetchDetails(fromCache: $0.fromCache, updatedCache: $0.updatedCache) }, + duration: duration + ) + } + + func willUpdate(package: PackageIdentity, repository: RepositorySpecifier) { + self.workspaceDelegate?.willUpdateRepository(package: package, repository: repository.location.description) + } + + func didUpdate(package: PackageIdentity, repository: RepositorySpecifier, duration: DispatchTimeInterval) { + self.workspaceDelegate?.didUpdateRepository( + package: package, + repository: repository.location.description, + duration: duration + ) + } +} + +struct WorkspaceRegistryDownloadsManagerDelegate: RegistryDownloadsManager.Delegate { + private weak var workspaceDelegate: Workspace.Delegate? + + init(workspaceDelegate: Workspace.Delegate) { + self.workspaceDelegate = workspaceDelegate + } + + func willFetch(package: PackageIdentity, version: Version, fetchDetails: RegistryDownloadsManager.FetchDetails) { + self.workspaceDelegate?.willFetchPackage( + package: package, + packageLocation: .none, + fetchDetails: PackageFetchDetails( + fromCache: fetchDetails.fromCache, + updatedCache: fetchDetails.updatedCache + ) + ) + } + + func didFetch( + package: PackageIdentity, + version: Version, + result: Result, + duration: DispatchTimeInterval + ) { + self.workspaceDelegate?.didFetchPackage( + package: package, + packageLocation: .none, + result: result.map { PackageFetchDetails(fromCache: $0.fromCache, updatedCache: $0.updatedCache) }, + duration: duration + ) + } + + func fetching(package: PackageIdentity, version: Version, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { + self.workspaceDelegate?.fetchingPackage( + package: package, + packageLocation: .none, + progress: bytesDownloaded, + total: totalBytesToDownload + ) + } +} + +struct WorkspaceRegistryClientDelegate: RegistryClient.Delegate { + private weak var workspaceDelegate: Workspace.Delegate? + + init(workspaceDelegate: Workspace.Delegate?) { + self.workspaceDelegate = workspaceDelegate + } + + func onUnsigned(registry: Registry, package: PackageIdentity, version: Version, completion: (Bool) -> Void) { + if let delegate = self.workspaceDelegate { + delegate.onUnsignedRegistryPackage( + registryURL: registry.url, + package: package, + version: version, + completion: completion + ) + } else { + // true == continue resolution + // false == stop dependency resolution + completion(true) + } + } + + func onUntrusted(registry: Registry, package: PackageIdentity, version: Version, completion: (Bool) -> Void) { + if let delegate = self.workspaceDelegate { + delegate.onUntrustedRegistryPackage( + registryURL: registry.url, + package: package, + version: version, + completion: completion + ) + } else { + // true == continue resolution + // false == stop dependency resolution + completion(true) + } + } +} + +struct WorkspaceBinaryArtifactsManagerDelegate: Workspace.BinaryArtifactsManager.Delegate { + private weak var workspaceDelegate: Workspace.Delegate? + + init(workspaceDelegate: Workspace.Delegate) { + self.workspaceDelegate = workspaceDelegate + } + + func willDownloadBinaryArtifact(from url: String, fromCache: Bool) { + self.workspaceDelegate?.willDownloadBinaryArtifact(from: url, fromCache: fromCache) + } + + func didDownloadBinaryArtifact( + from url: String, + result: Result<(path: AbsolutePath, fromCache: Bool), Error>, + duration: DispatchTimeInterval + ) { + self.workspaceDelegate?.didDownloadBinaryArtifact(from: url, result: result, duration: duration) + } + + func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { + self.workspaceDelegate?.downloadingBinaryArtifact( + from: url, + bytesDownloaded: bytesDownloaded, + totalBytesToDownload: totalBytesToDownload + ) + } + + func didDownloadAllBinaryArtifacts() { + self.workspaceDelegate?.didDownloadAllBinaryArtifacts() + } +} diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift new file mode 100644 index 00000000000..7c62fc49fcf --- /dev/null +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -0,0 +1,1219 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.InternalError +import class Basics.ObservabilityScope +import func Basics.os_signpost +import struct Basics.RelativePath +import enum Basics.SignpostName +import func Basics.temp_await +import class Basics.ThreadSafeKeyValueStore +import class Dispatch.DispatchGroup +import struct Dispatch.DispatchTime +import enum Dispatch.DispatchTimeInterval +import struct PackageGraph.Assignment +import enum PackageGraph.BoundVersion +import enum PackageGraph.ContainerUpdateStrategy +import protocol PackageGraph.CustomPackageContainer +import struct PackageGraph.DependencyResolverBinding +import protocol PackageGraph.DependencyResolverDelegate +import struct PackageGraph.Incompatibility +import struct PackageGraph.MultiplexResolverDelegate +import struct PackageGraph.ObservabilityDependencyResolverDelegate +import struct PackageGraph.PackageContainerConstraint +import struct PackageGraph.PackageGraphRoot +import struct PackageGraph.PackageGraphRootInput +import class PackageGraph.PinsStore +import struct PackageGraph.PubGrubDependencyResolver +import struct PackageGraph.Term +import class PackageLoading.ManifestLoader +import enum PackageModel.PackageDependency +import struct PackageModel.PackageIdentity +import struct PackageModel.PackageReference +import enum PackageModel.ProductFilter +import struct PackageModel.ToolsVersion +import struct SourceControl.Revision +import struct TSCUtility.Version + +extension Workspace { + enum ResolvedFileStrategy { + case lockFile + case update(forceResolution: Bool) + case bestEffort + } + + func _updateDependencies( + root: PackageGraphRootInput, + packages: [String] = [], + dryRun: Bool = false, + observabilityScope: ObservabilityScope + ) throws -> [(PackageReference, Workspace.PackageStateChange)]? { + let start = DispatchTime.now() + self.delegate?.willUpdateDependencies() + defer { + self.delegate?.didUpdateDependencies(duration: start.distance(to: .now())) + } + + // Create cache directories. + self.createCacheDirectories(observabilityScope: observabilityScope) + + // FIXME: this should not block + // Load the root manifests and currently checked out manifests. + let rootManifests = try temp_await { self.loadRootManifests( + packages: root.packages, + observabilityScope: observabilityScope, + completion: $0 + ) } + let rootManifestsMinimumToolsVersion = rootManifests.values.map(\.toolsVersion).min() ?? ToolsVersion.current + let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) + + // Load the current manifests. + let graphRoot = PackageGraphRoot( + input: root, + manifests: rootManifests, + dependencyMapper: self.dependencyMapper, + observabilityScope: observabilityScope + ) + let currentManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) + + // Abort if we're unable to load the pinsStore or have any diagnostics. + guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }) else { return nil } + + // Ensure we don't have any error at this point. + guard !observabilityScope.errorsReported else { + return nil + } + + // Add unversioned constraints for edited packages. + var updateConstraints = currentManifests.editedPackagesConstraints + + // Create constraints based on root manifest and pins for the update resolution. + updateConstraints += try graphRoot.constraints() + + let pins: PinsStore.Pins + if packages.isEmpty { + // No input packages so we have to do a full update. Set pins map to empty. + pins = [:] + } else { + // We have input packages so we have to partially update the package graph. Remove + // the pins for the input packages so only those packages are updated. + pins = pinsStore.pins + .filter { + !packages.contains($0.value.packageRef.identity.description) && !packages + .contains($0.value.packageRef.deprecatedName) + } + } + + // Resolve the dependencies. + let resolver = try self.createResolver(pins: pins, observabilityScope: observabilityScope) + self.activeResolver = resolver + + let updateResults = self.resolveDependencies( + resolver: resolver, + constraints: updateConstraints, + observabilityScope: observabilityScope + ) + + // Reset the active resolver. + self.activeResolver = nil + + guard !observabilityScope.errorsReported else { + return nil + } + + if dryRun { + return observabilityScope.trap { + try self.computePackageStateChanges( + root: graphRoot, + resolvedDependencies: updateResults, + updateBranches: true, + observabilityScope: observabilityScope + ) + } + } + + // Update the checkouts based on new dependency resolution. + let packageStateChanges = self.updateDependenciesCheckouts( + root: graphRoot, + updateResults: updateResults, + updateBranches: true, + observabilityScope: observabilityScope + ) + guard !observabilityScope.errorsReported else { + return nil + } + + // Load the updated manifests. + let updatedDependencyManifests = try self.loadDependencyManifests( + root: graphRoot, + observabilityScope: observabilityScope + ) + // If we have missing packages, something is fundamentally wrong with the resolution of the graph + let stillMissingPackages = try updatedDependencyManifests.missingPackages + guard stillMissingPackages.isEmpty else { + observabilityScope.emit(.exhaustedAttempts(missing: stillMissingPackages)) + return nil + } + + // Update the resolved file. + try self.saveResolvedFile( + pinsStore: pinsStore, + dependencyManifests: updatedDependencyManifests, + originHash: resolvedFileOriginHash, + rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, + observabilityScope: observabilityScope + ) + + // Update the binary target artifacts. + let addedOrUpdatedPackages = packageStateChanges.compactMap { $0.1.isAddedOrUpdated ? $0.0 : nil } + try self.updateBinaryArtifacts( + manifests: updatedDependencyManifests, + addedOrUpdatedPackages: addedOrUpdatedPackages, + observabilityScope: observabilityScope + ) + + return packageStateChanges + } + + @discardableResult + func _resolve( + root: PackageGraphRootInput, + explicitProduct: String?, + resolvedFileStrategy: ResolvedFileStrategy, + observabilityScope: ObservabilityScope + ) throws -> DependencyManifests { + let start = DispatchTime.now() + self.delegate?.willResolveDependencies() + defer { + self.delegate?.didResolveDependencies(duration: start.distance(to: .now())) + } + + switch resolvedFileStrategy { + case .lockFile: + observabilityScope.emit(info: "using '\(self.location.resolvedVersionsFile.basename)' file as lock file") + return try self._resolveBasedOnResolvedVersionsFile( + root: root, + explicitProduct: explicitProduct, + observabilityScope: observabilityScope + ) + case .update(let forceResolution): + return try resolveAndUpdateResolvedFile(forceResolution: forceResolution) + case .bestEffort: + guard !self.state.dependencies.hasEditedDependencies() else { + return try resolveAndUpdateResolvedFile(forceResolution: false) + } + guard self.fileSystem.exists(self.location.resolvedVersionsFile) else { + return try resolveAndUpdateResolvedFile(forceResolution: false) + } + + guard let pinsStore = try? self.pinsStore.load(), let storedHash = pinsStore.originHash else { + observabilityScope + .emit( + debug: "'\(self.location.resolvedVersionsFile.basename)' origin hash is missing. resolving and updating accordingly" + ) + return try resolveAndUpdateResolvedFile(forceResolution: false) + } + + let currentHash = try self.computeResolvedFileOriginHash(root: root) + guard storedHash == currentHash else { + observabilityScope + .emit( + debug: "'\(self.location.resolvedVersionsFile.basename)' origin hash does do not match manifest dependencies. resolving and updating accordingly" + ) + return try resolveAndUpdateResolvedFile(forceResolution: false) + } + + observabilityScope + .emit( + debug: "'\(self.location.resolvedVersionsFile.basename)' origin hash matches manifest dependencies, attempting resolution based on this file" + ) + let (manifests, precomputationResult) = try self.tryResolveBasedOnResolvedVersionsFile( + root: root, + explicitProduct: explicitProduct, + observabilityScope: observabilityScope + ) + switch precomputationResult { + case .notRequired: + return manifests + case .required(reason: .errorsPreviouslyReported): + return manifests + case .required(let reason): + // FIXME: ideally this is not done based on a side-effect + let reasonString = Self.format(workspaceResolveReason: reason) + observabilityScope + .emit( + debug: "resolution based on '\(self.location.resolvedVersionsFile.basename)' could not be completed because \(reasonString). resolving and updating accordingly" + ) + return try resolveAndUpdateResolvedFile(forceResolution: false) + } + } + + func resolveAndUpdateResolvedFile(forceResolution: Bool) throws -> DependencyManifests { + observabilityScope.emit(debug: "resolving and updating '\(self.location.resolvedVersionsFile.basename)'") + return try self.resolveAndUpdateResolvedFile( + root: root, + explicitProduct: explicitProduct, + forceResolution: forceResolution, + constraints: [], + observabilityScope: observabilityScope + ) + } + } + + private func computeResolvedFileOriginHash(root: PackageGraphRootInput) throws -> String { + var content = try root.packages.reduce(into: "") { partial, element in + let path = try ManifestLoader.findManifest( + packagePath: element, + fileSystem: self.fileSystem, + currentToolsVersion: self.currentToolsVersion + ) + try partial.append(self.fileSystem.readFileContents(path)) + } + content += root.dependencies.reduce(into: "") { partial, element in + partial += element.locationString + } + return content.sha256Checksum + } + + @discardableResult + func _resolveBasedOnResolvedVersionsFile( + root: PackageGraphRootInput, + explicitProduct: String?, + observabilityScope: ObservabilityScope + ) throws -> DependencyManifests { + let (manifests, precomputationResult) = try self.tryResolveBasedOnResolvedVersionsFile( + root: root, + explicitProduct: explicitProduct, + observabilityScope: observabilityScope + ) + switch precomputationResult { + case .notRequired: + return manifests + case .required(reason: .errorsPreviouslyReported): + return manifests + case .required(let reason): + // FIXME: ideally this is not done based on a side-effect + let reasonString = Self.format(workspaceResolveReason: reason) + if !self.fileSystem.exists(self.location.resolvedVersionsFile) { + observabilityScope + .emit( + error: "a resolved file is required when automatic dependency resolution is disabled and should be placed at \(self.location.resolvedVersionsFile.pathString). \(reasonString)" + ) + } else { + observabilityScope + .emit( + error: "an out-of-date resolved file was detected at \(self.location.resolvedVersionsFile.pathString), which is not allowed when automatic dependency resolution is disabled; please make sure to update the file to reflect the changes in dependencies. \(reasonString)" + ) + } + return manifests + } + } + + /// Resolves the dependencies according to the entries present in the Package.resolved file. + /// + /// This method bypasses the dependency resolution and resolves dependencies + /// according to the information in the resolved file. + fileprivate func tryResolveBasedOnResolvedVersionsFile( + root: PackageGraphRootInput, + explicitProduct: String?, + observabilityScope: ObservabilityScope + ) throws -> (DependencyManifests, ResolutionPrecomputationResult) { + // Ensure the cache path exists. + self.createCacheDirectories(observabilityScope: observabilityScope) + + // FIXME: this should not block + let rootManifests = try temp_await { self.loadRootManifests( + packages: root.packages, + observabilityScope: observabilityScope, + completion: $0 + ) } + let graphRoot = PackageGraphRoot( + input: root, + manifests: rootManifests, + explicitProduct: explicitProduct, + dependencyMapper: self.dependencyMapper, + observabilityScope: observabilityScope + ) + + // Load the pins store or abort now. + guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }), + !observabilityScope.errorsReported + else { + return try ( + self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope), + .notRequired + ) + } + + // Request all the containers to fetch them in parallel. + // + // We just request the packages here, repository manager will + // automatically manage the parallelism. + let group = DispatchGroup() + for pin in pinsStore.pins.values { + group.enter() + let observabilityScope = observabilityScope.makeChildScope( + description: "requesting package containers", + metadata: pin.packageRef.diagnosticsMetadata + ) + + let updateStrategy: ContainerUpdateStrategy = { + if self.configuration.skipDependenciesUpdates { + return .never + } else { + switch pin.state { + case .branch(_, let revision): + return .ifNeeded(revision: revision) + case .revision(let revision): + return .ifNeeded(revision: revision) + case .version(_, .some(let revision)): + return .ifNeeded(revision: revision) + case .version(_, .none): + return .always + } + } + }() + + self.packageContainerProvider.getContainer( + for: pin.packageRef, + updateStrategy: updateStrategy, + observabilityScope: observabilityScope, + on: .sharedConcurrent, + completion: { _ in group.leave() } + ) + } + group.wait() + + // Compute the pins that we need to actually clone. + // + // We require cloning if there is no checkout or if the checkout doesn't + // match with the pin. + let requiredPins = pinsStore.pins.values.filter { pin in + // also compare the location in case it has changed + guard let dependency = state.dependencies[comparingLocation: pin.packageRef] else { + return true + } + switch dependency.state { + case .sourceControlCheckout(let checkoutState): + return !pin.state.equals(checkoutState) + case .registryDownload(let version): + return !pin.state.equals(version) + case .edited, .fileSystem, .custom: + return true + } + } + + // Retrieve the required pins. + for pin in requiredPins { + observabilityScope.makeChildScope( + description: "retrieving dependency pins", + metadata: pin.packageRef.diagnosticsMetadata + ).trap { + switch pin.packageRef.kind { + case .localSourceControl, .remoteSourceControl: + _ = try self.checkoutRepository( + package: pin.packageRef, + at: pin.state, + observabilityScope: observabilityScope + ) + case .registry: + _ = try self.downloadRegistryArchive( + package: pin.packageRef, + at: pin.state, + observabilityScope: observabilityScope + ) + default: + throw InternalError("invalid pin type \(pin.packageRef.kind)") + } + } + } + + let currentManifests = try self.loadDependencyManifests( + root: graphRoot, + automaticallyAddManagedDependencies: true, + observabilityScope: observabilityScope + ) + + try self.updateBinaryArtifacts( + manifests: currentManifests, + addedOrUpdatedPackages: [], + observabilityScope: observabilityScope + ) + + let precomputationResult = try self.precomputeResolution( + root: graphRoot, + dependencyManifests: currentManifests, + pinsStore: pinsStore, + constraints: [], + observabilityScope: observabilityScope + ) + + return (currentManifests, precomputationResult) + } + + /// Implementation of resolve(root:diagnostics:). + /// + /// The extra constraints will be added to the main requirements. + /// It is useful in situations where a requirement is being + /// imposed outside of manifest and pins file. E.g., when using a command + /// like `$ swift package resolve foo --version 1.0.0`. + @discardableResult + func resolveAndUpdateResolvedFile( + root: PackageGraphRootInput, + explicitProduct: String? = nil, + forceResolution: Bool, + constraints: [PackageContainerConstraint], + observabilityScope: ObservabilityScope + ) throws -> DependencyManifests { + // Ensure the cache path exists and validate that edited dependencies. + self.createCacheDirectories(observabilityScope: observabilityScope) + + // FIXME: this should not block + // Load the root manifests and currently checked out manifests. + let rootManifests = try temp_await { self.loadRootManifests( + packages: root.packages, + observabilityScope: observabilityScope, + completion: $0 + ) } + let rootManifestsMinimumToolsVersion = rootManifests.values.map(\.toolsVersion).min() ?? ToolsVersion.current + let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) + + // Load the current manifests. + let graphRoot = PackageGraphRoot( + input: root, + manifests: rootManifests, + explicitProduct: explicitProduct, + dependencyMapper: self.dependencyMapper, + observabilityScope: observabilityScope + ) + let currentManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) + guard !observabilityScope.errorsReported else { + return currentManifests + } + + // load and update the pins store with any changes from loading the top level dependencies + guard let pinsStore = self.loadAndUpdatePinsStore( + dependencyManifests: currentManifests, + rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, + observabilityScope: observabilityScope + ) else { + // abort if PinsStore reported any errors. + return currentManifests + } + + // abort if PinsStore reported any errors. + guard !observabilityScope.errorsReported else { + return currentManifests + } + + // Compute the missing package identities. + let missingPackages = try currentManifests.missingPackages + + // Compute if we need to run the resolver. We always run the resolver if + // there are extra constraints. + if !missingPackages.isEmpty { + delegate?.willResolveDependencies(reason: .newPackages(packages: Array(missingPackages))) + } else if !constraints.isEmpty || forceResolution { + delegate?.willResolveDependencies(reason: .forced) + } else { + let result = try self.precomputeResolution( + root: graphRoot, + dependencyManifests: currentManifests, + pinsStore: pinsStore, + constraints: constraints, + observabilityScope: observabilityScope + ) + + switch result { + case .notRequired: + // since nothing changed we can exit early, + // but need update resolved file and download an missing binary artifact + try self.saveResolvedFile( + pinsStore: pinsStore, + dependencyManifests: currentManifests, + originHash: resolvedFileOriginHash, + rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, + observabilityScope: observabilityScope + ) + + try self.updateBinaryArtifacts( + manifests: currentManifests, + addedOrUpdatedPackages: [], + observabilityScope: observabilityScope + ) + + return currentManifests + case .required(let reason): + delegate?.willResolveDependencies(reason: reason) + } + } + + // Create the constraints. + var computedConstraints = [PackageContainerConstraint]() + computedConstraints += currentManifests.editedPackagesConstraints + computedConstraints += try graphRoot.constraints() + constraints + + // Perform dependency resolution. + let resolver = try self.createResolver(pins: pinsStore.pins, observabilityScope: observabilityScope) + self.activeResolver = resolver + + let result = self.resolveDependencies( + resolver: resolver, + constraints: computedConstraints, + observabilityScope: observabilityScope + ) + + // Reset the active resolver. + self.activeResolver = nil + + guard !observabilityScope.errorsReported else { + return currentManifests + } + + // Update the checkouts with dependency resolution result. + let packageStateChanges = self.updateDependenciesCheckouts( + root: graphRoot, + updateResults: result, + observabilityScope: observabilityScope + ) + guard !observabilityScope.errorsReported else { + return currentManifests + } + + // Update the pinsStore. + let updatedDependencyManifests = try self.loadDependencyManifests( + root: graphRoot, + observabilityScope: observabilityScope + ) + // If we still have missing packages, something is fundamentally wrong with the resolution of the graph + let stillMissingPackages = try updatedDependencyManifests.missingPackages + guard stillMissingPackages.isEmpty else { + observabilityScope.emit(.exhaustedAttempts(missing: stillMissingPackages)) + return updatedDependencyManifests + } + + // Update the resolved file. + try self.saveResolvedFile( + pinsStore: pinsStore, + dependencyManifests: updatedDependencyManifests, + originHash: resolvedFileOriginHash, + rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, + observabilityScope: observabilityScope + ) + + let addedOrUpdatedPackages = packageStateChanges.compactMap { $0.1.isAddedOrUpdated ? $0.0 : nil } + try self.updateBinaryArtifacts( + manifests: updatedDependencyManifests, + addedOrUpdatedPackages: addedOrUpdatedPackages, + observabilityScope: observabilityScope + ) + + return updatedDependencyManifests + } + + /// Updates the current working checkouts i.e. clone or remove based on the + /// provided dependency resolution result. + /// + /// - Parameters: + /// - updateResults: The updated results from dependency resolution. + /// - diagnostics: The diagnostics engine that reports errors, warnings + /// and notes. + /// - updateBranches: If the branches should be updated in case they're pinned. + @discardableResult + fileprivate func updateDependenciesCheckouts( + root: PackageGraphRoot, + updateResults: [DependencyResolverBinding], + updateBranches: Bool = false, + observabilityScope: ObservabilityScope + ) -> [(PackageReference, PackageStateChange)] { + // Get the update package states from resolved results. + guard let packageStateChanges = observabilityScope.trap({ + try self.computePackageStateChanges( + root: root, + resolvedDependencies: updateResults, + updateBranches: updateBranches, + observabilityScope: observabilityScope + ) + }) else { + return [] + } + + // First remove the checkouts that are no longer required. + for (packageRef, state) in packageStateChanges { + observabilityScope.makeChildScope( + description: "removing unneeded checkouts", + metadata: packageRef.diagnosticsMetadata + ).trap { + switch state { + case .added, .updated, .unchanged: + break + case .removed: + try self.remove(package: packageRef) + } + } + } + + // Update or clone new packages. + for (packageRef, state) in packageStateChanges { + observabilityScope.makeChildScope( + description: "updating or cloning new packages", + metadata: packageRef.diagnosticsMetadata + ).trap { + switch state { + case .added(let state): + _ = try self.updateDependency( + package: packageRef, + requirement: state.requirement, + productFilter: state.products, + observabilityScope: observabilityScope + ) + case .updated(let state): + _ = try self.updateDependency( + package: packageRef, + requirement: state.requirement, + productFilter: state.products, + observabilityScope: observabilityScope + ) + case .removed, .unchanged: + break + } + } + } + + // Inform the delegate if nothing was updated. + if packageStateChanges.filter({ $0.1 == .unchanged }).count == packageStateChanges.count { + delegate?.dependenciesUpToDate() + } + + return packageStateChanges + } + + private func updateDependency( + package: PackageReference, + requirement: PackageStateChange.Requirement, + productFilter: ProductFilter, + observabilityScope: ObservabilityScope + ) throws -> AbsolutePath { + switch requirement { + case .version(let version): + // FIXME: this should not block + let container = try temp_await { + packageContainerProvider.getContainer( + for: package, + updateStrategy: .never, + observabilityScope: observabilityScope, + on: .sharedConcurrent, + completion: $0 + ) + } + + if let container = container as? SourceControlPackageContainer { + // FIXME: We need to get the revision here, and we don't have a + // way to get it back out of the resolver which is very + // annoying. Maybe we should make an SPI on the provider for this? + guard let tag = container.getTag(for: version) else { + throw try InternalError( + "unable to get tag for \(package) \(version); available versions \(container.versionsDescending())" + ) + } + let revision = try container.getRevision(forTag: tag) + try container.checkIntegrity(version: version, revision: revision) + return try self.checkoutRepository( + package: package, + at: .version(version, revision: revision), + observabilityScope: observabilityScope + ) + } else if let _ = container as? RegistryPackageContainer { + return try self.downloadRegistryArchive( + package: package, + at: version, + observabilityScope: observabilityScope + ) + } else if let customContainer = container as? CustomPackageContainer { + let path = try customContainer.retrieve(at: version, observabilityScope: observabilityScope) + let dependency = try ManagedDependency( + packageRef: package, + state: .custom(version: version, path: path), + subpath: RelativePath(validating: "") + ) + self.state.dependencies.add(dependency) + try self.state.save() + return path + } else { + throw InternalError("invalid container for \(package.identity) of type \(package.kind)") + } + + case .revision(let revision, .none): + return try self.checkoutRepository( + package: package, + at: .revision(revision), + observabilityScope: observabilityScope + ) + + case .revision(let revision, .some(let branch)): + return try self.checkoutRepository( + package: package, + at: .branch(name: branch, revision: revision), + observabilityScope: observabilityScope + ) + + case .unversioned: + let dependency = try ManagedDependency.fileSystem(packageRef: package) + // this is silly since we just created it above, but no good way to force cast it and extract the path + guard case .fileSystem(let path) = dependency.state else { + throw InternalError("invalid package type: \(package.kind)") + } + + self.state.dependencies.add(dependency) + try self.state.save() + return path + } + } + + public enum ResolutionPrecomputationResult: Equatable { + case required(reason: WorkspaceResolveReason) + case notRequired + + public var isRequired: Bool { + switch self { + case .required: return true + case .notRequired: return false + } + } + } + + /// Computes if dependency resolution is required based on input constraints and pins. + /// + /// - Returns: Returns a result defining whether dependency resolution is required and the reason for it. + // @testable internal + public func precomputeResolution( + root: PackageGraphRoot, + dependencyManifests: DependencyManifests, + pinsStore: PinsStore, + constraints: [PackageContainerConstraint], + observabilityScope: ObservabilityScope + ) throws -> ResolutionPrecomputationResult { + let computedConstraints = + try root.constraints() + + // Include constraints from the manifests in the graph root. + root.manifests.values.flatMap { try $0.dependencyConstraints(productFilter: .everything) } + + dependencyManifests.dependencyConstraints + + constraints + + let precomputationProvider = ResolverPrecomputationProvider( + root: root, + dependencyManifests: dependencyManifests + ) + let resolver = PubGrubDependencyResolver( + provider: precomputationProvider, + pins: pinsStore.pins, + observabilityScope: observabilityScope + ) + let result = resolver.solve(constraints: computedConstraints) + + guard !observabilityScope.errorsReported else { + return .required(reason: .errorsPreviouslyReported) + } + + switch result { + case .success: + return .notRequired + case .failure(ResolverPrecomputationError.missingPackage(let package)): + return .required(reason: .newPackages(packages: [package])) + case .failure(ResolverPrecomputationError.differentRequirement(let package, let state, let requirement)): + return .required(reason: .packageRequirementChange( + package: package, + state: state, + requirement: requirement + )) + case .failure(let error): + return .required(reason: .other("\(error.interpolationDescription)")) + } + } + + /// Validates that each checked out managed dependency has an entry in pinsStore. + private func loadAndUpdatePinsStore( + dependencyManifests: DependencyManifests, + rootManifestsMinimumToolsVersion: ToolsVersion, + observabilityScope: ObservabilityScope + ) -> PinsStore? { + guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }) else { + return nil + } + + guard let requiredDependencies = observabilityScope + .trap({ try dependencyManifests.requiredPackages.filter(\.kind.isPinnable) }) + else { + return nil + } + for dependency in self.state.dependencies.filter(\.packageRef.kind.isPinnable) { + // a required dependency that is already loaded (managed) should be represented in the pins store. + // also comparing location as it may have changed at this point + if requiredDependencies.contains(where: { $0.equalsIncludingLocation(dependency.packageRef) }) { + // if pin not found, or location is different (it may have changed at this point) pin it + if pinsStore.pins[comparingLocation: dependency.packageRef] == .none { + pinsStore.pin(dependency) + } + } else if let pin = pinsStore.pins[dependency.packageRef.identity] { + // otherwise, it should *not* be in the pins store. + pinsStore.remove(pin) + } + } + + return pinsStore + } + + /// This enum represents state of an external package. + public enum PackageStateChange: Equatable, CustomStringConvertible { + /// The requirement imposed by the the state. + public enum Requirement: Equatable, CustomStringConvertible { + /// A version requirement. + case version(Version) + + /// A revision requirement. + case revision(Revision, branch: String?) + + case unversioned + + public var description: String { + switch self { + case .version(let version): + return "requirement(\(version))" + case .revision(let revision, let branch): + return "requirement(\(revision) \(branch ?? ""))" + case .unversioned: + return "requirement(unversioned)" + } + } + + public var prettyPrinted: String { + switch self { + case .version(let version): + return "\(version)" + case .revision(let revision, let branch): + return "\(revision) \(branch ?? "")" + case .unversioned: + return "unversioned" + } + } + } + + public struct State: Equatable { + public let requirement: Requirement + public let products: ProductFilter + public init(requirement: Requirement, products: ProductFilter) { + self.requirement = requirement + self.products = products + } + } + + /// The package is added. + case added(State) + + /// The package is removed. + case removed + + /// The package is unchanged. + case unchanged + + /// The package is updated. + case updated(State) + + public var description: String { + switch self { + case .added(let requirement): + return "added(\(requirement))" + case .removed: + return "removed" + case .unchanged: + return "unchanged" + case .updated(let requirement): + return "updated(\(requirement))" + } + } + + public var isAddedOrUpdated: Bool { + switch self { + case .added, .updated: + return true + case .unchanged, .removed: + return false + } + } + } + + /// Computes states of the packages based on last stored state. + fileprivate func computePackageStateChanges( + root: PackageGraphRoot, + resolvedDependencies: [DependencyResolverBinding], + updateBranches: Bool, + observabilityScope: ObservabilityScope + ) throws -> [(PackageReference, PackageStateChange)] { + // Load pins store and managed dependencies. + let pinsStore = try self.pinsStore.load() + var packageStateChanges: [PackageIdentity: (PackageReference, PackageStateChange)] = [:] + + // Set the states from resolved dependencies results. + for binding in resolvedDependencies { + // Get the existing managed dependency for this package ref, if any. + + // first find by identity only since edit location may be different by design + var currentDependency = self.state.dependencies[binding.package.identity] + // Check if this is an edited dependency. + if case .edited(let basedOn, _) = currentDependency?.state, let originalReference = basedOn?.packageRef { + packageStateChanges[originalReference.identity] = (originalReference, .unchanged) + } else { + // if not edited, also compare by location since it may have changed + currentDependency = self.state.dependencies[comparingLocation: binding.package] + } + + switch binding.boundVersion { + case .excluded: + throw InternalError("Unexpected excluded binding") + + case .unversioned: + // Ignore the root packages. + if root.packages.keys.contains(binding.package.identity) { + continue + } + + if let currentDependency { + switch currentDependency.state { + case .fileSystem, .edited: + packageStateChanges[binding.package.identity] = (binding.package, .unchanged) + case .sourceControlCheckout: + let newState = PackageStateChange.State(requirement: .unversioned, products: binding.products) + packageStateChanges[binding.package.identity] = (binding.package, .updated(newState)) + case .registryDownload: + throw InternalError("Unexpected unversioned binding for downloaded dependency") + case .custom: + throw InternalError("Unexpected unversioned binding for custom dependency") + } + } else { + let newState = PackageStateChange.State(requirement: .unversioned, products: binding.products) + packageStateChanges[binding.package.identity] = (binding.package, .added(newState)) + } + + case .revision(let identifier, let branch): + // Get the latest revision from the container. + // TODO: replace with async/await when available + guard let container = try (temp_await { + packageContainerProvider.getContainer( + for: binding.package, + updateStrategy: .never, + observabilityScope: observabilityScope, + on: .sharedConcurrent, + completion: $0 + ) + }) as? SourceControlPackageContainer else { + throw InternalError("invalid container for \(binding.package) expected a SourceControlPackageContainer") + } + var revision = try container.getRevision(forIdentifier: identifier) + let branch = branch ?? (identifier == revision.identifier ? nil : identifier) + + // If we have a branch and we shouldn't be updating the + // branches, use the revision from pin instead (if present). + if branch != nil, !updateBranches { + if case .branch(branch, let pinRevision) = pinsStore.pins.values + .first(where: { $0.packageRef == binding.package })?.state + { + revision = Revision(identifier: pinRevision) + } + } + + // First check if we have this dependency. + if let currentDependency { + // If current state and new state are equal, we don't need + // to do anything. + let newState: CheckoutState + if let branch { + newState = .branch(name: branch, revision: revision) + } else { + newState = .revision(revision) + } + if case .sourceControlCheckout(let checkoutState) = currentDependency.state, + checkoutState == newState + { + packageStateChanges[binding.package.identity] = (binding.package, .unchanged) + } else { + // Otherwise, we need to update this dependency to this revision. + let newState = PackageStateChange.State( + requirement: .revision(revision, branch: branch), + products: binding.products + ) + packageStateChanges[binding.package.identity] = (binding.package, .updated(newState)) + } + } else { + let newState = PackageStateChange.State( + requirement: .revision(revision, branch: branch), + products: binding.products + ) + packageStateChanges[binding.package.identity] = (binding.package, .added(newState)) + } + + case .version(let version): + let stateChange: PackageStateChange + switch currentDependency?.state { + case .sourceControlCheckout(.version(version, _)), .registryDownload(version), .custom(version, _): + stateChange = .unchanged + case .edited, .fileSystem, .sourceControlCheckout, .registryDownload, .custom: + stateChange = .updated(.init(requirement: .version(version), products: binding.products)) + case nil: + stateChange = .added(.init(requirement: .version(version), products: binding.products)) + } + packageStateChanges[binding.package.identity] = (binding.package, stateChange) + } + } + // Set the state of any old package that might have been removed. + for packageRef in self.state.dependencies.lazy.map(\.packageRef) + where packageStateChanges[packageRef.identity] == nil + { + packageStateChanges[packageRef.identity] = (packageRef, .removed) + } + + return Array(packageStateChanges.values) + } + + /// Creates resolver for the workspace. + fileprivate func createResolver( + pins: PinsStore.Pins, + observabilityScope: ObservabilityScope + ) throws -> PubGrubDependencyResolver { + var delegate: DependencyResolverDelegate + let observabilityDelegate = ObservabilityDependencyResolverDelegate(observabilityScope: observabilityScope) + if let workspaceDelegate = self.delegate { + delegate = MultiplexResolverDelegate([ + observabilityDelegate, + WorkspaceDependencyResolverDelegate(workspaceDelegate), + ]) + } else { + delegate = observabilityDelegate + } + + return PubGrubDependencyResolver( + provider: packageContainerProvider, + pins: pins, + skipDependenciesUpdates: self.configuration.skipDependenciesUpdates, + prefetchBasedOnResolvedFile: self.configuration.prefetchBasedOnResolvedFile, + observabilityScope: observabilityScope, + delegate: delegate + ) + } + + /// Runs the dependency resolver based on constraints provided and returns the results. + fileprivate func resolveDependencies( + resolver: PubGrubDependencyResolver, + constraints: [PackageContainerConstraint], + observabilityScope: ObservabilityScope + ) -> [DependencyResolverBinding] { + os_signpost(.begin, name: SignpostName.pubgrub) + let result = resolver.solve(constraints: constraints) + os_signpost(.end, name: SignpostName.pubgrub) + + // Take an action based on the result. + switch result { + case .success(let bindings): + return bindings + case .failure(let error): + observabilityScope.emit(error) + return [] + } + } + + /// Create the cache directories. + fileprivate func createCacheDirectories(observabilityScope: ObservabilityScope) { + observabilityScope.trap { + try fileSystem.createDirectory(self.repositoryManager.path, recursive: true) + try fileSystem.createDirectory(self.location.repositoriesCheckoutsDirectory, recursive: true) + try fileSystem.createDirectory(self.location.artifactsDirectory, recursive: true) + } + } +} + +private struct WorkspaceDependencyResolverDelegate: DependencyResolverDelegate { + private weak var workspaceDelegate: Workspace.Delegate? + private let resolving = ThreadSafeKeyValueStore() + + init(_ delegate: Workspace.Delegate) { + self.workspaceDelegate = delegate + } + + func willResolve(term: Term) { + // this may be called multiple time by the resolver for various version ranges, but we only want to propagate + // once since we report at package level + self.resolving.memoize(term.node.package.identity) { + self.workspaceDelegate?.willComputeVersion( + package: term.node.package.identity, + location: term.node.package.locationString + ) + return true + } + } + + func didResolve(term: Term, version: Version, duration: DispatchTimeInterval) { + self.workspaceDelegate?.didComputeVersion( + package: term.node.package.identity, + location: term.node.package.locationString, + version: version.description, + duration: duration + ) + } + + // noop + func derived(term: Term) {} + func conflict(conflict: Incompatibility) {} + func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility) {} + func partiallySatisfied( + term: Term, + by assignment: Assignment, + incompatibility: Incompatibility, + difference: Term + ) {} + func failedToResolve(incompatibility: Incompatibility) {} + func solved(result: [DependencyResolverBinding]) {} +} + +// FIXME: the manifest loading logic should be changed to use identity instead of location once identity is unique +// at that time we should remove this +// @available(*, deprecated) +extension PackageDependency { + var locationString: String { + switch self { + case .fileSystem(let settings): + return settings.path.pathString + case .sourceControl(let settings): + switch settings.location { + case .local(let path): + return path.pathString + case .remote(let url): + return url.absoluteString + } + case .registry: + // FIXME: placeholder + return self.identity.description + } + } +} + +extension Workspace.ManagedDependencies { + fileprivate func hasEditedDependencies() -> Bool { + self.contains(where: { + switch $0.state { + case .edited: + return true + default: + return false + } + }) + } +} diff --git a/Sources/Workspace/Workspace+Editing.swift b/Sources/Workspace/Workspace+Editing.swift new file mode 100644 index 00000000000..35a483fe2c8 --- /dev/null +++ b/Sources/Workspace/Workspace+Editing.swift @@ -0,0 +1,241 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import class Basics.ObservabilityScope +import struct Basics.RelativePath +import func Basics.temp_await +import struct PackageGraph.PackageGraphRootInput +import struct SourceControl.Revision +import class TSCBasic.InMemoryFileSystem + +extension Workspace { + /// Edit implementation. + func _edit( + packageName: String, + path: AbsolutePath? = nil, + revision: Revision? = nil, + checkoutBranch: String? = nil, + observabilityScope: ObservabilityScope + ) throws { + // Look up the dependency and check if we can edit it. + guard let dependency = self.state.dependencies[.plain(packageName)] else { + observabilityScope.emit(.dependencyNotFound(packageName: packageName)) + return + } + + let observabilityScope = observabilityScope.makeChildScope( + description: "editing package", + metadata: dependency.packageRef.diagnosticsMetadata + ) + + let checkoutState: CheckoutState + switch dependency.state { + case .sourceControlCheckout(let _checkoutState): + checkoutState = _checkoutState + case .edited: + observabilityScope.emit(error: "dependency '\(dependency.packageRef.identity)' already in edit mode") + return + case .fileSystem: + observabilityScope.emit(error: "local dependency '\(dependency.packageRef.identity)' can't be edited") + return + case .registryDownload: + observabilityScope.emit(error: "registry dependency '\(dependency.packageRef.identity)' can't be edited") + return + case .custom: + observabilityScope.emit(error: "custom dependency '\(dependency.packageRef.identity)' can't be edited") + return + } + + // If a path is provided then we use it as destination. If not, we + // use the folder with packageName inside editablesPath. + let destination = path ?? self.location.editsDirectory.appending(component: packageName) + + // If there is something present at the destination, we confirm it has + // a valid manifest with name same as the package we are trying to edit. + if fileSystem.exists(destination) { + // FIXME: this should not block + let manifest = try temp_await { + self.loadManifest( + packageIdentity: dependency.packageRef.identity, + packageKind: .fileSystem(destination), + packagePath: destination, + packageLocation: dependency.packageRef.locationString, + observabilityScope: observabilityScope, + completion: $0 + ) + } + + guard manifest.displayName == packageName else { + return observabilityScope + .emit( + error: "package at '\(destination)' is \(manifest.displayName) but was expecting \(packageName)" + ) + } + + // Emit warnings for branch and revision, if they're present. + if let checkoutBranch { + observabilityScope.emit(.editBranchNotCheckedOut( + packageName: packageName, + branchName: checkoutBranch + )) + } + if let revision { + observabilityScope.emit(.editRevisionNotUsed( + packageName: packageName, + revisionIdentifier: revision.identifier + )) + } + } else { + // Otherwise, create a checkout at the destination from our repository store. + // + // Get handle to the repository. + // TODO: replace with async/await when available + let repository = try dependency.packageRef.makeRepositorySpecifier() + let handle = try temp_await { + repositoryManager.lookup( + package: dependency.packageRef.identity, + repository: repository, + updateStrategy: .never, + observabilityScope: observabilityScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent, + completion: $0 + ) + } + let repo = try handle.open() + + // Do preliminary checks on branch and revision, if provided. + if let branch = checkoutBranch, repo.exists(revision: Revision(identifier: branch)) { + throw WorkspaceDiagnostics.BranchAlreadyExists(branch: branch) + } + if let revision, !repo.exists(revision: revision) { + throw WorkspaceDiagnostics.RevisionDoesNotExist(revision: revision.identifier) + } + + let workingCopy = try handle.createWorkingCopy(at: destination, editable: true) + try workingCopy.checkout(revision: revision ?? checkoutState.revision) + + // Checkout to the new branch if provided. + if let branch = checkoutBranch { + try workingCopy.checkout(newBranch: branch) + } + } + + // For unmanaged dependencies, create the symlink under editables dir. + if let path { + try fileSystem.createDirectory(self.location.editsDirectory) + // FIXME: We need this to work with InMem file system too. + if !(fileSystem is InMemoryFileSystem) { + let symLinkPath = self.location.editsDirectory.appending(component: packageName) + + // Cleanup any existing symlink. + if fileSystem.isSymlink(symLinkPath) { + try fileSystem.removeFileTree(symLinkPath) + } + + // FIXME: We should probably just warn in case we fail to create + // this symlink, which could happen if there is some non-symlink + // entry at this location. + try fileSystem.createSymbolicLink(symLinkPath, pointingAt: path, relative: false) + } + } + + // Remove the existing checkout. + do { + let oldCheckoutPath = self.location.repositoriesCheckoutSubdirectory(for: dependency) + try fileSystem.chmod(.userWritable, path: oldCheckoutPath, options: [.recursive, .onlyFiles]) + try fileSystem.removeFileTree(oldCheckoutPath) + } + + // Save the new state. + try self.state.dependencies.add( + dependency.edited(subpath: RelativePath(validating: packageName), unmanagedPath: path) + ) + try self.state.save() + } + + /// Unedit a managed dependency. See public API unedit(packageName:forceRemove:). + func unedit( + dependency: ManagedDependency, + forceRemove: Bool, + root: PackageGraphRootInput? = nil, + observabilityScope: ObservabilityScope + ) throws { + // Compute if we need to force remove. + var forceRemove = forceRemove + + // If the dependency isn't in edit mode, we can't unedit it. + guard case .edited(_, let unmanagedPath) = dependency.state else { + throw WorkspaceDiagnostics + .DependencyNotInEditMode(dependencyName: dependency.packageRef.identity.description) + } + + // Set force remove to true for unmanaged dependencies. Note that + // this only removes the symlink under the editable directory and + // not the actual unmanaged package. + if unmanagedPath != nil { + forceRemove = true + } + + // Form the edit working repo path. + let path = self.location.editSubdirectory(for: dependency) + // Check for uncommited and unpushed changes if force removal is off. + if !forceRemove { + let workingCopy = try repositoryManager.openWorkingCopy(at: path) + guard !workingCopy.hasUncommittedChanges() else { + throw WorkspaceDiagnostics.UncommitedChanges(repositoryPath: path) + } + guard try !workingCopy.hasUnpushedCommits() else { + throw WorkspaceDiagnostics.UnpushedChanges(repositoryPath: path) + } + } + // Remove the editable checkout from disk. + if fileSystem.exists(path) { + try fileSystem.removeFileTree(path) + } + // If this was the last editable dependency, remove the editables directory too. + if fileSystem.exists(self.location.editsDirectory), + try fileSystem.getDirectoryContents(self.location.editsDirectory).isEmpty + { + try fileSystem.removeFileTree(self.location.editsDirectory) + } + + if case .edited(let basedOn, _) = dependency.state, + case .sourceControlCheckout(let checkoutState) = basedOn?.state + { + // Restore the original checkout. + // + // The retrieve method will automatically update the managed dependency state. + _ = try self.checkoutRepository( + package: dependency.packageRef, + at: checkoutState, + observabilityScope: observabilityScope + ) + } else { + // The original dependency was removed, update the managed dependency state. + self.state.dependencies.remove(dependency.packageRef.identity) + try self.state.save() + } + + // Resolve the dependencies if workspace root is provided. We do this to + // ensure the unedited version of this dependency is resolved properly. + if let root { + try self._resolve( + root: root, + explicitProduct: .none, + resolvedFileStrategy: .update(forceResolution: false), + observabilityScope: observabilityScope + ) + } + } +} diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift new file mode 100644 index 00000000000..35eb95c863f --- /dev/null +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -0,0 +1,896 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.Diagnostic +import struct Basics.InternalError +import class Basics.ObservabilityScope +import struct Basics.SwiftVersion +import func Basics.temp_await +import class Basics.ThreadSafeKeyValueStore +import class Dispatch.DispatchGroup +import struct Dispatch.DispatchTime +import struct OrderedCollections.OrderedDictionary +import struct OrderedCollections.OrderedSet +import protocol PackageGraph.CustomPackageContainer +import struct PackageGraph.GraphLoadingNode +import struct PackageGraph.PackageContainerConstraint +import struct PackageGraph.PackageGraphRoot +import class PackageLoading.ManifestLoader +import struct PackageLoading.ManifestValidator +import struct PackageLoading.ToolsVersionParser +import class PackageModel.Manifest +import struct PackageModel.PackageIdentity +import struct PackageModel.PackageReference +import enum PackageModel.ProductFilter +import struct PackageModel.ToolsVersion +import protocol TSCBasic.FileSystem +import func TSCBasic.findCycle +import struct TSCBasic.KeyedPair +import struct TSCBasic.StringError +import func TSCBasic.topologicalSort +import func TSCBasic.transitiveClosure +import enum TSCUtility.Diagnostics +import struct TSCUtility.Version + +// MARK: - Manifest Loading and caching + +extension Workspace { + /// A struct representing all the current manifests (root + external) in a package graph. + public struct DependencyManifests { + /// The package graph root. + let root: PackageGraphRoot + + /// The dependency manifests in the transitive closure of root manifest. + let dependencies: [( + manifest: Manifest, + dependency: ManagedDependency, + productFilter: ProductFilter, + fileSystem: FileSystem + )] + + private let workspace: Workspace + + private let observabilityScope: ObservabilityScope + + private let _dependencies: LoadableResult<( + required: OrderedCollections.OrderedSet, + missing: OrderedCollections.OrderedSet + )> + + private let _constraints: LoadableResult<[PackageContainerConstraint]> + + fileprivate init( + root: PackageGraphRoot, + dependencies: [( + manifest: Manifest, + dependency: ManagedDependency, + productFilter: ProductFilter, + fileSystem: FileSystem + )], + workspace: Workspace, + observabilityScope: ObservabilityScope + ) { + self.root = root + self.dependencies = dependencies + self.workspace = workspace + self.observabilityScope = observabilityScope + self._dependencies = LoadableResult { + try Self.computeDependencies( + root: root, + dependencies: dependencies, + workspace: workspace, + observabilityScope: observabilityScope + ) + } + self._constraints = LoadableResult { + try Self.computeConstraints( + dependencies: dependencies, + workspace: workspace + ) + } + } + + /// Returns all manifests contained in DependencyManifests. + public var allDependencyManifests: OrderedCollections.OrderedDictionary< + PackageIdentity, + (manifest: Manifest, fs: FileSystem) + > { + self.dependencies.reduce(into: OrderedCollections.OrderedDictionary< + PackageIdentity, + (manifest: Manifest, fs: FileSystem) + >()) { partial, item in + partial[item.dependency.packageRef.identity] = (item.manifest, item.fileSystem) + } + } + + /// Computes the identities which are declared in the manifests but aren't present in dependencies. + public var missingPackages: [PackageReference] { + get throws { + try self._dependencies.load().missing.elements + } + } + + /// Computes the identities which are declared in the manifests but aren't present in dependencies. + public var requiredPackages: [PackageReference] { + get throws { + try self._dependencies.load().required.elements + } + } + + /// Returns the list of packages which are allowed to vend products with unsafe flags. + var unsafeAllowedPackages: Set { + var result = Set() + + for dependency in self.dependencies { + let dependency = dependency.dependency + switch dependency.state { + case .sourceControlCheckout(let checkout): + if checkout.isBranchOrRevisionBased { + result.insert(dependency.packageRef) + } + case .registryDownload, .edited, .custom: + continue + case .fileSystem: + result.insert(dependency.packageRef) + } + } + + // Root packages are always allowed to use unsafe flags. + result.formUnion(root.packageReferences) + + return result + } + + private static func computeDependencies( + root: PackageGraphRoot, + dependencies: [( + manifest: Manifest, + dependency: ManagedDependency, + productFilter: ProductFilter, + fileSystem: FileSystem + )], + workspace: Workspace, + observabilityScope: ObservabilityScope + ) throws + -> ( + required: OrderedCollections.OrderedSet, + missing: OrderedCollections.OrderedSet + ) + { + let manifestsMap: [PackageIdentity: Manifest] = try Dictionary( + throwingUniqueKeysWithValues: + root.packages.map { ($0.key, $0.value.manifest) } + + dependencies.map { + ($0.dependency.packageRef.identity, $0.manifest) + } + ) + + var inputIdentities: OrderedCollections.OrderedSet = [] + let inputNodes: [GraphLoadingNode] = root.packages.map { identity, package in + inputIdentities.append(package.reference) + let node = GraphLoadingNode( + identity: identity, + manifest: package.manifest, + productFilter: .everything + ) + return node + } + root.dependencies.compactMap { dependency in + let package = dependency.packageRef + inputIdentities.append(package) + return manifestsMap[dependency.identity].map { manifest in + GraphLoadingNode( + identity: dependency.identity, + manifest: manifest, + productFilter: dependency.productFilter + ) + } + } + + let topLevelDependencies = root.packages.flatMap { $1.manifest.dependencies.map(\.packageRef) } + + var requiredIdentities: OrderedCollections.OrderedSet = [] + _ = transitiveClosure(inputNodes) { node in + node.manifest.dependenciesRequired(for: node.productFilter).compactMap { dependency in + let package = dependency.packageRef + let (inserted, index) = requiredIdentities.append(package) + if !inserted { + let existing = requiredIdentities.elements[index] + // if identity already tracked, compare the locations and used the preferred variant + if existing.canonicalLocation == package.canonicalLocation { + // same literal location is fine + if existing.locationString != package.locationString { + // we prefer the top level dependencies + if topLevelDependencies.contains(where: { + $0.locationString == existing.locationString + }) { + observabilityScope.emit(debug: """ + similar variants of package '\(package.identity)' \ + found at '\(package.locationString)' and '\(existing.locationString)'. \ + using preferred root variant '\(existing.locationString)' + """) + } else { + let preferred = [existing, package].sorted(by: { + $0.locationString > $1.locationString + }).first! // safe + observabilityScope.emit(debug: """ + similar variants of package '\(package.identity)' \ + found at '\(package.locationString)' and '\(existing.locationString)'. \ + using preferred variant '\(preferred.locationString)' + """) + if preferred.locationString != existing.locationString { + requiredIdentities.remove(existing) + requiredIdentities.insert(preferred, at: index) + } + } + } + } else { + observabilityScope.emit(debug: """ + '\(package.identity)' from '\(package.locationString)' was omitted \ + from required dependencies because it has the same identity as the \ + one from '\(existing.locationString)' + """) + } + } + return manifestsMap[dependency.identity].map { manifest in + GraphLoadingNode( + identity: dependency.identity, + manifest: manifest, + productFilter: dependency.productFilter + ) + } + } + } + requiredIdentities = inputIdentities.union(requiredIdentities) + + let availableIdentities: Set = try Set(manifestsMap.map { + // FIXME: adding this guard to ensure refactoring is correct 9/21 + // we only care about remoteSourceControl for this validation. it would otherwise trigger for + // a dependency is put into edit mode, which we want to deprecate anyways + if case .remoteSourceControl = $0.1.packageKind { + let effectiveURL = workspace.mirrors.effective(for: $0.1.packageLocation) + guard effectiveURL == $0.1.packageKind.locationString else { + throw InternalError( + "effective url for \($0.1.packageLocation) is \(effectiveURL), different from expected \($0.1.packageKind.locationString)" + ) + } + } + return PackageReference(identity: $0.key, kind: $0.1.packageKind) + }) + // We should never have loaded a manifest we don't need. + assert( + availableIdentities.isSubset(of: requiredIdentities), + "\(availableIdentities) | \(requiredIdentities)" + ) + // These are the missing package identities. + let missingIdentities = requiredIdentities.subtracting(availableIdentities) + + return (requiredIdentities, missingIdentities) + } + + /// Returns constraints of the dependencies, including edited package constraints. + var dependencyConstraints: [PackageContainerConstraint] { + get throws { + try self._constraints.load() + } + } + + private static func computeConstraints( + dependencies: [( + manifest: Manifest, + dependency: ManagedDependency, + productFilter: ProductFilter, + fileSystem: FileSystem + )], + workspace: Workspace + ) throws -> [PackageContainerConstraint] { + var allConstraints = [PackageContainerConstraint]() + + for (externalManifest, managedDependency, productFilter, _) in dependencies { + // For edited packages, add a constraint with unversioned requirement so the + // resolver doesn't try to resolve it. + switch managedDependency.state { + case .edited: + // FIXME: We shouldn't need to construct a new package reference object here. + // We should get the correct one from managed dependency object. + let ref = PackageReference.fileSystem( + identity: managedDependency.packageRef.identity, + path: workspace.path(to: managedDependency) + ) + let constraint = PackageContainerConstraint( + package: ref, + requirement: .unversioned, + products: productFilter + ) + allConstraints.append(constraint) + case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: + break + } + allConstraints += try externalManifest.dependencyConstraints(productFilter: productFilter) + } + return allConstraints + } + + // FIXME: @testable(internal) + /// Returns a list of constraints for all 'edited' package. + public var editedPackagesConstraints: [PackageContainerConstraint] { + var constraints = [PackageContainerConstraint]() + + for (_, managedDependency, productFilter, _) in dependencies { + switch managedDependency.state { + case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: continue + case .edited: break + } + // FIXME: We shouldn't need to construct a new package reference object here. + // We should get the correct one from managed dependency object. + let ref = PackageReference.fileSystem( + identity: managedDependency.packageRef.identity, + path: workspace.path(to: managedDependency) + ) + let constraint = PackageContainerConstraint( + package: ref, + requirement: .unversioned, + products: productFilter + ) + constraints.append(constraint) + } + return constraints + } + } + + /// Returns the location of the dependency. + /// + /// Source control dependencies will return the subpath inside `checkoutsPath` and + /// Registry dependencies will return the subpath inside `registryDownloadsPath` and + /// edited dependencies will either return a subpath inside `editablesPath` or + /// a custom path. + public func path(to dependency: Workspace.ManagedDependency) -> AbsolutePath { + switch dependency.state { + case .sourceControlCheckout: + return self.location.repositoriesCheckoutSubdirectory(for: dependency) + case .registryDownload: + return self.location.registryDownloadSubdirectory(for: dependency) + case .edited(_, let path): + return path ?? self.location.editSubdirectory(for: dependency) + case .fileSystem(let path): + return path + case .custom(_, let path): + return path + } + } + + /// Returns manifest interpreter flags for a package. + // TODO: should this be throwing instead? + public func interpreterFlags(for packagePath: AbsolutePath) -> [String] { + do { + guard let manifestLoader = self.manifestLoader as? ManifestLoader else { + throw StringError("unexpected manifest loader kind") + } + + let manifestPath = try ManifestLoader.findManifest( + packagePath: packagePath, + fileSystem: self.fileSystem, + currentToolsVersion: self.currentToolsVersion + ) + let manifestToolsVersion = try ToolsVersionParser.parse( + manifestPath: manifestPath, + fileSystem: self.fileSystem + ) + + guard self.currentToolsVersion >= manifestToolsVersion || SwiftVersion.current.isDevelopment, + manifestToolsVersion >= ToolsVersion.minimumRequired + else { + throw StringError("invalid tools version") + } + return manifestLoader.interpreterFlags(for: manifestToolsVersion) + } catch { + // We ignore all failures here and return empty array. + return [] + } + } + + /// Load the manifests for the current dependency tree. + /// + /// This will load the manifests for the root package as well as all the + /// current dependencies from the working checkouts.l + public func loadDependencyManifests( + root: PackageGraphRoot, + automaticallyAddManagedDependencies: Bool = false, + observabilityScope: ObservabilityScope + ) throws -> DependencyManifests { + let prepopulateManagedDependencies: ([PackageReference]) throws -> Void = { refs in + // pre-populate managed dependencies if we are asked to do so (this happens when resolving to a resolved + // file) + if automaticallyAddManagedDependencies { + try refs.forEach { ref in + // Since we are creating managed dependencies based on the resolved file in this mode, but local + // packages aren't part of that file, they will be missing from it. So we're eagerly adding them + // here, but explicitly don't add any that are overridden by a root with the same identity since + // that would lead to loading the given package twice, once as a root and once as a dependency + // which violates various assumptions. + if case .fileSystem = ref.kind, !root.manifests.keys.contains(ref.identity) { + try self.state.dependencies.add(.fileSystem(packageRef: ref)) + } + } + observabilityScope.trap { try self.state.save() } + } + } + + // Utility Just because a raw tuple cannot be hashable. + struct Key: Hashable { + let identity: PackageIdentity + let productFilter: ProductFilter + } + + // Make a copy of dependencies as we might mutate them in the for loop. + let dependenciesToCheck = Array(self.state.dependencies) + // Remove any managed dependency which has become a root. + for dependency in dependenciesToCheck { + if root.packages.keys.contains(dependency.packageRef.identity) { + observabilityScope.makeChildScope( + description: "removing managed dependencies", + metadata: dependency.packageRef.diagnosticsMetadata + ).trap { + try self.remove(package: dependency.packageRef) + } + } + } + + // Validates that all the managed dependencies are still present in the file system. + self.fixManagedDependencies(observabilityScope: observabilityScope) + guard !observabilityScope.errorsReported else { + // return partial results + return DependencyManifests( + root: root, + dependencies: [], + workspace: self, + observabilityScope: observabilityScope + ) + } + + // Load root dependencies manifests (in parallel) + let rootDependencies = root.dependencies.map(\.packageRef) + try prepopulateManagedDependencies(rootDependencies) + let rootDependenciesManifests = try temp_await { self.loadManagedManifests( + for: rootDependencies, + observabilityScope: observabilityScope, + completion: $0 + ) } + + let topLevelManifests = root.manifests.merging(rootDependenciesManifests, uniquingKeysWith: { lhs, _ in + lhs // prefer roots! + }) + + // optimization: preload first level dependencies manifest (in parallel) + let firstLevelDependencies = topLevelManifests.values.map { $0.dependencies.map(\.packageRef) }.flatMap { $0 } + let firstLevelManifests = try temp_await { self.loadManagedManifests( + for: firstLevelDependencies, + observabilityScope: observabilityScope, + completion: $0 + ) } // FIXME: this should not block + + // Continue to load the rest of the manifest for this graph + // Creates a map of loaded manifests. We do this to avoid reloading the shared nodes. + var loadedManifests = firstLevelManifests + // Compute the transitive closure of available dependencies. + let topologicalSortInput = topLevelManifests.map { identity, manifest in KeyedPair( + manifest, + key: Key(identity: identity, productFilter: .everything) + ) } + let topologicalSortSuccessors: (KeyedPair) throws -> [KeyedPair] = { pair in + // optimization: preload manifest we know about in parallel + let dependenciesRequired = pair.item.dependenciesRequired(for: pair.key.productFilter) + let dependenciesToLoad = dependenciesRequired.map(\.packageRef) + .filter { !loadedManifests.keys.contains($0.identity) } + try prepopulateManagedDependencies(dependenciesToLoad) + let dependenciesManifests = try temp_await { self.loadManagedManifests( + for: dependenciesToLoad, + observabilityScope: observabilityScope, + completion: $0 + ) } + dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } + return dependenciesRequired.compactMap { dependency in + loadedManifests[dependency.identity].flatMap { + // we also compare the location as this function may attempt to load + // dependencies that have the same identity but from a different location + // which is an error case we diagnose an report about in the GraphLoading part which + // is prepared to handle the case where not all manifest are available + $0.canonicalPackageLocation == dependency.packageRef.canonicalLocation ? + KeyedPair( + $0, + key: Key(identity: dependency.identity, productFilter: dependency.productFilter) + ) : + nil + } + } + } + + // Look for any cycle in the dependencies. + if let cycle = try findCycle(topologicalSortInput, successors: topologicalSortSuccessors) { + observabilityScope.emit( + error: "cyclic dependency declaration found: " + + (cycle.path + cycle.cycle).map(\.key.identity.description).joined(separator: " -> ") + + " -> " + cycle.cycle[0].key.identity.description + ) + // return partial results + return DependencyManifests( + root: root, + dependencies: [], + workspace: self, + observabilityScope: observabilityScope + ) + } + let allManifestsWithPossibleDuplicates = try topologicalSort( + topologicalSortInput, + successors: topologicalSortSuccessors + ) + + // merge the productFilter of the same package (by identity) + var deduplication = [PackageIdentity: Int]() + var allManifests = [(identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter)]() + for pair in allManifestsWithPossibleDuplicates { + if let index = deduplication[pair.key.identity] { + let productFilter = allManifests[index].productFilter.merge(pair.key.productFilter) + allManifests[index] = (pair.key.identity, pair.item, productFilter) + } else { + deduplication[pair.key.identity] = allManifests.count + allManifests.append((pair.key.identity, pair.item, pair.key.productFilter)) + } + } + + let dependencyManifests = allManifests.filter { !root.manifests.values.contains($0.manifest) } + + // TODO: this check should go away when introducing explicit overrides + // check for overrides attempts with same name but different path + let rootManifestsByName = Array(root.manifests.values).spm_createDictionary { ($0.displayName, $0) } + dependencyManifests.forEach { _, manifest, _ in + if let override = rootManifestsByName[manifest.displayName], + override.packageLocation != manifest.packageLocation + { + observabilityScope + .emit( + error: "unable to override package '\(manifest.displayName)' because its identity '\(PackageIdentity(urlString: manifest.packageLocation))' doesn't match override's identity (directory name) '\(PackageIdentity(urlString: override.packageLocation))'" + ) + } + } + + let dependencies = try dependencyManifests.map { identity, manifest, productFilter -> ( + Manifest, + ManagedDependency, + ProductFilter, + FileSystem + ) in + guard let dependency = self.state.dependencies[identity] else { + throw InternalError("dependency not found for \(identity) at \(manifest.packageLocation)") + } + + let packageRef = PackageReference(identity: identity, kind: manifest.packageKind) + let fileSystem = try self.getFileSystem( + package: packageRef, + state: dependency.state, + observabilityScope: observabilityScope + ) + return (manifest, dependency, productFilter, fileSystem ?? self.fileSystem) + } + + return DependencyManifests( + root: root, + dependencies: dependencies, + workspace: self, + observabilityScope: observabilityScope + ) + } + + /// Loads the given manifests, if it is present in the managed dependencies. + private func loadManagedManifests( + for packages: [PackageReference], + observabilityScope: ObservabilityScope, + completion: @escaping (Result<[PackageIdentity: Manifest], Error>) -> Void + ) { + let sync = DispatchGroup() + let manifests = ThreadSafeKeyValueStore() + Set(packages).forEach { package in + sync.enter() + self.loadManagedManifest(for: package, observabilityScope: observabilityScope) { manifest in + defer { sync.leave() } + if let manifest { + manifests[package.identity] = manifest + } + } + } + + sync.notify(queue: .sharedConcurrent) { + completion(.success(manifests.get())) + } + } + + /// Loads the given manifest, if it is present in the managed dependencies. + private func loadManagedManifest( + for package: PackageReference, + observabilityScope: ObservabilityScope, + completion: @escaping (Manifest?) -> Void + ) { + // Check if this dependency is available. + // we also compare the location as this function may attempt to load + // dependencies that have the same identity but from a different location + // which is an error case we diagnose an report about in the GraphLoading part which + // is prepared to handle the case where not all manifest are available + guard let managedDependency = self.state.dependencies[comparingLocation: package] else { + return completion(.none) + } + + // Get the path of the package. + let packagePath = self.path(to: managedDependency) + + // The kind and version, if known. + let packageKind: PackageReference.Kind + let packageVersion: Version? + switch managedDependency.state { + case .sourceControlCheckout(let checkoutState): + packageKind = managedDependency.packageRef.kind + switch checkoutState { + case .version(let checkoutVersion, _): + packageVersion = checkoutVersion + default: + packageVersion = .none + } + case .registryDownload(let downloadedVersion): + packageKind = managedDependency.packageRef.kind + packageVersion = downloadedVersion + case .custom(let availableVersion, _): + packageKind = managedDependency.packageRef.kind + packageVersion = availableVersion + case .edited, .fileSystem: + packageKind = .fileSystem(packagePath) + packageVersion = .none + } + + let fileSystem: FileSystem? + do { + fileSystem = try self.getFileSystem( + package: package, + state: managedDependency.state, + observabilityScope: observabilityScope + ) + } catch { + // only warn here in case of issues since we should not even get here without a valid package container + observabilityScope.emit( + warning: "unexpected failure while accessing custom package container", + underlyingError: error + ) + fileSystem = nil + } + + // Load and return the manifest. + self.loadManifest( + packageIdentity: managedDependency.packageRef.identity, + packageKind: packageKind, + packagePath: packagePath, + packageLocation: managedDependency.packageRef.locationString, + packageVersion: packageVersion, + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) { result in + // error is added to diagnostics in the function above + completion(try? result.get()) + } + } + + /// Load the manifest at a given path. + /// + /// This is just a helper wrapper to the manifest loader. + func loadManifest( + packageIdentity: PackageIdentity, + packageKind: PackageReference.Kind, + packagePath: AbsolutePath, + packageLocation: String, + packageVersion: Version? = nil, + fileSystem: FileSystem? = nil, + observabilityScope: ObservabilityScope, + completion: @escaping (Result) -> Void + ) { + let fileSystem = fileSystem ?? self.fileSystem + + // Load the manifest, bracketed by the calls to the delegate callbacks. + delegate?.willLoadManifest( + packageIdentity: packageIdentity, + packagePath: packagePath, + url: packageLocation, + version: packageVersion, + packageKind: packageKind + ) + + let manifestLoadingScope = observabilityScope.makeChildScope(description: "Loading manifest") { + .packageMetadata(identity: packageIdentity, kind: packageKind) + } + + var manifestLoadingDiagnostics = [Diagnostic]() + + let start = DispatchTime.now() + self.manifestLoader.load( + packagePath: packagePath, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: packageVersion.map { (version: $0, revision: nil) }, + currentToolsVersion: self.currentToolsVersion, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + fileSystem: fileSystem, + observabilityScope: manifestLoadingScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) { result in + let duration = start.distance(to: .now()) + var result = result + switch result { + case .failure(let error): + manifestLoadingDiagnostics.append(.error(error)) + self.delegate?.didLoadManifest( + packageIdentity: packageIdentity, + packagePath: packagePath, + url: packageLocation, + version: packageVersion, + packageKind: packageKind, + manifest: nil, + diagnostics: manifestLoadingDiagnostics, + duration: duration + ) + case .success(let manifest): + let validator = ManifestValidator( + manifest: manifest, + sourceControlValidator: self.repositoryManager, + fileSystem: self.fileSystem + ) + let validationIssues = validator.validate() + if !validationIssues.isEmpty { + // Diagnostics.fatalError indicates that a more specific diagnostic has already been added. + result = .failure(Diagnostics.fatalError) + manifestLoadingDiagnostics.append(contentsOf: validationIssues) + } + self.delegate?.didLoadManifest( + packageIdentity: packageIdentity, + packagePath: packagePath, + url: packageLocation, + version: packageVersion, + packageKind: packageKind, + manifest: manifest, + diagnostics: manifestLoadingDiagnostics, + duration: duration + ) + } + manifestLoadingScope.emit(manifestLoadingDiagnostics) + completion(result) + } + } + + /// Validates that all the edited dependencies are still present in the file system. + /// If some checkout dependency is removed form the file system, clone it again. + /// If some edited dependency is removed from the file system, mark it as unedited and + /// fallback on the original checkout. + private func fixManagedDependencies(observabilityScope: ObservabilityScope) { + // Reset managed dependencies if the state file was removed during the lifetime of the Workspace object. + if !self.state.dependencies.isEmpty && !self.state.stateFileExists() { + try? self.state.reset() + } + + // Make a copy of dependencies as we might mutate them in the for loop. + let allDependencies = Array(self.state.dependencies) + for dependency in allDependencies { + observabilityScope.makeChildScope( + description: "copying managed dependencies", + metadata: dependency.packageRef.diagnosticsMetadata + ).trap { + // If the dependency is present, we're done. + let dependencyPath = self.path(to: dependency) + if fileSystem.isDirectory(dependencyPath) { + return + } + + switch dependency.state { + case .sourceControlCheckout(let checkoutState): + // If some checkout dependency has been removed, retrieve it again. + _ = try self.checkoutRepository( + package: dependency.packageRef, + at: checkoutState, + observabilityScope: observabilityScope + ) + observabilityScope + .emit(.checkedOutDependencyMissing(packageName: dependency.packageRef.identity.description)) + + case .registryDownload(let version): + // If some downloaded dependency has been removed, retrieve it again. + _ = try self.downloadRegistryArchive( + package: dependency.packageRef, + at: version, + observabilityScope: observabilityScope + ) + observabilityScope + .emit(.registryDependencyMissing(packageName: dependency.packageRef.identity.description)) + + case .custom(let version, let path): + let container = try temp_await { + self.packageContainerProvider.getContainer( + for: dependency.packageRef, + updateStrategy: .never, + observabilityScope: observabilityScope, + on: .sharedConcurrent, + completion: $0 + ) + } + if let customContainer = container as? CustomPackageContainer { + let newPath = try customContainer.retrieve(at: version, observabilityScope: observabilityScope) + observabilityScope + .emit(.customDependencyMissing(packageName: dependency.packageRef.identity.description)) + + // FIXME: We should be able to handle this case and also allow changed paths for registry and SCM downloads. + if newPath != path { + observabilityScope + .emit(error: "custom dependency was retrieved at a different path: \(newPath)") + } + } else { + observabilityScope.emit(error: "invalid custom dependency container: \(container)") + } + case .edited: + // If some edited dependency has been removed, mark it as unedited. + // + // Note: We don't resolve the dependencies when unediting + // here because we expect this method to be called as part + // of some other resolve operation (i.e. resolve, update, etc). + try self.unedit(dependency: dependency, forceRemove: true, observabilityScope: observabilityScope) + + observabilityScope + .emit(.editedDependencyMissing(packageName: dependency.packageRef.identity.description)) + + case .fileSystem: + self.state.dependencies.remove(dependency.packageRef.identity) + try self.state.save() + } + } + } + } + + private func getFileSystem( + package: PackageReference, + state: Workspace.ManagedDependency.State, + observabilityScope: ObservabilityScope + ) throws -> FileSystem? { + // Only custom containers may provide a file system. + guard self.customPackageContainerProvider != nil else { + return nil + } + + switch state { + // File-system based dependencies do not provide a custom file system object. + case .fileSystem: + return nil + case .custom: + let container = try temp_await { + self.packageContainerProvider.getContainer( + for: package, + updateStrategy: .never, + observabilityScope: observabilityScope, + on: .sharedConcurrent, + completion: $0 + ) + } + guard let customContainer = container as? CustomPackageContainer else { + observabilityScope.emit(error: "invalid custom dependency container: \(container)") + return nil + } + return try customContainer.getFileSystem() + default: + observabilityScope.emit(error: "invalid managed dependency state for custom dependency: \(state)") + return nil + } + } +} diff --git a/Sources/Workspace/Workspace+PackageContainer.swift b/Sources/Workspace/Workspace+PackageContainer.swift new file mode 100644 index 00000000000..4787e3adef4 --- /dev/null +++ b/Sources/Workspace/Workspace+PackageContainer.swift @@ -0,0 +1,103 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 class Basics.ObservabilityScope +import func Dispatch.dispatchPrecondition +import class Dispatch.DispatchQueue +import enum PackageFingerprint.FingerprintCheckingMode +import enum PackageGraph.ContainerUpdateStrategy +import protocol PackageGraph.PackageContainer +import protocol PackageGraph.PackageContainerProvider +import struct PackageModel.PackageReference + +// MARK: - Package container provider + +extension Workspace: PackageContainerProvider { + public func getContainer( + for package: PackageReference, + updateStrategy: ContainerUpdateStrategy, + observabilityScope: ObservabilityScope, + on queue: DispatchQueue, + completion: @escaping (Result) -> Void + ) { + do { + switch package.kind { + // If the container is local, just create and return a local package container. + case .root, .fileSystem: + let container = try FileSystemPackageContainer( + package: package, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + manifestLoader: self.manifestLoader, + currentToolsVersion: self.currentToolsVersion, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope + ) + queue.async { + completion(.success(container)) + } + // Resolve the container using the repository manager. + case .localSourceControl, .remoteSourceControl: + let repositorySpecifier = try package.makeRepositorySpecifier() + self.repositoryManager.lookup( + package: package.identity, + repository: repositorySpecifier, + updateStrategy: updateStrategy.repositoryUpdateStrategy, + observabilityScope: observabilityScope, + delegateQueue: queue, + callbackQueue: queue + ) { result in + dispatchPrecondition(condition: .onQueue(queue)) + // Create the container wrapper. + let result = result.tryMap { handle -> PackageContainer in + // Open the repository. + // + // FIXME: Do we care about holding this open for the lifetime of the container. + let repository = try handle.open() + return try SourceControlPackageContainer( + package: package, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + repositorySpecifier: repositorySpecifier, + repository: repository, + manifestLoader: self.manifestLoader, + currentToolsVersion: self.currentToolsVersion, + fingerprintStorage: self.fingerprints, + fingerprintCheckingMode: FingerprintCheckingMode + .map(self.configuration.fingerprintCheckingMode), + observabilityScope: observabilityScope + ) + } + completion(result) + } + // Resolve the container using the registry + case .registry: + let container = RegistryPackageContainer( + package: package, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + registryClient: self.registryClient, + manifestLoader: self.manifestLoader, + currentToolsVersion: self.currentToolsVersion, + observabilityScope: observabilityScope + ) + queue.async { + completion(.success(container)) + } + } + } catch { + queue.async { + completion(.failure(error)) + } + } + } +} diff --git a/Sources/Workspace/Workspace+Pinning.swift b/Sources/Workspace/Workspace+Pinning.swift new file mode 100644 index 00000000000..b51e69fb04f --- /dev/null +++ b/Sources/Workspace/Workspace+Pinning.swift @@ -0,0 +1,197 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 class Basics.ObservabilityScope +import class PackageGraph.PinsStore +import struct PackageModel.PackageReference +import struct PackageModel.ToolsVersion +import struct TSCUtility.Version + +extension Workspace { + /// Pins all of the current managed dependencies at their checkout state. + func saveResolvedFile( + pinsStore: PinsStore, + dependencyManifests: DependencyManifests, + originHash: String, + rootManifestsMinimumToolsVersion: ToolsVersion, + observabilityScope: ObservabilityScope + ) throws { + var dependenciesToPin = [ManagedDependency]() + let requiredDependencies = try dependencyManifests.requiredPackages.filter(\.kind.isPinnable) + for dependency in requiredDependencies { + if let managedDependency = self.state.dependencies[comparingLocation: dependency] { + dependenciesToPin.append(managedDependency) + } else if let managedDependency = self.state.dependencies[dependency.identity] { + observabilityScope + .emit( + info: "required dependency '\(dependency.identity)' from '\(dependency.locationString)' was not found in managed dependencies, using alternative location '\(managedDependency.packageRef.locationString)' instead" + ) + dependenciesToPin.append(ManagedDependency(packageRef: dependency, state: managedDependency.state, subpath: managedDependency.subpath)) + } else { + observabilityScope + .emit( + warning: "required dependency '\(dependency.identity)' from '\(dependency.locationString)' was not found in managed dependencies and will not be recorded in resolved file" + ) + } + } + + // try to load the pin store from disk so we can compare for any changes + // this is needed as we want to avoid re-writing the resolved files unless absolutely necessary + var needsUpdate = false + if let storedPinStore = try? self.pinsStore.load() { + // compare for any differences between the existing state and the stored one + // subtle changes between versions of SwiftPM could treat URLs differently + // in which case we don't want to cause unnecessary churn + if dependenciesToPin.count != storedPinStore.pins.count { + needsUpdate = true + } else { + for dependency in dependenciesToPin { + if let pin = storedPinStore.pins[comparingLocation: dependency.packageRef] { + if pin.state != PinsStore.Pin(dependency)?.state { + needsUpdate = true + break + } + } else { + needsUpdate = true + break + } + } + } + } else { + needsUpdate = true + } + + // exist early is there is nothing to do + if !needsUpdate { + return + } + + // reset the pinsStore and start pinning the required dependencies. + pinsStore.unpinAll() + for dependency in dependenciesToPin { + pinsStore.pin(dependency) + } + + observabilityScope.trap { + try pinsStore.saveState( + toolsVersion: rootManifestsMinimumToolsVersion, + originHash: originHash + ) + } + + // Ask resolved file watcher to update its value so we don't fire + // an extra event if the file was modified by us. + self.resolvedFileWatcher?.updateValue() + } + + /// Watch the Package.resolved for changes. + /// + /// This is useful if clients want to be notified when the Package.resolved + /// file is changed *outside* of libSwiftPM operations. For example, as part + /// of a git operation. + public func watchResolvedFile() throws { + // Return if we're already watching it. + guard self.resolvedFileWatcher == nil else { return } + self + .resolvedFileWatcher = try ResolvedFileWatcher( + resolvedFile: self.location + .resolvedVersionsFile + ) { [weak self] in + self?.delegate?.resolvedFileChanged() + } + } +} + +extension PinsStore { + /// Pin a managed dependency at its checkout state. + /// + /// This method does nothing if the dependency is in edited state. + func pin(_ dependency: Workspace.ManagedDependency) { + if let pin = PinsStore.Pin(dependency) { + self.add(pin) + } + } +} + +extension PinsStore.Pin { + fileprivate init?(_ dependency: Workspace.ManagedDependency) { + switch dependency.state { + case .sourceControlCheckout(.version(let version, let revision)): + self.init( + packageRef: dependency.packageRef, + state: .version(version, revision: revision.identifier) + ) + case .sourceControlCheckout(.branch(let branch, let revision)): + self.init( + packageRef: dependency.packageRef, + state: .branch(name: branch, revision: revision.identifier) + ) + case .sourceControlCheckout(.revision(let revision)): + self.init( + packageRef: dependency.packageRef, + state: .revision(revision.identifier) + ) + case .registryDownload(let version): + self.init( + packageRef: dependency.packageRef, + state: .version(version, revision: .none) + ) + case .edited, .fileSystem, .custom: + // NOOP + return nil + } + } +} + +extension PackageReference.Kind { + var isPinnable: Bool { + switch self { + case .remoteSourceControl, .localSourceControl, .registry: + return true + default: + return false + } + } +} + +extension PinsStore.PinState { + func equals(_ checkoutState: CheckoutState) -> Bool { + switch (self, checkoutState) { + case (.version(let lversion, let lrevision), .version(let rversion, let rrevision)): + return lversion == rversion && lrevision == rrevision.identifier + case (.branch(let lbranch, let lrevision), .branch(let rbranch, let rrevision)): + return lbranch == rbranch && lrevision == rrevision.identifier + case (.revision(let lrevision), .revision(let rrevision)): + return lrevision == rrevision.identifier + default: + return false + } + } + + func equals(_: Version) -> Bool { + switch self { + case .version(let version, _): + return version == version + default: + return false + } + } +} + +extension PinsStore.Pins { + subscript(comparingLocation package: PackageReference) -> PinsStore.Pin? { + if let pin = self[package.identity], pin.packageRef.equalsIncludingLocation(package) { + return pin + } + return .none + } +} diff --git a/Sources/Workspace/Workspace+Registry.swift b/Sources/Workspace/Workspace+Registry.swift new file mode 100644 index 00000000000..f1405227b6c --- /dev/null +++ b/Sources/Workspace/Workspace+Registry.swift @@ -0,0 +1,448 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import protocol Basics.FileSystem +import struct Basics.InternalError +import class Basics.ObservabilityScope +import struct Basics.SourceControlURL +import class Basics.ThreadSafeKeyValueStore +import func Basics.temp_await +import class PackageGraph.PinsStore +import protocol PackageLoading.ManifestLoaderProtocol +import protocol PackageModel.DependencyMapper +import protocol PackageModel.IdentityResolver +import class PackageModel.Manifest +import enum PackageModel.PackageDependency +import struct PackageModel.PackageIdentity +import struct PackageModel.PackageReference +import struct PackageModel.TargetDescription +import struct PackageModel.ToolsVersion +import class PackageRegistry.RegistryClient +import struct TSCUtility.Version + +// Need to import the whole module to get access to `+` operator on `DispatchTimeInterval` +import Dispatch + +extension Workspace { + // the goal of this code is to help align dependency identities across source control and registry origins + // the issue this solves is that dependencies will have different identities across the origins + // for example, source control based dependency on http://github.com/apple/swift-nio would have an identifier of + // "swift-nio" + // while in the registry, the same package will [likely] have an identifier of "apple.swift-nio" + // since there is not generally fire sure way to translate one system to the other (urls can vary widely, so the + // best we would be able to do is guess) + // what this code does is query the registry of it "knows" what the registry identity of URL is, and then use the + // registry identity instead of the URL bases one + // the code also supports a "full swizzle" mode in which it _replaces_ the source control dependency with a registry + // one which encourages the transition + // from source control based dependencies to registry based ones + + // TODO: + // 1. handle mixed situation when some versions on the registry but some on source control. we need a second lookup + // to make sure the version exists + // 2. handle registry returning multiple identifiers, how do we choose the right one? + struct RegistryAwareManifestLoader: ManifestLoaderProtocol { + private let underlying: ManifestLoaderProtocol + private let registryClient: RegistryClient + private let transformationMode: TransformationMode + + private let cacheTTL = DispatchTimeInterval.seconds(300) // 5m + private let identityLookupCache = ThreadSafeKeyValueStore< + SourceControlURL, + (result: Result, expirationTime: DispatchTime) + >() + + init( + underlying: ManifestLoaderProtocol, + registryClient: RegistryClient, + transformationMode: TransformationMode + ) { + self.underlying = underlying + self.registryClient = registryClient + self.transformationMode = transformationMode + } + + func load( + manifestPath: AbsolutePath, + manifestToolsVersion: ToolsVersion, + packageIdentity: PackageIdentity, + packageKind: PackageReference.Kind, + packageLocation: String, + packageVersion: (version: Version?, revision: String?)?, + identityResolver: any IdentityResolver, + dependencyMapper: any DependencyMapper, + fileSystem: any FileSystem, + observabilityScope: ObservabilityScope, + delegateQueue: DispatchQueue, + callbackQueue: DispatchQueue, + completion: @escaping (Result) -> Void + ) { + self.underlying.load( + manifestPath: manifestPath, + manifestToolsVersion: manifestToolsVersion, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: packageVersion, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + delegateQueue: delegateQueue, + callbackQueue: callbackQueue + ) { result in + switch result { + case .failure(let error): + completion(.failure(error)) + case .success(let manifest): + self.transformSourceControlDependenciesToRegistry( + manifest: manifest, + transformationMode: transformationMode, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: completion + ) + } + } + } + + func resetCache(observabilityScope: ObservabilityScope) { + self.underlying.resetCache(observabilityScope: observabilityScope) + } + + func purgeCache(observabilityScope: ObservabilityScope) { + self.underlying.purgeCache(observabilityScope: observabilityScope) + } + + private func transformSourceControlDependenciesToRegistry( + manifest: Manifest, + transformationMode: TransformationMode, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue, + completion: @escaping (Result) -> Void + ) { + let sync = DispatchGroup() + let transformations = ThreadSafeKeyValueStore() + for dependency in manifest.dependencies { + if case .sourceControl(let settings) = dependency, case .remote(let url) = settings.location { + sync.enter() + self.mapRegistryIdentity( + url: url, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue + ) { result in + defer { sync.leave() } + switch result { + case .failure(let error): + // do not raise error, only report it as warning + observabilityScope.emit( + warning: "failed querying registry identity for '\(url)'", + underlyingError: error + ) + case .success(.some(let identity)): + transformations[dependency] = identity + case .success(.none): + // no identity found + break + } + } + } + } + + // update the manifest with the transformed dependencies + sync.notify(queue: callbackQueue) { + do { + let updatedManifest = try self.transformManifest( + manifest: manifest, + transformations: transformations.get(), + transformationMode: transformationMode, + observabilityScope: observabilityScope + ) + completion(.success(updatedManifest)) + } catch { + return completion(.failure(error)) + } + } + } + + private func transformManifest( + manifest: Manifest, + transformations: [PackageDependency: PackageIdentity], + transformationMode: TransformationMode, + observabilityScope: ObservabilityScope + ) throws -> Manifest { + var targetDependencyPackageNameTransformations = [String: String]() + + var modifiedDependencies = [PackageDependency]() + for dependency in manifest.dependencies { + var modifiedDependency = dependency + if let registryIdentity = transformations[dependency] { + guard case .sourceControl(let settings) = dependency, case .remote = settings.location else { + // an implementation mistake + throw InternalError("unexpected non-source-control dependency: \(dependency)") + } + switch transformationMode { + case .identity: + // we replace the *identity* of the dependency in order to align the identities + // and de-dupe across source control and registry origins + observabilityScope + .emit( + info: "adjusting '\(dependency.locationString)' identity to registry identity of '\(registryIdentity)'." + ) + modifiedDependency = .sourceControl( + identity: registryIdentity, + nameForTargetDependencyResolutionOnly: settings.nameForTargetDependencyResolutionOnly, + location: settings.location, + requirement: settings.requirement, + productFilter: settings.productFilter + ) + case .swizzle: + // we replace the *entire* source control dependency with a registry one + // this helps de-dupe across source control and registry dependencies + // and also encourages use of registry over source control + switch settings.requirement { + case .exact, .range: + let requirement = try settings.requirement.asRegistryRequirement() + observabilityScope + .emit( + info: "swizzling '\(dependency.locationString)' with registry dependency '\(registryIdentity)'." + ) + targetDependencyPackageNameTransformations[dependency + .nameForTargetDependencyResolutionOnly] = registryIdentity.description + modifiedDependency = .registry( + identity: registryIdentity, + requirement: requirement, + productFilter: settings.productFilter + ) + case .branch, .revision: + // branch and revision dependencies are not supported by the registry + // in such case, the best we can do is to replace the *identity* of the + // source control dependency in order to align the identities + // and de-dupe across source control and registry origins + observabilityScope + .emit( + info: "adjusting '\(dependency.locationString)' identity to registry identity of '\(registryIdentity)'." + ) + modifiedDependency = .sourceControl( + identity: registryIdentity, + nameForTargetDependencyResolutionOnly: settings.nameForTargetDependencyResolutionOnly, + location: settings.location, + requirement: settings.requirement, + productFilter: settings.productFilter + ) + } + } + } + modifiedDependencies.append(modifiedDependency) + } + + var modifiedTargets = manifest.targets + if !transformations.isEmpty { + modifiedTargets = [] + for target in manifest.targets { + var modifiedDependencies = [TargetDescription.Dependency]() + for dependency in target.dependencies { + var modifiedDependency = dependency + if case .product(let name, let packageName, let moduleAliases, let condition) = dependency, + let packageName + { + // makes sure we use the updated package name for target based dependencies + if let modifiedPackageName = targetDependencyPackageNameTransformations[packageName] { + modifiedDependency = .product( + name: name, + package: modifiedPackageName, + moduleAliases: moduleAliases, + condition: condition + ) + } + } + modifiedDependencies.append(modifiedDependency) + } + + try modifiedTargets.append( + TargetDescription( + name: target.name, + dependencies: modifiedDependencies, + path: target.path, + url: target.url, + exclude: target.exclude, + sources: target.sources, + resources: target.resources, + publicHeadersPath: target.publicHeadersPath, + type: target.type, + packageAccess: target.packageAccess, + pkgConfig: target.pkgConfig, + providers: target.providers, + pluginCapability: target.pluginCapability, + settings: target.settings, + checksum: target.checksum, + pluginUsages: target.pluginUsages + ) + ) + } + } + + let modifiedManifest = Manifest( + displayName: manifest.displayName, + path: manifest.path, + packageKind: manifest.packageKind, + packageLocation: manifest.packageLocation, + defaultLocalization: manifest.defaultLocalization, + platforms: manifest.platforms, + version: manifest.version, + revision: manifest.revision, + toolsVersion: manifest.toolsVersion, + pkgConfig: manifest.pkgConfig, + providers: manifest.providers, + cLanguageStandard: manifest.cLanguageStandard, + cxxLanguageStandard: manifest.cxxLanguageStandard, + swiftLanguageVersions: manifest.swiftLanguageVersions, + dependencies: modifiedDependencies, + products: manifest.products, + targets: modifiedTargets + ) + + return modifiedManifest + } + + private func mapRegistryIdentity( + url: SourceControlURL, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue, + completion: @escaping (Result) -> Void + ) { + if let cached = self.identityLookupCache[url], cached.expirationTime > .now() { + switch cached.result { + case .success(let identity): + return completion(.success(identity)) + case .failure: + // server error, do not try again + return completion(.success(.none)) + } + } + + self.registryClient.lookupIdentities( + scmURL: url, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue + ) { result in + switch result { + case .failure(let error): + self.identityLookupCache[url] = (result: .failure(error), expirationTime: .now() + self.cacheTTL) + completion(.failure(error)) + case .success(let identities): + // FIXME: returns first result... need to consider how to address multiple ones + let identity = identities.sorted().first + self.identityLookupCache[url] = (result: .success(identity), expirationTime: .now() + self.cacheTTL) + completion(.success(identity)) + } + } + } + + enum TransformationMode { + case identity + case swizzle + + init?(_ seed: WorkspaceConfiguration.SourceControlToRegistryDependencyTransformation) { + switch seed { + case .identity: + self = .identity + case .swizzle: + self = .swizzle + case .disabled: + return nil + } + } + } + } +} + +extension PackageDependency.SourceControl.Requirement { + fileprivate func asRegistryRequirement() throws -> PackageDependency.Registry.Requirement { + switch self { + case .range(let versions): + return .range(versions) + case .exact(let version): + return .exact(version) + case .branch, .revision: + throw InternalError("invalid source control to registry requirement tranformation") + } + } +} + +// MARK: - Registry Source archive management + +extension Workspace { + func downloadRegistryArchive( + package: PackageReference, + at version: Version, + observabilityScope: ObservabilityScope + ) throws -> AbsolutePath { + // FIXME: this should not block + let downloadPath = try temp_await { + self.registryDownloadsManager.lookup( + package: package.identity, + version: version, + observabilityScope: observabilityScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent, + completion: $0 + ) + } + + // Record the new state. + observabilityScope.emit( + debug: "adding '\(package.identity)' (\(package.locationString)) to managed dependencies", + metadata: package.diagnosticsMetadata + ) + try self.state.dependencies.add( + .registryDownload( + packageRef: package, + version: version, + subpath: downloadPath.relative(to: self.location.registryDownloadDirectory) + ) + ) + try self.state.save() + + return downloadPath + } + + func downloadRegistryArchive( + package: PackageReference, + at pinState: PinsStore.PinState, + observabilityScope: ObservabilityScope + ) throws -> AbsolutePath { + switch pinState { + case .version(let version, _): + return try self.downloadRegistryArchive( + package: package, + at: version, + observabilityScope: observabilityScope + ) + default: + throw InternalError("invalid pin state: \(pinState)") + } + } + + func removeRegistryArchive(for dependency: ManagedDependency) throws { + guard case .registryDownload = dependency.state else { + throw InternalError("cannot remove source archive for \(dependency) with state \(dependency.state)") + } + + let downloadPath = self.location.registryDownloadSubdirectory(for: dependency) + try self.fileSystem.removeFileTree(downloadPath) + + // remove the local copy + try registryDownloadsManager.remove(package: dependency.packageRef.identity) + } +} diff --git a/Sources/Workspace/Workspace+Signing.swift b/Sources/Workspace/Workspace+Signing.swift new file mode 100644 index 00000000000..1da24b02d0e --- /dev/null +++ b/Sources/Workspace/Workspace+Signing.swift @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 enum PackageFingerprint.FingerprintCheckingMode +import struct PackageGraph.PackageGraph +import struct PackageModel.PackageIdentity +import struct PackageModel.RegistryReleaseMetadata +import enum PackageSigning.SigningEntityCheckingMode + +extension FingerprintCheckingMode { + static func map(_ checkingMode: WorkspaceConfiguration.CheckingMode) -> FingerprintCheckingMode { + switch checkingMode { + case .strict: + return .strict + case .warn: + return .warn + } + } +} + +extension SigningEntityCheckingMode { + static func map(_ checkingMode: WorkspaceConfiguration.CheckingMode) -> SigningEntityCheckingMode { + switch checkingMode { + case .strict: + return .strict + case .warn: + return .warn + } + } +} + +// MARK: - Signatures + +extension Workspace { + func validateSignatures( + packageGraph: PackageGraph, + expectedSigningEntities: [PackageIdentity: RegistryReleaseMetadata.SigningEntity] + ) throws { + try expectedSigningEntities.forEach { identity, expectedSigningEntity in + if let package = packageGraph.packages.first(where: { $0.identity == identity }) { + guard let actualSigningEntity = package.registryMetadata?.signature?.signedBy else { + throw SigningError.unsigned(package: identity, expected: expectedSigningEntity) + } + if actualSigningEntity != expectedSigningEntity { + throw SigningError.mismatchedSigningEntity( + package: identity, + expected: expectedSigningEntity, + actual: actualSigningEntity + ) + } + } else { + guard let mirror = self.mirrors.mirror(for: identity.description) else { + throw SigningError.expectedIdentityNotFound(package: identity) + } + let mirroredIdentity = PackageIdentity.plain(mirror) + guard mirroredIdentity.isRegistry else { + throw SigningError.expectedSignedMirroredToSourceControl( + package: identity, + expected: expectedSigningEntity + ) + } + guard let package = packageGraph.packages.first(where: { $0.identity == mirroredIdentity }) else { + // Unsure if this case is reachable in practice. + throw SigningError.expectedIdentityNotFound(package: identity) + } + guard let actualSigningEntity = package.registryMetadata?.signature?.signedBy else { + throw SigningError.unsigned(package: identity, expected: expectedSigningEntity) + } + if actualSigningEntity != expectedSigningEntity { + throw SigningError.mismatchedSigningEntity( + package: identity, + expected: expectedSigningEntity, + actual: actualSigningEntity + ) + } + } + } + } + + public enum SigningError: Swift.Error { + case expectedIdentityNotFound(package: PackageIdentity) + case expectedSignedMirroredToSourceControl( + package: PackageIdentity, + expected: RegistryReleaseMetadata.SigningEntity + ) + case mismatchedSigningEntity( + package: PackageIdentity, + expected: RegistryReleaseMetadata.SigningEntity, + actual: RegistryReleaseMetadata.SigningEntity + ) + case unsigned(package: PackageIdentity, expected: RegistryReleaseMetadata.SigningEntity) + } +} diff --git a/Sources/Workspace/Workspace+SourceControl.swift b/Sources/Workspace/Workspace+SourceControl.swift new file mode 100644 index 00000000000..810234d5b2a --- /dev/null +++ b/Sources/Workspace/Workspace+SourceControl.swift @@ -0,0 +1,269 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import struct Basics.InternalError +import class Basics.ObservabilityScope +import func Basics.temp_await +import struct Dispatch.DispatchTime +import enum PackageGraph.PackageRequirement +import class PackageGraph.PinsStore +import struct PackageModel.PackageReference +import struct SourceControl.Revision +import struct TSCUtility.Version + +// FIXME: this mixes quite a bit of workspace logic with repository specific one +// need to better separate the concerns +extension Workspace { + /// Create a local clone of the given `repository` checked out to `checkoutState`. + /// + /// If an existing clone is present, the repository will be reset to the + /// requested revision, if necessary. + /// + /// - Parameters: + /// - package: The package to clone. + /// - checkoutState: The state to check out. + /// - Returns: The path of the local repository. + /// - Throws: If the operation could not be satisfied. + func checkoutRepository( + package: PackageReference, + at checkoutState: CheckoutState, + observabilityScope: ObservabilityScope + ) throws -> AbsolutePath { + let repository = try package.makeRepositorySpecifier() + + // first fetch the repository + let checkoutPath = try self.fetchRepository( + package: package, + at: checkoutState.revision, + observabilityScope: observabilityScope + ) + + // Check out the given revision. + let workingCopy = try self.repositoryManager.openWorkingCopy(at: checkoutPath) + + // Inform the delegate that we're about to start. + delegate?.willCheckOut( + package: package.identity, + repository: repository.location.description, + revision: checkoutState.description, + at: checkoutPath + ) + let start = DispatchTime.now() + + // Do mutable-immutable dance because checkout operation modifies the disk state. + try fileSystem.chmod(.userWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) + try workingCopy.checkout(revision: checkoutState.revision) + try? fileSystem.chmod(.userUnWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) + + // Record the new state. + observabilityScope.emit( + debug: "adding '\(package.identity)' (\(package.locationString)) to managed dependencies", + metadata: package.diagnosticsMetadata + ) + try self.state.dependencies.add( + .sourceControlCheckout( + packageRef: package, + state: checkoutState, + subpath: checkoutPath.relative(to: self.location.repositoriesCheckoutsDirectory) + ) + ) + try self.state.save() + + // Inform the delegate that we're done. + let duration = start.distance(to: .now()) + delegate?.didCheckOut( + package: package.identity, + repository: repository.location.description, + revision: checkoutState.description, + at: checkoutPath, + duration: duration + ) + observabilityScope + .emit(debug: "`\(repository.location.description)` checked out at \(checkoutState.debugDescription)") + + return checkoutPath + } + + func checkoutRepository( + package: PackageReference, + at pinState: PinsStore.PinState, + observabilityScope: ObservabilityScope + ) throws -> AbsolutePath { + switch pinState { + case .version(let version, revision: let revision) where revision != nil: + return try self.checkoutRepository( + package: package, + at: .version(version, revision: .init(identifier: revision!)), // nil checked above + observabilityScope: observabilityScope + ) + case .branch(let branch, revision: let revision): + return try self.checkoutRepository( + package: package, + at: .branch(name: branch, revision: .init(identifier: revision)), + observabilityScope: observabilityScope + ) + case .revision(let revision): + return try self.checkoutRepository( + package: package, + at: .revision(.init(identifier: revision)), + observabilityScope: observabilityScope + ) + default: + throw InternalError("invalid pin state: \(pinState)") + } + } + + /// Fetch a given `package` and create a local checkout for it. + /// + /// This will first clone the repository into the canonical repositories + /// location, if necessary, and then check it out from there. + /// + /// - Returns: The path of the local repository. + /// - Throws: If the operation could not be satisfied. + private func fetchRepository( + package: PackageReference, + at revision: Revision, + observabilityScope: ObservabilityScope + ) throws -> AbsolutePath { + let repository = try package.makeRepositorySpecifier() + + // If we already have it, fetch to update the repo from its remote. + // also compare the location as it may have changed + if let dependency = self.state.dependencies[comparingLocation: package] { + let checkoutPath = self.location.repositoriesCheckoutSubdirectory(for: dependency) + + // Make sure the directory is not missing (we will have to clone again if not). + // This can become invalid if the build directory is moved. + fetch: if self.fileSystem.isDirectory(checkoutPath) { + // Fetch the checkout in case there are updates available. + let workingCopy = try self.repositoryManager.openWorkingCopy(at: checkoutPath) + + // Ensure that the alternative object store is still valid. + guard try self.repositoryManager.isValidWorkingCopy(workingCopy, for: repository) else { + observabilityScope + .emit( + debug: "working copy at '\(checkoutPath)' does not align with expected local path of '\(repository)'" + ) + break fetch + } + + // only update if necessary + if !workingCopy.exists(revision: revision) { + // The fetch operation may update contents of the checkout, + // so we need to do mutable-immutable dance. + try self.fileSystem.chmod(.userWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) + try workingCopy.fetch() + try? self.fileSystem.chmod(.userUnWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) + } + + return checkoutPath + } + } + + // If not, we need to get the repository from the checkouts. + // FIXME: this should not block + let handle = try temp_await { + self.repositoryManager.lookup( + package: package.identity, + repository: repository, + updateStrategy: .never, + observabilityScope: observabilityScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent, + completion: $0 + ) + } + + // Clone the repository into the checkouts. + let checkoutPath = self.location.repositoriesCheckoutsDirectory.appending(component: repository.basename) + + // Remove any existing content at that path. + try self.fileSystem.chmod(.userWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) + try self.fileSystem.removeFileTree(checkoutPath) + + // Inform the delegate that we're about to start. + self.delegate?.willCreateWorkingCopy( + package: package.identity, + repository: handle.repository.location.description, + at: checkoutPath + ) + let start = DispatchTime.now() + + // Create the working copy. + _ = try handle.createWorkingCopy(at: checkoutPath, editable: false) + + // Inform the delegate that we're done. + let duration = start.distance(to: .now()) + self.delegate?.didCreateWorkingCopy( + package: package.identity, + repository: handle.repository.location.description, + at: checkoutPath, + duration: duration + ) + + return checkoutPath + } + + /// Removes the clone and checkout of the provided specifier. + func removeRepository(dependency: ManagedDependency) throws { + guard case .sourceControlCheckout = dependency.state else { + throw InternalError("cannot remove repository for \(dependency) with state \(dependency.state)") + } + + // Remove the checkout. + let dependencyPath = self.location.repositoriesCheckoutSubdirectory(for: dependency) + let workingCopy = try self.repositoryManager.openWorkingCopy(at: dependencyPath) + guard !workingCopy.hasUncommittedChanges() else { + throw WorkspaceDiagnostics.UncommitedChanges(repositoryPath: dependencyPath) + } + + try self.fileSystem.chmod(.userWritable, path: dependencyPath, options: [.recursive, .onlyFiles]) + try self.fileSystem.removeFileTree(dependencyPath) + + // Remove the clone. + try self.repositoryManager.remove(repository: dependency.packageRef.makeRepositorySpecifier()) + } +} + +extension CheckoutState { + var revision: Revision { + switch self { + case .revision(let revision): + return revision + case .version(_, let revision): + return revision + case .branch(_, let revision): + return revision + } + } + + var isBranchOrRevisionBased: Bool { + switch self { + case .revision, .branch: + return true + case .version: + return false + } + } + + var requirement: PackageRequirement { + switch self { + case .revision(let revision): + return .revision(revision.identifier) + case .version(let version, _): + return .versionSet(.exact(version)) + case .branch(let branch, _): + return .revision(branch) + } + } +} diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index f5e152e34ed..ec4ef688a55 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -73,11 +73,11 @@ public final class WorkspaceState { /// Returns true if the state file exists on the filesystem. func stateFileExists() -> Bool { - return self.storage.fileExists() + self.storage.fileExists() } /// Returns true if the state file exists on the filesystem. - func reload() throws { + func reload() throws { let storedState = try self.storage.load() self.dependencies = storedState.dependencies self.artifacts = storedState.artifacts @@ -86,7 +86,7 @@ public final class WorkspaceState { // MARK: - Serialization -fileprivate struct WorkspaceStateStorage { +private struct WorkspaceStateStorage { private let path: AbsolutePath private let fileSystem: FileSystem private let encoder = JSONEncoder.makeWithDefaults() @@ -97,7 +97,7 @@ fileprivate struct WorkspaceStateStorage { self.fileSystem = fileSystem } - func load() throws -> (dependencies: Workspace.ManagedDependencies, artifacts: Workspace.ManagedArtifacts){ + func load() throws -> (dependencies: Workspace.ManagedDependencies, artifacts: Workspace.ManagedArtifacts) { if !self.fileSystem.exists(self.path) { return (dependencies: .init(), artifacts: .init()) } @@ -105,20 +105,20 @@ fileprivate struct WorkspaceStateStorage { return try self.fileSystem.withLock(on: self.path, type: .shared) { let version = try decoder.decode(path: self.path, fileSystem: self.fileSystem, as: Version.self) switch version.version { - case 1,2,3,4: + case 1, 2, 3, 4: let v4 = try self.decoder.decode(path: self.path, fileSystem: self.fileSystem, as: V4.self) - let dependencies = try v4.object.dependencies.map{ try Workspace.ManagedDependency($0) } - let artifacts = try v4.object.artifacts.map{ try Workspace.ManagedArtifact($0) } + let dependencies = try v4.object.dependencies.map { try Workspace.ManagedDependency($0) } + let artifacts = try v4.object.artifacts.map { try Workspace.ManagedArtifact($0) } return try (dependencies: .init(dependencies), artifacts: .init(artifacts)) case 5: let v5 = try self.decoder.decode(path: self.path, fileSystem: self.fileSystem, as: V5.self) - let dependencies = try v5.object.dependencies.map{ try Workspace.ManagedDependency($0) } - let artifacts = try v5.object.artifacts.map{ try Workspace.ManagedArtifact($0) } + let dependencies = try v5.object.dependencies.map { try Workspace.ManagedDependency($0) } + let artifacts = try v5.object.artifacts.map { try Workspace.ManagedArtifact($0) } return try (dependencies: .init(dependencies), artifacts: .init(artifacts)) case 6: let v6 = try self.decoder.decode(path: self.path, fileSystem: self.fileSystem, as: V6.self) - let dependencies = try v6.object.dependencies.map{ try Workspace.ManagedDependency($0) } - let artifacts = try v6.object.artifacts.map{ try Workspace.ManagedArtifact($0) } + let dependencies = try v6.object.dependencies.map { try Workspace.ManagedDependency($0) } + let artifacts = try v6.object.artifacts.map { try Workspace.ManagedArtifact($0) } return try (dependencies: .init(dependencies), artifacts: .init(artifacts)) default: throw StringError("unknown 'WorkspaceStateStorage' version '\(version.version)' at '\(self.path)'") @@ -149,7 +149,7 @@ fileprivate struct WorkspaceStateStorage { } func fileExists() -> Bool { - return self.fileSystem.exists(self.path) + self.fileSystem.exists(self.path) } } @@ -168,7 +168,7 @@ extension WorkspaceStateStorage { let version: Int let object: Container - init (dependencies: Workspace.ManagedDependencies, artifacts: Workspace.ManagedArtifacts) { + init(dependencies: Workspace.ManagedDependencies, artifacts: Workspace.ManagedArtifacts) { self.version = 6 self.object = .init( dependencies: dependencies.map { .init($0) }.sorted { $0.packageRef.identity < $1.packageRef.identity }, @@ -241,7 +241,10 @@ extension WorkspaceStateStorage { self.underlying = underlying } - static func decode(container: KeyedDecodingContainer, basedOn: Dependency?) throws -> State { + static func decode( + container: KeyedDecodingContainer, + basedOn: Dependency? + ) throws -> State { let kind = try container.decode(String.self, forKey: .name) switch kind { case "local", "fileSystem": @@ -252,14 +255,21 @@ extension WorkspaceStateStorage { return try self.init(underlying: .sourceControlCheckout(.init(checkout))) case "registryDownload": let version = try container.decode(String.self, forKey: .version) - return try self.init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) + return try self + .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) - return try self.init(underlying: .edited(basedOn: basedOn.map { try .init($0) }, unmanagedPath: path)) + return try self.init(underlying: .edited( + basedOn: basedOn.map { try .init($0) }, + unmanagedPath: path + )) case "custom": let version = try container.decode(String.self, forKey: .version) let path = try container.decode(AbsolutePath.self, forKey: .path) - return try self.init(underlying: .custom(version: TSCUtility.Version(versionString: version), path: path)) + return try self.init(underlying: .custom( + version: TSCUtility.Version(versionString: version), + path: path + )) default: throw StringError("unknown dependency state \(kind)") } @@ -412,7 +422,7 @@ extension WorkspaceStateStorage { let location: String let name: String - init (_ reference: PackageModel.PackageReference) { + init(_ reference: PackageModel.PackageReference) { self.identity = reference.identity.description switch reference.kind { case .root(let path): @@ -451,7 +461,7 @@ extension Workspace.ManagedDependency { try self.init( packageRef: .init(dependency.packageRef), state: dependency.state.underlying, - subpath: try RelativePath(validating: dependency.subpath) + subpath: RelativePath(validating: dependency.subpath) ) } } @@ -462,7 +472,7 @@ extension Workspace.ManagedArtifact { packageRef: .init(artifact.packageRef), targetName: artifact.targetName, source: artifact.source.underlying, - path: try AbsolutePath(validating: artifact.path), + path: AbsolutePath(validating: artifact.path), kind: artifact.kind.underlying ) } @@ -514,7 +524,7 @@ extension WorkspaceStateStorage { let version: Int let object: Container - init (dependencies: Workspace.ManagedDependencies, artifacts: Workspace.ManagedArtifacts) { + init(dependencies: Workspace.ManagedDependencies, artifacts: Workspace.ManagedArtifacts) { self.version = 5 self.object = .init( dependencies: dependencies.map { .init($0) }.sorted { $0.packageRef.identity < $1.packageRef.identity }, @@ -587,7 +597,10 @@ extension WorkspaceStateStorage { self.underlying = underlying } - static func decode(container: KeyedDecodingContainer, basedOn: Dependency?) throws -> State { + static func decode( + container: KeyedDecodingContainer, + basedOn: Dependency? + ) throws -> State { let kind = try container.decode(String.self, forKey: .name) switch kind { case "local", "fileSystem": @@ -598,14 +611,21 @@ extension WorkspaceStateStorage { return try self.init(underlying: .sourceControlCheckout(.init(checkout))) case "registryDownload": let version = try container.decode(String.self, forKey: .version) - return try self.init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) + return try self + .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) - return try self.init(underlying: .edited(basedOn: basedOn.map { try .init($0) }, unmanagedPath: path)) + return try self.init(underlying: .edited( + basedOn: basedOn.map { try .init($0) }, + unmanagedPath: path + )) case "custom": let version = try container.decode(String.self, forKey: .version) let path = try container.decode(AbsolutePath.self, forKey: .path) - return try self.init(underlying: .custom(version: TSCUtility.Version(versionString: version), path: path)) + return try self.init(underlying: .custom( + version: TSCUtility.Version(versionString: version), + path: path + )) default: throw StringError("unknown dependency state \(kind)") } @@ -728,7 +748,7 @@ extension WorkspaceStateStorage { let location: String let name: String - init (_ reference: PackageModel.PackageReference) { + init(_ reference: PackageModel.PackageReference) { self.identity = reference.identity.description switch reference.kind { case .root(let path): @@ -823,7 +843,6 @@ extension CheckoutState { } } - // MARK: - V1...4 format (deprecated) extension WorkspaceStateStorage { @@ -884,7 +903,11 @@ extension WorkspaceStateStorage { self.underlying = underlying } - static func decode(container: KeyedDecodingContainer, packageRef: PackageReference, basedOn: Dependency?) throws -> State { + static func decode( + container: KeyedDecodingContainer, + packageRef: PackageReference, + basedOn: Dependency? + ) throws -> State { let kind = try container.decode(String.self, forKey: .name) switch kind { case "local": @@ -894,7 +917,10 @@ extension WorkspaceStateStorage { return try self.init(underlying: .sourceControlCheckout(.init(checkout))) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) - return try self.init(underlying: .edited(basedOn: basedOn.map { try .init($0) }, unmanagedPath: path)) + return try self.init(underlying: .edited( + basedOn: basedOn.map { try .init($0) }, + unmanagedPath: path + )) default: throw StringError("unknown dependency state \(kind)") } @@ -1066,7 +1092,7 @@ extension CheckoutState { extension BinaryTarget.Kind { fileprivate static func forPath(_ path: AbsolutePath) -> Self { - if let kind = Self.allCases.first(where: { $0.fileExtension == path.extension }) { + if let kind = allCases.first(where: { $0.fileExtension == path.extension }) { return kind } return .unknown diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 516212eff57..ac27f2c3220 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -13,23 +13,22 @@ import _Concurrency import Basics import Foundation -import OrderedCollections -import PackageLoading -import PackageModel import PackageFingerprint import PackageGraph +import PackageLoading +import PackageModel import PackageRegistry import PackageSigning import SourceControl +import func TSCBasic.findCycle import protocol TSCBasic.HashAlgorithm import class TSCBasic.InMemoryFileSystem import struct TSCBasic.KeyedPair -import var TSCBasic.stderrStream import struct TSCBasic.SHA256 +import var TSCBasic.stderrStream import func TSCBasic.topologicalSort import func TSCBasic.transitiveClosure -import func TSCBasic.findCycle import enum TSCUtility.Diagnostics import struct TSCUtility.Version @@ -59,243 +58,11 @@ public enum WorkspaceResolveReason: Equatable { public struct PackageFetchDetails { /// Indicates if the package was fetched from the cache or from the remote. public let fromCache: Bool - /// Indicates wether the wether the package was already present in the cache and updated or if a clean fetch was performed. + /// Indicates wether the wether the package was already present in the cache and updated or if a clean fetch was + /// performed. public let updatedCache: Bool } -/// The delegate interface used by the workspace to report status information. -public protocol WorkspaceDelegate: AnyObject { - /// The workspace is about to load a package manifest (which might be in the cache, or might need to be parsed). Note that this does not include speculative loading of manifests that may occur during - /// dependency resolution; rather, it includes only the final manifest loading that happens after a particular package version has been checked out into a working directory. - func willLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) - - /// The workspace has loaded a package manifest, either successfully or not. The manifest is nil if an error occurs, in which case there will also be at least one error in the list of diagnostics (there may be warnings even if a manifest is loaded successfully). - func didLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind, manifest: Manifest?, diagnostics: [Diagnostic], duration: DispatchTimeInterval) - - /// The workspace has started fetching this package. - func willFetchPackage(package: PackageIdentity, packageLocation: String?, fetchDetails: PackageFetchDetails) - /// The workspace has finished fetching this package. - func didFetchPackage(package: PackageIdentity, packageLocation: String?, result: Result, duration: DispatchTimeInterval) - /// Called every time the progress of the package fetch operation updates. - func fetchingPackage(package: PackageIdentity, packageLocation: String?, progress: Int64, total: Int64?) - - /// The workspace has started updating this repository. - func willUpdateRepository(package: PackageIdentity, repository url: String) - /// The workspace has finished updating this repository. - func didUpdateRepository(package: PackageIdentity, repository url: String, duration: DispatchTimeInterval) - - /// The workspace has finished updating and all the dependencies are already up-to-date. - func dependenciesUpToDate() - - /// The workspace is about to clone a repository from the local cache to a working directory. - func willCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath) - /// The workspace has cloned a repository from the local cache to a working directory. The error indicates whether the operation failed or succeeded. - func didCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath, duration: DispatchTimeInterval) - - /// The workspace is about to check out a particular revision of a working directory. - func willCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath) - /// The workspace has checked out a particular revision of a working directory. The error indicates whether the operation failed or succeeded. - func didCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath, duration: DispatchTimeInterval) - - /// The workspace is removing this repository because it is no longer needed. - func removing(package: PackageIdentity, packageLocation: String?) - - /// Called when the resolver is about to be run. - func willResolveDependencies(reason: WorkspaceResolveReason) - - /// Called when the resolver begins to be compute the version for the repository. - func willComputeVersion(package: PackageIdentity, location: String) - /// Called when the resolver finished computing the version for the repository. - func didComputeVersion(package: PackageIdentity, location: String, version: String, duration: DispatchTimeInterval) - - /// Called when the Package.resolved file is changed *outside* of libSwiftPM operations. - /// - /// This is only fired when activated using Workspace's watchResolvedFile() method. - func resolvedFileChanged() - - /// The workspace has started downloading a binary artifact. - func willDownloadBinaryArtifact(from url: String) - /// The workspace has finished downloading a binary artifact. - func didDownloadBinaryArtifact(from url: String, result: Result, duration: DispatchTimeInterval) - /// The workspace is downloading a binary artifact. - func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) - /// The workspace finished downloading all binary artifacts. - func didDownloadAllBinaryArtifacts() - - // handlers for unsigned and untrusted registry based dependencies - func onUnsignedRegistryPackage(registryURL: URL, package: PackageModel.PackageIdentity, version: TSCUtility.Version, completion: (Bool) -> Void) - func onUntrustedRegistryPackage(registryURL: URL, package: PackageModel.PackageIdentity, version: TSCUtility.Version, completion: (Bool) -> Void) - - /// The workspace has started updating dependencies - func willUpdateDependencies() - /// The workspace has finished updating dependencies - func didUpdateDependencies(duration: DispatchTimeInterval) - - /// The workspace has started resolving dependencies - func willResolveDependencies() - /// The workspace has finished resolving dependencies - func didResolveDependencies(duration: DispatchTimeInterval) - - /// The workspace has started loading the graph to memory - func willLoadGraph() - /// The workspace has finished loading the graph to memory - func didLoadGraph(duration: DispatchTimeInterval) -} - -// FIXME: default implementation until the feature is stable, at which point we should remove this and force the clients to implement -extension WorkspaceDelegate { - public func onUnsignedRegistryPackage(registryURL: URL, package: PackageModel.PackageIdentity, version: TSCUtility.Version, completion: (Bool) -> Void) { - // true == continue resolution - // false == stop dependency resolution - completion(true) - } - - public func onUntrustedRegistryPackage(registryURL: URL, package: PackageModel.PackageIdentity, version: TSCUtility.Version, completion: (Bool) -> Void) { - // true == continue resolution - // false == stop dependency resolution - completion(true) - } -} - -private class WorkspaceRepositoryManagerDelegate: RepositoryManager.Delegate { - private unowned let workspaceDelegate: Workspace.Delegate - - init(workspaceDelegate: Workspace.Delegate) { - self.workspaceDelegate = workspaceDelegate - } - - func willFetch(package: PackageIdentity, repository: RepositorySpecifier, details: RepositoryManager.FetchDetails) { - self.workspaceDelegate.willFetchPackage(package: package, packageLocation: repository.location.description, fetchDetails: PackageFetchDetails(fromCache: details.fromCache, updatedCache: details.updatedCache) ) - } - - func fetching(package: PackageIdentity, repository: RepositorySpecifier, objectsFetched: Int, totalObjectsToFetch: Int) { - self.workspaceDelegate.fetchingPackage(package: package, packageLocation: repository.location.description, progress: Int64(objectsFetched), total: Int64(totalObjectsToFetch)) - } - - func didFetch(package: PackageIdentity, repository: RepositorySpecifier, result: Result, duration: DispatchTimeInterval) { - self.workspaceDelegate.didFetchPackage(package: package, packageLocation: repository.location.description, result: result.map{ PackageFetchDetails(fromCache: $0.fromCache, updatedCache: $0.updatedCache) }, duration: duration) - } - - func willUpdate(package: PackageIdentity, repository: RepositorySpecifier) { - self.workspaceDelegate.willUpdateRepository(package: package, repository: repository.location.description) - } - - func didUpdate(package: PackageIdentity, repository: RepositorySpecifier, duration: DispatchTimeInterval) { - self.workspaceDelegate.didUpdateRepository(package: package, repository: repository.location.description, duration: duration) - } -} - -private struct WorkspaceRegistryDownloadsManagerDelegate: RegistryDownloadsManager.Delegate { - private unowned let workspaceDelegate: Workspace.Delegate - - init(workspaceDelegate: Workspace.Delegate) { - self.workspaceDelegate = workspaceDelegate - } - - func willFetch(package: PackageIdentity, version: Version, fetchDetails: RegistryDownloadsManager.FetchDetails) { - self.workspaceDelegate.willFetchPackage(package: package, packageLocation: .none, fetchDetails: PackageFetchDetails(fromCache: fetchDetails.fromCache, updatedCache: fetchDetails.updatedCache) ) - } - - func didFetch(package: PackageIdentity, version: Version, result: Result, duration: DispatchTimeInterval) { - self.workspaceDelegate.didFetchPackage(package: package, packageLocation: .none, result: result.map{ PackageFetchDetails(fromCache: $0.fromCache, updatedCache: $0.updatedCache) }, duration: duration) - } - - func fetching(package: PackageIdentity, version: Version, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { - self.workspaceDelegate.fetchingPackage(package: package, packageLocation: .none, progress: bytesDownloaded, total: totalBytesToDownload) - } -} - -private struct WorkspaceRegistryClientDelegate: RegistryClient.Delegate { - private unowned let workspaceDelegate: Workspace.Delegate? - - init(workspaceDelegate: Workspace.Delegate?) { - self.workspaceDelegate = workspaceDelegate - } - - func onUnsigned(registry: Registry, package: PackageIdentity, version: Version, completion: (Bool) -> Void) { - if let delegate = self.workspaceDelegate { - delegate.onUnsignedRegistryPackage( - registryURL: registry.url, - package: package, - version: version, - completion: completion - ) - } else { - // true == continue resolution - // false == stop dependency resolution - completion(true) - } - } - - func onUntrusted(registry: Registry, package: PackageIdentity, version: Version, completion: (Bool) -> Void) { - if let delegate = self.workspaceDelegate { - delegate.onUntrustedRegistryPackage( - registryURL: registry.url, - package: package, - version: version, - completion: completion - ) - } else { - // true == continue resolution - // false == stop dependency resolution - completion(true) - } - } -} - -private struct WorkspaceDependencyResolverDelegate: DependencyResolverDelegate { - private unowned let workspaceDelegate: Workspace.Delegate - private let resolving = ThreadSafeKeyValueStore() - - init(_ delegate: Workspace.Delegate) { - self.workspaceDelegate = delegate - } - - func willResolve(term: Term) { - // this may be called multiple time by the resolver for various version ranges, but we only want to propagate once since we report at package level - resolving.memoize(term.node.package.identity) { - self.workspaceDelegate.willComputeVersion(package: term.node.package.identity, location: term.node.package.locationString) - return true - } - } - - func didResolve(term: Term, version: Version, duration: DispatchTimeInterval) { - self.workspaceDelegate.didComputeVersion(package: term.node.package.identity, location: term.node.package.locationString, version: version.description, duration: duration) - } - - // noop - func derived(term: Term) {} - func conflict(conflict: Incompatibility) {} - func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility) {} - func partiallySatisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility, difference: Term) {} - func failedToResolve(incompatibility: Incompatibility) {} - func solved(result: [(package: PackageReference, binding: BoundVersion, products: ProductFilter)]) {} -} - -private class WorkspaceBinaryArtifactsManagerDelegate: Workspace.BinaryArtifactsManager.Delegate { - private unowned let workspaceDelegate: Workspace.Delegate - - init(workspaceDelegate: Workspace.Delegate) { - self.workspaceDelegate = workspaceDelegate - } - - func willDownloadBinaryArtifact(from url: String) { - self.workspaceDelegate.willDownloadBinaryArtifact(from: url) - } - - func didDownloadBinaryArtifact(from url: String, result: Result, duration: DispatchTimeInterval) { - self.workspaceDelegate.didDownloadBinaryArtifact(from: url, result: result, duration: duration) - } - - func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { - self.workspaceDelegate.downloadingBinaryArtifact(from: url, bytesDownloaded: bytesDownloaded, totalBytesToDownload: totalBytesToDownload) - } - - func didDownloadAllBinaryArtifacts() { - self.workspaceDelegate.didDownloadAllBinaryArtifacts() - } -} - /// A workspace represents the state of a working project directory. /// /// The workspace is responsible for managing the persistent working state of a @@ -312,13 +79,13 @@ public class Workspace { public typealias Delegate = WorkspaceDelegate /// The delegate interface. - fileprivate weak var delegate: Delegate? + private(set) weak var delegate: Delegate? /// The workspace location. public let location: Location /// The mirrors config. - fileprivate let mirrors: DependencyMirrors + let mirrors: DependencyMirrors /// The current persisted state of the workspace. // public visibility for testing @@ -329,55 +96,56 @@ public class Workspace { public let pinsStore: LoadableResult /// The file system on which the workspace will operate. - fileprivate let fileSystem: FileSystem + let fileSystem: any FileSystem /// The host toolchain to use. - fileprivate let hostToolchain: UserToolchain + private let hostToolchain: UserToolchain /// The manifest loader to use. - fileprivate let manifestLoader: ManifestLoaderProtocol + let manifestLoader: ManifestLoaderProtocol /// The tools version currently in use. - fileprivate let currentToolsVersion: ToolsVersion + let currentToolsVersion: ToolsVersion /// Utility to resolve package identifiers // var for backwards compatibility with deprecated initializers, remove with them - fileprivate var identityResolver: IdentityResolver + let identityResolver: IdentityResolver + + /// Utility to map dependencies + let dependencyMapper: DependencyMapper /// The custom package container provider used by this workspace, if any. - fileprivate let customPackageContainerProvider: PackageContainerProvider? + let customPackageContainerProvider: PackageContainerProvider? /// The package container provider used by this workspace. - fileprivate var packageContainerProvider: PackageContainerProvider { - return self.customPackageContainerProvider ?? self + var packageContainerProvider: PackageContainerProvider { + self.customPackageContainerProvider ?? self } /// Source control repository manager used for interacting with source control based dependencies - // var for backwards compatibility with deprecated initializers, remove with them - fileprivate var repositoryManager: RepositoryManager + let repositoryManager: RepositoryManager /// The registry manager. - // var for backwards compatibility with deprecated initializers, remove with them - fileprivate var registryClient: RegistryClient + let registryClient: RegistryClient /// Registry based dependencies downloads manager used for interacting with registry based dependencies - fileprivate var registryDownloadsManager: RegistryDownloadsManager + let registryDownloadsManager: RegistryDownloadsManager /// Binary artifacts manager used for downloading and extracting binary artifacts - fileprivate let binaryArtifactsManager: BinaryArtifactsManager - + let binaryArtifactsManager: BinaryArtifactsManager + /// The package fingerprints storage - fileprivate let fingerprints: PackageFingerprintStorage? + let fingerprints: PackageFingerprintStorage? /// The workspace configuration settings - fileprivate let configuration: WorkspaceConfiguration + let configuration: WorkspaceConfiguration - // state + // MARK: State /// The active package resolver. This is set during a dependency resolution operation. - fileprivate var activeResolver: PubGrubDependencyResolver? + var activeResolver: PubGrubDependencyResolver? - fileprivate var resolvedFileWatcher: ResolvedFileWatcher? + var resolvedFileWatcher: ResolvedFileWatcher? /// Create a new package workspace. /// @@ -396,24 +164,26 @@ public class Workspace { /// - configuration: Configuration to fine tune the dependency resolution behavior. /// - cancellator: Cancellation handler /// - initializationWarningHandler: Initialization warnings handler - /// - customHostToolchain: Custom host toolchain. Used to create a customized ManifestLoader, customizing how manifest are loaded. + /// - customHostToolchain: Custom host toolchain. Used to create a customized ManifestLoader, customizing how + /// manifest are loaded. /// - customManifestLoader: Custom manifest loader. Used to customize how manifest are loaded. - /// - customPackageContainerProvider: Custom package container provider. Used to provide specialized package providers. + /// - customPackageContainerProvider: Custom package container provider. Used to provide specialized package + /// providers. /// - customRepositoryProvider: Custom repository provider. Used to customize source control access. /// - delegate: Delegate for workspace events public convenience init( - fileSystem: FileSystem, + fileSystem: any FileSystem, location: Location, - authorizationProvider: AuthorizationProvider? = .none, - registryAuthorizationProvider: AuthorizationProvider? = .none, + authorizationProvider: (any AuthorizationProvider)? = .none, + registryAuthorizationProvider: (any AuthorizationProvider)? = .none, configuration: WorkspaceConfiguration? = .none, cancellator: Cancellator? = .none, initializationWarningHandler: ((String) -> Void)? = .none, // optional customization used for advanced integration situations customHostToolchain: UserToolchain? = .none, - customManifestLoader: ManifestLoaderProtocol? = .none, - customPackageContainerProvider: PackageContainerProvider? = .none, - customRepositoryProvider: RepositoryProvider? = .none, + customManifestLoader: (any ManifestLoaderProtocol)? = .none, + customPackageContainerProvider: (any PackageContainerProvider)? = .none, + customRepositoryProvider: (any RepositoryProvider)? = .none, // delegate delegate: Delegate? = .none ) throws { @@ -439,6 +209,7 @@ public class Workspace { customRegistryClient: .none, customBinaryArtifactsManager: .none, customIdentityResolver: .none, + customDependencyMapper: .none, customChecksumAlgorithm: .none, delegate: delegate ) @@ -459,7 +230,8 @@ public class Workspace { /// - cancellator: Cancellation handler /// - initializationWarningHandler: Initialization warnings handler /// - customManifestLoader: Custom manifest loader. Used to customize how manifest are loaded. - /// - customPackageContainerProvider: Custom package container provider. Used to provide specialized package providers. + /// - customPackageContainerProvider: Custom package container provider. Used to provide specialized package + /// providers. /// - customRepositoryProvider: Custom repository provider. Used to customize source control access. /// - delegate: Delegate for workspace events public convenience init( @@ -493,7 +265,7 @@ public class Workspace { delegate: delegate ) } - + /// A convenience method for creating a workspace for the given root /// package path. /// @@ -508,8 +280,10 @@ public class Workspace { /// - configuration: Configuration to fine tune the dependency resolution behavior. /// - cancellator: Cancellation handler /// - initializationWarningHandler: Initialization warnings handler - /// - customHostToolchain: Custom host toolchain. Used to create a customized ManifestLoader, customizing how manifest are loaded. - /// - customPackageContainerProvider: Custom package container provider. Used to provide specialized package providers. + /// - customHostToolchain: Custom host toolchain. Used to create a customized ManifestLoader, customizing how + /// manifest are loaded. + /// - customPackageContainerProvider: Custom package container provider. Used to provide specialized package + /// providers. /// - customRepositoryProvider: Custom repository provider. Used to customize source control access. /// - delegate: Delegate for workspace events public convenience init( @@ -532,7 +306,8 @@ public class Workspace { let manifestLoader = ManifestLoader( toolchain: customHostToolchain, cacheDir: location.sharedManifestsCacheDirectory, - restrictImports: configuration?.restrictImports + importRestrictions: configuration?.manifestImportRestrictions, + delegate: delegate.map(WorkspaceManifestLoaderDelegate.init(workspaceDelegate:)) ) try self.init( fileSystem: fileSystem, @@ -577,6 +352,7 @@ public class Workspace { customRegistryClient: RegistryClient? = .none, customBinaryArtifactsManager: CustomBinaryArtifactsManager? = .none, customIdentityResolver: IdentityResolver? = .none, + customDependencyMapper: DependencyMapper? = .none, customChecksumAlgorithm: HashAlgorithm? = .none, // delegate delegate: Delegate? = .none @@ -603,6 +379,7 @@ public class Workspace { customRegistryClient: customRegistryClient, customBinaryArtifactsManager: customBinaryArtifactsManager, customIdentityResolver: customIdentityResolver, + customDependencyMapper: customDependencyMapper, customChecksumAlgorithm: customChecksumAlgorithm, delegate: delegate ) @@ -632,37 +409,47 @@ public class Workspace { customRegistryClient: RegistryClient?, customBinaryArtifactsManager: CustomBinaryArtifactsManager?, customIdentityResolver: IdentityResolver?, + customDependencyMapper: DependencyMapper?, customChecksumAlgorithm: HashAlgorithm?, // delegate delegate: Delegate? ) throws { - // we do not store an observabilityScope in the workspace initializer as the workspace is designed to be long lived. + // we do not store an observabilityScope in the workspace initializer as the workspace is designed to be long + // lived. // instead, observabilityScope is passed into the individual workspace methods which are short lived. let initializationWarningHandler = initializationWarningHandler ?? warnToStderr - // validate locations, returning a potentially modified one to deal with non-accessible or non-writable shared locations - let location = try location.validatingSharedLocations(fileSystem: fileSystem, warningHandler: initializationWarningHandler) + // validate locations, returning a potentially modified one to deal with non-accessible or non-writable shared + // locations + let location = try location.validatingSharedLocations( + fileSystem: fileSystem, + warningHandler: initializationWarningHandler + ) let currentToolsVersion = customToolsVersion ?? ToolsVersion.current let hostToolchain = try customHostToolchain ?? UserToolchain(swiftSDK: .hostSwiftSDK()) var manifestLoader = customManifestLoader ?? ManifestLoader( toolchain: hostToolchain, cacheDir: location.sharedManifestsCacheDirectory, - restrictImports: configuration?.restrictImports + importRestrictions: configuration?.manifestImportRestrictions ) + // set delegate if not set + if let manifestLoader = manifestLoader as? ManifestLoader, manifestLoader.delegate == nil { + manifestLoader.delegate = delegate.map(WorkspaceManifestLoaderDelegate.init(workspaceDelegate:)) + } let configuration = configuration ?? .default let mirrors = try customMirrors ?? Workspace.Configuration.Mirrors( fileSystem: fileSystem, - localMirrorsFile: try location.localMirrorsConfigurationFile, + localMirrorsFile: location.localMirrorsConfigurationFile, sharedMirrorsFile: location.sharedMirrorsConfigurationFile ).mirrors - let identityResolver = customIdentityResolver ?? DefaultIdentityResolver( locationMapper: mirrors.effective(for:), identityMapper: mirrors.effectiveIdentity(for:) ) + let dependencyMapper = customDependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver) let checksumAlgorithm = customChecksumAlgorithm ?? SHA256() let repositoryProvider = customRepositoryProvider ?? GitRepositoryProvider() @@ -683,7 +470,7 @@ public class Workspace { directoryPath: $0 ) } - + let signingEntities = customSigningEntities ?? location.sharedSigningEntitiesDirectory.map { FilePackageSigningEntityStorage( fileSystem: fileSystem, @@ -717,14 +504,17 @@ public class Workspace { let registryDownloadsManager = RegistryDownloadsManager( fileSystem: fileSystem, path: location.registryDownloadDirectory, - cachePath: configuration.sharedDependenciesCacheEnabled ? location.sharedRegistryDownloadsCacheDirectory : .none, + cachePath: configuration.sharedDependenciesCacheEnabled ? location + .sharedRegistryDownloadsCacheDirectory : .none, registryClient: registryClient, delegate: delegate.map(WorkspaceRegistryDownloadsManagerDelegate.init(workspaceDelegate:)) ) // register the registry dependencies downloader with the cancellation handler cancellator?.register(name: "registry downloads", handler: registryDownloadsManager) - if let transformationMode = RegistryAwareManifestLoader.TransformationMode(configuration.sourceControlToRegistryDependencyTransformation) { + if let transformationMode = RegistryAwareManifestLoader + .TransformationMode(configuration.sourceControlToRegistryDependencyTransformation) + { manifestLoader = RegistryAwareManifestLoader( underlying: manifestLoader, registryClient: registryClient, @@ -737,6 +527,7 @@ public class Workspace { authorizationProvider: authorizationProvider, hostToolchain: hostToolchain, checksumAlgorithm: checksumAlgorithm, + cachePath: customBinaryArtifactsManager?.useCache == false || !configuration.sharedDependenciesCacheEnabled ? .none : location.sharedBinaryArtifactsCacheDirectory, customHTTPClient: customBinaryArtifactsManager?.httpClient, customArchiver: customBinaryArtifactsManager?.archiver, delegate: delegate.map(WorkspaceBinaryArtifactsManagerDelegate.init(workspaceDelegate:)) @@ -762,6 +553,7 @@ public class Workspace { self.binaryArtifactsManager = binaryArtifactsManager self.identityResolver = identityResolver + self.dependencyMapper = dependencyMapper self.fingerprints = fingerprints self.pinsStore = LoadableResult { @@ -784,7 +576,6 @@ public class Workspace { // MARK: - Public API extension Workspace { - /// Puts a dependency in edit mode creating a checkout in editables directory. /// /// - Parameters: @@ -836,10 +627,18 @@ extension Workspace { observabilityScope.emit(.dependencyNotFound(packageName: packageName)) return } - - let observabilityScope = observabilityScope.makeChildScope(description: "editing package", metadata: dependency.packageRef.diagnosticsMetadata) - try self.unedit(dependency: dependency, forceRemove: forceRemove, root: root, observabilityScope: observabilityScope) + let observabilityScope = observabilityScope.makeChildScope( + description: "editing package", + metadata: dependency.packageRef.diagnosticsMetadata + ) + + try self.unedit( + dependency: dependency, + forceRemove: forceRemove, + root: root, + observabilityScope: observabilityScope + ) } /// Perform dependency resolution if needed. @@ -858,7 +657,8 @@ extension Workspace { try self._resolve( root: root, explicitProduct: explicitProduct, - resolvedFileStrategy: forceResolvedVersions ? .lockFile : forceResolution ? .update(forceResolution: true) : .bestEffort, + resolvedFileStrategy: forceResolvedVersions ? .lockFile : forceResolution ? .update(forceResolution: true) : + .bestEffort, observabilityScope: observabilityScope ) } @@ -889,7 +689,10 @@ extension Workspace { throw StringError("dependency '\(packageName)' was not found") } - let observabilityScope = observabilityScope.makeChildScope(description: "editing package", metadata: dependency.packageRef.diagnosticsMetadata) + let observabilityScope = observabilityScope.makeChildScope( + description: "editing package", + metadata: dependency.packageRef.diagnosticsMetadata + ) let defaultRequirement: PackageRequirement switch dependency.state { @@ -916,7 +719,11 @@ extension Workspace { } // If any products are required, the rest of the package graph will supply those constraints. - let constraint = PackageContainerConstraint(package: dependency.packageRef, requirement: requirement, products: .nothing) + let constraint = PackageContainerConstraint( + package: dependency.packageRef, + requirement: requirement, + products: .nothing + ) // Run the resolution. try self.resolveAndUpdateResolvedFile( @@ -953,25 +760,30 @@ extension Workspace { self.location.repositoriesCheckoutsDirectory, self.location.artifactsDirectory, self.state.storagePath, - ].map({ path -> String in + ].map { path -> String in // Assert that these are present inside data directory. assert(path.parentDirectory == self.location.scratchDirectory) return path.basename - }) + } // If we have no data yet, we're done. - guard fileSystem.exists(self.location.scratchDirectory) else { + guard self.fileSystem.exists(self.location.scratchDirectory) else { return } - guard let contents = observabilityScope.trap({ try fileSystem.getDirectoryContents(self.location.scratchDirectory) }) else { + guard let contents = observabilityScope + .trap({ try fileSystem.getDirectoryContents(self.location.scratchDirectory) }) + else { return } // Remove all but protected paths. let contentsToRemove = Set(contents).subtracting(protectedAssets) for name in contentsToRemove { - try? fileSystem.removeFileTree(AbsolutePath(validating: name, relativeTo: self.location.scratchDirectory)) + try? self.fileSystem.removeFileTree(AbsolutePath( + validating: name, + relativeTo: self.location.scratchDirectory + )) } } @@ -991,13 +803,17 @@ extension Workspace { /// - observabilityScope: The observability scope that reports errors, warnings, etc public func reset(observabilityScope: ObservabilityScope) { let removed = observabilityScope.trap { () -> Bool in - try self.fileSystem.chmod(.userWritable, path: self.location.repositoriesCheckoutsDirectory, options: [.recursive, .onlyFiles]) + try self.fileSystem.chmod( + .userWritable, + path: self.location.repositoriesCheckoutsDirectory, + options: [.recursive, .onlyFiles] + ) // Reset state. try self.resetState() return true } - guard (removed ?? false) else { + guard removed ?? false else { return } @@ -1035,103 +851,12 @@ extension Workspace { dryRun: Bool = false, observabilityScope: ObservabilityScope ) throws -> [(PackageReference, Workspace.PackageStateChange)]? { - let start = DispatchTime.now() - self.delegate?.willUpdateDependencies() - defer { - self.delegate?.didUpdateDependencies(duration: start.distance(to: .now())) - } - - // Create cache directories. - self.createCacheDirectories(observabilityScope: observabilityScope) - - // FIXME: this should not block - // Load the root manifests and currently checked out manifests. - let rootManifests = try temp_await { self.loadRootManifests(packages: root.packages, observabilityScope: observabilityScope, completion: $0) } - let rootManifestsMinimumToolsVersion = rootManifests.values.map{ $0.toolsVersion }.min() ?? ToolsVersion.current - let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) - - // Load the current manifests. - let graphRoot = PackageGraphRoot(input: root, manifests: rootManifests) - let currentManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) - - // Abort if we're unable to load the pinsStore or have any diagnostics. - guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }) else { return nil } - - // Ensure we don't have any error at this point. - guard !observabilityScope.errorsReported else { - return nil - } - - // Add unversioned constraints for edited packages. - var updateConstraints = currentManifests.editedPackagesConstraints() - - // Create constraints based on root manifest and pins for the update resolution. - updateConstraints += try graphRoot.constraints() - - let pins: PinsStore.Pins - if packages.isEmpty { - // No input packages so we have to do a full update. Set pins map to empty. - pins = [:] - } else { - // We have input packages so we have to partially update the package graph. Remove - // the pins for the input packages so only those packages are updated. - pins = pinsStore.pins.filter{ !packages.contains($0.value.packageRef.identity.description) && !packages.contains($0.value.packageRef.deprecatedName) } - } - - // Resolve the dependencies. - let resolver = try self.createResolver(pins: pins, observabilityScope: observabilityScope) - self.activeResolver = resolver - - let updateResults = self.resolveDependencies( - resolver: resolver, - constraints: updateConstraints, - observabilityScope: observabilityScope - ) - - // Reset the active resolver. - self.activeResolver = nil - - guard !observabilityScope.errorsReported else { - return nil - } - - if dryRun { - return observabilityScope.trap { - return try self.computePackageStateChanges(root: graphRoot, resolvedDependencies: updateResults, updateBranches: true, observabilityScope: observabilityScope) - } - } - - // Update the checkouts based on new dependency resolution. - let packageStateChanges = self.updateDependenciesCheckouts(root: graphRoot, updateResults: updateResults, updateBranches: true, observabilityScope: observabilityScope) - - // Load the updated manifests. - let updatedDependencyManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) - // If we have missing packages, something is fundamentally wrong with the resolution of the graph - let stillMissingPackages = try updatedDependencyManifests.computePackages().missing - guard stillMissingPackages.isEmpty else { - let missing = stillMissingPackages.map{ $0.description } - observabilityScope.emit(error: "exhausted attempts to resolve the dependencies graph, with '\(missing.sorted().joined(separator: "', '"))' unresolved.") - return nil - } - - // Update the resolved file. - try self.saveResolvedFile( - pinsStore: pinsStore, - dependencyManifests: updatedDependencyManifests, - originHash: resolvedFileOriginHash, - rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, - observabilityScope: observabilityScope - ) - - // Update the binary target artifacts. - let addedOrUpdatedPackages = packageStateChanges.compactMap({ $0.1.isAddedOrUpdated ? $0.0 : nil }) - try self.updateBinaryArtifacts( - manifests: updatedDependencyManifests, - addedOrUpdatedPackages: addedOrUpdatedPackages, + try self._updateDependencies( + root: root, + packages: packages, + dryRun: dryRun, observabilityScope: observabilityScope ) - - return nil } @discardableResult @@ -1151,7 +876,8 @@ extension Workspace { } // reload state in case it was modified externally (eg by another process) before reloading the graph - // long running host processes (ie IDEs) need this in case other SwiftPM processes (ie CLI) made changes to the state + // long running host processes (ie IDEs) need this in case other SwiftPM processes (ie CLI) made changes to the + // state // such hosts processes call loadPackageGraph to make sure the workspace state is correct try self.state.reload() @@ -1163,24 +889,29 @@ extension Workspace { observabilityScope: observabilityScope ) - let binaryArtifacts = self.state.artifacts.reduce(into: [PackageIdentity: [String: BinaryArtifact]]()) { partial, artifact in - partial[artifact.packageRef.identity, default: [:]][artifact.targetName] = BinaryArtifact(kind: artifact.kind, originURL: artifact.originURL, path: artifact.path) - } + let binaryArtifacts = self.state.artifacts + .reduce(into: [PackageIdentity: [String: BinaryArtifact]]()) { partial, artifact in + partial[artifact.packageRef.identity, default: [:]][artifact.targetName] = BinaryArtifact( + kind: artifact.kind, + originURL: artifact.originURL, + path: artifact.path + ) + } // Load the graph. let packageGraph = try PackageGraph.load( root: manifests.root, identityResolver: self.identityResolver, additionalFileRules: self.configuration.additionalFileRules, - externalManifests: manifests.allDependencyManifests(), - requiredDependencies: manifests.computePackages().required, - unsafeAllowedPackages: manifests.unsafeAllowedPackages(), + externalManifests: manifests.allDependencyManifests, + requiredDependencies: manifests.requiredPackages, + unsafeAllowedPackages: manifests.unsafeAllowedPackages, binaryArtifacts: binaryArtifacts, shouldCreateMultipleTestProducts: self.configuration.shouldCreateMultipleTestProducts, createREPLProduct: self.configuration.createREPLProduct, customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, testEntryPointPath: testEntryPointPath, - fileSystem: fileSystem, + fileSystem: self.fileSystem, observabilityScope: observabilityScope ) @@ -1206,8 +937,11 @@ extension Workspace { } /// Loads and returns manifests at the given paths. - public func loadRootManifests(packages: [AbsolutePath], observabilityScope: ObservabilityScope) async throws -> [AbsolutePath: Manifest] { - return try await withCheckedThrowingContinuation{ continuation in + public func loadRootManifests( + packages: [AbsolutePath], + observabilityScope: ObservabilityScope + ) async throws -> [AbsolutePath: Manifest] { + try await withCheckedThrowingContinuation { continuation in self.loadRootManifests(packages: packages, observabilityScope: observabilityScope) { result in continuation.resume(with: result) } @@ -1215,10 +949,11 @@ extension Workspace { } /// Loads and returns manifests at the given paths. + @available(*, noasync, message: "Use the async alternative") public func loadRootManifests( packages: [AbsolutePath], observabilityScope: ObservabilityScope, - completion: @escaping(Result<[AbsolutePath: Manifest], Error>) -> Void + completion: @escaping (Result<[AbsolutePath: Manifest], Error>) -> Void ) { let lock = NSLock() let sync = DispatchGroup() @@ -1256,8 +991,11 @@ extension Workspace { } /// Loads and returns manifest at the given path. - public func loadRootManifest(at path: AbsolutePath, observabilityScope: ObservabilityScope) async throws -> Manifest { - return try await withCheckedThrowingContinuation{ continuation in + public func loadRootManifest( + at path: AbsolutePath, + observabilityScope: ObservabilityScope + ) async throws -> Manifest { + try await withCheckedThrowingContinuation { continuation in self.loadRootManifest(at: path, observabilityScope: observabilityScope) { result in continuation.resume(with: result) } @@ -1271,9 +1009,11 @@ extension Workspace { completion: @escaping (Result) -> Void ) { self.loadRootManifests(packages: [path], observabilityScope: observabilityScope) { result in - completion(result.tryMap{ - // normally, we call loadRootManifests which attempts to load any manifest it can and report errors via diagnostics - // in this case, we want to load a specific manifest, so if the diagnostics contains an error we want to throw + completion(result.tryMap { + // normally, we call loadRootManifests which attempts to load any manifest it can and report errors via + // diagnostics + // in this case, we want to load a specific manifest, so if the diagnostics contains an error we want to + // throw guard !observabilityScope.errorsReported else { throw Diagnostics.fatalError } @@ -1287,7 +1027,7 @@ extension Workspace { /// Loads root package public func loadRootPackage(at path: AbsolutePath, observabilityScope: ObservabilityScope) async throws -> Package { - return try await withCheckedThrowingContinuation{ continuation in + try await withCheckedThrowingContinuation { continuation in self.loadRootPackage(at: path, observabilityScope: observabilityScope) { result in continuation.resume(with: result) } @@ -1298,7 +1038,7 @@ extension Workspace { public func loadRootPackage( at path: AbsolutePath, observabilityScope: ObservabilityScope, - completion: @escaping(Result) -> Void + completion: @escaping (Result) -> Void ) { self.loadRootManifest(at: path, observabilityScope: observabilityScope) { result in let result = result.tryMap { manifest -> Package in @@ -1306,21 +1046,37 @@ extension Workspace { // radar/82263304 // compute binary artifacts for the sake of constructing a project model - // note this does not actually download remote artifacts and as such does not have the artifact's type or path - let binaryArtifacts = try manifest.targets.filter{ $0.type == .binary }.reduce(into: [String: BinaryArtifact]()) { partial, target in - if let path = target.path { - let artifactPath = try manifest.path.parentDirectory.appending(RelativePath(validating: path)) - guard let (_, artifactKind) = try BinaryArtifactsManager.deriveBinaryArtifact(fileSystem: self.fileSystem, path: artifactPath, observabilityScope: observabilityScope) else { - throw StringError("\(artifactPath) does not contain binary artifact") + // note this does not actually download remote artifacts and as such does not have the artifact's type + // or path + let binaryArtifacts = try manifest.targets.filter { $0.type == .binary } + .reduce(into: [String: BinaryArtifact]()) { partial, target in + if let path = target.path { + let artifactPath = try manifest.path.parentDirectory + .appending(RelativePath(validating: path)) + guard let (_, artifactKind) = try BinaryArtifactsManager.deriveBinaryArtifact( + fileSystem: self.fileSystem, + path: artifactPath, + observabilityScope: observabilityScope + ) else { + throw StringError("\(artifactPath) does not contain binary artifact") + } + partial[target.name] = BinaryArtifact( + kind: artifactKind, + originURL: .none, + path: artifactPath + ) + } else if let url = target.url.flatMap(URL.init(string:)) { + let fakePath = try manifest.path.parentDirectory.appending(components: "remote", "archive") + .appending(RelativePath(validating: url.lastPathComponent)) + partial[target.name] = BinaryArtifact( + kind: .unknown, + originURL: url.absoluteString, + path: fakePath + ) + } else { + throw InternalError("a binary target should have either a path or a URL and a checksum") } - partial[target.name] = BinaryArtifact(kind: artifactKind , originURL: .none, path: artifactPath) - } else if let url = target.url.flatMap(URL.init(string:)) { - let fakePath = try manifest.path.parentDirectory.appending(components: "remote", "archive").appending(RelativePath(validating: url.lastPathComponent)) - partial[target.name] = BinaryArtifact(kind: .unknown, originURL: url.absoluteString, path: fakePath) - } else { - throw InternalError("a binary target should have either a path or a URL and a checksum") } - } let builder = PackageBuilder( identity: identity, @@ -1339,10 +1095,15 @@ extension Workspace { } public func loadPluginImports( - packageGraph: PackageGraph, - completion: @escaping(Result<[PackageIdentity: [String: [String]]], Error>) -> Void) { - let pluginTargets = packageGraph.allTargets.filter{$0.type == .plugin} - let scanner = SwiftcImportScanner(swiftCompilerEnvironment: hostToolchain.swiftCompilerEnvironment, swiftCompilerFlags: hostToolchain.swiftCompilerFlags + ["-I", hostToolchain.swiftPMLibrariesLocation.pluginLibraryPath.pathString], swiftCompilerPath: hostToolchain.swiftCompilerPath) + packageGraph: PackageGraph + ) async throws -> [PackageIdentity: [String: [String]]] { + let pluginTargets = packageGraph.allTargets.filter { $0.type == .plugin } + let scanner = SwiftcImportScanner( + swiftCompilerEnvironment: hostToolchain.swiftCompilerEnvironment, + swiftCompilerFlags: self.hostToolchain + .swiftCompilerFlags + ["-I", self.hostToolchain.swiftPMLibrariesLocation.pluginLibraryPath.pathString], + swiftCompilerPath: self.hostToolchain.swiftCompilerPath + ) var importList = [PackageIdentity: [String: [String]]]() for pluginTarget in pluginTargets { @@ -1356,2053 +1117,130 @@ extension Workspace { } for path in paths { - do { - let result = try temp_await { - scanner.scanImports(path, callbackQueue: DispatchQueue.sharedConcurrent, completion: $0) - } - importList[pkgId]?[pluginTarget.name]?.append(contentsOf: result) - } catch { - completion(.failure(error)) - } + let result = try await scanner.scanImports(path) + importList[pkgId]?[pluginTarget.name]?.append(contentsOf: result) } } - completion(.success(importList)) + return importList + } + + public func loadPackage( + with identity: PackageIdentity, + packageGraph: PackageGraph, + observabilityScope: ObservabilityScope + ) async throws -> Package { + try await safe_async { + self.loadPackage(with: identity, packageGraph: packageGraph, observabilityScope: observabilityScope, completion: $0) + } } - /// Loads a single package in the context of a previously loaded graph. This can be useful for incremental loading in a longer-lived program, like an IDE. + /// Loads a single package in the context of a previously loaded graph. This can be useful for incremental loading + /// in a longer-lived program, like an IDE. + @available(*, noasync, message: "Use the async alternative") public func loadPackage( with identity: PackageIdentity, packageGraph: PackageGraph, observabilityScope: ObservabilityScope, - completion: @escaping(Result) -> Void) { - guard let previousPackage = packageGraph.packages.first(where: { $0.identity == identity }) else { - return completion(.failure(StringError("could not find package with identity \(identity)"))) - } + completion: @escaping (Result) -> Void + ) { + guard let previousPackage = packageGraph.packages.first(where: { $0.identity == identity }) else { + return completion(.failure(StringError("could not find package with identity \(identity)"))) + } - self.loadManifest(packageIdentity: identity, - packageKind: previousPackage.underlyingPackage.manifest.packageKind, - packagePath: previousPackage.path, - packageLocation: previousPackage.underlyingPackage.manifest.packageLocation, - observabilityScope: observabilityScope) { result in - let result = result.tryMap { manifest -> Package in - let builder = PackageBuilder( - identity: identity, - manifest: manifest, - productFilter: .everything, // TODO: this will not be correct when reloading a transitive dependencies if `ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION` is enabled - path: previousPackage.path, - additionalFileRules: self.configuration.additionalFileRules, - binaryArtifacts: packageGraph.binaryArtifacts[identity] ?? [:], - shouldCreateMultipleTestProducts: self.configuration.shouldCreateMultipleTestProducts, - createREPLProduct: self.configuration.createREPLProduct, - fileSystem: self.fileSystem, - observabilityScope: observabilityScope - ) - return try builder.construct() - } - completion(result) + self.loadManifest( + packageIdentity: identity, + packageKind: previousPackage.underlyingPackage.manifest.packageKind, + packagePath: previousPackage.path, + packageLocation: previousPackage.underlyingPackage.manifest.packageLocation, + observabilityScope: observabilityScope + ) { result in + let result = result.tryMap { manifest -> Package in + let builder = PackageBuilder( + identity: identity, + manifest: manifest, + productFilter: .everything, + // TODO: this will not be correct when reloading a transitive dependencies if `ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION` is enabled + path: previousPackage.path, + additionalFileRules: self.configuration.additionalFileRules, + binaryArtifacts: packageGraph.binaryArtifacts[identity] ?? [:], + shouldCreateMultipleTestProducts: self.configuration.shouldCreateMultipleTestProducts, + createREPLProduct: self.configuration.createREPLProduct, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope + ) + return try builder.construct() } + completion(result) + } } - /// Returns `true` if the file at the given path might influence build settings for a `swiftc` or `clang` invocation generated by SwiftPM. + /// Returns `true` if the file at the given path might influence build settings for a `swiftc` or `clang` invocation + /// generated by SwiftPM. public func fileAffectsSwiftOrClangBuildSettings(filePath: AbsolutePath, packageGraph: PackageGraph) -> Bool { // TODO: Implement a more sophisticated check that also verifies if the file is in the sources directories of the passed in `packageGraph`. - return FileRuleDescription.builtinRules.contains { fileRuleDescription in + FileRuleDescription.builtinRules.contains { fileRuleDescription in fileRuleDescription.match(path: filePath, toolsVersion: self.currentToolsVersion) } } -} - -// MARK: - Editing Functions + public func acceptIdentityChange( + package: PackageIdentity, + version: Version, + signingEntity: SigningEntity, + origin: SigningEntity.Origin, + observabilityScope: ObservabilityScope, + callbackQueue: DispatchQueue, + completion: @escaping (Result) -> Void + ) { + self.registryClient.changeSigningEntityFromVersion( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: observabilityScope, + callbackQueue: callbackQueue, + completion: completion + ) + } +} extension Workspace { - /// Edit implementation. - fileprivate func _edit( - packageName: String, - path: AbsolutePath? = nil, - revision: Revision? = nil, - checkoutBranch: String? = nil, - observabilityScope: ObservabilityScope - ) throws { - // Look up the dependency and check if we can edit it. - guard let dependency = self.state.dependencies[.plain(packageName)] else { - observabilityScope.emit(.dependencyNotFound(packageName: packageName)) - return + /// Removes the clone and checkout of the provided specifier. + /// + /// - Parameters: + /// - package: The package to remove + func remove(package: PackageReference) throws { + guard let dependency = self.state.dependencies[package.identity] else { + throw InternalError("trying to remove \(package.identity) which isn't in workspace") } - - let observabilityScope = observabilityScope.makeChildScope(description: "editing package", metadata: dependency.packageRef.diagnosticsMetadata) - let checkoutState: CheckoutState - switch dependency.state { - case .sourceControlCheckout(let _checkoutState): - checkoutState = _checkoutState - case .edited: - observabilityScope.emit(error: "dependency '\(dependency.packageRef.identity)' already in edit mode") - return - case .fileSystem: - observabilityScope.emit(error: "local dependency '\(dependency.packageRef.identity)' can't be edited") - return - case .registryDownload: - observabilityScope.emit(error: "registry dependency '\(dependency.packageRef.identity)' can't be edited") - return - case .custom: - observabilityScope.emit(error: "custom dependency '\(dependency.packageRef.identity)' can't be edited") + // We only need to update the managed dependency structure to "remove" + // a local package. + // + // Note that we don't actually remove a local package from disk. + if case .fileSystem = dependency.state { + self.state.dependencies.remove(package.identity) + try self.state.save() return } - // If a path is provided then we use it as destination. If not, we - // use the folder with packageName inside editablesPath. - let destination = path ?? self.location.editsDirectory.appending(component: packageName) - - // If there is something present at the destination, we confirm it has - // a valid manifest with name same as the package we are trying to edit. - if fileSystem.exists(destination) { - // FIXME: this should not block - let manifest = try temp_await { - self.loadManifest(packageIdentity: dependency.packageRef.identity, - packageKind: .fileSystem(destination), - packagePath: destination, - packageLocation: dependency.packageRef.locationString, - observabilityScope: observabilityScope, - completion: $0) - } + // Inform the delegate. + let repository = try? dependency.packageRef.makeRepositorySpecifier() + self.delegate?.removing(package: package.identity, packageLocation: repository?.location.description) - guard manifest.displayName == packageName else { - return observabilityScope.emit(error: "package at '\(destination)' is \(manifest.displayName) but was expecting \(packageName)") - } + // Compute the dependency which we need to remove. + let dependencyToRemove: ManagedDependency - // Emit warnings for branch and revision, if they're present. - if let checkoutBranch { - observabilityScope.emit(.editBranchNotCheckedOut( - packageName: packageName, - branchName: checkoutBranch)) - } - if let revision { - observabilityScope.emit(.editRevisionNotUsed( - packageName: packageName, - revisionIdentifier: revision.identifier)) - } - } else { - // Otherwise, create a checkout at the destination from our repository store. - // - // Get handle to the repository. - // TODO: replace with async/await when available - let repository = try dependency.packageRef.makeRepositorySpecifier() - let handle = try temp_await { - repositoryManager.lookup( - package: dependency.packageRef.identity, - repository: repository, - updateStrategy: .never, - observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } - let repo = try handle.open() - - // Do preliminary checks on branch and revision, if provided. - if let branch = checkoutBranch, repo.exists(revision: Revision(identifier: branch)) { - throw WorkspaceDiagnostics.BranchAlreadyExists(branch: branch) - } - if let revision, !repo.exists(revision: revision) { - throw WorkspaceDiagnostics.RevisionDoesNotExist(revision: revision.identifier) - } - - let workingCopy = try handle.createWorkingCopy(at: destination, editable: true) - try workingCopy.checkout(revision: revision ?? checkoutState.revision) - - // Checkout to the new branch if provided. - if let branch = checkoutBranch { - try workingCopy.checkout(newBranch: branch) - } - } - - // For unmanaged dependencies, create the symlink under editables dir. - if let path { - try fileSystem.createDirectory(self.location.editsDirectory) - // FIXME: We need this to work with InMem file system too. - if !(fileSystem is InMemoryFileSystem) { - let symLinkPath = self.location.editsDirectory.appending(component: packageName) - - // Cleanup any existing symlink. - if fileSystem.isSymlink(symLinkPath) { - try fileSystem.removeFileTree(symLinkPath) - } - - // FIXME: We should probably just warn in case we fail to create - // this symlink, which could happen if there is some non-symlink - // entry at this location. - try fileSystem.createSymbolicLink(symLinkPath, pointingAt: path, relative: false) - } - } - - // Remove the existing checkout. - do { - let oldCheckoutPath = self.location.repositoriesCheckoutSubdirectory(for: dependency) - try fileSystem.chmod(.userWritable, path: oldCheckoutPath, options: [.recursive, .onlyFiles]) - try fileSystem.removeFileTree(oldCheckoutPath) - } - - // Save the new state. - self.state.dependencies.add( - try dependency.edited(subpath: RelativePath(validating: packageName), unmanagedPath: path) - ) - try self.state.save() - } - - /// Unedit a managed dependency. See public API unedit(packageName:forceRemove:). - fileprivate func unedit( - dependency: ManagedDependency, - forceRemove: Bool, - root: PackageGraphRootInput? = nil, - observabilityScope: ObservabilityScope - ) throws { - - // Compute if we need to force remove. - var forceRemove = forceRemove - - // If the dependency isn't in edit mode, we can't unedit it. - guard case .edited(_, let unmanagedPath) = dependency.state else { - throw WorkspaceDiagnostics.DependencyNotInEditMode(dependencyName: dependency.packageRef.identity.description) - } - - // Set force remove to true for unmanaged dependencies. Note that - // this only removes the symlink under the editable directory and - // not the actual unmanaged package. - if unmanagedPath != nil { - forceRemove = true - } - - // Form the edit working repo path. - let path = self.location.editSubdirectory(for: dependency) - // Check for uncommited and unpushed changes if force removal is off. - if !forceRemove { - let workingCopy = try repositoryManager.openWorkingCopy(at: path) - guard !workingCopy.hasUncommittedChanges() else { - throw WorkspaceDiagnostics.UncommitedChanges(repositoryPath: path) - } - guard try !workingCopy.hasUnpushedCommits() else { - throw WorkspaceDiagnostics.UnpushedChanges(repositoryPath: path) - } - } - // Remove the editable checkout from disk. - if fileSystem.exists(path) { - try fileSystem.removeFileTree(path) - } - // If this was the last editable dependency, remove the editables directory too. - if fileSystem.exists(self.location.editsDirectory), try fileSystem.getDirectoryContents(self.location.editsDirectory).isEmpty { - try fileSystem.removeFileTree(self.location.editsDirectory) - } - - if case .edited(let basedOn, _) = dependency.state, case .sourceControlCheckout(let checkoutState) = basedOn?.state { - // Restore the original checkout. - // - // The retrieve method will automatically update the managed dependency state. - _ = try self.checkoutRepository(package: dependency.packageRef, at: checkoutState, observabilityScope: observabilityScope) - } else { - // The original dependency was removed, update the managed dependency state. - self.state.dependencies.remove(dependency.packageRef.identity) - try self.state.save() - } - - // Resolve the dependencies if workspace root is provided. We do this to - // ensure the unedited version of this dependency is resolved properly. - if let root { - try self._resolve( - root: root, - explicitProduct: .none, - resolvedFileStrategy: .update(forceResolution: false), - observabilityScope: observabilityScope - ) - } - } - -} - -// MARK: - Pinning Functions - -extension Workspace { - /// Pins all of the current managed dependencies at their checkout state. - fileprivate func saveResolvedFile( - pinsStore: PinsStore, - dependencyManifests: DependencyManifests, - originHash: String, - rootManifestsMinimumToolsVersion: ToolsVersion, - observabilityScope: ObservabilityScope - ) throws { - var dependenciesToPin = [ManagedDependency]() - let requiredDependencies = try dependencyManifests.computePackages().required.filter({ $0.kind.isPinnable }) - for dependency in requiredDependencies { - if let managedDependency = self.state.dependencies[comparingLocation: dependency] { - dependenciesToPin.append(managedDependency) - } else { - observabilityScope.emit(warning: "required dependency \(dependency.identity) (\(dependency.locationString)) was not found in managed dependencies and will not be recorded in resolved file") - } - } - - // try to load the pin store from disk so we can compare for any changes - // this is needed as we want to avoid re-writing the resolved files unless absolutely necessary - var needsUpdate = false - if let storedPinStore = try? self.pinsStore.load() { - // compare for any differences between the existing state and the stored one - // subtle changes between versions of SwiftPM could treat URLs differently - // in which case we don't want to cause unnecessary churn - if dependenciesToPin.count != storedPinStore.pins.count { - needsUpdate = true - } else { - for dependency in dependenciesToPin { - if let pin = storedPinStore.pins.first(where: { $0.value.packageRef.equalsIncludingLocation(dependency.packageRef) }) { - if pin.value.state != PinsStore.Pin(dependency)?.state { - needsUpdate = true - break - } - } else { - needsUpdate = true - break - } - } - } - } else { - needsUpdate = true - } - - // exist early is there is nothing to do - if !needsUpdate { - return - } - - // reset the pinsStore and start pinning the required dependencies. - pinsStore.unpinAll() - for dependency in dependenciesToPin { - pinsStore.pin(dependency) - } - - observabilityScope.trap { - try pinsStore.saveState( - toolsVersion: rootManifestsMinimumToolsVersion, - originHash: originHash - ) - } - - // Ask resolved file watcher to update its value so we don't fire - // an extra event if the file was modified by us. - self.resolvedFileWatcher?.updateValue() - } -} - -fileprivate extension PinsStore { - /// Pin a managed dependency at its checkout state. - /// - /// This method does nothing if the dependency is in edited state. - func pin(_ dependency: Workspace.ManagedDependency) { - if let pin = PinsStore.Pin(dependency) { - self.add(pin) - } - } -} - -fileprivate extension PinsStore.Pin { - init?(_ dependency: Workspace.ManagedDependency) { - switch dependency.state { - case .sourceControlCheckout(.version(let version, let revision)): - self.init( - packageRef: dependency.packageRef, - state: .version(version, revision: revision.identifier) - ) - case .sourceControlCheckout(.branch(let branch, let revision)): - self.init( - packageRef: dependency.packageRef, - state: .branch(name: branch, revision: revision.identifier) - ) - case .sourceControlCheckout(.revision(let revision)): - self.init( - packageRef: dependency.packageRef, - state: .revision(revision.identifier) - ) - case .registryDownload(let version): - self.init( - packageRef: dependency.packageRef, - state: .version(version, revision: .none) - ) - case .edited, .fileSystem, .custom: - // NOOP - return nil - } - } -} - -// MARK: - Manifest Loading and caching - -extension Workspace { - /// A struct representing all the current manifests (root + external) in a package graph. - public struct DependencyManifests { - /// The package graph root. - let root: PackageGraphRoot - - /// The dependency manifests in the transitive closure of root manifest. - let dependencies: [(manifest: Manifest, dependency: ManagedDependency, productFilter: ProductFilter, fileSystem: FileSystem)] - - private let workspace: Workspace - - fileprivate init( - root: PackageGraphRoot, - dependencies: [(manifest: Manifest, dependency: ManagedDependency, productFilter: ProductFilter, fileSystem: FileSystem)], - workspace: Workspace - ) { - self.root = root - self.dependencies = dependencies - self.workspace = workspace - } - - /// Returns all manifests contained in DependencyManifests. - public func allDependencyManifests() -> OrderedCollections.OrderedDictionary { - return self.dependencies.reduce(into: OrderedCollections.OrderedDictionary()) { partial, item in - partial[item.dependency.packageRef.identity] = (item.manifest, item.fileSystem) - } - } - - /// Computes the identities which are declared in the manifests but aren't present in dependencies. - public func missingPackages() throws -> Set { - return try self.computePackages().missing - } - - /// Returns the list of packages which are allowed to vend products with unsafe flags. - func unsafeAllowedPackages() -> Set { - var result = Set() - - for dependency in self.dependencies { - let dependency = dependency.dependency - switch dependency.state { - case .sourceControlCheckout(let checkout): - if checkout.isBranchOrRevisionBased { - result.insert(dependency.packageRef) - } - case .registryDownload, .edited, .custom: - continue - case .fileSystem: - result.insert(dependency.packageRef) - } - } - - // Root packages are always allowed to use unsafe flags. - result.formUnion(root.packageReferences) - - return result - } - - func computePackages() throws -> (required: Set, missing: Set) { - let manifestsMap: [PackageIdentity: Manifest] = try Dictionary(throwingUniqueKeysWithValues: - self.root.packages.map { ($0.key, $0.value.manifest) } + - self.dependencies.map { ($0.dependency.packageRef.identity, $0.manifest) } - ) - - var inputIdentities: Set = [] - let inputNodes: [GraphLoadingNode] = self.root.packages.map{ identity, package in - inputIdentities.insert(package.reference) - let node = GraphLoadingNode(identity: identity, manifest: package.manifest, productFilter: .everything, fileSystem: self.workspace.fileSystem) - return node - } + self.root.dependencies.compactMap{ dependency in - let package = dependency.createPackageRef() - inputIdentities.insert(package) - return manifestsMap[dependency.identity].map { manifest in - GraphLoadingNode(identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, fileSystem: self.workspace.fileSystem) - } - } - - // FIXME: this is dropping legitimate packages with equal identities and should be revised as part of the identity work - var requiredIdentities: Set = [] - _ = transitiveClosure(inputNodes) { node in - return node.manifest.dependenciesRequired(for: node.productFilter).compactMap{ dependency in - let package = dependency.createPackageRef() - requiredIdentities.insert(package) - return manifestsMap[dependency.identity].map { manifest in - GraphLoadingNode(identity: dependency.identity, manifest: manifest, productFilter: dependency.productFilter, fileSystem: self.workspace.fileSystem) - } - } - } - // FIXME: This should be an ordered set. - requiredIdentities = inputIdentities.union(requiredIdentities) - - let availableIdentities: Set = try Set(manifestsMap.map { - // FIXME: adding this guard to ensure refactoring is correct 9/21 - // we only care about remoteSourceControl for this validation. it would otherwise trigger for - // a dependency is put into edit mode, which we want to deprecate anyways - if case .remoteSourceControl = $0.1.packageKind { - let effectiveURL = workspace.mirrors.effective(for: $0.1.packageLocation) - guard effectiveURL == $0.1.packageKind.locationString else { - throw InternalError("effective url for \($0.1.packageLocation) is \(effectiveURL), different from expected \($0.1.packageKind.locationString)") - } - } - return PackageReference(identity: $0.key, kind: $0.1.packageKind) - }) - // We should never have loaded a manifest we don't need. - assert(availableIdentities.isSubset(of: requiredIdentities), "\(availableIdentities) | \(requiredIdentities)") - // These are the missing package identities. - let missingIdentities = requiredIdentities.subtracting(availableIdentities) - - return (requiredIdentities, missingIdentities) - } - - /// Returns constraints of the dependencies, including edited package constraints. - func dependencyConstraints() throws -> [PackageContainerConstraint] { - var allConstraints = [PackageContainerConstraint]() - - for (externalManifest, managedDependency, productFilter, _) in dependencies { - // For edited packages, add a constraint with unversioned requirement so the - // resolver doesn't try to resolve it. - switch managedDependency.state { - case .edited: - // FIXME: We shouldn't need to construct a new package reference object here. - // We should get the correct one from managed dependency object. - let ref = PackageReference.fileSystem( - identity: managedDependency.packageRef.identity, - path: workspace.path(to: managedDependency) - ) - let constraint = PackageContainerConstraint( - package: ref, - requirement: .unversioned, - products: productFilter) - allConstraints.append(constraint) - case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: - break - } - allConstraints += try externalManifest.dependencyConstraints(productFilter: productFilter) - } - return allConstraints - } - - // FIXME: @testable(internal) - /// Returns a list of constraints for all 'edited' package. - public func editedPackagesConstraints() -> [PackageContainerConstraint] { - var constraints = [PackageContainerConstraint]() - - for (_, managedDependency, productFilter, _) in dependencies { - switch managedDependency.state { - case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: continue - case .edited: break - } - // FIXME: We shouldn't need to construct a new package reference object here. - // We should get the correct one from managed dependency object. - let ref = PackageReference.fileSystem( - identity: managedDependency.packageRef.identity, - path: workspace.path(to: managedDependency) - ) - let constraint = PackageContainerConstraint( - package: ref, - requirement: .unversioned, - products: productFilter) - constraints.append(constraint) - } - return constraints - } - } - - /// Watch the Package.resolved for changes. - /// - /// This is useful if clients want to be notified when the Package.resolved - /// file is changed *outside* of libSwiftPM operations. For example, as part - /// of a git operation. - public func watchResolvedFile() throws { - // Return if we're already watching it. - guard self.resolvedFileWatcher == nil else { return } - self.resolvedFileWatcher = try ResolvedFileWatcher(resolvedFile: self.location.resolvedVersionsFile) { [weak self] in - self?.delegate?.resolvedFileChanged() - } - } - - /// Create the cache directories. - fileprivate func createCacheDirectories(observabilityScope: ObservabilityScope) { - observabilityScope.trap { - try fileSystem.createDirectory(self.repositoryManager.path, recursive: true) - try fileSystem.createDirectory(self.location.repositoriesCheckoutsDirectory, recursive: true) - try fileSystem.createDirectory(self.location.artifactsDirectory, recursive: true) - } - } - - /// Returns the location of the dependency. - /// - /// Source control dependencies will return the subpath inside `checkoutsPath` and - /// Registry dependencies will return the subpath inside `registryDownloadsPath` and - /// edited dependencies will either return a subpath inside `editablesPath` or - /// a custom path. - public func path(to dependency: Workspace.ManagedDependency) -> AbsolutePath { - switch dependency.state { - case .sourceControlCheckout: - return self.location.repositoriesCheckoutSubdirectory(for: dependency) - case .registryDownload: - return self.location.registryDownloadSubdirectory(for: dependency) - case .edited(_, let path): - return path ?? self.location.editSubdirectory(for: dependency) - case .fileSystem(let path): - return path - case .custom(_, let path): - return path - } - } - - /// Returns manifest interpreter flags for a package. - // TODO: should this be throwing instead? - public func interpreterFlags(for packagePath: AbsolutePath) -> [String] { - do { - guard let manifestLoader = self.manifestLoader as? ManifestLoader else { - throw StringError("unexpected manifest loader kind") - } - - let manifestPath = try ManifestLoader.findManifest(packagePath: packagePath, fileSystem: self.fileSystem, currentToolsVersion: self.currentToolsVersion) - let manifestToolsVersion = try ToolsVersionParser.parse(manifestPath: manifestPath, fileSystem: self.fileSystem) - - guard self.currentToolsVersion >= manifestToolsVersion || SwiftVersion.current.isDevelopment, manifestToolsVersion >= ToolsVersion.minimumRequired else { - throw StringError("invalid tools version") - } - return manifestLoader.interpreterFlags(for: manifestToolsVersion) - } catch { - // We ignore all failures here and return empty array. - return [] - } - } - - /// Load the manifests for the current dependency tree. - /// - /// This will load the manifests for the root package as well as all the - /// current dependencies from the working checkouts.l - public func loadDependencyManifests( - root: PackageGraphRoot, - automaticallyAddManagedDependencies: Bool = false, - observabilityScope: ObservabilityScope - ) throws -> DependencyManifests { - // Utility Just because a raw tuple cannot be hashable. - struct Key: Hashable { - let identity: PackageIdentity - let productFilter: ProductFilter - } - - // Make a copy of dependencies as we might mutate them in the for loop. - let dependenciesToCheck = Array(self.state.dependencies) - // Remove any managed dependency which has become a root. - for dependency in dependenciesToCheck { - if root.packages.keys.contains(dependency.packageRef.identity) { - observabilityScope.makeChildScope(description: "removing managed dependencies", metadata: dependency.packageRef.diagnosticsMetadata).trap { - try self.remove(package: dependency.packageRef) - } - } - } - - // Validates that all the managed dependencies are still present in the file system. - self.fixManagedDependencies(observabilityScope: observabilityScope) - guard !observabilityScope.errorsReported else { - // return partial results - return DependencyManifests(root: root, dependencies: [], workspace: self) - } - - // Load root dependencies manifests (in parallel) - let rootDependencies = root.dependencies.map{ $0.createPackageRef() } - let rootDependenciesManifests = try temp_await { self.loadManagedManifests(for: rootDependencies, observabilityScope: observabilityScope, completion: $0) } - - let topLevelManifests = root.manifests.merging(rootDependenciesManifests, uniquingKeysWith: { lhs, rhs in - return lhs // prefer roots! - }) - - // optimization: preload first level dependencies manifest (in parallel) - let firstLevelDependencies = topLevelManifests.values.map { $0.dependencies.map{ $0.createPackageRef() } }.flatMap({ $0 }) - let firstLevelManifests = try temp_await { self.loadManagedManifests(for: firstLevelDependencies, observabilityScope: observabilityScope, completion: $0) } // FIXME: this should not block - - // Continue to load the rest of the manifest for this graph - // Creates a map of loaded manifests. We do this to avoid reloading the shared nodes. - var loadedManifests = firstLevelManifests - // Compute the transitive closure of available dependencies. - let topologicalSortInput = topLevelManifests.map { identity, manifest in KeyedPair(manifest, key: Key(identity: identity, productFilter: .everything)) } - let topologicalSortSuccessors: (KeyedPair) throws -> [KeyedPair] = { pair in - // optimization: preload manifest we know about in parallel - let dependenciesRequired = pair.item.dependenciesRequired(for: pair.key.productFilter) - let dependenciesToLoad = dependenciesRequired.map{ $0.createPackageRef() }.filter { !loadedManifests.keys.contains($0.identity) } - // pre-populate managed dependencies if we are asked to do so (this happens when resolving to a resolved file) - if automaticallyAddManagedDependencies { - try dependenciesToLoad.forEach { ref in - // Since we are creating managed dependencies based on the resolved file in this mode, but local packages aren't part of that file, they will be missing from it. So we're eagerly adding them here, but explicitly don't add any that are overridden by a root with the same identity since that would lead to loading the given package twice, once as a root and once as a dependency which violates various assumptions. - if case .fileSystem = ref.kind, !root.manifests.keys.contains(ref.identity) { - try self.state.dependencies.add(.fileSystem(packageRef: ref)) - } - } - observabilityScope.trap { try self.state.save() } - } - let dependenciesManifests = try temp_await { self.loadManagedManifests(for: dependenciesToLoad, observabilityScope: observabilityScope, completion: $0) } - dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } - return dependenciesRequired.compactMap { dependency in - loadedManifests[dependency.identity].flatMap { - // we also compare the location as this function may attempt to load - // dependencies that have the same identity but from a different location - // which is an error case we diagnose an report about in the GraphLoading part which - // is prepared to handle the case where not all manifest are available - $0.canonicalPackageLocation == dependency.createPackageRef().canonicalLocation ? - KeyedPair($0, key: Key(identity: dependency.identity, productFilter: dependency.productFilter)) : nil - } - } - } - - // Look for any cycle in the dependencies. - if let cycle = try findCycle(topologicalSortInput, successors: topologicalSortSuccessors) { - observabilityScope.emit( - error: "cyclic dependency declaration found: " + - (cycle.path + cycle.cycle).map({ $0.key.identity.description }).joined(separator: " -> ") + - " -> " + cycle.cycle[0].key.identity.description - ) - // return partial results - return DependencyManifests(root: root, dependencies: [], workspace: self) - } - let allManifestsWithPossibleDuplicates = try topologicalSort(topologicalSortInput, successors: topologicalSortSuccessors) - - // merge the productFilter of the same package (by identity) - var deduplication = [PackageIdentity: Int]() - var allManifests = [(identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter)]() - for pair in allManifestsWithPossibleDuplicates { - if let index = deduplication[pair.key.identity] { - let productFilter = allManifests[index].productFilter.merge(pair.key.productFilter) - allManifests[index] = (pair.key.identity, pair.item, productFilter) - } else { - deduplication[pair.key.identity] = allManifests.count - allManifests.append((pair.key.identity, pair.item, pair.key.productFilter)) - } - } - - let dependencyManifests = allManifests.filter{ !root.manifests.values.contains($0.manifest) } - - // TODO: this check should go away when introducing explicit overrides - // check for overrides attempts with same name but different path - let rootManifestsByName = Array(root.manifests.values).spm_createDictionary{ ($0.displayName, $0) } - dependencyManifests.forEach { identity, manifest, _ in - if let override = rootManifestsByName[manifest.displayName], override.packageLocation != manifest.packageLocation { - observabilityScope.emit(error: "unable to override package '\(manifest.displayName)' because its identity '\(PackageIdentity(urlString: manifest.packageLocation))' doesn't match override's identity (directory name) '\(PackageIdentity(urlString: override.packageLocation))'") - } - } - - let dependencies = try dependencyManifests.map{ identity, manifest, productFilter -> (Manifest, ManagedDependency, ProductFilter, FileSystem) in - guard let dependency = self.state.dependencies[identity] else { - throw InternalError("dependency not found for \(identity) at \(manifest.packageLocation)") - } - - let packageRef = PackageReference(identity: identity, kind: manifest.packageKind) - let fileSystem = try self.getFileSystem(package: packageRef, state: dependency.state, observabilityScope: observabilityScope) - return (manifest, dependency, productFilter, fileSystem ?? self.fileSystem) - } - - return DependencyManifests(root: root, dependencies: dependencies, workspace: self) - } - - /// Loads the given manifests, if it is present in the managed dependencies. - private func loadManagedManifests(for packages: [PackageReference], observabilityScope: ObservabilityScope, completion: @escaping (Result<[PackageIdentity: Manifest], Error>) -> Void) { - let sync = DispatchGroup() - let manifests = ThreadSafeKeyValueStore() - Set(packages).forEach { package in - sync.enter() - self.loadManagedManifest(for: package, observabilityScope: observabilityScope) { manifest in - defer { sync.leave() } - if let manifest { - manifests[package.identity] = manifest - } - } - } - - sync.notify(queue: .sharedConcurrent) { - completion(.success(manifests.get())) - } - } - - /// Loads the given manifest, if it is present in the managed dependencies. - fileprivate func loadManagedManifest( - for package: PackageReference, - observabilityScope: ObservabilityScope, - completion: @escaping (Manifest?) -> Void - ) { - // Check if this dependency is available. - // we also compare the location as this function may attempt to load - // dependencies that have the same identity but from a different location - // which is an error case we diagnose an report about in the GraphLoading part which - // is prepared to handle the case where not all manifest are available - guard let managedDependency = self.state.dependencies[comparingLocation: package] else { - return completion(.none) - } - - // Get the path of the package. - let packagePath = self.path(to: managedDependency) - - // The kind and version, if known. - let packageKind: PackageReference.Kind - let packageVersion: Version? - switch managedDependency.state { - case .sourceControlCheckout(let checkoutState): - packageKind = managedDependency.packageRef.kind - switch checkoutState { - case .version(let checkoutVersion, _): - packageVersion = checkoutVersion - default: - packageVersion = .none - } - case .registryDownload(let downloadedVersion): - packageKind = managedDependency.packageRef.kind - packageVersion = downloadedVersion - case .custom(let availableVersion, _): - packageKind = managedDependency.packageRef.kind - packageVersion = availableVersion - case .edited, .fileSystem: - packageKind = .fileSystem(packagePath) - packageVersion = .none - } - - let fileSystem: FileSystem? - do { - fileSystem = try self.getFileSystem(package: package, state: managedDependency.state, observabilityScope: observabilityScope) - } catch { - // only warn here in case of issues since we should not even get here without a valid package container - observabilityScope.emit( - warning: "unexpected failure while accessing custom package container", - underlyingError: error - ) - fileSystem = nil - } - - // Load and return the manifest. - self.loadManifest( - packageIdentity: managedDependency.packageRef.identity, - packageKind: packageKind, - packagePath: packagePath, - packageLocation: managedDependency.packageRef.locationString, - packageVersion: packageVersion, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) { result in - // error is added to diagnostics in the function above - completion(try? result.get()) - } - } - - /// Load the manifest at a given path. - /// - /// This is just a helper wrapper to the manifest loader. - fileprivate func loadManifest( - packageIdentity: PackageIdentity, - packageKind: PackageReference.Kind, - packagePath: AbsolutePath, - packageLocation: String, - packageVersion: Version? = nil, - fileSystem: FileSystem? = nil, - observabilityScope: ObservabilityScope, - completion: @escaping (Result) -> Void - ) { - let fileSystem = fileSystem ?? self.fileSystem - - // Load the manifest, bracketed by the calls to the delegate callbacks. - delegate?.willLoadManifest(packageIdentity: packageIdentity, packagePath: packagePath, url: packageLocation, version: packageVersion, packageKind: packageKind) - - let manifestLoadingScope = observabilityScope.makeChildScope(description: "Loading manifest") { - .packageMetadata(identity: packageIdentity, kind: packageKind) - } - - var manifestLoadingDiagnostics = [Diagnostic]() - - let start = DispatchTime.now() - self.manifestLoader.load( - packagePath: packagePath, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: packageVersion.map { (version: $0, revision: nil) }, - currentToolsVersion: self.currentToolsVersion, - identityResolver: self.identityResolver, - fileSystem: fileSystem, - observabilityScope: manifestLoadingScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent - ) { result in - let duration = start.distance(to: .now()) - var result = result - switch result { - case .failure(let error): - manifestLoadingDiagnostics.append(.error(error)) - self.delegate?.didLoadManifest(packageIdentity: packageIdentity, packagePath: packagePath, url: packageLocation, version: packageVersion, packageKind: packageKind, manifest: nil, diagnostics: manifestLoadingDiagnostics, duration: duration) - case .success(let manifest): - let validator = ManifestValidator(manifest: manifest, sourceControlValidator: self.repositoryManager, fileSystem: self.fileSystem) - let validationIssues = validator.validate() - if !validationIssues.isEmpty { - // Diagnostics.fatalError indicates that a more specific diagnostic has already been added. - result = .failure(Diagnostics.fatalError) - manifestLoadingDiagnostics.append(contentsOf: validationIssues) - } - self.delegate?.didLoadManifest(packageIdentity: packageIdentity, packagePath: packagePath, url: packageLocation, version: packageVersion, packageKind: packageKind, manifest: manifest, diagnostics: manifestLoadingDiagnostics, duration: duration) - } - manifestLoadingScope.emit(manifestLoadingDiagnostics) - completion(result) - } - } - - /// Validates that all the edited dependencies are still present in the file system. - /// If some checkout dependency is removed form the file system, clone it again. - /// If some edited dependency is removed from the file system, mark it as unedited and - /// fallback on the original checkout. - fileprivate func fixManagedDependencies(observabilityScope: ObservabilityScope) { - - // Reset managed dependencies if the state file was removed during the lifetime of the Workspace object. - if !self.state.dependencies.isEmpty && !self.state.stateFileExists() { - try? self.state.reset() - } - - // Make a copy of dependencies as we might mutate them in the for loop. - let allDependencies = Array(self.state.dependencies) - for dependency in allDependencies { - observabilityScope.makeChildScope(description: "copying managed dependencies", metadata: dependency.packageRef.diagnosticsMetadata).trap { - // If the dependency is present, we're done. - let dependencyPath = self.path(to: dependency) - if fileSystem.isDirectory(dependencyPath) { - return - } - - switch dependency.state { - case .sourceControlCheckout(let checkoutState): - // If some checkout dependency has been removed, retrieve it again. - _ = try self.checkoutRepository(package: dependency.packageRef, at: checkoutState, observabilityScope: observabilityScope) - observabilityScope.emit(.checkedOutDependencyMissing(packageName: dependency.packageRef.identity.description)) - - case .registryDownload(let version): - // If some downloaded dependency has been removed, retrieve it again. - _ = try self.downloadRegistryArchive(package: dependency.packageRef, at: version, observabilityScope: observabilityScope) - observabilityScope.emit(.registryDependencyMissing(packageName: dependency.packageRef.identity.description)) - - case .custom(let version, let path): - let container = try temp_await { - self.packageContainerProvider.getContainer( - for: dependency.packageRef, - updateStrategy: .never, - observabilityScope: observabilityScope, - on: .sharedConcurrent, - completion: $0 - ) - } - if let customContainer = container as? CustomPackageContainer { - let newPath = try customContainer.retrieve(at: version, observabilityScope: observabilityScope) - observabilityScope.emit(.customDependencyMissing(packageName: dependency.packageRef.identity.description)) - - // FIXME: We should be able to handle this case and also allow changed paths for registry and SCM downloads. - if newPath != path { - observabilityScope.emit(error: "custom dependency was retrieved at a different path: \(newPath)") - } - } else { - observabilityScope.emit(error: "invalid custom dependency container: \(container)") - } - case .edited: - // If some edited dependency has been removed, mark it as unedited. - // - // Note: We don't resolve the dependencies when unediting - // here because we expect this method to be called as part - // of some other resolve operation (i.e. resolve, update, etc). - try self.unedit(dependency: dependency, forceRemove: true, observabilityScope: observabilityScope) - - observabilityScope.emit(.editedDependencyMissing(packageName: dependency.packageRef.identity.description)) - - case .fileSystem: - self.state.dependencies.remove(dependency.packageRef.identity) - try self.state.save() - } - } - } - } -} - -// MARK: - Binary artifacts - -extension Workspace { - fileprivate func updateBinaryArtifacts( - manifests: DependencyManifests, - addedOrUpdatedPackages: [PackageReference], - observabilityScope: ObservabilityScope - ) throws { - let manifestArtifacts = try self.binaryArtifactsManager.parseArtifacts(from: manifests, observabilityScope: observabilityScope) - - var artifactsToRemove: [ManagedArtifact] = [] - var artifactsToAdd: [ManagedArtifact] = [] - var artifactsToDownload: [BinaryArtifactsManager.RemoteArtifact] = [] - var artifactsToExtract: [ManagedArtifact] = [] - - for artifact in state.artifacts { - if !manifestArtifacts.local.contains(where: { $0.packageRef == artifact.packageRef && $0.targetName == artifact.targetName }) && - !manifestArtifacts.remote.contains(where: { $0.packageRef == artifact.packageRef && $0.targetName == artifact.targetName }) { - artifactsToRemove.append(artifact) - } - } - - for artifact in manifestArtifacts.local { - let existingArtifact = self.state.artifacts[ - packageIdentity: artifact.packageRef.identity, - targetName: artifact.targetName - ] - - if artifact.path.extension?.lowercased() == "zip" { - // If we already have an artifact that was extracted from an archive with the same checksum, - // we don't need to extract the artifact again. - if case .local(let existingChecksum) = existingArtifact?.source, existingChecksum == (try self.binaryArtifactsManager.checksum(forBinaryArtifactAt: artifact.path)) { - continue - } - - artifactsToExtract.append(artifact) - } else { - guard let _ = try BinaryArtifactsManager.deriveBinaryArtifact(fileSystem: self.fileSystem, path: artifact.path, observabilityScope: observabilityScope) else { - observabilityScope.emit(.localArtifactNotFound(artifactPath: artifact.path, targetName: artifact.targetName)) - continue - } - artifactsToAdd.append(artifact) - } - - if let existingArtifact, isAtArtifactsDirectory(existingArtifact) { - // Remove the old extracted artifact, be it local archived or remote one. - artifactsToRemove.append(existingArtifact) - } - } - - for artifact in manifestArtifacts.remote { - let existingArtifact = self.state.artifacts[ - packageIdentity: artifact.packageRef.identity, - targetName: artifact.targetName - ] - - if let existingArtifact { - if case .remote(let existingURL, let existingChecksum) = existingArtifact.source { - // If we already have an artifact with the same checksum, we don't need to download it again. - if artifact.checksum == existingChecksum { - continue - } - - let urlChanged = artifact.url != URL(string: existingURL) - // If the checksum is different but the package wasn't updated, this is a security risk. - if !urlChanged && !addedOrUpdatedPackages.contains(artifact.packageRef) { - observabilityScope.emit(.artifactChecksumChanged(targetName: artifact.targetName)) - continue - } - } - - if isAtArtifactsDirectory(existingArtifact) { - // Remove the old extracted artifact, be it local archived or remote one. - artifactsToRemove.append(existingArtifact) - } - } - - artifactsToDownload.append(artifact) - } - - // Remove the artifacts and directories which are not needed anymore. - observabilityScope.trap { - for artifact in artifactsToRemove { - state.artifacts.remove(packageIdentity: artifact.packageRef.identity, targetName: artifact.targetName) - - if isAtArtifactsDirectory(artifact) { - try fileSystem.removeFileTree(artifact.path) - } - } - - for directory in try fileSystem.getDirectoryContents(self.location.artifactsDirectory) { - let directoryPath = self.location.artifactsDirectory.appending(component: directory) - if try fileSystem.isDirectory(directoryPath) && fileSystem.getDirectoryContents(directoryPath).isEmpty { - try fileSystem.removeFileTree(directoryPath) - } - } - } - - guard !observabilityScope.errorsReported else { - throw Diagnostics.fatalError - } - - // Download the artifacts - let downloadedArtifacts = try self.binaryArtifactsManager.download( - artifactsToDownload, - artifactsDirectory: self.location.artifactsDirectory, - observabilityScope: observabilityScope - ) - artifactsToAdd.append(contentsOf: downloadedArtifacts) - - // Extract the local archived artifacts - let extractedLocalArtifacts = try self.binaryArtifactsManager.extract( - artifactsToExtract, - artifactsDirectory: self.location.artifactsDirectory, - observabilityScope: observabilityScope - ) - artifactsToAdd.append(contentsOf: extractedLocalArtifacts) - - // Add the new artifacts - for artifact in artifactsToAdd { - self.state.artifacts.add(artifact) - } - - guard !observabilityScope.errorsReported else { - throw Diagnostics.fatalError - } - - observabilityScope.trap { - try self.state.save() - } - - func isAtArtifactsDirectory(_ artifact: ManagedArtifact) -> Bool { - artifact.path.isDescendant(of: self.location.artifactsDirectory) - } - } -} - -// MARK: - Dependency Management - -extension Workspace { - enum ResolvedFileStrategy { - case lockFile - case update(forceResolution: Bool) - case bestEffort - } - - @discardableResult - private func _resolve( - root: PackageGraphRootInput, - explicitProduct: String?, - resolvedFileStrategy: ResolvedFileStrategy, - observabilityScope: ObservabilityScope - ) throws -> DependencyManifests { - let start = DispatchTime.now() - self.delegate?.willResolveDependencies() - defer { - self.delegate?.didResolveDependencies(duration: start.distance(to: .now())) - } - - switch resolvedFileStrategy { - case .lockFile: - observabilityScope.emit(info: "using '\(self.location.resolvedVersionsFile.basename)' file as lock file") - return try self._resolveBasedOnResolvedVersionsFile( - root: root, - explicitProduct: explicitProduct, - observabilityScope: observabilityScope - ) - case .update(let forceResolution): - return try resolveAndUpdateResolvedFile(forceResolution: forceResolution) - case .bestEffort: - guard !self.state.dependencies.hasEditedDependencies() else { - return try resolveAndUpdateResolvedFile(forceResolution: false) - } - guard self.fileSystem.exists(self.location.resolvedVersionsFile) else { - return try resolveAndUpdateResolvedFile(forceResolution: false) - } - - guard let pinsStore = try? self.pinsStore.load(), let storedHash = pinsStore.originHash else { - observabilityScope.emit(debug: "'\(self.location.resolvedVersionsFile.basename)' origin hash is missing. resolving and updating accordingly") - return try resolveAndUpdateResolvedFile(forceResolution: false) - } - - let currentHash = try self.computeResolvedFileOriginHash(root: root) - guard storedHash == currentHash else { - observabilityScope.emit(debug: "'\(self.location.resolvedVersionsFile.basename)' origin hash does do not match manifest dependencies. resolving and updating accordingly") - return try resolveAndUpdateResolvedFile(forceResolution: false) - } - - observabilityScope.emit(debug: "'\(self.location.resolvedVersionsFile.basename)' origin hash matches manifest dependencies, attempting resolution based on this file") - let (manifests, precomputationResult) = try self.tryResolveBasedOnResolvedVersionsFile( - root: root, - explicitProduct: explicitProduct, - observabilityScope: observabilityScope - ) - switch precomputationResult { - case .notRequired: - return manifests - case .required(reason: .errorsPreviouslyReported): - return manifests - case .required(let reason): - // FIXME: ideally this is not done based on a side-effect - let reasonString = Self.format(workspaceResolveReason: reason) - observabilityScope.emit(debug: "resolution based on '\(self.location.resolvedVersionsFile.basename)' could not be completed because \(reasonString). resolving and updating accordingly") - return try resolveAndUpdateResolvedFile(forceResolution: false) - } - } - - func resolveAndUpdateResolvedFile(forceResolution: Bool) throws -> DependencyManifests { - observabilityScope.emit(debug: "resolving and updating '\(self.location.resolvedVersionsFile.basename)'") - return try self.resolveAndUpdateResolvedFile( - root: root, - explicitProduct: explicitProduct, - forceResolution: forceResolution, - constraints: [], - observabilityScope: observabilityScope - ) - } - } - - private func computeResolvedFileOriginHash(root: PackageGraphRootInput) throws -> String { - var content = try root.packages.reduce(into: "", { partial, element in - let path = try ManifestLoader.findManifest( - packagePath: element, - fileSystem: self.fileSystem, - currentToolsVersion: self.currentToolsVersion - ) - partial.append(try self.fileSystem.readFileContents(path)) - }) - content += root.dependencies.reduce(into: "", { partial, element in - partial += element.locationString - }) - return content.sha256Checksum - } - - @discardableResult - func _resolveBasedOnResolvedVersionsFile( - root: PackageGraphRootInput, - explicitProduct: String?, - observabilityScope: ObservabilityScope) throws -> DependencyManifests { - let (manifests, precomputationResult) = try self.tryResolveBasedOnResolvedVersionsFile( - root: root, - explicitProduct: explicitProduct, - observabilityScope: observabilityScope - ) - switch precomputationResult { - case .notRequired: - return manifests - case .required(reason: .errorsPreviouslyReported): - return manifests - case .required(let reason): - // FIXME: ideally this is not done based on a side-effect - let reasonString = Self.format(workspaceResolveReason: reason) - if !self.fileSystem.exists(self.location.resolvedVersionsFile) { - observabilityScope.emit(error: "a resolved file is required when automatic dependency resolution is disabled and should be placed at \(self.location.resolvedVersionsFile.pathString). \(reasonString)") - } else { - observabilityScope.emit(error: "an out-of-date resolved file was detected at \(self.location.resolvedVersionsFile.pathString), which is not allowed when automatic dependency resolution is disabled; please make sure to update the file to reflect the changes in dependencies. \(reasonString)") - } - return manifests - } - } - - /// Resolves the dependencies according to the entries present in the Package.resolved file. - /// - /// This method bypasses the dependency resolution and resolves dependencies - /// according to the information in the resolved file. - fileprivate func tryResolveBasedOnResolvedVersionsFile( - root: PackageGraphRootInput, - explicitProduct: String?, - observabilityScope: ObservabilityScope - ) throws -> (DependencyManifests, ResolutionPrecomputationResult) { - // Ensure the cache path exists. - self.createCacheDirectories(observabilityScope: observabilityScope) - - // FIXME: this should not block - let rootManifests = try temp_await { self.loadRootManifests(packages: root.packages, observabilityScope: observabilityScope, completion: $0) } - let graphRoot = PackageGraphRoot(input: root, manifests: rootManifests, explicitProduct: explicitProduct) - - // Load the pins store or abort now. - guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }), !observabilityScope.errorsReported else { - return (try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope), .notRequired) - } - - // Request all the containers to fetch them in parallel. - // - // We just request the packages here, repository manager will - // automatically manage the parallelism. - let group = DispatchGroup() - for pin in pinsStore.pins.values { - group.enter() - let observabilityScope = observabilityScope.makeChildScope(description: "requesting package containers", metadata: pin.packageRef.diagnosticsMetadata) - - let updateStrategy: ContainerUpdateStrategy = { - if self.configuration.skipDependenciesUpdates { - return .never - } else { - switch pin.state { - case .branch: - return .always - case .revision(let revision): - return .ifNeeded(revision: revision) - case .version(_, let .some(revision)): - return .ifNeeded(revision: revision) - case .version(_, .none): - return .always - } - } - }() - - self.packageContainerProvider.getContainer( - for: pin.packageRef, - updateStrategy: updateStrategy, - observabilityScope: observabilityScope, - on: .sharedConcurrent, - completion: { _ in group.leave() } - ) - } - group.wait() - - // Compute the pins that we need to actually clone. - // - // We require cloning if there is no checkout or if the checkout doesn't - // match with the pin. - let requiredPins = pinsStore.pins.values.filter{ pin in - // also compare the location in case it has changed - guard let dependency = state.dependencies[comparingLocation: pin.packageRef] else { - return true - } - switch dependency.state { - case .sourceControlCheckout(let checkoutState): - return !pin.state.equals(checkoutState) - case .registryDownload(let version): - return !pin.state.equals(version) - case .edited, .fileSystem, .custom: - return true - } - } - - // Retrieve the required pins. - for pin in requiredPins { - observabilityScope.makeChildScope(description: "retrieving dependency pins", metadata: pin.packageRef.diagnosticsMetadata).trap { - switch pin.packageRef.kind { - case .localSourceControl, .remoteSourceControl: - _ = try self.checkoutRepository(package: pin.packageRef, at: pin.state, observabilityScope: observabilityScope) - case .registry: - _ = try self.downloadRegistryArchive(package: pin.packageRef, at: pin.state, observabilityScope: observabilityScope) - default: - throw InternalError("invalid pin type \(pin.packageRef.kind)") - } - } - } - - let currentManifests = try self.loadDependencyManifests(root: graphRoot, automaticallyAddManagedDependencies: true, observabilityScope: observabilityScope) - - try self.updateBinaryArtifacts(manifests: currentManifests, addedOrUpdatedPackages: [], observabilityScope: observabilityScope) - - let precomputationResult = try self.precomputeResolution( - root: graphRoot, - dependencyManifests: currentManifests, - pinsStore: pinsStore, - constraints: [], - observabilityScope: observabilityScope - ) - - return (currentManifests, precomputationResult) - } - - /// Implementation of resolve(root:diagnostics:). - /// - /// The extra constraints will be added to the main requirements. - /// It is useful in situations where a requirement is being - /// imposed outside of manifest and pins file. E.g., when using a command - /// like `$ swift package resolve foo --version 1.0.0`. - @discardableResult - fileprivate func resolveAndUpdateResolvedFile( - root: PackageGraphRootInput, - explicitProduct: String? = nil, - forceResolution: Bool, - constraints: [PackageContainerConstraint], - observabilityScope: ObservabilityScope - ) throws -> DependencyManifests { - // Ensure the cache path exists and validate that edited dependencies. - self.createCacheDirectories(observabilityScope: observabilityScope) - - // FIXME: this should not block - // Load the root manifests and currently checked out manifests. - let rootManifests = try temp_await { self.loadRootManifests(packages: root.packages, observabilityScope: observabilityScope, completion: $0) } - let rootManifestsMinimumToolsVersion = rootManifests.values.map{ $0.toolsVersion }.min() ?? ToolsVersion.current - let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) - - // Load the current manifests. - let graphRoot = PackageGraphRoot(input: root, manifests: rootManifests, explicitProduct: explicitProduct) - let currentManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) - guard !observabilityScope.errorsReported else { - return currentManifests - } - - // load and update the pins store with any changes from loading the top level dependencies - guard let pinsStore = self.loadAndUpdatePinsStore( - dependencyManifests: currentManifests, - rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, - observabilityScope: observabilityScope - ) else { - // abort if PinsStore reported any errors. - return currentManifests - } - - // abort if PinsStore reported any errors. - guard !observabilityScope.errorsReported else { - return currentManifests - } - - // Compute the missing package identities. - let missingPackages = try currentManifests.missingPackages() - - // Compute if we need to run the resolver. We always run the resolver if - // there are extra constraints. - if !missingPackages.isEmpty { - delegate?.willResolveDependencies(reason: .newPackages(packages: Array(missingPackages))) - } else if !constraints.isEmpty || forceResolution { - delegate?.willResolveDependencies(reason: .forced) - } else { - let result = try self.precomputeResolution( - root: graphRoot, - dependencyManifests: currentManifests, - pinsStore: pinsStore, - constraints: constraints, - observabilityScope: observabilityScope - ) - - switch result { - case .notRequired: - // since nothing changed we can exit early, - // but need update resolved file and download an missing binary artifact - try self.saveResolvedFile( - pinsStore: pinsStore, - dependencyManifests: currentManifests, - originHash: resolvedFileOriginHash, - rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, - observabilityScope: observabilityScope - ) - - try self.updateBinaryArtifacts( - manifests: currentManifests, - addedOrUpdatedPackages: [], - observabilityScope: observabilityScope - ) - - return currentManifests - case .required(let reason): - delegate?.willResolveDependencies(reason: reason) - } - } - - // Create the constraints. - var computedConstraints = [PackageContainerConstraint]() - computedConstraints += currentManifests.editedPackagesConstraints() - computedConstraints += try graphRoot.constraints() + constraints - - // Perform dependency resolution. - let resolver = try self.createResolver(pins: pinsStore.pins, observabilityScope: observabilityScope) - self.activeResolver = resolver - - let result = self.resolveDependencies( - resolver: resolver, - constraints: computedConstraints, - observabilityScope: observabilityScope - ) - - // Reset the active resolver. - self.activeResolver = nil - - guard !observabilityScope.errorsReported else { - return currentManifests - } - - // Update the checkouts with dependency resolution result. - let packageStateChanges = self.updateDependenciesCheckouts(root: graphRoot, updateResults: result, observabilityScope: observabilityScope) - guard !observabilityScope.errorsReported else { - return currentManifests - } - - // Update the pinsStore. - let updatedDependencyManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) - // If we still have missing packages, something is fundamentally wrong with the resolution of the graph - let stillMissingPackages = try updatedDependencyManifests.computePackages().missing - guard stillMissingPackages.isEmpty else { - let missing = stillMissingPackages.map{ $0.description } - observabilityScope.emit(error: "exhausted attempts to resolve the dependencies graph, with '\(missing.sorted().joined(separator: "', '"))' unresolved.") - return updatedDependencyManifests - } - - // Update the resolved file. - try self.saveResolvedFile( - pinsStore: pinsStore, - dependencyManifests: updatedDependencyManifests, - originHash: resolvedFileOriginHash, - rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion, - observabilityScope: observabilityScope - ) - - let addedOrUpdatedPackages = packageStateChanges.compactMap({ $0.1.isAddedOrUpdated ? $0.0 : nil }) - try self.updateBinaryArtifacts( - manifests: updatedDependencyManifests, - addedOrUpdatedPackages: addedOrUpdatedPackages, - observabilityScope: observabilityScope - ) - - return updatedDependencyManifests - } - - - /// Updates the current working checkouts i.e. clone or remove based on the - /// provided dependency resolution result. - /// - /// - Parameters: - /// - updateResults: The updated results from dependency resolution. - /// - diagnostics: The diagnostics engine that reports errors, warnings - /// and notes. - /// - updateBranches: If the branches should be updated in case they're pinned. - @discardableResult - fileprivate func updateDependenciesCheckouts( - root: PackageGraphRoot, - updateResults: [(PackageReference, BoundVersion, ProductFilter)], - updateBranches: Bool = false, - observabilityScope: ObservabilityScope - ) -> [(PackageReference, PackageStateChange)] { - // Get the update package states from resolved results. - guard let packageStateChanges = observabilityScope.trap({ - try self.computePackageStateChanges(root: root, resolvedDependencies: updateResults, updateBranches: updateBranches, observabilityScope: observabilityScope) - }) else { - return [] - } - - // First remove the checkouts that are no longer required. - for (packageRef, state) in packageStateChanges { - observabilityScope.makeChildScope(description: "removing unneeded checkouts", metadata: packageRef.diagnosticsMetadata).trap { - switch state { - case .added, .updated, .unchanged: break - case .removed: - try remove(package: packageRef) - } - } - } - - // Update or clone new packages. - for (packageRef, state) in packageStateChanges { - observabilityScope.makeChildScope(description: "updating or cloning new packages", metadata: packageRef.diagnosticsMetadata).trap { - switch state { - case .added(let state): - _ = try self.updateDependency(package: packageRef, requirement: state.requirement, productFilter: state.products, observabilityScope: observabilityScope) - case .updated(let state): - _ = try self.updateDependency(package: packageRef, requirement: state.requirement, productFilter: state.products, observabilityScope: observabilityScope) - case .removed, .unchanged: break - } - } - } - - // Inform the delegate if nothing was updated. - if packageStateChanges.filter({ $0.1 == .unchanged }).count == packageStateChanges.count { - delegate?.dependenciesUpToDate() - } - - return packageStateChanges - } - - private func updateDependency( - package: PackageReference, - requirement: PackageStateChange.Requirement, - productFilter: ProductFilter, - observabilityScope: ObservabilityScope - ) throws -> AbsolutePath { - switch requirement { - case .version(let version): - // FIXME: this should not block - let container = try temp_await { - packageContainerProvider.getContainer( - for: package, - updateStrategy: .never, - observabilityScope: observabilityScope, - on: .sharedConcurrent, - completion: $0 - ) - } - - if let container = container as? SourceControlPackageContainer { - // FIXME: We need to get the revision here, and we don't have a - // way to get it back out of the resolver which is very - // annoying. Maybe we should make an SPI on the provider for this? - guard let tag = container.getTag(for: version) else { - throw InternalError("unable to get tag for \(package) \(version); available versions \(try container.versionsDescending())") - } - let revision = try container.getRevision(forTag: tag) - try container.checkIntegrity(version: version, revision: revision) - return try self.checkoutRepository(package: package, at: .version(version, revision: revision), observabilityScope: observabilityScope) - } else if let _ = container as? RegistryPackageContainer { - return try self.downloadRegistryArchive(package: package, at: version, observabilityScope: observabilityScope) - } else if let customContainer = container as? CustomPackageContainer { - let path = try customContainer.retrieve(at: version, observabilityScope: observabilityScope) - let dependency = try ManagedDependency(packageRef: package, state: .custom(version: version, path: path), subpath: RelativePath(validating: "")) - self.state.dependencies.add(dependency) - try self.state.save() - return path - } else { - throw InternalError("invalid container for \(package.identity) of type \(package.kind)") - } - - case .revision(let revision, .none): - return try self.checkoutRepository(package: package, at: .revision(revision), observabilityScope: observabilityScope) - - case .revision(let revision, .some(let branch)): - return try self.checkoutRepository(package: package, at: .branch(name: branch, revision: revision), observabilityScope: observabilityScope) - - case .unversioned: - let dependency = try ManagedDependency.fileSystem(packageRef: package) - // this is silly since we just created it above, but no good way to force cast it and extract the path - guard case .fileSystem(let path) = dependency.state else { - throw InternalError("invalid package type: \(package.kind)") - } - - self.state.dependencies.add(dependency) - try self.state.save() - return path - } - } - - public enum ResolutionPrecomputationResult: Equatable { - case required(reason: WorkspaceResolveReason) - case notRequired - - public var isRequired: Bool { - switch self { - case .required: return true - case .notRequired: return false - } - } - } - - /// Computes if dependency resolution is required based on input constraints and pins. - /// - /// - Returns: Returns a result defining whether dependency resolution is required and the reason for it. - // @testable internal - public func precomputeResolution( - root: PackageGraphRoot, - dependencyManifests: DependencyManifests, - pinsStore: PinsStore, - constraints: [PackageContainerConstraint], - observabilityScope: ObservabilityScope - ) throws -> ResolutionPrecomputationResult { - let computedConstraints = - try root.constraints() + - // Include constraints from the manifests in the graph root. - root.manifests.values.flatMap{ try $0.dependencyConstraints(productFilter: .everything) } + - dependencyManifests.dependencyConstraints() + - constraints - - let precomputationProvider = ResolverPrecomputationProvider(root: root, dependencyManifests: dependencyManifests) - let resolver = PubGrubDependencyResolver(provider: precomputationProvider, pins: pinsStore.pins, observabilityScope: observabilityScope) - let result = resolver.solve(constraints: computedConstraints) - - guard !observabilityScope.errorsReported else { - return .required(reason: .errorsPreviouslyReported) - } - - switch result { - case .success: - return .notRequired - case .failure(ResolverPrecomputationError.missingPackage(let package)): - return .required(reason: .newPackages(packages: [package])) - case .failure(ResolverPrecomputationError.differentRequirement(let package, let state, let requirement)): - return .required(reason: .packageRequirementChange( - package: package, - state: state, - requirement: requirement - )) - case .failure(let error): - return .required(reason: .other("\(error.interpolationDescription)")) - } - } - - /// Validates that each checked out managed dependency has an entry in pinsStore. - private func loadAndUpdatePinsStore( - dependencyManifests: DependencyManifests, - rootManifestsMinimumToolsVersion: ToolsVersion, - observabilityScope: ObservabilityScope - ) -> PinsStore? { - guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }) else { - return nil - } - - guard let requiredDependencies = observabilityScope.trap({ try dependencyManifests.computePackages().required.filter({ $0.kind.isPinnable }) }) else { - return nil - } - for dependency in self.state.dependencies.filter({ $0.packageRef.kind.isPinnable }) { - // a required dependency that is already loaded (managed) should be represented in the pins store. - // also comparing location as it may have changed at this point - if requiredDependencies.contains(where: { $0.equalsIncludingLocation(dependency.packageRef) }) { - let pin = pinsStore.pins[dependency.packageRef.identity] - // if pin not found, or location is different (it may have changed at this point) pin it - if !(pin?.packageRef.equalsIncludingLocation(dependency.packageRef) ?? false) { - pinsStore.pin(dependency) - } - } else if let pin = pinsStore.pins[dependency.packageRef.identity] { - // otherwise, it should *not* be in the pins store. - pinsStore.remove(pin) - } - } - - return pinsStore - } - - /// This enum represents state of an external package. - public enum PackageStateChange: Equatable, CustomStringConvertible { - - /// The requirement imposed by the the state. - public enum Requirement: Equatable, CustomStringConvertible { - /// A version requirement. - case version(Version) - - /// A revision requirement. - case revision(Revision, branch: String?) - - case unversioned - - public var description: String { - switch self { - case .version(let version): - return "requirement(\(version))" - case .revision(let revision, let branch): - return "requirement(\(revision) \(branch ?? ""))" - case .unversioned: - return "requirement(unversioned)" - } - } - - public var prettyPrinted: String { - switch self { - case .version(let version): - return "\(version)" - case .revision(let revision, let branch): - return "\(revision) \(branch ?? "")" - case .unversioned: - return "unversioned" - } - } - } - public struct State: Equatable { - public let requirement: Requirement - public let products: ProductFilter - public init(requirement: Requirement, products: ProductFilter) { - self.requirement = requirement - self.products = products - } - } - - /// The package is added. - case added(State) - - /// The package is removed. - case removed - - /// The package is unchanged. - case unchanged - - /// The package is updated. - case updated(State) - - public var description: String { - switch self { - case .added(let requirement): - return "added(\(requirement))" - case .removed: - return "removed" - case .unchanged: - return "unchanged" - case .updated(let requirement): - return "updated(\(requirement))" - } - } - - public var isAddedOrUpdated: Bool { - switch self { - case .added, .updated: - return true - case .unchanged, .removed: - return false - } - } - } - - /// Computes states of the packages based on last stored state. - fileprivate func computePackageStateChanges( - root: PackageGraphRoot, - resolvedDependencies: [(PackageReference, BoundVersion, ProductFilter)], - updateBranches: Bool, - observabilityScope: ObservabilityScope - ) throws -> [(PackageReference, PackageStateChange)] { - // Load pins store and managed dependencies. - let pinsStore = try self.pinsStore.load() - var packageStateChanges: [PackageIdentity: (PackageReference, PackageStateChange)] = [:] - - // Set the states from resolved dependencies results. - for (packageRef, binding, products) in resolvedDependencies { - // Get the existing managed dependency for this package ref, if any. - - // first find by identity only since edit location may be different by design - var currentDependency = self.state.dependencies[packageRef.identity] - // Check if this is an edited dependency. - if case .edited(let basedOn, _) = currentDependency?.state, let originalReference = basedOn?.packageRef { - packageStateChanges[originalReference.identity] = (originalReference, .unchanged) - } else { - // if not edited, also compare by location since it may have changed - currentDependency = self.state.dependencies[comparingLocation: packageRef] - } - - switch binding { - case .excluded: - throw InternalError("Unexpected excluded binding") - - case .unversioned: - // Ignore the root packages. - if root.packages.keys.contains(packageRef.identity) { - continue - } - - if let currentDependency { - switch currentDependency.state { - case .fileSystem, .edited: - packageStateChanges[packageRef.identity] = (packageRef, .unchanged) - case .sourceControlCheckout: - let newState = PackageStateChange.State(requirement: .unversioned, products: products) - packageStateChanges[packageRef.identity] = (packageRef, .updated(newState)) - case .registryDownload: - throw InternalError("Unexpected unversioned binding for downloaded dependency") - case .custom: - throw InternalError("Unexpected unversioned binding for custom dependency") - } - } else { - let newState = PackageStateChange.State(requirement: .unversioned, products: products) - packageStateChanges[packageRef.identity] = (packageRef, .added(newState)) - } - - case .revision(let identifier, let branch): - // Get the latest revision from the container. - // TODO: replace with async/await when available - guard let container = (try temp_await { - packageContainerProvider.getContainer( - for: packageRef, - updateStrategy: .never, - observabilityScope: observabilityScope, - on: .sharedConcurrent, - completion: $0 - ) - }) as? SourceControlPackageContainer else { - throw InternalError("invalid container for \(packageRef) expected a SourceControlPackageContainer") - } - var revision = try container.getRevision(forIdentifier: identifier) - let branch = branch ?? (identifier == revision.identifier ? nil : identifier) - - // If we have a branch and we shouldn't be updating the - // branches, use the revision from pin instead (if present). - if branch != nil, !updateBranches { - if case .branch(branch, let pinRevision) = pinsStore.pins.values.first(where: { $0.packageRef == packageRef })?.state { - revision = Revision(identifier: pinRevision) - } - } - - // First check if we have this dependency. - if let currentDependency { - // If current state and new state are equal, we don't need - // to do anything. - let newState: CheckoutState - if let branch { - newState = .branch(name: branch, revision: revision) - } else { - newState = .revision(revision) - } - if case .sourceControlCheckout(let checkoutState) = currentDependency.state, checkoutState == newState { - packageStateChanges[packageRef.identity] = (packageRef, .unchanged) - } else { - // Otherwise, we need to update this dependency to this revision. - let newState = PackageStateChange.State(requirement: .revision(revision, branch: branch), products: products) - packageStateChanges[packageRef.identity] = (packageRef, .updated(newState)) - } - } else { - let newState = PackageStateChange.State(requirement: .revision(revision, branch: branch), products: products) - packageStateChanges[packageRef.identity] = (packageRef, .added(newState)) - } - - case .version(let version): - let stateChange: PackageStateChange - switch currentDependency?.state { - case .sourceControlCheckout(.version(version, _)), .registryDownload(version), .custom(version, _): - stateChange = .unchanged - case .edited, .fileSystem, .sourceControlCheckout, .registryDownload, .custom: - stateChange = .updated(.init(requirement: .version(version), products: products)) - case nil: - stateChange = .added(.init(requirement: .version(version), products: products)) - } - packageStateChanges[packageRef.identity] = (packageRef, stateChange) - } - } - // Set the state of any old package that might have been removed. - for packageRef in self.state.dependencies.lazy.map({ $0.packageRef }) where packageStateChanges[packageRef.identity] == nil { - packageStateChanges[packageRef.identity] = (packageRef, .removed) - } - - return Array(packageStateChanges.values) - } - - /// Creates resolver for the workspace. - fileprivate func createResolver(pins: PinsStore.Pins, observabilityScope: ObservabilityScope) throws -> PubGrubDependencyResolver { - var delegate: DependencyResolverDelegate - let observabilityDelegate = ObservabilityDependencyResolverDelegate(observabilityScope: observabilityScope) - if let workspaceDelegate = self.delegate { - delegate = MultiplexResolverDelegate([ - observabilityDelegate, - WorkspaceDependencyResolverDelegate(workspaceDelegate), - ]) - } else { - delegate = observabilityDelegate - } - - return PubGrubDependencyResolver( - provider: packageContainerProvider, - pins: pins, - skipDependenciesUpdates: self.configuration.skipDependenciesUpdates, - prefetchBasedOnResolvedFile: self.configuration.prefetchBasedOnResolvedFile, - observabilityScope: observabilityScope, - delegate: delegate - ) - } - - /// Runs the dependency resolver based on constraints provided and returns the results. - fileprivate func resolveDependencies( - resolver: PubGrubDependencyResolver, - constraints: [PackageContainerConstraint], - observabilityScope: ObservabilityScope - ) -> [(package: PackageReference, binding: BoundVersion, products: ProductFilter)] { - - os_signpost(.begin, name: SignpostName.pubgrub) - let result = resolver.solve(constraints: constraints) - os_signpost(.end, name: SignpostName.pubgrub) - - // Take an action based on the result. - switch result { - case .success(let bindings): - return bindings - case .failure(let error): - observabilityScope.emit(error) - return [] - } - } -} - - - -/// A result which can be loaded. -/// -/// It is useful for objects that holds a state on disk and needs to be -/// loaded frequently. -public final class LoadableResult { - /// The constructor closure for the value. - private let construct: () throws -> Value - - /// Create a loadable result. - public init(_ construct: @escaping () throws -> Value) { - self.construct = construct - } - - /// Load and return the result. - public func loadResult() -> Result { - return Result(catching: { - try self.construct() - }) - } - - /// Load and return the value. - public func load() throws -> Value { - return try loadResult().get() - } -} - -// MARK: - Package container provider - -extension Workspace: PackageContainerProvider { - public func getContainer( - for package: PackageReference, - updateStrategy: ContainerUpdateStrategy, - observabilityScope: ObservabilityScope, - on queue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - do { - switch package.kind { - // If the container is local, just create and return a local package container. - case .root, .fileSystem: - let container = try FileSystemPackageContainer( - package: package, - identityResolver: self.identityResolver, - manifestLoader: self.manifestLoader, - currentToolsVersion: self.currentToolsVersion, - fileSystem: self.fileSystem, - observabilityScope: observabilityScope - ) - queue.async { - completion(.success(container)) - } - // Resolve the container using the repository manager. - case .localSourceControl, .remoteSourceControl: - let repositorySpecifier = try package.makeRepositorySpecifier() - self.repositoryManager.lookup( - package: package.identity, - repository: repositorySpecifier, - updateStrategy: updateStrategy.repositoryUpdateStrategy, - observabilityScope: observabilityScope, - delegateQueue: queue, - callbackQueue: queue - ) { result in - dispatchPrecondition(condition: .onQueue(queue)) - // Create the container wrapper. - let result = result.tryMap { handle -> PackageContainer in - // Open the repository. - // - // FIXME: Do we care about holding this open for the lifetime of the container. - let repository = try handle.open() - return try SourceControlPackageContainer( - package: package, - identityResolver: self.identityResolver, - repositorySpecifier: repositorySpecifier, - repository: repository, - manifestLoader: self.manifestLoader, - currentToolsVersion: self.currentToolsVersion, - fingerprintStorage: self.fingerprints, - fingerprintCheckingMode: FingerprintCheckingMode.map(self.configuration.fingerprintCheckingMode), - observabilityScope: observabilityScope - ) - } - completion(result) - } - // Resolve the container using the registry - case .registry: - let container = RegistryPackageContainer( - package: package, - identityResolver: self.identityResolver, - registryClient: self.registryClient, - manifestLoader: self.manifestLoader, - currentToolsVersion: self.currentToolsVersion, - observabilityScope: observabilityScope - ) - queue.async { - completion(.success(container)) - } - } - } catch { - queue.async { - completion(.failure(error)) - } - } - } - - /// Removes the clone and checkout of the provided specifier. - /// - /// - Parameters: - /// - package: The package to remove - func remove(package: PackageReference) throws { - guard let dependency = self.state.dependencies[package.identity] else { - throw InternalError("trying to remove \(package.identity) which isn't in workspace") - } - - // We only need to update the managed dependency structure to "remove" - // a local package. - // - // Note that we don't actually remove a local package from disk. - if case .fileSystem = dependency.state { - self.state.dependencies.remove(package.identity) - try self.state.save() - return - } - - // Inform the delegate. - let repository = try? dependency.packageRef.makeRepositorySpecifier() - delegate?.removing(package: package.identity, packageLocation: repository?.location.description) - - // Compute the dependency which we need to remove. - let dependencyToRemove: ManagedDependency - - if case .edited(let _basedOn, let unmanagedPath) = dependency.state, let basedOn = _basedOn { - // Remove the underlying dependency for edited packages. - dependencyToRemove = basedOn - let updatedDependency = Workspace.ManagedDependency.edited( - packageRef: dependency.packageRef, - subpath: dependency.subpath, - basedOn: .none, - unmanagedPath: unmanagedPath - ) - self.state.dependencies.add(updatedDependency) + if case .edited(let _basedOn, let unmanagedPath) = dependency.state, let basedOn = _basedOn { + // Remove the underlying dependency for edited packages. + dependencyToRemove = basedOn + let updatedDependency = Workspace.ManagedDependency.edited( + packageRef: dependency.packageRef, + subpath: dependency.subpath, + basedOn: .none, + unmanagedPath: unmanagedPath + ) + self.state.dependencies.add(updatedDependency) } else { dependencyToRemove = dependency self.state.dependencies.remove(dependencyToRemove.packageRef.identity) @@ -3424,346 +1262,10 @@ extension Workspace: PackageContainerProvider { } } -extension FingerprintCheckingMode { - static func map(_ checkingMode: WorkspaceConfiguration.CheckingMode) -> FingerprintCheckingMode { - switch checkingMode { - case .strict: - return .strict - case .warn: - return .warn - } - } -} - -extension SigningEntityCheckingMode { - static func map(_ checkingMode: WorkspaceConfiguration.CheckingMode) -> SigningEntityCheckingMode { - switch checkingMode { - case .strict: - return .strict - case .warn: - return .warn - } - } -} - -// MARK: - Source control repository management - -// FIXME: this mixes quite a bit of workspace logic with repository specific one -// need to better separate the concerns -extension Workspace { - /// Create a local clone of the given `repository` checked out to `checkoutState`. - /// - /// If an existing clone is present, the repository will be reset to the - /// requested revision, if necessary. - /// - /// - Parameters: - /// - package: The package to clone. - /// - checkoutState: The state to check out. - /// - Returns: The path of the local repository. - /// - Throws: If the operation could not be satisfied. - func checkoutRepository( - package: PackageReference, - at checkoutState: CheckoutState, - observabilityScope: ObservabilityScope - ) throws -> AbsolutePath { - let repository = try package.makeRepositorySpecifier() - // first fetch the repository. - let checkoutPath = try self.fetchRepository(package: package, observabilityScope: observabilityScope) - - // Check out the given revision. - let workingCopy = try self.repositoryManager.openWorkingCopy(at: checkoutPath) - - // Inform the delegate that we're about to start. - delegate?.willCheckOut(package: package.identity, repository: repository.location.description, revision: checkoutState.description, at: checkoutPath) - let start = DispatchTime.now() - - // Do mutable-immutable dance because checkout operation modifies the disk state. - try fileSystem.chmod(.userWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) - try workingCopy.checkout(revision: checkoutState.revision) - try? fileSystem.chmod(.userUnWritable, path: checkoutPath, options: [.recursive, .onlyFiles]) - - // Record the new state. - observabilityScope.emit(debug: "adding '\(package.identity)' (\(package.locationString)) to managed dependencies", metadata: package.diagnosticsMetadata) - self.state.dependencies.add( - try .sourceControlCheckout( - packageRef: package, - state: checkoutState, - subpath: checkoutPath.relative(to: self.location.repositoriesCheckoutsDirectory) - ) - ) - try self.state.save() - - // Inform the delegate that we're done. - let duration = start.distance(to: .now()) - delegate?.didCheckOut(package: package.identity, repository: repository.location.description, revision: checkoutState.description, at: checkoutPath, duration: duration) - observabilityScope.emit(debug: "`\(repository.location.description)` checked out at \(checkoutState.debugDescription)") - - return checkoutPath - } - - func checkoutRepository( - package: PackageReference, - at pinState: PinsStore.PinState, - observabilityScope: ObservabilityScope - ) throws -> AbsolutePath { - switch pinState { - case .version(let version, revision: let revision) where revision != nil: - return try self.checkoutRepository( - package: package, - at: .version(version, revision: .init(identifier: revision!)), // nil checked above - observabilityScope: observabilityScope - ) - case .branch(let branch, revision: let revision): - return try self.checkoutRepository( - package: package, - at: .branch(name: branch, revision: .init(identifier: revision)), - observabilityScope: observabilityScope - ) - case .revision(let revision): - return try self.checkoutRepository( - package: package, - at: .revision(.init(identifier: revision)), - observabilityScope: observabilityScope - ) - default: - throw InternalError("invalid pin state: \(pinState)") - } - } - - /// Fetch a given `package` and create a local checkout for it. - /// - /// This will first clone the repository into the canonical repositories - /// location, if necessary, and then check it out from there. - /// - /// - Returns: The path of the local repository. - /// - Throws: If the operation could not be satisfied. - private func fetchRepository(package: PackageReference, observabilityScope: ObservabilityScope) throws -> AbsolutePath { - // If we already have it, fetch to update the repo from its remote. - // also compare the location as it may have changed - if let dependency = self.state.dependencies[comparingLocation: package] { - let path = self.location.repositoriesCheckoutSubdirectory(for: dependency) - - // Make sure the directory is not missing (we will have to clone again - // if not). - fetch: if self.fileSystem.isDirectory(path) { - // Fetch the checkout in case there are updates available. - let workingCopy = try self.repositoryManager.openWorkingCopy(at: path) - - // Ensure that the alternative object store is still valid. - // - // This can become invalid if the build directory is moved. - guard workingCopy.isAlternateObjectStoreValid() else { - break fetch - } - - // The fetch operation may update contents of the checkout, so - // we need do mutable-immutable dance. - try self.fileSystem.chmod(.userWritable, path: path, options: [.recursive, .onlyFiles]) - try workingCopy.fetch() - try? self.fileSystem.chmod(.userUnWritable, path: path, options: [.recursive, .onlyFiles]) - - return path - } - } - - // If not, we need to get the repository from the checkouts. - let repository = try package.makeRepositorySpecifier() - // FIXME: this should not block - let handle = try temp_await { - self.repositoryManager.lookup( - package: package.identity, - repository: repository, - updateStrategy: .never, - observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } - - // Clone the repository into the checkouts. - let path = self.location.repositoriesCheckoutsDirectory.appending(component: repository.basename) - - // Remove any existing content at that path. - try self.fileSystem.chmod(.userWritable, path: path, options: [.recursive, .onlyFiles]) - try self.fileSystem.removeFileTree(path) - - // Inform the delegate that we're about to start. - self.delegate?.willCreateWorkingCopy(package: package.identity, repository: handle.repository.location.description, at: path) - let start = DispatchTime.now() - - // Create the working copy. - _ = try handle.createWorkingCopy(at: path, editable: false) - - // Inform the delegate that we're done. - let duration = start.distance(to: .now()) - self.delegate?.didCreateWorkingCopy(package: package.identity, repository: handle.repository.location.description, at: path, duration: duration) - - return path - } - - /// Removes the clone and checkout of the provided specifier. - fileprivate func removeRepository(dependency: ManagedDependency) throws { - guard case .sourceControlCheckout = dependency.state else { - throw InternalError("cannot remove repository for \(dependency) with state \(dependency.state)") - } - - // Remove the checkout. - let dependencyPath = self.location.repositoriesCheckoutSubdirectory(for: dependency) - let workingCopy = try self.repositoryManager.openWorkingCopy(at: dependencyPath) - guard !workingCopy.hasUncommittedChanges() else { - throw WorkspaceDiagnostics.UncommitedChanges(repositoryPath: dependencyPath) - } - - try self.fileSystem.chmod(.userWritable, path: dependencyPath, options: [.recursive, .onlyFiles]) - try self.fileSystem.removeFileTree(dependencyPath) - - // Remove the clone. - try self.repositoryManager.remove(repository: dependency.packageRef.makeRepositorySpecifier()) - } -} - -// MARK: - Registry Source archive management - - extension Workspace { - func downloadRegistryArchive( - package: PackageReference, - at version: Version, - observabilityScope: ObservabilityScope - ) throws -> AbsolutePath { - // FIXME: this should not block - let downloadPath = try temp_await { - self.registryDownloadsManager.lookup( - package: package.identity, - version: version, - observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } - - // Record the new state. - observabilityScope.emit(debug: "adding '\(package.identity)' (\(package.locationString)) to managed dependencies", metadata: package.diagnosticsMetadata) - self.state.dependencies.add( - try .registryDownload( - packageRef: package, - version: version, - subpath: downloadPath.relative(to: self.location.registryDownloadDirectory) - ) - ) - try self.state.save() - - return downloadPath - } - - func downloadRegistryArchive( - package: PackageReference, - at pinState: PinsStore.PinState, - observabilityScope: ObservabilityScope - ) throws -> AbsolutePath { - switch pinState { - case .version(let version, _): - return try self.downloadRegistryArchive( - package: package, - at: version, - observabilityScope: observabilityScope - ) - default: - throw InternalError("invalid pin state: \(pinState)") - } - } - - func removeRegistryArchive(for dependency: ManagedDependency) throws { - guard case .registryDownload = dependency.state else { - throw InternalError("cannot remove source archive for \(dependency) with state \(dependency.state)") - } - - let downloadPath = self.location.registryDownloadSubdirectory(for: dependency) - try self.fileSystem.removeFileTree(downloadPath) - - // remove the local copy - try registryDownloadsManager.remove(package: dependency.packageRef.identity) - } - - public func acceptIdentityChange( - package: PackageIdentity, - version: Version, - signingEntity: SigningEntity, - origin: SigningEntity.Origin, - observabilityScope: ObservabilityScope, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - self.registryClient.changeSigningEntityFromVersion( - package: package, - version: version, - signingEntity: signingEntity, - origin: origin, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue, - completion: completion - ) - } - } - -// MARK: - Signatures - -extension Workspace { - private func validateSignatures( - packageGraph: PackageGraph, - expectedSigningEntities: [PackageIdentity: RegistryReleaseMetadata.SigningEntity] - ) throws { - try expectedSigningEntities.forEach { identity, expectedSigningEntity in - if let package = packageGraph.packages.first(where: { $0.identity == identity }) { - guard let actualSigningEntity = package.registryMetadata?.signature?.signedBy else { - throw SigningError.unsigned(package: identity, expected: expectedSigningEntity) - } - if actualSigningEntity != expectedSigningEntity { - throw SigningError.mismatchedSigningEntity( - package: identity, - expected: expectedSigningEntity, - actual: actualSigningEntity - ) - } - } else { - guard let mirror = self.mirrors.mirror(for: identity.description) else { - throw SigningError.expectedIdentityNotFound(package: identity) - } - let mirroredIdentity = PackageIdentity.plain(mirror) - guard mirroredIdentity.isRegistry else { - throw SigningError.expectedSignedMirroredToSourceControl(package: identity, expected: expectedSigningEntity) - } - guard let package = packageGraph.packages.first(where: { $0.identity == mirroredIdentity }) else { - // Unsure if this case is reachable in practice. - throw SigningError.expectedIdentityNotFound(package: identity) - } - guard let actualSigningEntity = package.registryMetadata?.signature?.signedBy else { - throw SigningError.unsigned(package: identity, expected: expectedSigningEntity) - } - if actualSigningEntity != expectedSigningEntity { - throw SigningError.mismatchedSigningEntity( - package: identity, - expected: expectedSigningEntity, - actual: actualSigningEntity - ) - } - } - } - } - - public enum SigningError: Swift.Error { - case expectedIdentityNotFound(package: PackageIdentity) - case expectedSignedMirroredToSourceControl(package: PackageIdentity, expected: RegistryReleaseMetadata.SigningEntity) - case mismatchedSigningEntity(package: PackageIdentity, expected: RegistryReleaseMetadata.SigningEntity, actual: RegistryReleaseMetadata.SigningEntity) - case unsigned(package: PackageIdentity, expected: RegistryReleaseMetadata.SigningEntity) - } -} - // MARK: - Utility extensions -fileprivate extension Workspace.ManagedArtifact { - var originURL: String? { +extension Workspace.ManagedArtifact { + fileprivate var originURL: String? { switch self.source { case .remote(let url, _): return url @@ -3773,29 +1275,7 @@ fileprivate extension Workspace.ManagedArtifact { } } -// FIXME: the manifest loading logic should be changed to use identity instead of location once identity is unique -// at that time we should remove this -//@available(*, deprecated) -fileprivate extension PackageDependency { - var locationString: String { - switch self { - case .fileSystem(let settings): - return settings.path.pathString - case .sourceControl(let settings): - switch settings.location { - case .local(let path): - return path.pathString - case .remote(let url): - return url.absoluteString - } - case .registry: - // FIXME: placeholder - return self.identity.description - } - } -} - -internal extension PackageReference { +extension PackageReference { func makeRepositorySpecifier() throws -> RepositorySpecifier { switch self.kind { case .localSourceControl(let path): @@ -3808,20 +1288,9 @@ internal extension PackageReference { } } -fileprivate extension PackageReference.Kind { - var isPinnable: Bool { - switch self { - case .remoteSourceControl, .localSourceControl, .registry: - return true - default: - return false - } - } -} - // FIXME: remove this when remove the single call site that uses it -fileprivate extension PackageDependency { - var isLocal: Bool { +extension PackageDependency { + private var isLocal: Bool { switch self { case .fileSystem: return true @@ -3845,7 +1314,8 @@ extension Workspace { case .forced: result.append("it was forced") case .newPackages(let packages): - let dependencies = packages.lazy.map({ "'\($0.identity)' (\($0.kind.locationString))" }).joined(separator: ", ") + let dependencies = packages.lazy.map { "'\($0.identity)' (\($0.kind.locationString))" } + .joined(separator: ", ") result.append("the following dependencies were added: \(dependencies)") case .packageRequirementChange(let package, let state, let requirement): result.append("dependency '\(package.identity)' (\(package.kind.locationString)) was ") @@ -3855,7 +1325,7 @@ extension Workspace { switch checkoutState.requirement { case .versionSet(.exact(let version)): result.append("resolved to '\(version)'") - case .versionSet(_): + case .versionSet: // Impossible break case .revision(let revision): @@ -3893,144 +1363,23 @@ extension Workspace { } } -extension PinsStore.PinState { - fileprivate func equals(_ checkoutState: CheckoutState) -> Bool { - switch (self, checkoutState) { - case (.version(let lversion, let lrevision), .version(let rversion, let rrevision)): - return lversion == rversion && lrevision == rrevision.identifier - case (.branch(let lbranch, let lrevision), .branch(let rbranch, let rrevision)): - return lbranch == rbranch && lrevision == rrevision.identifier - case (.revision(let lrevision), .revision(let rrevision)): - return lrevision == rrevision.identifier - default: - return false - } - } - - fileprivate func equals(_ version: Version) -> Bool { - switch self { - case .version(let version, _): - return version == version - default: - return false - } - } -} - -extension CheckoutState { - fileprivate var revision: Revision { - get { - switch self { - case .revision(let revision): - return revision - case .version(_, let revision): - return revision - case .branch(_, let revision): - return revision - } - } - } - - fileprivate var isBranchOrRevisionBased: Bool { - switch self { - case .revision, .branch: - return true - case .version: - return false - } - } - - fileprivate var requirement: PackageRequirement { - switch self { - case .revision(let revision): - return .revision(revision.identifier) - case .version(let version, _): - return .versionSet(.exact(version)) - case .branch(let branch, _): - return .revision(branch) - } - } -} - -extension Workspace { - fileprivate func getFileSystem(package: PackageReference, state: Workspace.ManagedDependency.State, observabilityScope: ObservabilityScope) throws -> FileSystem? { - // Only custom containers may provide a file system. - guard self.customPackageContainerProvider != nil else { - return nil - } - - switch state { - // File-system based dependencies do not provide a custom file system object. - case .fileSystem: - return nil - case .custom: - let container = try temp_await { - self.packageContainerProvider.getContainer( - for: package, - updateStrategy: .never, - observabilityScope: observabilityScope, - on: .sharedConcurrent, - completion: $0 - ) - } - guard let customContainer = container as? CustomPackageContainer else { - observabilityScope.emit(error: "invalid custom dependency container: \(container)") - return nil - } - return try customContainer.getFileSystem() - default: - observabilityScope.emit(error: "invalid managed dependency state for custom dependency: \(state)") - return nil - } - } -} - extension Workspace.Location { /// Returns the path to the dependency's repository checkout directory. - fileprivate func repositoriesCheckoutSubdirectory(for dependency: Workspace.ManagedDependency) -> AbsolutePath { + func repositoriesCheckoutSubdirectory(for dependency: Workspace.ManagedDependency) -> AbsolutePath { self.repositoriesCheckoutsDirectory.appending(dependency.subpath) } /// Returns the path to the dependency's download directory. - fileprivate func registryDownloadSubdirectory(for dependency: Workspace.ManagedDependency) -> AbsolutePath { + func registryDownloadSubdirectory(for dependency: Workspace.ManagedDependency) -> AbsolutePath { self.registryDownloadDirectory.appending(dependency.subpath) } /// Returns the path to the dependency's edit directory. - fileprivate func editSubdirectory(for dependency: Workspace.ManagedDependency) -> AbsolutePath { + func editSubdirectory(for dependency: Workspace.ManagedDependency) -> AbsolutePath { self.editsDirectory.appending(dependency.subpath) } } -extension FileSystem { - // helper to decide if an archive directory would benefit from stripping first level - fileprivate func shouldStripFirstLevel(archiveDirectory: AbsolutePath, acceptableExtensions: [String]? = nil) throws -> Bool { - let subdirectories = try self.getDirectoryContents(archiveDirectory) - .map{ archiveDirectory.appending(component: $0) } - .filter { self.isDirectory($0) } - - // single top-level directory required - guard subdirectories.count == 1, let rootDirectory = subdirectories.first else { - return false - } - - // no acceptable extensions defined, so the single top-level directory is a good candidate - guard let acceptableExtensions else { - return true - } - - // the single top-level directory is already one of the acceptable extensions, so no need to strip - if rootDirectory.extension.map({ acceptableExtensions.contains($0) }) ?? false { - return false - } - - // see if there is "grand-child" directory with one of the acceptable extensions - return try self.getDirectoryContents(rootDirectory) - .map{ rootDirectory.appending(component: $0) } - .first{ $0.extension.map { acceptableExtensions.contains($0) } ?? false } != nil - } -} - extension Workspace.Location { func validatingSharedLocations( fileSystem: FileSystem, @@ -4042,7 +1391,9 @@ extension Workspace.Location { keyPath: \.sharedConfigurationDirectory, fileSystem: fileSystem, getOrCreateHandler: { - try fileSystem.getOrCreateSwiftPMConfigurationDirectory(warningHandler: self.emitDeprecatedConfigurationWarning ? warningHandler : { _ in }) + try fileSystem.getOrCreateSwiftPMConfigurationDirectory( + warningHandler: self.emitDeprecatedConfigurationWarning ? warningHandler : { _ in } + ) }, warningHandler: warningHandler ) @@ -4090,323 +1441,16 @@ extension Workspace.Location { } else { if !fileSystem.isWritable(sharedDirectory) { self[keyPath: keyPath] = nil - warningHandler("\(sharedDirectory) is not accessible or not writable, disabling user-level cache features.") - } - } - } - } -} - -extension Workspace { - // the goal of this code is to help align dependency identities across source control and registry origins - // the issue this solves is that dependencies will have different identities across the origins - // for example, source control based dependency on http://github.com/apple/swift-nio would have an identifier of "swift-nio" - // while in the registry, the same package will [likely] have an identifier of "apple.swift-nio" - // since there is not generally fire sure way to translate one system to the other (urls can vary widely, so the best we would be able to do is guess) - // what this code does is query the registry of it "knows" what the registry identity of URL is, and then use the registry identity instead of the URL bases one - // the code also supports a "full swizzle" mode in which it _replaces_ the source control dependency with a registry one which encourages the transition - // from source control based dependencies to registry based ones - - // TODO - // 1. handle mixed situation when some versions on the registry but some on source control. we need a second lookup to make sure the version exists - // 2. handle registry returning multiple identifiers, how do we choose the right one? - fileprivate struct RegistryAwareManifestLoader: ManifestLoaderProtocol { - private let underlying: ManifestLoaderProtocol - private let registryClient: RegistryClient - private let transformationMode: TransformationMode - - private let cacheTTL = DispatchTimeInterval.seconds(300) // 5m - private let identityLookupCache = ThreadSafeKeyValueStore, expirationTime: DispatchTime)>() - - init(underlying: ManifestLoaderProtocol, registryClient: RegistryClient, transformationMode: TransformationMode) { - self.underlying = underlying - self.registryClient = registryClient - self.transformationMode = transformationMode - } - - func load( - manifestPath: AbsolutePath, - manifestToolsVersion: ToolsVersion, - packageIdentity: PackageIdentity, - packageKind: PackageReference.Kind, - packageLocation: String, - packageVersion: (version: Version?, revision: String?)?, - identityResolver: IdentityResolver, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - self.underlying.load( - manifestPath: manifestPath, - manifestToolsVersion: manifestToolsVersion, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: packageVersion, - identityResolver: identityResolver, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue - ) { result in - switch result { - case .failure(let error): - completion(.failure(error)) - case .success(let manifest): - self.transformSourceControlDependenciesToRegistry( - manifest: manifest, - transformationMode: transformationMode, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue, - completion: completion - ) - } - } - } - - func resetCache(observabilityScope: ObservabilityScope) { - self.underlying.resetCache(observabilityScope: observabilityScope) - } - - func purgeCache(observabilityScope: ObservabilityScope) { - self.underlying.purgeCache(observabilityScope: observabilityScope) - } - - private func transformSourceControlDependenciesToRegistry( - manifest: Manifest, - transformationMode: TransformationMode, - observabilityScope: ObservabilityScope, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - let sync = DispatchGroup() - let transformations = ThreadSafeKeyValueStore() - for dependency in manifest.dependencies { - if case .sourceControl(let settings) = dependency, case .remote(let url) = settings.location { - sync.enter() - self.mapRegistryIdentity(url: url, observabilityScope: observabilityScope, callbackQueue: callbackQueue) { result in - defer { sync.leave() } - switch result { - case .failure(let error): - // do not raise error, only report it as warning - observabilityScope.emit( - warning: "failed querying registry identity for '\(url)'", - underlyingError: error - ) - case .success(.some(let identity)): - transformations[dependency] = identity - case .success(.none): - // no identity found - break - } - } - } - } - - // update the manifest with the transformed dependencies - sync.notify(queue: callbackQueue) { - do { - let updatedManifest = try self.transformManifest( - manifest: manifest, - transformations: transformations.get(), - transformationMode: transformationMode, - observabilityScope: observabilityScope - ) - completion(.success(updatedManifest)) - } - catch { - return completion(.failure(error)) - } - } - } - - private func transformManifest( - manifest: Manifest, - transformations: [PackageDependency: PackageIdentity], - transformationMode: TransformationMode, - observabilityScope: ObservabilityScope - ) throws -> Manifest { - var targetDependencyPackageNameTransformations = [String: String]() - - var modifiedDependencies = [PackageDependency]() - for dependency in manifest.dependencies { - var modifiedDependency = dependency - if let registryIdentity = transformations[dependency] { - guard case .sourceControl(let settings) = dependency, case .remote = settings.location else { - // an implementation mistake - throw InternalError("unexpected non-source-control dependency: \(dependency)") - } - switch transformationMode { - case .identity: - // we replace the *identity* of the dependency in order to align the identities - // and de-dupe across source control and registry origins - observabilityScope.emit(info: "adjusting '\(dependency.locationString)' identity to registry identity of '\(registryIdentity)'.") - modifiedDependency = .sourceControl( - identity: registryIdentity, - nameForTargetDependencyResolutionOnly: settings.nameForTargetDependencyResolutionOnly, - location: settings.location, - requirement: settings.requirement, - productFilter: settings.productFilter - ) - case .swizzle: - // we replace the *entire* source control dependency with a registry one - // this helps de-dupe across source control and registry dependencies - // and also encourages use of registry over source control - switch settings.requirement { - case .exact, .range: - let requirement = try settings.requirement.asRegistryRequirement() - observabilityScope.emit(info: "swizzling '\(dependency.locationString)' with registry dependency '\(registryIdentity)'.") - targetDependencyPackageNameTransformations[dependency.nameForTargetDependencyResolutionOnly] = registryIdentity.description - modifiedDependency = .registry( - identity: registryIdentity, - requirement: requirement, - productFilter: settings.productFilter - ) - case .branch, .revision: - // branch and revision dependencies are not supported by the registry - // in such case, the best we can do is to replace the *identity* of the - // source control dependency in order to align the identities - // and de-dupe across source control and registry origins - observabilityScope.emit(info: "adjusting '\(dependency.locationString)' identity to registry identity of '\(registryIdentity)'.") - modifiedDependency = .sourceControl( - identity: registryIdentity, - nameForTargetDependencyResolutionOnly: settings.nameForTargetDependencyResolutionOnly, - location: settings.location, - requirement: settings.requirement, - productFilter: settings.productFilter - ) - } - } - } - modifiedDependencies.append(modifiedDependency) - } - - var modifiedTargets = manifest.targets - if !transformations.isEmpty { - modifiedTargets = [] - for target in manifest.targets { - var modifiedDependencies = [TargetDescription.Dependency]() - for dependency in target.dependencies { - var modifiedDependency = dependency - if case .product(let name, let packageName, let moduleAliases, let condition) = dependency, let packageName = packageName { - // makes sure we use the updated package name for target based dependencies - if let modifiedPackageName = targetDependencyPackageNameTransformations[packageName] { - modifiedDependency = .product(name: name, package: modifiedPackageName, moduleAliases: moduleAliases, condition: condition) - } - } - modifiedDependencies.append(modifiedDependency) - } - - modifiedTargets.append( - try TargetDescription( - name: target.name, - dependencies: modifiedDependencies, - path: target.path, - url: target.url, - exclude: target.exclude, - sources: target.sources, - resources: target.resources, - publicHeadersPath: target.publicHeadersPath, - type: target.type, - packageAccess: target.packageAccess, - pkgConfig: target.pkgConfig, - providers: target.providers, - pluginCapability: target.pluginCapability, - settings: target.settings, - checksum: target.checksum, - pluginUsages: target.pluginUsages - ) + warningHandler( + "\(sharedDirectory) is not accessible or not writable, disabling user-level cache features." ) } } - - let modifiedManifest = Manifest( - displayName: manifest.displayName, - path: manifest.path, - packageKind: manifest.packageKind, - packageLocation: manifest.packageLocation, - defaultLocalization: manifest.defaultLocalization, - platforms: manifest.platforms, - version: manifest.version, - revision: manifest.revision, - toolsVersion: manifest.toolsVersion, - pkgConfig: manifest.pkgConfig, - providers: manifest.providers, - cLanguageStandard: manifest.cLanguageStandard, - cxxLanguageStandard: manifest.cxxLanguageStandard, - swiftLanguageVersions: manifest.swiftLanguageVersions, - dependencies: modifiedDependencies, - products: manifest.products, - targets: modifiedTargets - ) - - return modifiedManifest - } - - private func mapRegistryIdentity( - url: SourceControlURL, - observabilityScope: ObservabilityScope, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - if let cached = self.identityLookupCache[url], cached.expirationTime > .now() { - switch cached.result { - case .success(let identity): - return completion(.success(identity)) - case .failure: - // server error, do not try again - return completion(.success(.none)) - } - } - - self.registryClient.lookupIdentities(scmURL: url, observabilityScope: observabilityScope, callbackQueue: callbackQueue) { result in - switch result { - case .failure(let error): - self.identityLookupCache[url] = (result: .failure(error), expirationTime: .now() + self.cacheTTL) - completion(.failure(error)) - case .success(let identities): - // FIXME: returns first result... need to consider how to address multiple ones - let identity = identities.sorted().first - self.identityLookupCache[url] = (result: .success(identity), expirationTime: .now() + self.cacheTTL) - completion(.success(identity)) - } - } - } - - enum TransformationMode { - case identity - case swizzle - - init?(_ seed: WorkspaceConfiguration.SourceControlToRegistryDependencyTransformation) { - switch seed { - case .identity: - self = .identity - case .swizzle: - self = .swizzle - case .disabled: - return nil - } - } - } - } -} - -fileprivate extension PackageDependency.SourceControl.Requirement { - func asRegistryRequirement() throws -> PackageDependency.Registry.Requirement { - switch self { - case .range(let versions): - return .range(versions) - case .exact(let version): - return .exact(version) - case .branch, .revision: - throw InternalError("invalid source control to registry requirement tranformation") } } } -fileprivate func warnToStderr(_ message: String) { +private func warnToStderr(_ message: String) { TSCBasic.stderrStream.write("warning: \(message)\n") TSCBasic.stderrStream.flush() } @@ -4426,16 +1470,3 @@ extension ContainerUpdateStrategy { } } } - -extension Workspace.ManagedDependencies { - func hasEditedDependencies() -> Bool { - self.contains(where: { - switch $0.state { - case .edited(_, _): - return true - default: - return false - } - }) - } -} diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index e6de8eef4fb..6dddf351b21 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -34,6 +34,9 @@ struct PIFBuilderParameters { /// An array of paths to search for pkg-config `.pc` files. let pkgConfigDirectories: [AbsolutePath] + + /// The toolchain's SDK root path. + let sdkRootPath: AbsolutePath? } /// PIF object builder for a package graph. @@ -97,7 +100,7 @@ public final class PIFBuilder { try PIF.sign(topLevelObject.workspace) let pifData = try encoder.encode(topLevelObject) - return String(data: pifData, encoding: .utf8)! + return String(decoding: pifData, as: UTF8.self) } /// Constructs a `PIF.TopLevelObject` representing the package graph. @@ -298,7 +301,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { PlatformRegistry.default.knownPlatforms.forEach { guard let platform = PIF.BuildSettings.Platform.from(platform: $0) else { return } - guard let supportedPlatform = package.platforms.getDerived(for: $0) else { return } + let supportedPlatform = package.platforms.getDerived(for: $0, usingXCTest: false) if !supportedPlatform.options.isEmpty { settings[.SPECIALIZATION_SDK_OPTIONS, for: platform] = supportedPlatform.options } @@ -427,11 +430,11 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { // Tests can have a custom deployment target based on the minimum supported by XCTest. if mainTarget.underlyingTarget.type == .test { - settings[.MACOSX_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .macOS) - settings[.IPHONEOS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .iOS) - settings[.TVOS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .tvOS) - settings[.WATCHOS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .watchOS) - settings[.XROS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .visionOS) + settings[.MACOSX_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .macOS, usingXCTest: true) + settings[.IPHONEOS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .iOS, usingXCTest: true) + settings[.TVOS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .tvOS, usingXCTest: true) + settings[.WATCHOS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .watchOS, usingXCTest: true) + settings[.XROS_DEPLOYMENT_TARGET] = mainTarget.platforms.deploymentTarget(for: .visionOS, usingXCTest: true) } if product.type == .executable { @@ -711,6 +714,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { for result in try pkgConfigArgs( for: systemTarget, pkgConfigDirectories: parameters.pkgConfigDirectories, + sdkRootPath: parameters.sdkRootPath, fileSystem: fileSystem, observabilityScope: observabilityScope ) { @@ -1414,17 +1418,8 @@ extension Array where Element == ResolvedTarget.Dependency { } extension SupportedPlatforms { - func deploymentTarget(for platform: PackageModel.Platform) -> String? { - if let supportedPlatform = self.getDerived(for: platform) { - return supportedPlatform.version.versionString - } else if platform == .macCatalyst { - // If there is no deployment target specified for Mac Catalyst, fall back to the iOS deployment target. - return deploymentTarget(for: .iOS) - } else if platform.oldestSupportedVersion != .unknown { - return platform.oldestSupportedVersion.versionString - } else { - return nil - } + func deploymentTarget(for platform: PackageModel.Platform, usingXCTest: Bool = false) -> String? { + return self.getDerived(for: platform, usingXCTest: usingXCTest).version.versionString } } @@ -1577,7 +1572,7 @@ extension Array where Element == PackageConditionProtocol { result += PIF.PlatformFilter.driverKitFilters case .wasi: - result += PIF.PlatformFilter.webAsssemblyFilters + result += PIF.PlatformFilter.webAssemblyFilters case .openbsd: result += PIF.PlatformFilter.openBSDFilters @@ -1648,8 +1643,8 @@ extension PIF.PlatformFilter { .init(platform: "openbsd"), ] - /// Web Assembly platform filters. - public static let webAsssemblyFilters: [PIF.PlatformFilter] = [ + /// WebAssembly platform filters. + public static let webAssemblyFilters: [PIF.PlatformFilter] = [ .init(platform: "wasi"), ] } diff --git a/Sources/XCBuildSupport/XCBuildDelegate.swift b/Sources/XCBuildSupport/XCBuildDelegate.swift index 46393936a2a..f7c0ae13bf7 100644 --- a/Sources/XCBuildSupport/XCBuildDelegate.swift +++ b/Sources/XCBuildSupport/XCBuildDelegate.swift @@ -81,7 +81,7 @@ extension XCBuildDelegate: XCBuildOutputParserDelegate { } case .taskComplete(let info): queue.async { - self.buildSystem.delegate?.buildSystem(self.buildSystem, didStartCommand: BuildSystemCommand(name: "\(info.taskID)", description: info.result.rawValue)) + self.buildSystem.delegate?.buildSystem(self.buildSystem, didFinishCommand: BuildSystemCommand(name: "\(info.taskID)", description: info.result.rawValue)) } case .buildDiagnostic(let info): queue.async { @@ -127,7 +127,7 @@ extension XCBuildDelegate: XCBuildOutputParserDelegate { self.buildSystem.delegate?.buildSystem(self.buildSystem, didFinishWithResult: true) } } - case .buildStarted, .preparationComplete, .targetUpToDate, .targetStarted, .targetComplete, .taskUpToDate: + case .buildStarted, .preparationComplete, .targetUpToDate, .targetStarted, .targetComplete, .taskUpToDate, .unknown: break } } diff --git a/Sources/XCBuildSupport/XCBuildMessage.swift b/Sources/XCBuildSupport/XCBuildMessage.swift index b68c9bb9abd..b7fd1c790f6 100644 --- a/Sources/XCBuildSupport/XCBuildMessage.swift +++ b/Sources/XCBuildSupport/XCBuildMessage.swift @@ -122,6 +122,7 @@ public enum XCBuildMessage { case taskOutput(TaskOutputInfo) case taskComplete(TaskCompleteInfo) case targetDiagnostic(TargetDiagnosticInfo) + case unknown } extension XCBuildMessage.BuildDiagnosticInfo: Codable, Equatable, Sendable {} @@ -285,7 +286,7 @@ extension XCBuildMessage: Codable, Equatable, Sendable { case "targetDiagnostic": self = try .targetDiagnostic(TargetDiagnosticInfo(from: decoder)) default: - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "invalid kind \(kind)") + self = .unknown } } @@ -335,6 +336,9 @@ extension XCBuildMessage: Codable, Equatable, Sendable { case let .targetDiagnostic(info): try container.encode("targetDiagnostic", forKey: .kind) try info.encode(to: encoder) + case .unknown: + assertionFailure() + break } } } diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index d130a7a3c81..ec1bb2ebe05 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -55,7 +55,8 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { builtProducts.append( BuiltTestProduct( productName: product.name, - binaryPath: binaryPath + binaryPath: binaryPath, + packagePath: package.path ) ) } @@ -317,10 +318,11 @@ extension BuildConfiguration { extension PIFBuilderParameters { public init(_ buildParameters: BuildParameters) { self.init( - enableTestability: buildParameters.enableTestability, + enableTestability: buildParameters.testingParameters.enableTestability, shouldCreateDylibForDynamicProducts: buildParameters.shouldCreateDylibForDynamicProducts, toolchainLibDir: (try? buildParameters.toolchain.toolchainLibDir) ?? .root, - pkgConfigDirectories: buildParameters.pkgConfigDirectories + pkgConfigDirectories: buildParameters.pkgConfigDirectories, + sdkRootPath: buildParameters.toolchain.sdkRootPath ) } } diff --git a/Sources/dummy-swiftc/main.swift b/Sources/dummy-swiftc/main.swift new file mode 100644 index 00000000000..18bcd91ad61 --- /dev/null +++ b/Sources/dummy-swiftc/main.swift @@ -0,0 +1,27 @@ +// This program can be used as `swiftc` in order to influence `-version` output + +import Foundation + +import class TSCBasic.Process + +let info = ProcessInfo.processInfo +let env = info.environment + +if info.arguments.last == "-version" { + if let customSwiftVersion = env["CUSTOM_SWIFT_VERSION"] { + print(customSwiftVersion) + } else { + print("999.0") + } +} else { + let swiftPath: String + if let swiftOriginalPath = env["SWIFT_ORIGINAL_PATH"] { + swiftPath = swiftOriginalPath + } else { + fatalError("need `SWIFT_ORIGINAL_PATH` in the environment") + } + + let result = try Process.popen(arguments: [swiftPath] + info.arguments.dropFirst()) + print(try result.utf8Output()) + print(try result.utf8stderrOutput()) +} diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 839027a8b2f..86ce514687a 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -118,6 +118,10 @@ struct SwiftBootstrapBuildTool: ParsableCommand { case error } + /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying + @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default") + public var shouldDisableLocalRpath: Bool = false + private var buildSystem: BuildSystemProvider.Kind { #if os(macOS) // Force the Xcode build system if we want to build more than one arch. @@ -188,7 +192,8 @@ struct SwiftBootstrapBuildTool: ParsableCommand { buildFlags: self.buildFlags, manifestBuildFlags: self.manifestFlags, useIntegratedSwiftDriver: self.useIntegratedSwiftDriver, - explicitTargetDependencyImportCheck: self.explicitTargetDependencyImportCheck + explicitTargetDependencyImportCheck: self.explicitTargetDependencyImportCheck, + shouldDisableLocalRpath: self.shouldDisableLocalRpath ) } catch _ as Diagnostics { throw ExitCode.failure @@ -197,6 +202,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { struct Builder { let identityResolver: IdentityResolver + let dependencyMapper: DependencyMapper let hostToolchain: UserToolchain let targetToolchain: UserToolchain let fileSystem: FileSystem @@ -214,6 +220,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { } self.identityResolver = DefaultIdentityResolver() + self.dependencyMapper = DefaultDependencyMapper(identityResolver: self.identityResolver) self.hostToolchain = try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK(originalWorkingDirectory: cwd)) self.targetToolchain = hostToolchain // TODO: support cross-compilation? self.fileSystem = fileSystem @@ -230,7 +237,8 @@ struct SwiftBootstrapBuildTool: ParsableCommand { buildFlags: BuildFlags, manifestBuildFlags: [String], useIntegratedSwiftDriver: Bool, - explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode + explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode, + shouldDisableLocalRpath: Bool ) throws { let buildSystem = try createBuildSystem( packagePath: packagePath, @@ -242,6 +250,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { manifestBuildFlags: manifestBuildFlags, useIntegratedSwiftDriver: useIntegratedSwiftDriver, explicitTargetDependencyImportCheck: explicitTargetDependencyImportCheck, + shouldDisableLocalRpath: shouldDisableLocalRpath, logLevel: logLevel ) try buildSystem.build(subset: .allExcludingTests) @@ -257,6 +266,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { manifestBuildFlags: [String], useIntegratedSwiftDriver: Bool, explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode, + shouldDisableLocalRpath: Bool, logLevel: Basics.Diagnostic.Severity ) throws -> BuildSystem { @@ -275,10 +285,17 @@ struct SwiftBootstrapBuildTool: ParsableCommand { targetTriple: self.targetToolchain.targetTriple, flags: buildFlags, architectures: architectures, - useIntegratedSwiftDriver: useIntegratedSwiftDriver, isXcodeBuildSystemEnabled: buildSystem == .xcode, - explicitTargetDependencyImportCheckingMode: explicitTargetDependencyImportCheck == .error ? .error : .none, - verboseOutput: logLevel <= .info + driverParameters: .init( + explicitTargetDependencyImportCheckingMode: explicitTargetDependencyImportCheck == .error ? .error : .none, + useIntegratedSwiftDriver: useIntegratedSwiftDriver + ), + linkingParameters: .init( + shouldDisableLocalRpath: shouldDisableLocalRpath + ), + outputParameters: .init( + isVerbose: logLevel <= .info + ) ) let manifestLoader = createManifestLoader(manifestBuildFlags: manifestBuildFlags) @@ -337,7 +354,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { let input = loadedManifests.map { identity, manifest in KeyedPair(manifest, key: identity) } _ = try topologicalSort(input) { pair in let dependenciesRequired = pair.item.dependenciesRequired(for: .everything) - let dependenciesToLoad = dependenciesRequired.map{ $0.createPackageRef() }.filter { !loadedManifests.keys.contains($0.identity) } + let dependenciesToLoad = dependenciesRequired.map{ $0.packageRef }.filter { !loadedManifests.keys.contains($0.identity) } let dependenciesManifests = try temp_await { self.loadManifests(manifestLoader: manifestLoader, packages: dependenciesToLoad, completion: $0) } dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } return dependenciesRequired.compactMap { dependency in @@ -349,7 +366,8 @@ struct SwiftBootstrapBuildTool: ParsableCommand { let packageGraphRoot = PackageGraphRoot( input: .init(packages: [packagePath]), - manifests: [packagePath: rootPackageManifest] + manifests: [packagePath: rootPackageManifest], + observabilityScope: observabilityScope ) return try PackageGraph.load( @@ -409,6 +427,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { packageLocation: package.locationString, packageVersion: .none, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: fileSystem, observabilityScope: observabilityScope, delegateQueue: .sharedConcurrent, diff --git a/Tests/BasicsTests/Archiver/TarArchiverTests.swift b/Tests/BasicsTests/Archiver/TarArchiverTests.swift index 2efb0848171..d4c3ca748ca 100644 --- a/Tests/BasicsTests/Archiver/TarArchiverTests.swift +++ b/Tests/BasicsTests/Archiver/TarArchiverTests.swift @@ -19,51 +19,51 @@ import class TSCBasic.InMemoryFileSystem import struct TSCBasic.FileSystemError final class TarArchiverTests: XCTestCase { - func testSuccess() throws { - try testWithTemporaryDirectory { tmpdir in + func testSuccess() async throws { + try await testWithTemporaryDirectory { tmpdir in let archiver = TarArchiver(fileSystem: localFileSystem) let inputArchivePath = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "archive.tar.gz") - try archiver.extract(from: inputArchivePath, to: tmpdir) + try await archiver.extract(from: inputArchivePath, to: tmpdir) let content = tmpdir.appending("file") XCTAssert(localFileSystem.exists(content)) XCTAssertEqual((try? localFileSystem.readFileContents(content))?.cString, "Hello World!") } } - func testArchiveDoesntExist() { + func testArchiveDoesntExist() async { let fileSystem = InMemoryFileSystem() let archiver = TarArchiver(fileSystem: fileSystem) let archive = AbsolutePath("/archive.tar.gz") - XCTAssertThrowsError(try archiver.extract(from: archive, to: "/")) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: archive, to: "/")) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.noEntry, archive)) } } - func testDestinationDoesntExist() throws { + func testDestinationDoesntExist() async throws { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.tar.gz") let archiver = TarArchiver(fileSystem: fileSystem) let destination = AbsolutePath("/destination") - XCTAssertThrowsError(try archiver.extract(from: "/archive.tar.gz", to: destination)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: "/archive.tar.gz", to: destination)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.notDirectory, destination)) } } - func testDestinationIsFile() throws { + func testDestinationIsFile() async { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.tar.gz", "/destination") let archiver = TarArchiver(fileSystem: fileSystem) let destination = AbsolutePath("/destination") - XCTAssertThrowsError(try archiver.extract(from: "/archive.tar.gz", to: destination)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: "/archive.tar.gz", to: destination)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.notDirectory, destination)) } } - func testInvalidArchive() throws { - try testWithTemporaryDirectory { tmpdir in + func testInvalidArchive() async throws { + try await testWithTemporaryDirectory { tmpdir in let archiver = TarArchiver(fileSystem: localFileSystem) let inputArchivePath = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.tar.gz") - XCTAssertThrowsError(try archiver.extract(from: inputArchivePath, to: tmpdir)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: inputArchivePath, to: tmpdir)) { error in #if os(Linux) XCTAssertMatch((error as? StringError)?.description, .contains("not in gzip format")) #else @@ -73,39 +73,39 @@ final class TarArchiverTests: XCTestCase { } } - func testValidation() throws { + func testValidation() async throws { // valid - try testWithTemporaryDirectory { _ in + try await testWithTemporaryDirectory { _ in let archiver = TarArchiver(fileSystem: localFileSystem) let path = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "archive.tar.gz") - XCTAssertTrue(try archiver.validate(path: path)) + try await XCTAssertAsyncTrue(try await archiver.validate(path: path)) } // invalid - try testWithTemporaryDirectory { _ in + try await testWithTemporaryDirectory { _ in let archiver = TarArchiver(fileSystem: localFileSystem) let path = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.tar.gz") - XCTAssertFalse(try archiver.validate(path: path)) + try await XCTAssertAsyncFalse(try await archiver.validate(path: path)) } // error - try testWithTemporaryDirectory { _ in + try await testWithTemporaryDirectory { _ in let archiver = TarArchiver(fileSystem: localFileSystem) let path = AbsolutePath.root.appending("does_not_exist.tar.gz") - XCTAssertThrowsError(try archiver.validate(path: path)) { error in + await XCTAssertAsyncThrowsError(try await archiver.validate(path: path)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.noEntry, path)) } } } - func testCompress() throws { + func testCompress() async throws { #if os(Linux) guard SPM_posix_spawn_file_actions_addchdir_np_supported() else { throw XCTSkip("working directory not supported on this platform") } #endif - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let archiver = TarArchiver(fileSystem: localFileSystem) let rootDir = tmpdir.appending(component: UUID().uuidString) @@ -122,12 +122,12 @@ final class TarArchiverTests: XCTestCase { try localFileSystem.writeFileContents(dir2.appending("file4.txt"), string: "Hello World 4!") let archivePath = tmpdir.appending(component: UUID().uuidString + ".tar.gz") - try archiver.compress(directory: rootDir, to: archivePath) + try await archiver.compress(directory: rootDir, to: archivePath) XCTAssertFileExists(archivePath) let extractRootDir = tmpdir.appending(component: UUID().uuidString) try localFileSystem.createDirectory(extractRootDir) - try archiver.extract(from: archivePath, to: extractRootDir) + try await archiver.extract(from: archivePath, to: extractRootDir) try localFileSystem.stripFirstLevel(of: extractRootDir) XCTAssertFileExists(extractRootDir.appending("file1.txt")) diff --git a/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift b/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift index 685d6e954ee..363b20f3537 100644 --- a/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift +++ b/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift @@ -19,60 +19,60 @@ import class TSCBasic.InMemoryFileSystem import struct TSCBasic.FileSystemError final class UniversalArchiverTests: XCTestCase { - func testSuccess() throws { - try testWithTemporaryDirectory { tmpdir in + func testSuccess() async throws { + try await testWithTemporaryDirectory { tmpdir in let archiver = UniversalArchiver(localFileSystem) let inputArchivePath = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "archive.tar.gz") let tarDestination = tmpdir.appending("tar") try localFileSystem.createDirectory(tarDestination) - try archiver.extract(from: inputArchivePath, to: tarDestination) + try await archiver.extract(from: inputArchivePath, to: tarDestination) let tarContent = tarDestination.appending("file") XCTAssert(localFileSystem.exists(tarContent)) XCTAssertEqual((try? localFileSystem.readFileContents(tarContent))?.cString, "Hello World!") let zipDestination = tmpdir.appending("zip") try localFileSystem.createDirectory(zipDestination) - try archiver.extract(from: inputArchivePath, to: zipDestination) + try await archiver.extract(from: inputArchivePath, to: zipDestination) let zipContent = zipDestination.appending("file") XCTAssert(localFileSystem.exists(zipContent)) XCTAssertEqual((try? localFileSystem.readFileContents(zipContent))?.cString, "Hello World!") } } - func testArchiveDoesntExist() { + func testArchiveDoesntExist() async { let fileSystem = InMemoryFileSystem() let archiver = UniversalArchiver(fileSystem) let archive = AbsolutePath("/archive.tar.gz") - XCTAssertThrowsError(try archiver.extract(from: archive, to: "/")) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: archive, to: "/")) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.noEntry, archive)) } } - func testDestinationDoesntExist() throws { + func testDestinationDoesntExist() async throws { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.tar.gz") let archiver = UniversalArchiver(fileSystem) let destination = AbsolutePath("/destination") - XCTAssertThrowsError(try archiver.extract(from: "/archive.tar.gz", to: destination)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: "/archive.tar.gz", to: destination)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.notDirectory, destination)) } } - func testDestinationIsFile() throws { + func testDestinationIsFile() async throws { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.tar.gz", "/destination") let archiver = UniversalArchiver(fileSystem) let destination = AbsolutePath("/destination") - XCTAssertThrowsError(try archiver.extract(from: "/archive.tar.gz", to: destination)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: "/archive.tar.gz", to: destination)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.notDirectory, destination)) } } - func testInvalidArchive() throws { - try testWithTemporaryDirectory { tmpdir in + func testInvalidArchive() async throws { + try await testWithTemporaryDirectory { tmpdir in let archiver = UniversalArchiver(localFileSystem) var inputArchivePath = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.tar.gz") - XCTAssertThrowsError(try archiver.extract(from: inputArchivePath, to: tmpdir)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: inputArchivePath, to: tmpdir)) { error in #if os(Linux) XCTAssertMatch((error as? StringError)?.description, .contains("not in gzip format")) #else @@ -82,7 +82,7 @@ final class UniversalArchiverTests: XCTestCase { inputArchivePath = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.zip") - XCTAssertThrowsError(try archiver.extract(from: inputArchivePath, to: tmpdir)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: inputArchivePath, to: tmpdir)) { error in #if os(Windows) XCTAssertMatch((error as? StringError)?.description, .contains("Unrecognized archive format")) #else @@ -92,39 +92,39 @@ final class UniversalArchiverTests: XCTestCase { } } - func testValidation() throws { + func testValidation() async throws { // valid - try testWithTemporaryDirectory { _ in + try await testWithTemporaryDirectory { _ in let archiver = UniversalArchiver(localFileSystem) let path = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "archive.tar.gz") - XCTAssertTrue(try archiver.validate(path: path)) + try await XCTAssertAsyncTrue(try await archiver.validate(path: path)) } // invalid - try testWithTemporaryDirectory { _ in + try await testWithTemporaryDirectory { _ in let archiver = UniversalArchiver(localFileSystem) let path = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.tar.gz") - XCTAssertFalse(try archiver.validate(path: path)) + try await XCTAssertAsyncFalse(try await archiver.validate(path: path)) } // error - try testWithTemporaryDirectory { _ in + try await testWithTemporaryDirectory { _ in let archiver = UniversalArchiver(localFileSystem) let path = AbsolutePath.root.appending("does_not_exist.tar.gz") - XCTAssertThrowsError(try archiver.validate(path: path)) { error in + await XCTAssertAsyncThrowsError(try await archiver.validate(path: path)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.noEntry, path)) } } } - func testCompress() throws { + func testCompress() async throws { #if os(Linux) guard SPM_posix_spawn_file_actions_addchdir_np_supported() else { throw XCTSkip("working directory not supported on this platform") } #endif - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let archiver = UniversalArchiver(localFileSystem) let rootDir = tmpdir.appending(component: UUID().uuidString) @@ -141,12 +141,12 @@ final class UniversalArchiverTests: XCTestCase { try localFileSystem.writeFileContents(dir2.appending("file4.txt"), string: "Hello World 4!") var archivePath = tmpdir.appending(component: UUID().uuidString + ".tar.gz") - try archiver.compress(directory: rootDir, to: archivePath) + try await archiver.compress(directory: rootDir, to: archivePath) XCTAssertFileExists(archivePath) var extractRootDir = tmpdir.appending(component: UUID().uuidString) try localFileSystem.createDirectory(extractRootDir) - try archiver.extract(from: archivePath, to: extractRootDir) + try await archiver.extract(from: archivePath, to: extractRootDir) try localFileSystem.stripFirstLevel(of: extractRootDir) XCTAssertFileExists(extractRootDir.appending("file1.txt")) @@ -177,12 +177,12 @@ final class UniversalArchiverTests: XCTestCase { ) archivePath = tmpdir.appending(component: UUID().uuidString + ".zip") - try archiver.compress(directory: rootDir, to: archivePath) + try await archiver.compress(directory: rootDir, to: archivePath) XCTAssertFileExists(archivePath) extractRootDir = tmpdir.appending(component: UUID().uuidString) try localFileSystem.createDirectory(extractRootDir) - try archiver.extract(from: archivePath, to: extractRootDir) + try await archiver.extract(from: archivePath, to: extractRootDir) try localFileSystem.stripFirstLevel(of: extractRootDir) XCTAssertFileExists(extractRootDir.appending("file1.txt")) diff --git a/Tests/BasicsTests/Archiver/ZipArchiverTests.swift b/Tests/BasicsTests/Archiver/ZipArchiverTests.swift index 5883e7c42d6..d0441be4b56 100644 --- a/Tests/BasicsTests/Archiver/ZipArchiverTests.swift +++ b/Tests/BasicsTests/Archiver/ZipArchiverTests.swift @@ -19,50 +19,50 @@ import class TSCBasic.InMemoryFileSystem import struct TSCBasic.FileSystemError class ZipArchiverTests: XCTestCase { - func testZipArchiverSuccess() throws { - try testWithTemporaryDirectory { tmpdir in + func testZipArchiverSuccess() async throws { + try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) let inputArchivePath = AbsolutePath(#file).parentDirectory.appending(components: "Inputs", "archive.zip") - try archiver.extract(from: inputArchivePath, to: tmpdir) + try await archiver.extract(from: inputArchivePath, to: tmpdir) let content = tmpdir.appending("file") XCTAssert(localFileSystem.exists(content)) XCTAssertEqual((try? localFileSystem.readFileContents(content))?.cString, "Hello World!") } } - func testZipArchiverArchiveDoesntExist() { + func testZipArchiverArchiveDoesntExist() async { let fileSystem = InMemoryFileSystem() let archiver = ZipArchiver(fileSystem: fileSystem) let archive = AbsolutePath("/archive.zip") - XCTAssertThrowsError(try archiver.extract(from: archive, to: "/")) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: archive, to: "/")) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.noEntry, archive)) } } - func testZipArchiverDestinationDoesntExist() throws { + func testZipArchiverDestinationDoesntExist() async throws { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.zip") let archiver = ZipArchiver(fileSystem: fileSystem) let destination = AbsolutePath("/destination") - XCTAssertThrowsError(try archiver.extract(from: "/archive.zip", to: destination)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: "/archive.zip", to: destination)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.notDirectory, destination)) } } - func testZipArchiverDestinationIsFile() throws { + func testZipArchiverDestinationIsFile() async throws { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.zip", "/destination") let archiver = ZipArchiver(fileSystem: fileSystem) let destination = AbsolutePath("/destination") - XCTAssertThrowsError(try archiver.extract(from: "/archive.zip", to: destination)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: "/archive.zip", to: destination)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.notDirectory, destination)) } } - func testZipArchiverInvalidArchive() throws { - try testWithTemporaryDirectory { tmpdir in + func testZipArchiverInvalidArchive() async throws { + try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) let inputArchivePath = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.zip") - XCTAssertThrowsError(try archiver.extract(from: inputArchivePath, to: tmpdir)) { error in + await XCTAssertAsyncThrowsError(try await archiver.extract(from: inputArchivePath, to: tmpdir)) { error in #if os(Windows) XCTAssertMatch((error as? StringError)?.description, .contains("Unrecognized archive format")) #else @@ -72,39 +72,39 @@ class ZipArchiverTests: XCTestCase { } } - func testValidation() throws { + func testValidation() async throws { // valid - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) let path = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "archive.zip") - XCTAssertTrue(try archiver.validate(path: path)) + try await XCTAssertAsyncTrue(try await archiver.validate(path: path)) } // invalid - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) let path = AbsolutePath(#file).parentDirectory .appending(components: "Inputs", "invalid_archive.zip") - XCTAssertFalse(try archiver.validate(path: path)) + try await XCTAssertAsyncFalse(try await archiver.validate(path: path)) } // error - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) let path = AbsolutePath.root.appending("does_not_exist.zip") - XCTAssertThrowsError(try archiver.validate(path: path)) { error in + await XCTAssertAsyncThrowsError(try await archiver.validate(path: path)) { error in XCTAssertEqual(error as? FileSystemError, FileSystemError(.noEntry, path)) } } } - func testCompress() throws { + func testCompress() async throws { #if os(Linux) guard SPM_posix_spawn_file_actions_addchdir_np_supported() else { throw XCTSkip("working directory not supported on this platform") } #endif - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) let rootDir = tmpdir.appending(component: UUID().uuidString) @@ -121,12 +121,12 @@ class ZipArchiverTests: XCTestCase { try localFileSystem.writeFileContents(dir2.appending("file4.txt"), string: "Hello World 4!") let archivePath = tmpdir.appending(component: UUID().uuidString + ".zip") - try archiver.compress(directory: rootDir, to: archivePath) + try await archiver.compress(directory: rootDir, to: archivePath) XCTAssertFileExists(archivePath) let extractRootDir = tmpdir.appending(component: UUID().uuidString) try localFileSystem.createDirectory(extractRootDir) - try archiver.extract(from: archivePath, to: extractRootDir) + try await archiver.extract(from: archivePath, to: extractRootDir) try localFileSystem.stripFirstLevel(of: extractRootDir) XCTAssertFileExists(extractRootDir.appending("file1.txt")) @@ -158,117 +158,3 @@ class ZipArchiverTests: XCTestCase { } } } - -class ArchiverTests: XCTestCase { - func testCancel() throws { - struct MockArchiver: Archiver, Cancellable { - var supportedExtensions: Set = [] - - let cancelSemaphores = ThreadSafeArrayStore() - let startGroup = DispatchGroup() - let finishGroup = DispatchGroup() - - func extract(from archivePath: AbsolutePath, to destinationPath: AbsolutePath, completion: @escaping (Result) -> Void) { - let cancelSemaphore = DispatchSemaphore(value: 0) - self.cancelSemaphores.append(cancelSemaphore) - - self.startGroup.enter() - DispatchQueue.sharedConcurrent.async { - self.startGroup.leave() - self.finishGroup.enter() - defer { self.finishGroup.leave() } - switch cancelSemaphore.wait(timeout: .now() + .seconds(5)) { - case .success: - completion(.success(())) - case .timedOut: - completion(.failure(StringError("should be cancelled"))) - } - } - } - - func compress(directory: AbsolutePath, to destinationPath: AbsolutePath, completion: @escaping (Result) -> Void) { - let cancelSemaphore = DispatchSemaphore(value: 0) - self.cancelSemaphores.append(cancelSemaphore) - - self.startGroup.enter() - DispatchQueue.sharedConcurrent.async { - self.startGroup.leave() - self.finishGroup.enter() - defer { self.finishGroup.leave() } - switch cancelSemaphore.wait(timeout: .now() + .seconds(5)) { - case .success: - completion(.success(())) - case .timedOut: - completion(.failure(StringError("should be cancelled"))) - } - } - } - - func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { - let cancelSemaphore = DispatchSemaphore(value: 0) - self.cancelSemaphores.append(cancelSemaphore) - - self.startGroup.enter() - DispatchQueue.sharedConcurrent.async { - self.startGroup.leave() - self.finishGroup.enter() - defer { self.finishGroup.leave() } - switch cancelSemaphore.wait(timeout: .now() + .seconds(5)) { - case .success: - completion(.success(true)) - case .timedOut: - completion(.failure(StringError("should be cancelled"))) - } - } - } - - func cancel(deadline: DispatchTime) throws { - for semaphore in self.cancelSemaphores.get() { - semaphore.signal() - } - } - } - - let observability = ObservabilitySystem.makeForTesting() - let cancellator = Cancellator(observabilityScope: observability.topScope) - - let archiver = MockArchiver() - cancellator.register(name: "archiver", handler: archiver) - - archiver.extract(from: .root, to: .root) { result in - XCTAssertResultSuccess(result) - } - - archiver.compress(directory: .root, to: .root) { result in - XCTAssertResultSuccess(result) - } - - archiver.validate(path: .root) { result in - XCTAssertResultSuccess(result) - } - - XCTAssertEqual(.success, archiver.startGroup.wait(timeout: .now() + .seconds(5)), "timeout waiting for tasks to start") - - try cancellator.cancel(deadline: .now() + .seconds(5)) - - XCTAssertEqual(.success, archiver.finishGroup.wait(timeout: .now() + .seconds(5)), "timeout waiting for tasks to finish") - } -} - -extension Archiver { - func extract(from: AbsolutePath, to: AbsolutePath) throws { - try temp_await { - self.extract(from: from, to: to, completion: $0) - } - } - func compress(directory: AbsolutePath, to: AbsolutePath) throws { - try temp_await { - self.compress(directory: directory, to: to, completion: $0) - } - } - func validate(path: AbsolutePath) throws -> Bool { - try temp_await { - self.validate(path: path, completion: $0) - } - } -} diff --git a/Tests/BasicsTests/AuthorizationProviderTests.swift b/Tests/BasicsTests/AuthorizationProviderTests.swift index 93fc9bf7cc4..84560bccb98 100644 --- a/Tests/BasicsTests/AuthorizationProviderTests.swift +++ b/Tests/BasicsTests/AuthorizationProviderTests.swift @@ -24,8 +24,8 @@ final class AuthorizationProviderTests: XCTestCase { self.assertAuthentication(provider, for: url, expected: (user, password)) } - func testNetrc() throws { - try testWithTemporaryDirectory { tmpPath in + func testNetrc() async throws { + try await testWithTemporaryDirectory { tmpPath in let netrcPath = tmpPath.appending(".netrc") let provider = try NetrcAuthorizationProvider(path: netrcPath, fileSystem: localFileSystem) @@ -39,20 +39,14 @@ final class AuthorizationProviderTests: XCTestCase { let otherPassword = UUID().uuidString // Add - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: url, user: user, password: password, callback: callback) - }) - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: otherURL, user: user, password: otherPassword, callback: callback) - }) + try await provider.addOrUpdate(for: url, user: user, password: password) + try await provider.addOrUpdate(for: otherURL, user: user, password: otherPassword) self.assertAuthentication(provider, for: url, expected: (user, password)) // Update - the new password is appended to the end of file let newPassword = UUID().uuidString - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: url, user: user, password: newPassword, callback: callback) - }) + try await provider.addOrUpdate(for: url, user: user, password: newPassword) // .netrc file now contains two entries for `url`: one with `password` and the other with `newPassword`. // `NetrcAuthorizationProvider` returns the last entry it finds. @@ -140,36 +134,30 @@ final class AuthorizationProviderTests: XCTestCase { let httpsPassword = UUID().uuidString // Add - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: httpURL, user: user, password: httpPassword, callback: callback) - }) - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: httpsURL, user: user, password: httpsPassword, callback: callback) - }) + try await provider.addOrUpdate(for: httpURL, user: user, password: httpPassword) + try await provider.addOrUpdate(for: httpsURL, user: user, password: httpsPassword) self.assertAuthentication(provider, for: httpURL, expected: (user, httpPassword)) self.assertAuthentication(provider, for: httpsURL, expected: (user, httpsPassword)) // Update let newHTTPPassword = UUID().uuidString - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: httpURL, user: user, password: newHTTPPassword, callback: callback) - }) + try await provider.addOrUpdate(for: httpURL, user: user, password: newHTTPPassword) + let newHTTPSPassword = UUID().uuidString - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: httpsURL, user: user, password: newHTTPSPassword, callback: callback) - }) + try await provider.addOrUpdate(for: httpsURL, user: user, password: newHTTPSPassword) + // Existing password is updated self.assertAuthentication(provider, for: httpURL, expected: (user, newHTTPPassword)) self.assertAuthentication(provider, for: httpsURL, expected: (user, newHTTPSPassword)) // Delete - XCTAssertNoThrow(try temp_await { callback in provider.remove(for: httpURL, callback: callback) }) + try await provider.remove(for: httpURL) XCTAssertNil(provider.authentication(for: httpURL)) self.assertAuthentication(provider, for: httpsURL, expected: (user, newHTTPSPassword)) - XCTAssertNoThrow(try temp_await { callback in provider.remove(for: httpsURL, callback: callback) }) + try await provider.remove(for: httpsURL) XCTAssertNil(provider.authentication(for: httpsURL)) #endif } @@ -189,36 +177,29 @@ final class AuthorizationProviderTests: XCTestCase { let portPassword = UUID().uuidString // Add - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: noPortURL, user: user, password: noPortPassword, callback: callback) - }) - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: portURL, user: user, password: portPassword, callback: callback) - }) + try await provider.addOrUpdate(for: noPortURL, user: user, password: noPortPassword) + try await provider.addOrUpdate(for: portURL, user: user, password: portPassword) self.assertAuthentication(provider, for: noPortURL, expected: (user, noPortPassword)) self.assertAuthentication(provider, for: portURL, expected: (user, portPassword)) // Update let newPortPassword = UUID().uuidString - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: portURL, user: user, password: newPortPassword, callback: callback) - }) + try await provider.addOrUpdate(for: portURL, user: user, password: newPortPassword) + let newNoPortPassword = UUID().uuidString - XCTAssertNoThrow(try temp_await { callback in - provider.addOrUpdate(for: noPortURL, user: user, password: newNoPortPassword, callback: callback) - }) + try await provider.addOrUpdate(for: noPortURL, user: user, password: newNoPortPassword) // Existing password is updated self.assertAuthentication(provider, for: portURL, expected: (user, newPortPassword)) self.assertAuthentication(provider, for: noPortURL, expected: (user, newNoPortPassword)) // Delete - XCTAssertNoThrow(try temp_await { callback in provider.remove(for: noPortURL, callback: callback) }) + try await provider.remove(for: noPortURL) XCTAssertNil(provider.authentication(for: noPortURL)) self.assertAuthentication(provider, for: portURL, expected: (user, newPortPassword)) - XCTAssertNoThrow(try temp_await { callback in provider.remove(for: portURL, callback: callback) }) + try await provider.remove(for: portURL) XCTAssertNil(provider.authentication(for: portURL)) #endif } @@ -274,7 +255,7 @@ final class AuthorizationProviderTests: XCTestCase { XCTAssertEqual(authentication?.password, expected.password) XCTAssertEqual( provider.httpAuthorizationHeader(for: url), - "Basic " + "\(expected.user):\(expected.password)".data(using: .utf8)!.base64EncodedString() + "Basic " + Data("\(expected.user):\(expected.password)".utf8).base64EncodedString() ) } } diff --git a/Tests/BasicsTests/HTTPClientTests.swift b/Tests/BasicsTests/HTTPClientTests.swift index 7531b8e77ae..bf2da10c76a 100644 --- a/Tests/BasicsTests/HTTPClientTests.swift +++ b/Tests/BasicsTests/HTTPClientTests.swift @@ -40,7 +40,7 @@ final class HTTPClientTests: XCTestCase { let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in XCTAssertEqual(request.url, url, "url should match") @@ -58,10 +58,10 @@ final class HTTPClientTests: XCTestCase { func testPost() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in XCTAssertEqual(request.url, url, "url should match") @@ -80,10 +80,10 @@ final class HTTPClientTests: XCTestCase { func testPut() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in XCTAssertEqual(request.url, url, "url should match") @@ -104,7 +104,7 @@ final class HTTPClientTests: XCTestCase { let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in XCTAssertEqual(request.url, url, "url should match") diff --git a/Tests/BasicsTests/LegacyHTTPClientTests.swift b/Tests/BasicsTests/LegacyHTTPClientTests.swift index 6e66e33284f..67c78f381da 100644 --- a/Tests/BasicsTests/LegacyHTTPClientTests.swift +++ b/Tests/BasicsTests/LegacyHTTPClientTests.swift @@ -52,7 +52,7 @@ final class LegacyHTTPClientTests: XCTestCase { let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let handler: LegacyHTTPClient.Handler = { request, _, completion in XCTAssertEqual(request.url, url, "url should match") @@ -82,10 +82,10 @@ final class LegacyHTTPClientTests: XCTestCase { func testPost() { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let handler: LegacyHTTPClient.Handler = { request, _, completion in XCTAssertEqual(request.url, url, "url should match") @@ -116,10 +116,10 @@ final class LegacyHTTPClientTests: XCTestCase { func testPut() { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let handler: LegacyHTTPClient.Handler = { request, _, completion in XCTAssertEqual(request.url, url, "url should match") @@ -152,7 +152,7 @@ final class LegacyHTTPClientTests: XCTestCase { let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let responseStatus = Int.random(in: 201 ..< 500) let responseHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) let handler: LegacyHTTPClient.Handler = { request, _, completion in XCTAssertEqual(request.url, url, "url should match") @@ -596,7 +596,7 @@ final class LegacyHTTPClientTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() let cancellator = Cancellator(observabilityScope: observability.topScope) - let total = 10 + let total = min(10, ProcessInfo.processInfo.activeProcessorCount / 2) // this DispatchGroup is used to wait for the requests to start before calling cancel let startGroup = DispatchGroup() // this DispatchGroup is used to park the delayed threads that would be cancelled diff --git a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift index 763c1d7ca56..ecefe820d5c 100644 --- a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift +++ b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift @@ -18,23 +18,27 @@ final class SerializedJSONTests: XCTestCase { var path = try AbsolutePath(validating: #"/test\backslashes"#) var json: SerializedJSON = "\(path)" +#if os(Windows) + XCTAssertEqual(json.underlying, #"\\test\\backslashes"#) +#else XCTAssertEqual(json.underlying, #"/test\\backslashes"#) +#endif #if os(Windows) path = try AbsolutePath(validating: #"\\?\C:\Users"#) json = "\(path)" - XCTAssertEqual(json.underlying, #"\\\\?\\C:\\Users"#) + XCTAssertEqual(json.underlying, #"C:\\Users"#) - path = try AbsolutePath(validating: #"\\.\UNC\server\share\n\"#) + path = try AbsolutePath(validating: #"\\.\UNC\server\share\"#) json = "\(path)" - XCTAssertEqual(json.underlying, #"\\\\.\\UNC\\server\\share\\"#) + XCTAssertEqual(json.underlying, #"\\.\\UNC\\server\\share"#) path = try AbsolutePath(validating: #"\??\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}\\"#) json = "\(path)" - XCTAssertEqual(json.underlying, #"\\??\\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}\\"#) + XCTAssertEqual(json.underlying, #"\\??\\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}"#) #endif } } diff --git a/Tests/BasicsTests/TripleTests.swift b/Tests/BasicsTests/TripleTests.swift index bc4ebbf800a..011d6247b05 100644 --- a/Tests/BasicsTests/TripleTests.swift +++ b/Tests/BasicsTests/TripleTests.swift @@ -122,6 +122,12 @@ final class TripleTests: XCTestCase { XCTAssertTriple("mips-apple-darwin19", forPlatformVersion: "", is: "mips-apple-darwin") XCTAssertTriple("mips-apple-darwin19", forPlatformVersion: "22", is: "mips-apple-darwin22") + + XCTAssertTriple("arm64-apple-ios-simulator", forPlatformVersion: "", is: "arm64-apple-ios-simulator") + XCTAssertTriple("arm64-apple-ios-simulator", forPlatformVersion: "13.0", is: "arm64-apple-ios13.0-simulator") + + XCTAssertTriple("arm64-apple-ios12-simulator", forPlatformVersion: "", is: "arm64-apple-ios-simulator") + XCTAssertTriple("arm64-apple-ios12-simulator", forPlatformVersion: "13.0", is: "arm64-apple-ios13.0-simulator") } func testKnownTripleParsing() { @@ -158,5 +164,60 @@ final class TripleTests: XCTestCase { XCTAssertTriple("aarch64-unknown-linux-android", matches: (.aarch64, nil, nil, .linux, .android, .elf)) XCTAssertTriple("x86_64-unknown-windows-msvc", matches: (.x86_64, nil, nil, .win32, .msvc, .coff)) XCTAssertTriple("wasm32-unknown-wasi", matches: (.wasm32, nil, nil, .wasi, nil, .wasm)) + } + + func testTriple() { + let linux = try? Triple("x86_64-unknown-linux-gnu") + XCTAssertNotNil(linux) + XCTAssertEqual(linux!.os, .linux) + XCTAssertEqual(linux!.osVersion, Triple.Version.zero) + XCTAssertEqual(linux!.environment, .gnu) + + let macos = try? Triple("x86_64-apple-macosx10.15") + XCTAssertNotNil(macos!) + XCTAssertEqual(macos!.osVersion, .init(parse: "10.15")) + let newVersion = "10.12" + let tripleString = macos!.tripleString(forPlatformVersion: newVersion) + XCTAssertEqual(tripleString, "x86_64-apple-macosx10.12") + let macosNoX = try? Triple("x86_64-apple-macos12.2") + XCTAssertNotNil(macosNoX!) + XCTAssertEqual(macosNoX!.os, .macosx) + XCTAssertEqual(macosNoX!.osVersion, .init(parse: "12.2")) + + let android = try? Triple("aarch64-unknown-linux-android24") + XCTAssertNotNil(android) + XCTAssertEqual(android!.os, .linux) + XCTAssertEqual(android!.environment, .android) + + let linuxWithABIVersion = try? Triple("x86_64-unknown-linux-gnu42") + XCTAssertEqual(linuxWithABIVersion!.environment, .gnu) + } + + func testEquality() throws { + let macOSTriple = try Triple("arm64-apple-macos") + let macOSXTriple = try Triple("arm64-apple-macosx") + XCTAssertEqual(macOSTriple, macOSXTriple) + + let intelMacOSTriple = try Triple("x86_64-apple-macos") + XCTAssertNotEqual(macOSTriple, intelMacOSTriple) + + let linuxWithoutGNUABI = try Triple("x86_64-unknown-linux") + let linuxWithGNUABI = try Triple("x86_64-unknown-linux-gnu") + XCTAssertNotEqual(linuxWithoutGNUABI, linuxWithGNUABI) + } + + func testWASI() throws { + let wasi = try Triple("wasm32-unknown-wasi") + + // WASI dynamic libraries are only experimental, + // but SwiftPM requires this property not to crash. + _ = wasi.dynamicLibraryExtension + } + + func testIsRuntimeCompatibleWith() throws { + try XCTAssertTrue(Triple("x86_64-apple-macosx").isRuntimeCompatible(with: Triple("x86_64-apple-macosx"))) + try XCTAssertTrue(Triple("x86_64-unknown-linux").isRuntimeCompatible(with: Triple("x86_64-unknown-linux"))) + try XCTAssertFalse(Triple("x86_64-apple-macosx").isRuntimeCompatible(with: Triple("x86_64-apple-linux"))) + try XCTAssertTrue(Triple("x86_64-apple-macosx14.0").isRuntimeCompatible(with: Triple("x86_64-apple-macosx13.0"))) } } diff --git a/Tests/BasicsTests/URLSessionHTTPClientTests.swift b/Tests/BasicsTests/URLSessionHTTPClientTests.swift index 195e513bf5a..cbe0054eb56 100644 --- a/Tests/BasicsTests/URLSessionHTTPClientTests.swift +++ b/Tests/BasicsTests/URLSessionHTTPClientTests.swift @@ -36,7 +36,7 @@ final class URLSessionHTTPClientTest: XCTestCase { let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("HEAD", url) { request in self.assertRequestHeaders(request.allHTTPHeaderFields, expected: requestHeaders) @@ -70,7 +70,7 @@ final class URLSessionHTTPClientTest: XCTestCase { let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("GET", url) { request in self.assertRequestHeaders(request.allHTTPHeaderFields, expected: requestHeaders) @@ -101,11 +101,11 @@ final class URLSessionHTTPClientTest: XCTestCase { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("POST", url) { request in // FIXME: @@ -138,11 +138,11 @@ final class URLSessionHTTPClientTest: XCTestCase { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("PUT", url) { request in XCTAssertEqual(request.httpBody, requestBody) @@ -177,7 +177,7 @@ final class URLSessionHTTPClientTest: XCTestCase { let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("DELETE", url) { request in self.assertRequestHeaders(request.allHTTPHeaderFields, expected: requestHeaders) @@ -287,7 +287,7 @@ final class URLSessionHTTPClientTest: XCTestCase { #endif let netrcContent = "machine protected.downloader-tests.com login anonymous password qwerty" let netrc = try NetrcAuthorizationWrapper(underlying: NetrcParser.parse(netrcContent)) - let authData = "anonymous:qwerty".data(using: .utf8)! + let authData = Data("anonymous:qwerty".utf8) let testAuthHeader = "Basic \(authData.base64EncodedString())" let configuration = URLSessionConfiguration.default @@ -356,9 +356,10 @@ final class URLSessionHTTPClientTest: XCTestCase { // https://github.com/apple/swift-corelibs-foundation/pull/2593 tries to address the latter part try XCTSkipIf(true, "test is only supported on macOS") #endif + try XCTSkipIfCI() let netrcContent = "default login default password default" let netrc = try NetrcAuthorizationWrapper(underlying: NetrcParser.parse(netrcContent)) - let authData = "default:default".data(using: .utf8)! + let authData = Data("default:default".utf8) let testAuthHeader = "Basic \(authData.base64EncodedString())" let configuration = URLSessionConfiguration.default @@ -577,7 +578,7 @@ final class URLSessionHTTPClientTest: XCTestCase { let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("HEAD", url) { request in self.assertRequestHeaders(request.allHTTPHeaderFields, expected: requestHeaders) @@ -602,7 +603,7 @@ final class URLSessionHTTPClientTest: XCTestCase { let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("GET", url) { request in self.assertRequestHeaders(request.allHTTPHeaderFields, expected: requestHeaders) @@ -623,11 +624,11 @@ final class URLSessionHTTPClientTest: XCTestCase { let url = URL("http://async-post-test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("POST", url) { request in // FIXME: @@ -651,11 +652,11 @@ final class URLSessionHTTPClientTest: XCTestCase { let url = URL("http://async-put-test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) - let requestBody = UUID().uuidString.data(using: .utf8) + let requestBody = Data(UUID().uuidString.utf8) let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("PUT", url) { request in XCTAssertEqual(request.httpBody, requestBody) @@ -681,7 +682,7 @@ final class URLSessionHTTPClientTest: XCTestCase { let responseStatus = 200 let responseHeaders = [UUID().uuidString: UUID().uuidString] - let responseBody = UUID().uuidString.data(using: .utf8) + let responseBody = Data(UUID().uuidString.utf8) MockURLProtocol.onRequest("DELETE", url) { request in self.assertRequestHeaders(request.allHTTPHeaderFields, expected: requestHeaders) @@ -756,7 +757,7 @@ final class URLSessionHTTPClientTest: XCTestCase { #endif let netrcContent = "machine async-protected.downloader-tests.com login anonymous password qwerty" let netrc = try NetrcAuthorizationWrapper(underlying: NetrcParser.parse(netrcContent)) - let authData = "anonymous:qwerty".data(using: .utf8)! + let authData = Data("anonymous:qwerty".utf8) let testAuthHeader = "Basic \(authData.base64EncodedString())" let configuration = URLSessionConfiguration.default @@ -814,7 +815,7 @@ final class URLSessionHTTPClientTest: XCTestCase { #endif let netrcContent = "default login default password default" let netrc = try NetrcAuthorizationWrapper(underlying: NetrcParser.parse(netrcContent)) - let authData = "default:default".data(using: .utf8)! + let authData = Data("default:default".utf8) let testAuthHeader = "Basic \(authData.base64EncodedString())" let configuration = URLSessionConfiguration.default diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 9a1f417e4ad..de65bbb6d43 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -10,9 +10,9 @@ // //===----------------------------------------------------------------------===// -import Basics +@testable import Basics @testable import Build -@_implementationOnly import DriverSupport +import DriverSupport import PackageLoading @testable import PackageGraph @testable import PackageModel @@ -29,7 +29,6 @@ import enum TSCUtility.Diagnostics final class BuildPlanTests: XCTestCase { let inputsDir = AbsolutePath(#file).parentDirectory.appending(components: "Inputs") - private let driverSupport = DriverSupport() /// The j argument. private var j: String { @@ -43,12 +42,14 @@ final class BuildPlanTests: XCTestCase { "/barPkg/Sources/BarLogging/file.swift" ) let observability = ObservabilitySystem.makeForTesting() + let fooPkg: AbsolutePath = "/fooPkg" + let barPkg: AbsolutePath = "/barPkg" XCTAssertThrowsError(try loadPackageGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( displayName: "fooPkg", - path: "/fooPkg", + path: fooPkg, products: [ ProductDescription(name: "Logging", type: .library(.dynamic), targets: ["FooLogging"]), ], @@ -57,7 +58,7 @@ final class BuildPlanTests: XCTestCase { ]), Manifest.createFileSystemManifest( displayName: "barPkg", - path: "/barPkg", + path: barPkg, products: [ ProductDescription(name: "Logging", type: .library(.static), targets: ["BarLogging"]), ], @@ -69,24 +70,21 @@ final class BuildPlanTests: XCTestCase { path: "/thisPkg", toolsVersion: .v5_8, dependencies: [ - .localSourceControl(path: "/fooPkg", requirement: .upToNextMajor(from: "1.0.0")), - .localSourceControl(path: "/barPkg", requirement: .upToNextMajor(from: "2.0.0")), + .localSourceControl(path: fooPkg, requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: barPkg, requirement: .upToNextMajor(from: "2.0.0")), ], targets: [ TargetDescription(name: "exe", - dependencies: [.product(name: "Logging", - package: "fooPkg" - ), - .product(name: "Logging", - package: "barPkg" - ), + dependencies: [.product(name: "Logging", package: "fooPkg"), + .product(name: "Logging", package: "barPkg"), ], type: .executable), ]), ], observabilityScope: observability.topScope )) { error in - XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Logging' in: 'barpkg' (at '/barPkg'), 'foopkg' (at '/fooPkg')") + XCTAssertEqual((error as? PackageGraphError)?.description, + "multiple products named 'Logging' in: 'barpkg' (at '\(barPkg)'), 'foopkg' (at '\(fooPkg)')") } } @@ -418,13 +416,15 @@ final class BuildPlanTests: XCTestCase { "/barPkg/Sources/BarLogging/file.swift" ) let observability = ObservabilitySystem.makeForTesting() + let fooPkg: AbsolutePath = "/fooPkg" + let barPkg: AbsolutePath = "/barPkg" XCTAssertThrowsError(try loadPackageGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( displayName: "fooPkg", - path: "/fooPkg", + path: fooPkg, toolsVersion: .v5_8, products: [ ProductDescription(name: "Logging", type: .library(.automatic), targets: ["FooLogging"]), @@ -434,7 +434,7 @@ final class BuildPlanTests: XCTestCase { ]), Manifest.createFileSystemManifest( displayName: "barPkg", - path: "/barPkg", + path: barPkg, products: [ ProductDescription(name: "Logging", type: .library(.automatic), targets: ["BarLogging"]), ], @@ -450,19 +450,16 @@ final class BuildPlanTests: XCTestCase { ], targets: [ TargetDescription(name: "exe", - dependencies: [.product(name: "Logging", - package: "fooPkg" - ), - .product(name: "Logging", - package: "barPkg" - ), + dependencies: [.product(name: "Logging", package: "fooPkg"), + .product(name: "Logging", package: "barPkg"), ], type: .executable), ]), ], observabilityScope: observability.topScope )) { error in - XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Logging' in: 'barpkg' (at '/barPkg'), 'foopkg' (at '/fooPkg')") + XCTAssertEqual((error as? PackageGraphError)?.description, + "multiple products named 'Logging' in: 'barpkg' (at '\(barPkg)'), 'foopkg' (at '\(fooPkg)')") } } @@ -530,9 +527,10 @@ final class BuildPlanTests: XCTestCase { } func testPackageNameFlag() throws { - let isFlagSupportedInDriver = try driverSupport.checkToolchainDriverFlags(flags: ["package-name"], toolchain: UserToolchain.default, fileSystem: localFileSystem) + try XCTSkipIfCI() // test is disabled because it isn't stable, see rdar://118239206 + let isFlagSupportedInDriver = try DriverSupport.checkToolchainDriverFlags(flags: ["package-name"], toolchain: UserToolchain.default, fileSystem: localFileSystem) try fixture(name: "Miscellaneous/PackageNameFlag") { fixturePath in - let (stdout, _) = try executeSwiftBuild(fixturePath.appending("appPkg"), extraArgs: ["-v"]) + let (stdout, _) = try executeSwiftBuild(fixturePath.appending("appPkg"), extraArgs: ["-vv"]) XCTAssertMatch(stdout, .contains("-module-name Foo")) XCTAssertMatch(stdout, .contains("-module-name Zoo")) XCTAssertMatch(stdout, .contains("-module-name Bar")) @@ -552,7 +550,7 @@ final class BuildPlanTests: XCTestCase { } func testTargetsWithPackageAccess() throws { - let isFlagSupportedInDriver = try driverSupport.checkToolchainDriverFlags(flags: ["package-name"], toolchain: UserToolchain.default, fileSystem: localFileSystem) + let isFlagSupportedInDriver = try DriverSupport.checkToolchainDriverFlags(flags: ["package-name"], toolchain: UserToolchain.default, fileSystem: localFileSystem) try fixture(name: "Miscellaneous/TargetPackageAccess") { fixturePath in let (stdout, _) = try executeSwiftBuild(fixturePath.appending("libPkg"), extraArgs: ["-v"]) if isFlagSupportedInDriver { @@ -869,8 +867,9 @@ final class BuildPlanTests: XCTestCase { let llbuild = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: observability.topScope) try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) + let swiftGetVersionFilePath = try XCTUnwrap(llbuild.swiftGetVersionFiles.first?.value) XCTAssertMatch(contents, .contains(""" - inputs: ["\(Pkg.appending(components: "Sources", "exe", "main.swift").escapedPathString())","\(buildPath.appending(components: "PkgLib.swiftmodule").escapedPathString())","\(buildPath.appending(components: "exe.build", "sources").escapedPathString())"] + inputs: ["\(Pkg.appending(components: "Sources", "exe", "main.swift").escapedPathString)","\(swiftGetVersionFilePath.escapedPathString)","\(buildPath.appending(components: "PkgLib.swiftmodule").escapedPathString)","\(buildPath.appending(components: "exe.build", "sources").escapedPathString)"] """)) } @@ -898,8 +897,9 @@ final class BuildPlanTests: XCTestCase { try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) let buildPath = plan.buildParameters.dataPath.appending(components: "debug") + let swiftGetVersionFilePath = try XCTUnwrap(llbuild.swiftGetVersionFiles.first?.value) XCTAssertMatch(contents, .contains(""" - inputs: ["\(Pkg.appending(components: "Sources", "exe", "main.swift").escapedPathString())","\(buildPath.appending(components: "exe.build", "sources").escapedPathString())"] + inputs: ["\(Pkg.appending(components: "Sources", "exe", "main.swift").escapedPathString)","\(swiftGetVersionFilePath.escapedPathString)","\(buildPath.appending(components: "exe.build", "sources").escapedPathString)"] """)) } } @@ -1184,6 +1184,10 @@ final class BuildPlanTests: XCTestCase { #endif args += [hostTriple.isWindows() ? "-gdwarf" : "-g"] + + if hostTriple.isLinux() { + args += ["-fno-omit-frame-pointer"] + } XCTAssertEqual(try ext.basicArguments(isCXX: false), args) XCTAssertEqual(try ext.objects, [buildPath.appending(components: "extlib.build", "extlib.c.o")]) @@ -1214,6 +1218,11 @@ final class BuildPlanTests: XCTestCase { args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] #endif args += [hostTriple.isWindows() ? "-gdwarf" : "-g"] + + if hostTriple.isLinux() { + args += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try exe.basicArguments(isCXX: false), args) XCTAssertEqual(try exe.objects, [buildPath.appending(components: "exe.build", "main.c.o")]) XCTAssertEqual(exe.moduleMap, nil) @@ -1446,8 +1455,8 @@ final class BuildPlanTests: XCTestCase { let llbuild = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: observability.topScope) try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) - XCTAssertMatch(contents, .contains(#"-std=gnu99","-c","\#(Pkg.appending(components: "Sources", "lib", "lib.c").escapedPathString())"#)) - XCTAssertMatch(contents, .contains(#"-std=c++1z","-c","\#(Pkg.appending(components: "Sources", "lib", "libx.cpp").escapedPathString())"#)) + XCTAssertMatch(contents, .contains(#"-std=gnu99","-c","\#(Pkg.appending(components: "Sources", "lib", "lib.c").escapedPathString)"#)) + XCTAssertMatch(contents, .contains(#"-std=c++1z","-c","\#(Pkg.appending(components: "Sources", "lib", "libx.cpp").escapedPathString)"#)) let swiftInteropLib = try result.target(for: "swiftInteropLib").swiftTarget().compileArguments() XCTAssertMatch(swiftInteropLib, [.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++1z", .anySequence]) @@ -1803,6 +1812,11 @@ final class BuildPlanTests: XCTestCase { args += ["-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))"] #endif args += [hostTriple.isWindows() ? "-gdwarf" : "-g"] + + if hostTriple.isLinux() { + args += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) @@ -2000,6 +2014,12 @@ final class BuildPlanTests: XCTestCase { #if os(macOS) let version = MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString + let rpathsForBackdeployment: [String] + if let version = try? Version(string: version, lenient: true), version.major < 12 { + rpathsForBackdeployment = ["-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx"] + } else { + rpathsForBackdeployment = [] + } XCTAssertEqual(try result.buildProduct(for: "PkgPackageTests").linkArguments(), [ result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, @@ -2007,9 +2027,9 @@ final class BuildPlanTests: XCTestCase { "-module-name", "PkgPackageTests", "-Xlinker", "-bundle", "-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../../", - "@\(buildPath.appending(components: "PkgPackageTests.product", "Objects.LinkFileList"))", - "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", - "-target", "\(hostTriple.tripleString(forPlatformVersion: version))", + "@\(buildPath.appending(components: "PkgPackageTests.product", "Objects.LinkFileList"))"] + + rpathsForBackdeployment + + ["-target", "\(hostTriple.tripleString(forPlatformVersion: version))", "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "Foo.swiftmodule").pathString, "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "FooTests.swiftmodule").pathString, "-g", @@ -3004,6 +3024,11 @@ final class BuildPlanTests: XCTestCase { #endif expectedExeBasicArgs += [triple.isWindows() ? "-gdwarf" : "-g"] + + if triple.isLinux() { + expectedExeBasicArgs += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try exe.basicArguments(isCXX: false), expectedExeBasicArgs) XCTAssertEqual(try exe.objects, [buildPath.appending(components: "exe.build", "main.c.o")]) XCTAssertEqual(exe.moduleMap, nil) @@ -3025,6 +3050,11 @@ final class BuildPlanTests: XCTestCase { triple.isWindows() ? "-gdwarf" : "-g", triple.isWindows() ? "-gdwarf" : "-g", ] + + if triple.isLinux() { + expectedLibBasicArgs += ["-fno-omit-frame-pointer"] + } + XCTAssertEqual(try lib.basicArguments(isCXX: true), expectedLibBasicArgs) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.cpp.o")]) @@ -3535,7 +3565,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) var parameters = mockBuildParameters(targetTriple: .wasi) - parameters.shouldLinkStaticSwiftStdlib = true + parameters.linkingParameters.shouldLinkStaticSwiftStdlib = true let result = try BuildPlanResult(plan: BuildPlan( buildParameters: parameters, graph: graph, @@ -3554,7 +3584,7 @@ final class BuildPlanTests: XCTestCase { "-fblocks", "-fmodules", "-fmodule-name=lib", "-I", Pkg.appending(components: "Sources", "lib", "include").pathString, "-fmodules-cache-path=\(buildPath.appending(components: "ModuleCache"))", - "-g", + "-g" ] XCTAssertEqual(try lib.basicArguments(isCXX: false), args) XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) @@ -3631,7 +3661,10 @@ final class BuildPlanTests: XCTestCase { func createResult(for triple: Basics.Triple) throws -> BuildPlanResult { try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(canRenameEntrypointFunctionName: true, targetTriple: triple), + buildParameters: mockBuildParameters( + canRenameEntrypointFunctionName: true, + targetTriple: triple + ), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -3766,6 +3799,75 @@ final class BuildPlanTests: XCTestCase { #endif } + func testPlatformsCustomTriple() throws { + let fileSystem = InMemoryFileSystem(emptyFiles: + "/A/Sources/ATarget/foo.swift", + "/B/Sources/BTarget/foo.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadPackageGraph( + fileSystem: fileSystem, + manifests: [ + Manifest.createRootManifest( + displayName: "A", + path: "/A", + platforms: [ + PlatformDescription(name: "ios", version: "11.0"), + PlatformDescription(name: "macos", version: "10.13"), + ], + toolsVersion: .v5, + dependencies: [ + .localSourceControl(path: "/B", requirement: .upToNextMajor(from: "1.0.0")), + ], + targets: [ + TargetDescription(name: "ATarget", dependencies: ["BLibrary"]), + ]), + Manifest.createFileSystemManifest( + displayName: "B", + path: "/B", + platforms: [ + PlatformDescription(name: "ios", version: "10.0"), + PlatformDescription(name: "macos", version: "10.12"), + ], + toolsVersion: .v5, + products: [ + ProductDescription(name: "BLibrary", type: .library(.automatic), targets: ["BTarget"]), + ], + targets: [ + TargetDescription(name: "BTarget", dependencies: []), + ]), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(targetTriple: .init("arm64-apple-ios")), + graph: graph, + fileSystem: fileSystem, + observabilityScope: observability.topScope + )) + + let targetTriple = try Triple("arm64-apple-ios") + + let aTarget = try result.target(for: "ATarget").swiftTarget().compileArguments() + let expectedVersion = Platform.iOS.oldestSupportedVersion.versionString + + XCTAssertMatch(aTarget, [ + .equal("-target"), + .equal(targetTriple.tripleString(forPlatformVersion: expectedVersion)), + .anySequence + ]) + + let bTarget = try result.target(for: "BTarget").swiftTarget().compileArguments() + XCTAssertMatch(bTarget, [ + .equal("-target"), + .equal(targetTriple.tripleString(forPlatformVersion: expectedVersion)), + .anySequence + ]) + } + func testPlatformsValidation() throws { let fileSystem = InMemoryFileSystem(emptyFiles: "/A/Sources/ATarget/foo.swift", @@ -3937,18 +4039,68 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(dep, [.anySequence, "-DDEP", .anySequence]) let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) - XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-g", .end]) + XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-g", "-fno-omit-frame-pointer", .end]) let bar = try result.target(for: "bar").swiftTarget().compileArguments() - XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-g", "-Xcc", "-g", .end]) + XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", .end]) + XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) let linkExe = try result.buildProduct(for: "exe").linkArguments() XCTAssertMatch(linkExe, [.anySequence, "-lsqlite3", "-llibz", "-Ilfoo", "-L", "lbar", "-g", .end]) } + // omit frame pointers explicitly set to true + do { + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters( + targetTriple: .x86_64Linux, + omitFramePointers: true + ), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + let dep = try result.target(for: "t1").swiftTarget().compileArguments() + XCTAssertMatch(dep, [.anySequence, "-DDEP", .anySequence]) + + let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) + XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-g", "-fomit-frame-pointer", .end]) + + let bar = try result.target(for: "bar").swiftTarget().compileArguments() + XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-g", "-Xcc", "-g", "-Xcc", "-fomit-frame-pointer", .end]) + + let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fomit-frame-pointer", .end]) + } + + // omit frame pointers explicitly set to false + do { + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters( + targetTriple: .x86_64Linux, + omitFramePointers: false + ), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + let dep = try result.target(for: "t1").swiftTarget().compileArguments() + XCTAssertMatch(dep, [.anySequence, "-DDEP", .anySequence]) + + let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) + XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", "-g", "-fno-omit-frame-pointer", .end]) + + let bar = try result.target(for: "bar").swiftTarget().compileArguments() + XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=default", "-enable-upcoming-feature", "BestFeature", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) + + let exe = try result.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) + } + do { let result = try createResult(for: .x86_64MacOS) @@ -3967,11 +4119,8 @@ final class BuildPlanTests: XCTestCase { } func testExtraBuildFlags() throws { - let libpath = AbsolutePath("/fake/path/lib") - let fs = InMemoryFileSystem(emptyFiles: "/A/Sources/exe/main.swift", - libpath.appending(components: "libSomething.dylib").pathString, "" ) @@ -4002,7 +4151,7 @@ final class BuildPlanTests: XCTestCase { )) let exe = try result.buildProduct(for: "exe").linkArguments() - XCTAssertMatch(exe, [.anySequence, "-L", "/path/to/foo", "-L/path/to/foo", "-Xlinker", "-rpath=foo", "-Xlinker", "-rpath", "-Xlinker", "foo", "-L", "\(libpath)"]) + XCTAssertMatch(exe, [.anySequence, "-L", "/path/to/foo", "-L/path/to/foo", "-Xlinker", "-rpath=foo", "-Xlinker", "-rpath", "-Xlinker", "foo"]) } func testUserToolchainCompileFlags() throws { @@ -4019,6 +4168,9 @@ final class BuildPlanTests: XCTestCase { Manifest.createRootManifest( displayName: "Pkg", path: "/Pkg", + products: [ + ProductDescription(name: "exe", type: .executable, targets: ["exe"]), + ], targets: [ TargetDescription(name: "exe", dependencies: ["lib"]), TargetDescription(name: "lib", dependencies: []), @@ -4032,11 +4184,15 @@ final class BuildPlanTests: XCTestCase { toolset: .init( knownTools: [ .cCompiler: .init(extraCLIOptions: ["-I/fake/sdk/sysroot", "-clang-flag-from-json"]), - .swiftCompiler: .init(extraCLIOptions: ["-swift-flag-from-json"]) + .swiftCompiler: .init(extraCLIOptions: ["-use-ld=lld", "-swift-flag-from-json"]) ], rootPaths: try UserToolchain.default.swiftSDK.toolset.rootPaths ), - pathsConfiguration: .init(sdkRootPath: "/fake/sdk") + pathsConfiguration: .init( + sdkRootPath: "/fake/sdk", + swiftResourcesPath: "/fake/lib/swift", + swiftStaticResourcesPath: "/fake/lib/swift_static" + ) ) let mockToolchain = try UserToolchain(swiftSDK: userSwiftSDK) let extraBuildParameters = mockBuildParameters(toolchain: mockToolchain, @@ -4063,7 +4219,57 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(try lib.basicArguments(isCXX: false), args) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, ["-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-flag-from-json", "-g", "-swift-command-line-flag", .anySequence, "-Xcc", "-clang-flag-from-json", "-Xcc", "-g", "-Xcc", "-clang-command-line-flag"]) + XCTAssertMatch(exe, [ + "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", + .anySequence, + "-resource-dir", "\(AbsolutePath("/fake/lib/swift"))", + .anySequence, + "-swift-flag-from-json", + .anySequence, + "-swift-command-line-flag", + .anySequence, + "-Xcc", "-clang-flag-from-json", + .anySequence, + "-Xcc", "-clang-command-line-flag" + ]) + + let exeProduct = try result.buildProduct(for: "exe").linkArguments() + XCTAssertMatch(exeProduct, [ + .anySequence, + "-resource-dir", "\(AbsolutePath("/fake/lib/swift"))", + "-Xclang-linker", "-resource-dir", + "-Xclang-linker", "\(AbsolutePath("/fake/lib/swift/clang"))", + .anySequence + ]) + + let staticBuildParameters = { + var copy = extraBuildParameters + copy.linkingParameters.shouldLinkStaticSwiftStdlib = true + // pick a triple with support for static linking + copy.targetTriple = .x86_64Linux + return copy + }() + let staticResult = try BuildPlanResult(plan: BuildPlan( + buildParameters: staticBuildParameters, + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + let staticExe = try staticResult.target(for: "exe").swiftTarget().compileArguments() + XCTAssertMatch(staticExe, [ + .anySequence, + "-resource-dir", "\(AbsolutePath("/fake/lib/swift_static"))", + .anySequence]) + + let staticExeProduct = try staticResult.buildProduct(for: "exe").linkArguments() + XCTAssertMatch(staticExeProduct, [ + .anySequence, + "-resource-dir", "\(AbsolutePath("/fake/lib/swift_static"))", + "-Xclang-linker", "-resource-dir", + "-Xclang-linker", "\(AbsolutePath("/fake/lib/swift/clang"))", + .anySequence + ]) } func testUserToolchainWithToolsetCompileFlags() throws { @@ -4103,7 +4309,7 @@ final class BuildPlanTests: XCTestCase { .cxxCompiler: .init(extraCLIOptions: [jsonFlag(tool: .cxxCompiler)]), .swiftCompiler: .init(extraCLIOptions: [jsonFlag(tool: .swiftCompiler)]), .librarian: .init(path: "/fake/toolchain/usr/bin/librarian"), - .linker: .init(extraCLIOptions: [jsonFlag(tool: .linker)]), + .linker: .init(path: "/fake/toolchain/usr/bin/linker", extraCLIOptions: [jsonFlag(tool: .linker)]), ], rootPaths: try UserToolchain.default.swiftSDK.toolset.rootPaths) let targetTriple = try Triple("armv7em-unknown-none-macho") @@ -4184,7 +4390,9 @@ final class BuildPlanTests: XCTestCase { // Compile Swift Target let exeCompileArguments = try result.target(for: "exe").swiftTarget().compileArguments() let exeCompileArgumentsPattern: [StringPattern] = [ - jsonFlag(tool: .swiftCompiler), "-g", cliFlag(tool: .swiftCompiler), + jsonFlag(tool: .swiftCompiler), + "-ld-path=/fake/toolchain/usr/bin/linker", + "-g", cliFlag(tool: .swiftCompiler), .anySequence, "-Xcc", jsonFlag(tool: .cCompiler), "-Xcc", "-g", "-Xcc", cliFlag(tool: .cCompiler), // TODO: Pass -Xcxx flags to swiftc (#6491) @@ -4207,7 +4415,9 @@ final class BuildPlanTests: XCTestCase { // Link Product let exeLinkArguments = try result.buildProduct(for: "exe").linkArguments() let exeLinkArgumentsPattern: [StringPattern] = [ - jsonFlag(tool: .swiftCompiler), "-g", cliFlag(tool: .swiftCompiler), + jsonFlag(tool: .swiftCompiler), + "-ld-path=/fake/toolchain/usr/bin/linker", + "-g", cliFlag(tool: .swiftCompiler), .anySequence, "-Xlinker", jsonFlag(tool: .linker), "-Xlinker", cliFlag(tool: .linker), ] @@ -4246,14 +4456,17 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let targetTriple = try UserToolchain.default.targetTriple - let sdkIncludeSearchPath = "/usr/lib/swift_static/none/include" - let sdkLibrarySearchPath = "/usr/lib/swift_static/none/lib" + let sdkIncludeSearchPath = AbsolutePath("/usr/lib/swift_static/none/include") + let sdkLibrarySearchPath = AbsolutePath("/usr/lib/swift_static/none/lib") let swiftSDK = try SwiftSDK( targetTriple: targetTriple, properties: .init( sdkRootPath: "/fake/sdk", - includeSearchPaths: [sdkIncludeSearchPath], - librarySearchPaths: [sdkLibrarySearchPath])) + includeSearchPaths: [sdkIncludeSearchPath.pathString], + librarySearchPaths: [sdkLibrarySearchPath.pathString]), + toolset: .init(knownTools: [ + .swiftCompiler: .init(extraCLIOptions: ["-use-ld=lld"]), + ])) let toolchain = try UserToolchain(swiftSDK: swiftSDK) let buildParameters = mockBuildParameters(toolchain: toolchain) let result = try BuildPlanResult(plan: BuildPlan( @@ -4333,6 +4546,7 @@ final class BuildPlanTests: XCTestCase { let llbuild = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: observability.topScope) try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) + let swiftGetVersionFilePath = try XCTUnwrap(llbuild.swiftGetVersionFiles.first?.value) #if os(Windows) let suffix = ".exe" @@ -4340,8 +4554,8 @@ final class BuildPlanTests: XCTestCase { let suffix = "" #endif XCTAssertMatch(contents, .contains(""" - inputs: ["\(PkgA.appending(components: "Sources", "swiftlib", "lib.swift").escapedPathString())","\(buildPath.appending(components: "exe\(suffix)").escapedPathString())","\(buildPath.appending(components: "swiftlib.build", "sources").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "swiftlib.build", "lib.swift.o").escapedPathString())","\(buildPath.escapedPathString()) + inputs: ["\(PkgA.appending(components: "Sources", "swiftlib", "lib.swift").escapedPathString)","\(swiftGetVersionFilePath.escapedPathString)","\(buildPath.appending(components: "exe\(suffix)").escapedPathString)","\(buildPath.appending(components: "swiftlib.build", "sources").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "swiftlib.build", "lib.swift.o").escapedPathString)","\(buildPath.escapedPathString) """)) } @@ -4392,10 +4606,10 @@ final class BuildPlanTests: XCTestCase { try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) XCTAssertMatch(contents, .contains(""" - "\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString())": + "\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString)": tool: clang - inputs: ["\(buildPath.appending(components: "Foo.swiftmodule").escapedPathString())","\(PkgA.appending(components: "Sources", "Bar", "main.m").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "Foo.swiftmodule").escapedPathString)","\(PkgA.appending(components: "Sources", "Bar", "main.m").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString)"] description: "Compiling Bar main.m" """)) } @@ -4458,10 +4672,10 @@ final class BuildPlanTests: XCTestCase { try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) XCTAssertMatch(contents, .contains(""" - "\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString())": + "\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString)": tool: clang - inputs: ["\(buildPath.appending(components: "Foo.swiftmodule").escapedPathString())","\(PkgA.appending(components: "Sources", "Bar", "main.m").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "Foo.swiftmodule").escapedPathString)","\(PkgA.appending(components: "Sources", "Bar", "main.m").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString)"] description: "Compiling Bar main.m" """)) } @@ -4530,10 +4744,10 @@ final class BuildPlanTests: XCTestCase { try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) XCTAssertMatch(contents, .contains(""" - "\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString())": + "\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString)": tool: clang - inputs: ["\(buildPath.appending(components: "\(dynamicLibraryPrefix)Foo\(dynamicLibraryExtension)").escapedPathString())","\(PkgA.appending(components: "Sources", "Bar", "main.m").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "\(dynamicLibraryPrefix)Foo\(dynamicLibraryExtension)").escapedPathString)","\(PkgA.appending(components: "Sources", "Bar", "main.m").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "Bar.build", "main.m.o").escapedPathString)"] description: "Compiling Bar main.m" """)) } @@ -4580,20 +4794,20 @@ final class BuildPlanTests: XCTestCase { try llbuild.generateManifest(at: yaml) let contents: String = try fs.readFileContents(yaml) XCTAssertMatch(contents, .contains(""" - "\(buildPath.appending(components: "exe.build", "exe.swiftmodule.o").escapedPathString())": + "\(buildPath.appending(components: "exe.build", "exe.swiftmodule.o").escapedPathString)": tool: shell - inputs: ["\(buildPath.appending(components: "exe.build", "exe.swiftmodule").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "exe.build", "exe.swiftmodule.o").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "exe.build", "exe.swiftmodule").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "exe.build", "exe.swiftmodule.o").escapedPathString)"] description: "Wrapping AST for exe for debugging" - args: ["\(result.plan.buildParameters.toolchain.swiftCompilerPath.escapedPathString())","-modulewrap","\(buildPath.appending(components: "exe.build", "exe.swiftmodule").escapedPathString())","-o","\(buildPath.appending(components: "exe.build", "exe.swiftmodule.o").escapedPathString())","-target","x86_64-unknown-linux-gnu"] + args: ["\(result.plan.buildParameters.toolchain.swiftCompilerPath.escapedPathString)","-modulewrap","\(buildPath.appending(components: "exe.build", "exe.swiftmodule").escapedPathString)","-o","\(buildPath.appending(components: "exe.build", "exe.swiftmodule.o").escapedPathString)","-target","x86_64-unknown-linux-gnu"] """)) XCTAssertMatch(contents, .contains(""" - "\(buildPath.appending(components: "lib.build", "lib.swiftmodule.o").escapedPathString())": + "\(buildPath.appending(components: "lib.build", "lib.swiftmodule.o").escapedPathString)": tool: shell - inputs: ["\(buildPath.appending(components: "lib.swiftmodule").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "lib.build", "lib.swiftmodule.o").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "lib.swiftmodule").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "lib.build", "lib.swiftmodule.o").escapedPathString)"] description: "Wrapping AST for lib for debugging" - args: ["\(result.plan.buildParameters.toolchain.swiftCompilerPath.escapedPathString())","-modulewrap","\(buildPath.appending(components: "lib.swiftmodule").escapedPathString())","-o","\(buildPath.appending(components: "lib.build", "lib.swiftmodule.o").escapedPathString())","-target","x86_64-unknown-linux-gnu"] + args: ["\(result.plan.buildParameters.toolchain.swiftCompilerPath.escapedPathString)","-modulewrap","\(buildPath.appending(components: "lib.swiftmodule").escapedPathString)","-o","\(buildPath.appending(components: "lib.build", "lib.swiftmodule.o").escapedPathString)","-target","x86_64-unknown-linux-gnu"] """)) } @@ -4642,28 +4856,28 @@ final class BuildPlanTests: XCTestCase { XCTAssertMatch(contents, .contains(""" "C.rary-debug.a": tool: shell - inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString())","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "library.a").escapedPathString())"] - description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString())" - args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString())","/LIB","/OUT:\(buildPath.appending(components: "library.a").escapedPathString())","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString)","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString)","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "library.a").escapedPathString)"] + description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString)" + args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString)","/LIB","/OUT:\(buildPath.appending(components: "library.a").escapedPathString)","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString)"] """)) } else if result.plan.buildParameters.targetTriple.isDarwin() { XCTAssertMatch(contents, .contains(""" "C.rary-debug.a": tool: shell - inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "library.a").escapedPathString())"] - description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString())" - args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString())","-static","-o","\(buildPath.appending(components: "library.a").escapedPathString())","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString)","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "library.a").escapedPathString)"] + description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString)" + args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString)","-static","-o","\(buildPath.appending(components: "library.a").escapedPathString)","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString)"] """)) - } else { // assume Unix `ar` is the librarian + } else { // assume `llvm-ar` is the librarian XCTAssertMatch(contents, .contains(""" "C.rary-debug.a": tool: shell - inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString())","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString())","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"] - outputs: ["\(buildPath.appending(components: "library.a").escapedPathString())"] - description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString())" - args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString())","crs","\(buildPath.appending(components: "library.a").escapedPathString())","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString())"] + inputs: ["\(buildPath.appending(components: "rary.build", "rary.swift.o").escapedPathString)","\(buildPath.appending(components: "rary.build", "rary.swiftmodule.o").escapedPathString)","\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString)"] + outputs: ["\(buildPath.appending(components: "library.a").escapedPathString)"] + description: "Archiving \(buildPath.appending(components: "library.a").escapedPathString)" + args: ["\(result.plan.buildParameters.toolchain.librarianPath.escapedPathString)","crs","\(buildPath.appending(components: "library.a").escapedPathString)","@\(buildPath.appending(components: "rary.product", "Objects.LinkFileList").escapedPathString)"] """)) } } @@ -4915,7 +5129,7 @@ final class BuildPlanTests: XCTestCase { } } - func testXCFrameworkBinaryTargets(platform: String, arch: String, destinationTriple: Basics.Triple) throws { + func testXCFrameworkBinaryTargets(platform: String, arch: String, targetTriple: Basics.Triple) throws { let Pkg: AbsolutePath = "/Pkg" let fs = InMemoryFileSystem(emptyFiles: @@ -5024,7 +5238,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(targetTriple: destinationTriple), + buildParameters: mockBuildParameters(targetTriple: targetTriple), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5070,16 +5284,19 @@ final class BuildPlanTests: XCTestCase { } func testXCFrameworkBinaryTargets() throws { - try testXCFrameworkBinaryTargets(platform: "macos", arch: "x86_64", destinationTriple: .x86_64MacOS) + try testXCFrameworkBinaryTargets(platform: "macos", arch: "x86_64", targetTriple: .x86_64MacOS) let arm64Triple = try Basics.Triple("arm64-apple-macosx") - try testXCFrameworkBinaryTargets(platform: "macos", arch: "arm64", destinationTriple: arm64Triple) + try testXCFrameworkBinaryTargets(platform: "macos", arch: "arm64", targetTriple: arm64Triple) let arm64eTriple = try Basics.Triple("arm64e-apple-macosx") - try testXCFrameworkBinaryTargets(platform: "macos", arch: "arm64e", destinationTriple: arm64eTriple) + try testXCFrameworkBinaryTargets(platform: "macos", arch: "arm64e", targetTriple: arm64eTriple) } - func testArtifactsArchiveBinaryTargets(artifactTriples:[Basics.Triple], destinationTriple: Basics.Triple) throws -> Bool { + func testArtifactsArchiveBinaryTargets( + artifactTriples: [Basics.Triple], + targetTriple: Basics.Triple + ) throws -> Bool { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift") let artifactName = "my-tool" @@ -5133,7 +5350,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(targetTriple: destinationTriple), + buildParameters: mockBuildParameters(targetTriple: targetTriple), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5148,16 +5365,16 @@ final class BuildPlanTests: XCTestCase { } func testArtifactsArchiveBinaryTargets() throws { - XCTAssertTrue(try testArtifactsArchiveBinaryTargets(artifactTriples: [.x86_64MacOS], destinationTriple: .x86_64MacOS)) + XCTAssertTrue(try testArtifactsArchiveBinaryTargets(artifactTriples: [.x86_64MacOS], targetTriple: .x86_64MacOS)) do { let triples = try ["arm64-apple-macosx", "x86_64-apple-macosx", "x86_64-unknown-linux-gnu"].map(Basics.Triple.init) - XCTAssertTrue(try testArtifactsArchiveBinaryTargets(artifactTriples: triples, destinationTriple: triples.first!)) + XCTAssertTrue(try testArtifactsArchiveBinaryTargets(artifactTriples: triples, targetTriple: triples.first!)) } do { let triples = try ["x86_64-unknown-linux-gnu"].map(Basics.Triple.init) - XCTAssertFalse(try testArtifactsArchiveBinaryTargets(artifactTriples: triples, destinationTriple: .x86_64MacOS)) + XCTAssertFalse(try testArtifactsArchiveBinaryTargets(artifactTriples: triples, targetTriple: .x86_64MacOS)) } } @@ -5219,12 +5436,13 @@ final class BuildPlanTests: XCTestCase { let yaml = buildPath.appending("release.yaml") let llbuild = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: observability.topScope) try llbuild.generateManifest(at: yaml) + let swiftGetVersionFilePath = try XCTUnwrap(llbuild.swiftGetVersionFiles.first?.value) let yamlContents: String = try fs.readFileContents(yaml) - XCTAssertMatch(yamlContents, .contains(""" - inputs: ["/Pkg/Snippets/ASnippet.swift","/Pkg/.build/debug/Lib.swiftmodule" - """)) - + let inputs: SerializedJSON = """ + inputs: ["\(AbsolutePath("/Pkg/Snippets/ASnippet.swift"))","\(swiftGetVersionFilePath)","\(AbsolutePath("/Pkg/.build/debug/Lib.swiftmodule"))" + """ + XCTAssertMatch(yamlContents, .contains(inputs.underlying)) } private func sanitizerTest(_ sanitizer: PackageModel.Sanitizer, expectedName: String) throws { @@ -5392,4 +5610,80 @@ final class BuildPlanTests: XCTestCase { XCTFail("expected a Swift target") } } + + func testBasicSwiftPackageWithoutLocalRpath() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Pkg/Sources/exe/main.swift", + "/Pkg/Sources/lib/lib.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [ + TargetDescription(name: "exe", dependencies: ["lib"]), + TargetDescription(name: "lib", dependencies: []), + ]), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(shouldDisableLocalRpath: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + result.checkProductsCount(1) + result.checkTargetsCount(2) + + let buildPath = result.plan.buildParameters.dataPath.appending(components: "debug") + + #if os(macOS) + let linkArguments = [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", + "-emit-executable", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", + "-target", defaultTargetTriple, + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "exe.build", "exe.swiftmodule").pathString, + "-Xlinker", "-add_ast_path", "-Xlinker", buildPath.appending(components: "lib.swiftmodule").pathString, + "-g", + ] + #elseif os(Windows) + let linkArguments = [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "exe.exe").pathString, + "-module-name", "exe", + "-emit-executable", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-target", defaultTargetTriple, + "-g", "-use-ld=lld", "-Xlinker", "-debug:dwarf", + ] + #else + let linkArguments = [ + result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "exe").pathString, + "-module-name", "exe", + "-emit-executable", + "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", + "-target", defaultTargetTriple, + "-g" + ] + #endif + + XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), linkArguments) + XCTAssertNoDiagnostics(observability.diagnostics) + } } diff --git a/Tests/BuildTests/IncrementalBuildTests.swift b/Tests/BuildTests/IncrementalBuildTests.swift index 3f20d6280c4..c8e966d8b0f 100644 --- a/Tests/BuildTests/IncrementalBuildTests.swift +++ b/Tests/BuildTests/IncrementalBuildTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import PackageModel import SPMTestSupport import XCTest @@ -36,6 +37,7 @@ import XCTest final class IncrementalBuildTests: XCTestCase { func testIncrementalSingleModuleCLibraryInSources() throws { + try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") try fixture(name: "CFamilyTargets/CLibrarySources") { fixturePath in // Build it once and capture the log (this will be a full build). let (fullLog, _) = try executeSwiftBuild(fixturePath) @@ -93,6 +95,7 @@ final class IncrementalBuildTests: XCTestCase { } func testBuildManifestCaching() throws { + try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") try fixture(name: "ValidLayouts/SingleModule/Library") { fixturePath in @discardableResult func build() throws -> String { @@ -126,6 +129,7 @@ final class IncrementalBuildTests: XCTestCase { } func testDisableBuildManifestCaching() throws { + try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") try fixture(name: "ValidLayouts/SingleModule/Library") { fixturePath in @discardableResult func build() throws -> String { diff --git a/Tests/BuildTests/LLBuildManifestBuilderTests.swift b/Tests/BuildTests/LLBuildManifestBuilderTests.swift new file mode 100644 index 00000000000..5f8b4a3279c --- /dev/null +++ b/Tests/BuildTests/LLBuildManifestBuilderTests.swift @@ -0,0 +1,181 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 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 Basics +@testable import Build +import LLBuildManifest +import PackageGraph +import PackageModel +import struct SPMBuildCore.BuildParameters +import SPMTestSupport +import class TSCBasic.InMemoryFileSystem +import XCTest + +final class LLBuildManifestBuilderTests: XCTestCase { + func testCreateProductCommand() throws { + let pkg = AbsolutePath("/pkg") + let fs = InMemoryFileSystem( + emptyFiles: + pkg.appending(components: "Sources", "exe", "main.swift").pathString + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: .init(validating: pkg.pathString), + targets: [ + TargetDescription(name: "exe"), + ] + ), + ], + observabilityScope: observability.topScope + ) + + // macOS, release build + + var buildParameters = mockBuildParameters(environment: BuildEnvironment( + platform: .macOS, + configuration: .release + )) + var plan = try BuildPlan( + buildParameters: buildParameters, + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + + var result = try BuildPlanResult(plan: plan) + var buildProduct = try result.buildProduct(for: "exe") + + var llbuild = LLBuildManifestBuilder( + plan, + fileSystem: localFileSystem, + observabilityScope: observability.topScope + ) + try llbuild.createProductCommand(buildProduct) + + let basicReleaseCommandNames = [ + AbsolutePath("/path/to/build/release/exe.product/Objects.LinkFileList").pathString, + "", + "C.exe-release.exe", + ] + + XCTAssertEqual( + llbuild.manifest.commands.map(\.key).sorted(), + basicReleaseCommandNames.sorted() + ) + + // macOS, debug build + + buildParameters = mockBuildParameters(environment: BuildEnvironment( + platform: .macOS, + configuration: .debug + )) + plan = try BuildPlan( + buildParameters: buildParameters, + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + + result = try BuildPlanResult(plan: plan) + buildProduct = try result.buildProduct(for: "exe") + + llbuild = LLBuildManifestBuilder(plan, fileSystem: localFileSystem, observabilityScope: observability.topScope) + try llbuild.createProductCommand(buildProduct) + + let entitlementsCommandName = "C.exe-debug.exe-entitlements" + let basicDebugCommandNames = [ + AbsolutePath("/path/to/build/debug/exe.product/Objects.LinkFileList").pathString, + "", + "C.exe-debug.exe", + ] + + XCTAssertEqual( + llbuild.manifest.commands.map(\.key).sorted(), + (basicDebugCommandNames + [ + AbsolutePath("/path/to/build/debug/exe-entitlement.plist").pathString, + entitlementsCommandName, + ]).sorted() + ) + + guard let entitlementsCommand = llbuild.manifest.commands[entitlementsCommandName]?.tool as? ShellTool else { + XCTFail("unexpected entitlements command type") + return + } + + XCTAssertEqual( + entitlementsCommand.inputs, + [ + .file("/path/to/build/debug/exe", isMutated: true), + .file("/path/to/build/debug/exe-entitlement.plist"), + ] + ) + XCTAssertEqual( + entitlementsCommand.outputs, + [ + .virtual("exe-debug.exe-CodeSigning"), + ] + ) + + // Linux, release build + + buildParameters = mockBuildParameters(environment: BuildEnvironment( + platform: .linux, + configuration: .release + )) + plan = try BuildPlan( + buildParameters: buildParameters, + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + + result = try BuildPlanResult(plan: plan) + buildProduct = try result.buildProduct(for: "exe") + + llbuild = LLBuildManifestBuilder(plan, fileSystem: localFileSystem, observabilityScope: observability.topScope) + try llbuild.createProductCommand(buildProduct) + + XCTAssertEqual( + llbuild.manifest.commands.map(\.key).sorted(), + basicReleaseCommandNames.sorted() + ) + + // Linux, debug build + + buildParameters = mockBuildParameters(environment: BuildEnvironment( + platform: .linux, + configuration: .debug + )) + plan = try BuildPlan( + buildParameters: buildParameters, + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + + result = try BuildPlanResult(plan: plan) + buildProduct = try result.buildProduct(for: "exe") + + llbuild = LLBuildManifestBuilder(plan, fileSystem: localFileSystem, observabilityScope: observability.topScope) + try llbuild.createProductCommand(buildProduct) + + XCTAssertEqual( + llbuild.manifest.commands.map(\.key).sorted(), + basicDebugCommandNames.sorted() + ) + } +} diff --git a/Tests/BuildTests/LLBuildManifestTests.swift b/Tests/BuildTests/LLBuildManifestTests.swift deleted file mode 100644 index becb6aacbb5..00000000000 --- a/Tests/BuildTests/LLBuildManifestTests.swift +++ /dev/null @@ -1,123 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-2021 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 Basics -import LLBuildManifest -import XCTest - -import class TSCBasic.InMemoryFileSystem - -// FIXME: This should be in its own test target. -final class LLBuildManifestTests: XCTestCase { - func testBasics() throws { - var manifest = BuildManifest() - - let root: AbsolutePath = "/some" - - manifest.defaultTarget = "main" - manifest.addPhonyCmd( - name: "C.Foo", - inputs: [ - .file(root.appending(components: "file.c")), - .directory(root.appending(components: "dir")), - .directoryStructure(root.appending(components: "dir", "structure")), - ], - outputs: [.virtual("Foo")] - ) - - manifest.addNode(.virtual("Foo"), toTarget: "main") - - let fs = InMemoryFileSystem() - try ManifestWriter(fileSystem: fs).write(manifest, at: "/manifest.yaml") - - let contents: String = try fs.readFileContents("/manifest.yaml") - - // FIXME(#5475) - use the platform's preferred separator for directory - // indicators - XCTAssertEqual(contents.replacingOccurrences(of: "\\\\", with: "\\"), """ - client: - name: basic - tools: {} - targets: - "main": [""] - default: "main" - nodes: - "\(root.appending(components: "dir", "structure"))/": - is-directory-structure: true - content-exclusion-patterns: [".git",".build"] - commands: - "C.Foo": - tool: phony - inputs: ["\(root.appending(components: "file.c"))","\(root.appending(components: "dir"))/","\(root.appending(components: "dir", "structure"))/"] - outputs: [""] - - - """) - } - - func testShellCommands() throws { - var manifest = BuildManifest() - - let root: AbsolutePath = .root - - manifest.defaultTarget = "main" - manifest.addShellCmd( - name: "shelley", - description: "Shelley, Keats, and Byron", - inputs: [ - .file(root.appending(components: "file.in")) - ], - outputs: [ - .file(root.appending(components: "file.out")) - ], - arguments: [ - "foo", "bar", "baz" - ], - environment: [ - "ABC": "DEF", - "G H": "I J K", - ], - workingDirectory: "/wdir", - allowMissingInputs: true - ) - - manifest.addNode(.file("/file.out"), toTarget: "main") - - let fs = InMemoryFileSystem() - try ManifestWriter(fileSystem: fs).write(manifest, at: "/manifest.yaml") - - let contents: String = try fs.readFileContents("/manifest.yaml") - - XCTAssertEqual(contents.replacingOccurrences(of: "\\\\", with: "\\"), """ - client: - name: basic - tools: {} - targets: - "main": ["\(root.appending(components: "file.out"))"] - default: "main" - commands: - "shelley": - tool: shell - inputs: ["\(root.appending(components: "file.in"))"] - outputs: ["\(root.appending(components: "file.out"))"] - description: "Shelley, Keats, and Byron" - args: ["foo","bar","baz"] - env: - "ABC": "DEF" - "G H": "I J K" - working-directory: "/wdir" - allow-missing-inputs: true - - - """) - } -} diff --git a/Tests/BuildTests/MockBuildTestHelper.swift b/Tests/BuildTests/MockBuildTestHelper.swift index 39ceeac8479..fe15ee8d93e 100644 --- a/Tests/BuildTests/MockBuildTestHelper.swift +++ b/Tests/BuildTests/MockBuildTestHelper.swift @@ -22,17 +22,19 @@ struct MockToolchain: PackageModel.Toolchain { let librarianPath = AbsolutePath("/fake/path/to/link.exe") #elseif os(iOS) || os(macOS) || os(tvOS) || os(watchOS) let librarianPath = AbsolutePath("/fake/path/to/libtool") -#elseif os(Android) - let librarianPath = AbsolutePath("/fake/path/to/llvm-ar") #else - let librarianPath = AbsolutePath("/fake/path/to/ar") + let librarianPath = AbsolutePath("/fake/path/to/llvm-ar") #endif let swiftCompilerPath = AbsolutePath("/fake/path/to/swiftc") let includeSearchPaths = [AbsolutePath]() let librarySearchPaths = [AbsolutePath]() + let swiftResourcesPath: AbsolutePath? = nil + let swiftStaticResourcesPath: AbsolutePath? = nil let isSwiftDevelopmentToolchain = false + let sdkRootPath: AbsolutePath? = nil let swiftPluginServerPath: AbsolutePath? = nil let extraFlags = PackageModel.BuildFlags() + let installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default func getClangCompiler() throws -> AbsolutePath { return "/fake/path/to/clang" @@ -70,12 +72,14 @@ func mockBuildParameters( toolchain: PackageModel.Toolchain = MockToolchain(), flags: PackageModel.BuildFlags = PackageModel.BuildFlags(), shouldLinkStaticSwiftStdlib: Bool = false, + shouldDisableLocalRpath: Bool = false, canRenameEntrypointFunctionName: Bool = false, targetTriple: Basics.Triple = hostTriple, indexStoreMode: BuildParameters.IndexStoreMode = .off, useExplicitModuleBuild: Bool = false, linkerDeadStrip: Bool = true, - linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil + linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil, + omitFramePointers: Bool? = nil ) -> BuildParameters { return try! BuildParameters( dataPath: buildPath, @@ -86,12 +90,22 @@ func mockBuildParameters( flags: flags, pkgConfigDirectories: [], workers: 3, - shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib, - canRenameEntrypointFunctionName: canRenameEntrypointFunctionName, indexStoreMode: indexStoreMode, - useExplicitModuleBuild: useExplicitModuleBuild, - linkerDeadStrip: linkerDeadStrip, - linkTimeOptimizationMode: linkTimeOptimizationMode + debuggingParameters: .init( + targetTriple: targetTriple, + shouldEnableDebuggingEntitlement: config == .debug, + omitFramePointers: omitFramePointers + ), + driverParameters: .init( + canRenameEntrypointFunctionName: canRenameEntrypointFunctionName, + useExplicitModuleBuild: useExplicitModuleBuild + ), + linkingParameters: .init( + linkerDeadStrip: linkerDeadStrip, + linkTimeOptimizationMode: linkTimeOptimizationMode, + shouldDisableLocalRpath: shouldDisableLocalRpath, + shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib + ) ) } @@ -110,7 +124,7 @@ func mockBuildParameters(environment: BuildEnvironment) -> BuildParameters { fatalError("unsupported platform in tests") } - return mockBuildParameters(config: environment.configuration ?? .debug, targetTriple: triple) + return mockBuildParameters(config: environment.configuration ?? .debug, targetTriple: triple) } enum BuildError: Swift.Error { diff --git a/Tests/BuildTests/ModuleAliasingBuildTests.swift b/Tests/BuildTests/ModuleAliasingBuildTests.swift index b4a88bb03e4..d543e08a837 100644 --- a/Tests/BuildTests/ModuleAliasingBuildTests.swift +++ b/Tests/BuildTests/ModuleAliasingBuildTests.swift @@ -161,6 +161,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "thisPkg", path: "/thisPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/fooPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/barPkg", requirement: .upToNextMajor(from: "2.0.0")), @@ -176,7 +177,8 @@ final class ModuleAliasingBuildTests: XCTestCase { name: "Logging", package: "barPkg", moduleAliases: ["Logging": "BarLogging"] - )] + )], + type: .executable ), ] ), @@ -203,6 +205,8 @@ final class ModuleAliasingBuildTests: XCTestCase { } func testModuleAliasingDuplicateDylibProductNames() throws { + let fooPkg: AbsolutePath = "/fooPkg" + let barPkg: AbsolutePath = "/barPkg" let fs = InMemoryFileSystem( emptyFiles: "/thisPkg/Sources/exe/main.swift", @@ -215,7 +219,7 @@ final class ModuleAliasingBuildTests: XCTestCase { manifests: [ Manifest.createFileSystemManifest( displayName: "fooPkg", - path: "/fooPkg", + path: fooPkg, products: [ ProductDescription(name: "Logging", type: .library(.dynamic), targets: ["Logging"]), ], @@ -225,7 +229,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ), Manifest.createFileSystemManifest( displayName: "barPkg", - path: "/barPkg", + path: barPkg, products: [ ProductDescription(name: "Logging", type: .library(.dynamic), targets: ["Logging"]), ], @@ -258,11 +262,13 @@ final class ModuleAliasingBuildTests: XCTestCase { ], observabilityScope: observability.topScope )) { error in - XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Logging' in: 'barpkg' (at '/barPkg'), 'foopkg' (at '/fooPkg')") + XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Logging' in: 'barpkg' (at '\(barPkg)'), 'foopkg' (at '\(fooPkg)')") } } func testModuleAliasingDuplicateDylibStaticLibProductNames() throws { + let fooPkg: AbsolutePath = "/fooPkg" + let barPkg: AbsolutePath = "/barPkg" let fs = InMemoryFileSystem( emptyFiles: "/thisPkg/Sources/exe/main.swift", @@ -275,7 +281,7 @@ final class ModuleAliasingBuildTests: XCTestCase { manifests: [ Manifest.createFileSystemManifest( displayName: "fooPkg", - path: "/fooPkg", + path: fooPkg, products: [ ProductDescription(name: "Logging", type: .library(.dynamic), targets: ["Logging"]), ], @@ -285,7 +291,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ), Manifest.createFileSystemManifest( displayName: "barPkg", - path: "/barPkg", + path: barPkg, products: [ ProductDescription(name: "Logging", type: .library(.static), targets: ["Logging"]), ], @@ -318,7 +324,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ], observabilityScope: observability.topScope )) { error in - XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Logging' in: 'barpkg' (at '/barPkg'), 'foopkg' (at '/fooPkg')") + XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Logging' in: 'barpkg' (at '\(barPkg)'), 'foopkg' (at '\(fooPkg)')") } } @@ -357,6 +363,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "thisPkg", path: "/thisPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/fooPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/barPkg", requirement: .upToNextMajor(from: "2.0.0")), @@ -372,7 +379,8 @@ final class ModuleAliasingBuildTests: XCTestCase { name: "Logging", package: "barPkg", moduleAliases: ["Logging": "BarLogging"] - )] + )], + type: .executable ), ] ), @@ -439,6 +447,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "thisPkg", path: "/thisPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/fooPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/bazPkg", requirement: .upToNextMajor(from: "2.0.0")), @@ -454,7 +463,8 @@ final class ModuleAliasingBuildTests: XCTestCase { name: "Logging", package: "bazPkg", moduleAliases: ["Logging": "BazLogging"] - )] + )], + type: .executable ), ] ), @@ -654,6 +664,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "thisPkg", path: "/thisPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/fooPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/barPkg", requirement: .upToNextMajor(from: "2.0.0")), @@ -671,7 +682,8 @@ final class ModuleAliasingBuildTests: XCTestCase { name: "Logging", package: "barPkg", moduleAliases: ["Logging": "BarLogging"] - )] + )], + type: .executable ), TargetDescription(name: "Logging", dependencies: []), ] @@ -3470,6 +3482,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "xpkg", path: "/xPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/yPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/zPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3517,6 +3530,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "apkg", path: "/aPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/bPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/cPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3544,6 +3558,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "appPkg", path: "/appPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/xPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/aPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3562,7 +3577,8 @@ final class ModuleAliasingBuildTests: XCTestCase { package: "apkg", moduleAliases: ["Utils": "AUtils"] ), - ] + ], + type: .executable ), ] ), @@ -3637,6 +3653,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "xpkg", path: "/xPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/yPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/zPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3675,6 +3692,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "appPkg", path: "/appPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/xPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/aPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3692,7 +3710,8 @@ final class ModuleAliasingBuildTests: XCTestCase { package: "apkg", moduleAliases: ["Utils": "AUtils"] ), - ] + ], + type: .executable ), ] ), @@ -3762,6 +3781,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "xpkg", path: "/xPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/yPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/zPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3799,6 +3819,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "appPkg", path: "/appPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/xPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/aPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -3817,7 +3838,8 @@ final class ModuleAliasingBuildTests: XCTestCase { package: "apkg", moduleAliases: ["Utils": "AUtils"] ), - ] + ], + type: .executable ), ] ), @@ -4046,6 +4068,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createFileSystemManifest( displayName: "xpkg", path: "/xPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/yPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/zPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -4083,6 +4106,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createFileSystemManifest( displayName: "apkg", path: "/aPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/bPkg", requirement: .upToNextMajor(from: "1.0.0")), ], @@ -4107,6 +4131,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "appPkg", path: "/appPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/aPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/xPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -4126,7 +4151,8 @@ final class ModuleAliasingBuildTests: XCTestCase { name: "X", package: "xpkg", moduleAliases: ["FooUtils": "XFUtils"] - )] + )], + type: .executable ), ] ), @@ -4208,6 +4234,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createFileSystemManifest( displayName: "xpkg", path: "/xPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/yPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/zPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -4246,6 +4273,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createFileSystemManifest( displayName: "apkg", path: "/aPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/bPkg", requirement: .upToNextMajor(from: "1.0.0")), ], @@ -4270,6 +4298,7 @@ final class ModuleAliasingBuildTests: XCTestCase { Manifest.createRootManifest( displayName: "appPkg", path: "/appPkg", + toolsVersion: .v5_9, dependencies: [ .localSourceControl(path: "/aPkg", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/xPkg", requirement: .upToNextMajor(from: "1.0.0")), @@ -4292,7 +4321,8 @@ final class ModuleAliasingBuildTests: XCTestCase { "ZUtils": "XUtils", "FooUtils": "XFooUtils", ] - )] + )], + type: .executable ), ] ), @@ -4501,4 +4531,141 @@ final class ModuleAliasingBuildTests: XCTestCase { .contains { $0.target.name == "ZUtils" || $0.target.moduleAliases?["Utils"] == "ZUtils" } ) } + + func testProductAliasingDoesNotBreakPackagesWithOlderToolsVersions() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Lunch/Sources/MyTarget/file.swift", + "/Some/Sources/Some/file.swift", + "/Other/Sources/Other/file.swift" + ) + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "lunch", + path: "/Lunch", + dependencies: [ + .fileSystem(path: "/Some"), + .fileSystem(path: "/Other"), + ], + targets: [ + TargetDescription( + name: "MyTarget", + dependencies: [ + .byName(name: "SomeProduct", condition: nil), + .product(name: "Other", package: nil, moduleAliases: ["Other": "Other2"], condition: nil), + ] + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "Some", + path: "/Some", + products: [ + ProductDescription( + name: "SomeProduct", + type: .library(.automatic), + targets: ["Some"] + ) + ], + targets: [ + TargetDescription(name: "Some"), + ] + ), + Manifest.createFileSystemManifest( + displayName: "Other", + path: "/Other", + products: [ + ProductDescription( + name: "Other", + type: .library(.automatic), + targets: ["Other"] + ) + ], + targets: [ + TargetDescription(name: "Other"), + ] + ) + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + let result = try BuildPlanResult(plan: try BuildPlan( + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + result.checkTargetsCount(3) + } + + func testProductAliasingWarnsIfPackageWithOlderToolsVersionIsPossibleCauseOfConflict() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Lunch/Sources/MyTarget/file.swift", + "/Some/Sources/Some/file.swift", + "/Other/Sources/Some/file.swift" + ) + let observability = ObservabilitySystem.makeForTesting() + do { + _ = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "lunch", + path: "/Lunch", + dependencies: [ + .fileSystem(path: "/Some"), + .fileSystem(path: "/Other"), + ], + targets: [ + TargetDescription( + name: "MyTarget", + dependencies: [ + .byName(name: "SomeProduct", condition: nil), + .product(name: "Some", package: nil, moduleAliases: ["Some": "Some2"], condition: nil), + ] + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "Some", + path: "/Some", + products: [ + ProductDescription( + name: "SomeProduct", + type: .library(.automatic), + targets: ["Some"] + ) + ], + targets: [ + TargetDescription(name: "Some"), + ] + ), + Manifest.createFileSystemManifest( + displayName: "Some", + path: "/Other", + products: [ + ProductDescription( + name: "SomeProduct", + type: .library(.automatic), + targets: ["Some"] + ) + ], + targets: [ + TargetDescription(name: "Some"), + ] + ) + ], + observabilityScope: observability.topScope + ) + + XCTFail("unexpectedly resolved the package graph successfully") + } catch { + XCTAssertEqual(error.interpolationDescription, "multiple products named 'SomeProduct' in: 'other' (at '\(AbsolutePath("/Other"))'), 'some' (at '\(AbsolutePath("/Some"))')") + } + XCTAssertEqual(observability.diagnostics.map { $0.description }.sorted(), ["[warning]: product aliasing requires tools-version 5.2 or later, so it is not supported by \'other\'", "[warning]: product aliasing requires tools-version 5.2 or later, so it is not supported by \'some\'"]) + } } diff --git a/Tests/CommandsTests/APIDiffTests.swift b/Tests/CommandsTests/APIDiffTests.swift index 4ee9728cfce..7ab9a5fdd20 100644 --- a/Tests/CommandsTests/APIDiffTests.swift +++ b/Tests/CommandsTests/APIDiffTests.swift @@ -13,7 +13,7 @@ import Basics import Build import Commands -@_implementationOnly import DriverSupport +import DriverSupport import Foundation import PackageModel import SourceControl @@ -24,8 +24,6 @@ import XCTest import enum TSCBasic.ProcessEnv final class APIDiffTests: CommandsTestCase { - private let driverSupport = DriverSupport() - @discardableResult private func execute( _ args: [String], @@ -56,7 +54,7 @@ final class APIDiffTests: CommandsTestCase { // not all of which can be tested for easily. Fortunately, we can test for the // `-disable-fail-on-error` option, and any version which supports this flag // will meet the other requirements. - guard driverSupport.checkSupportedFrontendFlags(flags: ["disable-fail-on-error"], toolchain: try UserToolchain.default, fileSystem: localFileSystem) else { + guard DriverSupport.checkSupportedFrontendFlags(flags: ["disable-fail-on-error"], toolchain: try UserToolchain.default, fileSystem: localFileSystem) else { throw XCTSkip("swift-api-digester is too old") } } diff --git a/Tests/CommandsTests/BuildToolTests.swift b/Tests/CommandsTests/BuildToolTests.swift index 59f2a56d64d..78788f4e574 100644 --- a/Tests/CommandsTests/BuildToolTests.swift +++ b/Tests/CommandsTests/BuildToolTests.swift @@ -12,6 +12,7 @@ import Basics @testable import Commands +@testable import CoreCommands import PackageGraph import PackageLoading import PackageModel @@ -22,7 +23,8 @@ import XCTest struct BuildResult { let binPath: AbsolutePath - let output: String + let stdout: String + let stderr: String let binContents: [String] } @@ -30,27 +32,32 @@ final class BuildToolTests: CommandsTestCase { @discardableResult private func execute( _ args: [String] = [], - environment: [String : String]? = nil, + environment: [String: String]? = nil, packagePath: AbsolutePath? = nil ) throws -> (stdout: String, stderr: String) { - return try SwiftPM.Build.execute(args, packagePath: packagePath, env: environment) + try SwiftPM.Build.execute(args, packagePath: packagePath, env: environment) } - func build(_ args: [String], packagePath: AbsolutePath? = nil) throws -> BuildResult { - let (output, _) = try execute(args, packagePath: packagePath) + func build(_ args: [String], packagePath: AbsolutePath? = nil, isRelease: Bool = false) throws -> BuildResult { + let buildConfigurationArguments = isRelease ? ["-c", "release"] : [] + let (stdout, stderr) = try execute(args + buildConfigurationArguments, packagePath: packagePath) defer { try! SwiftPM.Package.execute(["clean"], packagePath: packagePath) } - let (binPathOutput, _) = try execute(["--show-bin-path"], packagePath: packagePath) + let (binPathOutput, _) = try execute( + ["--show-bin-path"] + buildConfigurationArguments, + packagePath: packagePath + ) let binPath = try AbsolutePath(validating: binPathOutput.trimmingCharacters(in: .whitespacesAndNewlines)) let binContents = try localFileSystem.getDirectoryContents(binPath).filter { guard let contents = try? localFileSystem.getDirectoryContents(binPath.appending(component: $0)) else { return true } - // Filter directories which only contain an output file map since we didn't build anything for those which is what `binContents` is meant to represent. + // Filter directories which only contain an output file map since we didn't build anything for those which + // is what `binContents` is meant to represent. return contents != ["output-file-map.json"] } - return BuildResult(binPath: binPath, output: output, binContents: binContents) + return BuildResult(binPath: binPath, stdout: stdout, stderr: stderr, binContents: binContents) } - + func testUsage() throws { let stdout = try execute(["-help"]).stdout XCTAssertMatch(stdout, .contains("USAGE: swift build")) @@ -80,38 +87,59 @@ final class BuildToolTests: CommandsTestCase { // Verify the warning flow try fixture(name: "Miscellaneous/ImportOfMissingDependency") { path in let fullPath = try resolveSymlinks(path) - XCTAssertThrowsError(try build(["--explicit-target-dependency-import-check=warn"], packagePath: fullPath)) { error in + XCTAssertThrowsError(try self.build( + ["--explicit-target-dependency-import-check=warn"], + packagePath: fullPath + )) { error in guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = error else { XCTFail() return } - XCTAssertTrue(stderr.contains("warning: Target A imports another target (B) in the package without declaring it a dependency."), "got stdout: \(stdout), stderr: \(stderr)") + XCTAssertTrue( + stderr.contains( + "warning: Target A imports another target (B) in the package without declaring it a dependency." + ), + "got stdout: \(stdout), stderr: \(stderr)" + ) } } // Verify the error flow try fixture(name: "Miscellaneous/ImportOfMissingDependency") { path in let fullPath = try resolveSymlinks(path) - XCTAssertThrowsError(try build(["--explicit-target-dependency-import-check=error"], packagePath: fullPath)) { error in + XCTAssertThrowsError(try self.build( + ["--explicit-target-dependency-import-check=error"], + packagePath: fullPath + )) { error in guard case SwiftPMError.executionFailure(_, _, let stderr) = error else { XCTFail() return } - XCTAssertTrue(stderr.contains("error: Target A imports another target (B) in the package without declaring it a dependency."), "got stdout: \(stdout), stderr: \(stderr)") + XCTAssertTrue( + stderr.contains( + "error: Target A imports another target (B) in the package without declaring it a dependency." + ), + "got stdout: \(stdout), stderr: \(stderr)" + ) } } // Verify that the default does not run the check try fixture(name: "Miscellaneous/ImportOfMissingDependency") { path in let fullPath = try resolveSymlinks(path) - XCTAssertThrowsError(try build([], packagePath: fullPath)) { error in + XCTAssertThrowsError(try self.build([], packagePath: fullPath)) { error in guard case SwiftPMError.executionFailure(_, _, let stderr) = error else { XCTFail() return } - XCTAssertFalse(stderr.contains("warning: Target A imports another target (B) in the package without declaring it a dependency."), "got stdout: \(stdout), stderr: \(stderr)") + XCTAssertFalse( + stderr.contains( + "warning: Target A imports another target (B) in the package without declaring it a dependency." + ), + "got stdout: \(stdout), stderr: \(stderr)" + ) } } } @@ -119,31 +147,52 @@ final class BuildToolTests: CommandsTestCase { func testBinPathAndSymlink() throws { try fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in let fullPath = try resolveSymlinks(fixturePath) - let targetPath = fullPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent()) + let targetPath = try fullPath.appending( + components: ".build", + UserToolchain.default.targetTriple.platformBuildPathComponent + ) let xcbuildTargetPath = fullPath.appending(components: ".build", "apple") - XCTAssertEqual(try execute(["--show-bin-path"], packagePath: fullPath).stdout, - "\(targetPath.appending("debug").pathString)\n") - XCTAssertEqual(try execute(["-c", "release", "--show-bin-path"], packagePath: fullPath).stdout, - "\(targetPath.appending("release").pathString)\n") + XCTAssertEqual( + try self.execute(["--show-bin-path"], packagePath: fullPath).stdout, + "\(targetPath.appending("debug").pathString)\n" + ) + XCTAssertEqual( + try self.execute(["-c", "release", "--show-bin-path"], packagePath: fullPath).stdout, + "\(targetPath.appending("release").pathString)\n" + ) // Print correct path when building with XCBuild. - let xcodeDebugOutput = try execute(["--build-system", "xcode", "--show-bin-path"], packagePath: fullPath).stdout - let xcodeReleaseOutput = try execute(["--build-system", "xcode", "-c", "release", "--show-bin-path"], packagePath: fullPath).stdout - #if os(macOS) - XCTAssertEqual(xcodeDebugOutput, "\(xcbuildTargetPath.appending(components: "Products", "Debug").pathString)\n") - XCTAssertEqual(xcodeReleaseOutput, "\(xcbuildTargetPath.appending(components: "Products", "Release").pathString)\n") - #else + let xcodeDebugOutput = try execute(["--build-system", "xcode", "--show-bin-path"], packagePath: fullPath) + .stdout + let xcodeReleaseOutput = try execute( + ["--build-system", "xcode", "-c", "release", "--show-bin-path"], + packagePath: fullPath + ).stdout + #if os(macOS) + XCTAssertEqual( + xcodeDebugOutput, + "\(xcbuildTargetPath.appending(components: "Products", "Debug").pathString)\n" + ) + XCTAssertEqual( + xcodeReleaseOutput, + "\(xcbuildTargetPath.appending(components: "Products", "Release").pathString)\n" + ) + #else XCTAssertEqual(xcodeDebugOutput, "\(targetPath.appending("debug").pathString)\n") XCTAssertEqual(xcodeReleaseOutput, "\(targetPath.appending("release").pathString)\n") - #endif + #endif // Test symlink. - try execute(packagePath: fullPath) - XCTAssertEqual(try resolveSymlinks(fullPath.appending(components: ".build", "debug")), - targetPath.appending("debug")) - try execute(["-c", "release"], packagePath: fullPath) - XCTAssertEqual(try resolveSymlinks(fullPath.appending(components: ".build", "release")), - targetPath.appending("release")) + try self.execute(packagePath: fullPath) + XCTAssertEqual( + try resolveSymlinks(fullPath.appending(components: ".build", "debug")), + targetPath.appending("debug") + ) + try self.execute(["-c", "release"], packagePath: fullPath) + XCTAssertEqual( + try resolveSymlinks(fullPath.appending(components: ".build", "release")), + targetPath.appending("release") + ) } } @@ -160,7 +209,12 @@ final class BuildToolTests: CommandsTestCase { do { let (_, stderr) = try execute(["--product", "lib1"], packagePath: fullPath) try SwiftPM.Package.execute(["clean"], packagePath: fullPath) - XCTAssertMatch(stderr, .contains("'--product' cannot be used with the automatic product 'lib1'; building the default target instead")) + XCTAssertMatch( + stderr, + .contains( + "'--product' cannot be used with the automatic product 'lib1'; building the default target instead" + ) + ) } do { @@ -169,32 +223,53 @@ final class BuildToolTests: CommandsTestCase { XCTAssertNoMatch(result.binContents, ["exec1"]) } - XCTAssertThrowsCommandExecutionError(try execute(["--product", "exec1", "--target", "exec2"], packagePath: fixturePath)) { error in + XCTAssertThrowsCommandExecutionError(try self.execute( + ["--product", "exec1", "--target", "exec2"], + packagePath: fixturePath + )) { error in XCTAssertMatch(error.stderr, .contains("error: '--product' and '--target' are mutually exclusive")) } - XCTAssertThrowsCommandExecutionError(try execute(["--product", "exec1", "--build-tests"], packagePath: fixturePath)) { error in + XCTAssertThrowsCommandExecutionError(try self.execute( + ["--product", "exec1", "--build-tests"], + packagePath: fixturePath + )) { error in XCTAssertMatch(error.stderr, .contains("error: '--product' and '--build-tests' are mutually exclusive")) } - XCTAssertThrowsCommandExecutionError(try execute(["--build-tests", "--target", "exec2"], packagePath: fixturePath)) { error in + XCTAssertThrowsCommandExecutionError(try self.execute( + ["--build-tests", "--target", "exec2"], + packagePath: fixturePath + )) { error in XCTAssertMatch(error.stderr, .contains("error: '--target' and '--build-tests' are mutually exclusive")) } - XCTAssertThrowsCommandExecutionError(try execute(["--build-tests", "--target", "exec2", "--product", "exec1"], packagePath: fixturePath)) { error in - XCTAssertMatch(error.stderr, .contains("error: '--product', '--target', and '--build-tests' are mutually exclusive")) + XCTAssertThrowsCommandExecutionError(try self.execute( + ["--build-tests", "--target", "exec2", "--product", "exec1"], + packagePath: fixturePath + )) { error in + XCTAssertMatch( + error.stderr, + .contains("error: '--product', '--target', and '--build-tests' are mutually exclusive") + ) } - XCTAssertThrowsCommandExecutionError(try execute(["--product", "UnkownProduct"], packagePath: fixturePath)){ error in + XCTAssertThrowsCommandExecutionError(try self.execute( + ["--product", "UnkownProduct"], + packagePath: fixturePath + )) { error in XCTAssertMatch(error.stderr, .contains("error: no product named 'UnkownProduct'")) } - XCTAssertThrowsCommandExecutionError(try execute(["--target", "UnkownTarget"], packagePath: fixturePath)) { error in + XCTAssertThrowsCommandExecutionError(try self.execute( + ["--target", "UnkownTarget"], + packagePath: fixturePath + )) { error in XCTAssertMatch(error.stderr, .contains("error: no target named 'UnkownTarget'")) } } } - + func testAtMainSupport() throws { try fixture(name: "Miscellaneous/AtMainSupport") { fixturePath in let fullPath = try resolveSymlinks(fixturePath) @@ -239,7 +314,9 @@ final class BuildToolTests: CommandsTestCase { XCTAssertNoMatch(result.binContents, ["BLibrary.a"]) // FIXME: We create the modulemap during build planning, hence this uglyness. - let bTargetBuildDir = ((try? localFileSystem.getDirectoryContents(result.binPath.appending("BTarget1.build"))) ?? []).filter{ $0 != moduleMapFilename } + let bTargetBuildDir = + ((try? localFileSystem.getDirectoryContents(result.binPath.appending("BTarget1.build"))) ?? []) + .filter { $0 != moduleMapFilename } XCTAssertTrue(bTargetBuildDir.isEmpty, "bTargetBuildDir should be empty") XCTAssertNoMatch(result.binContents, ["cexec"]) @@ -290,7 +367,7 @@ final class BuildToolTests: CommandsTestCase { do { // test second time, to stabilize the cache - try execute(packagePath: fixturePath) + try self.execute(packagePath: fixturePath) } do { @@ -322,22 +399,30 @@ final class BuildToolTests: CommandsTestCase { try XCTSkipIf(true, "test requires `xcbuild` and is therefore only supported on macOS") #endif try fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in - // Try building using XCBuild with default parameters. This should succeed. We build verbosely so we get full command lines. + // Try building using XCBuild with default parameters. This should succeed. We build verbosely so we get + // full command lines. let defaultOutput = try execute(["-c", "debug", "-v"], packagePath: fixturePath).stdout - + // Look for certain things in the output from XCBuild. - XCTAssertMatch(defaultOutput, .contains("-target \(try UserToolchain.default.targetTriple.tripleString(forPlatformVersion: ""))")) + XCTAssertMatch( + defaultOutput, + try .contains("-target \(UserToolchain.default.targetTriple.tripleString(forPlatformVersion: ""))") + ) } } func testXcodeBuildSystemWithAdditionalBuildFlags() throws { - try XCTSkipIf(true, "Disabled for now because it is hitting 'IR generation failure: Cannot read legacy layout file' in CI (rdar://88828632)") + try XCTSkipIf( + true, + "Disabled for now because it is hitting 'IR generation failure: Cannot read legacy layout file' in CI (rdar://88828632)" + ) #if !os(macOS) try XCTSkipIf(true, "test requires `xcbuild` and is therefore only supported on macOS") #endif try fixture(name: "ValidLayouts/SingleModule/ExecutableMixed") { fixturePath in - // Try building using XCBuild with additional flags. This should succeed. We build verbosely so we get full command lines. + // Try building using XCBuild with additional flags. This should succeed. We build verbosely so we get + // full command lines. let defaultOutput = try execute( [ "--build-system", "xcode", @@ -363,16 +448,22 @@ final class BuildToolTests: CommandsTestCase { try XCTSkipIf(true, "test requires `xcbuild` and is therefore only supported on macOS") #endif try fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in - // Try building using XCBuild without specifying overrides. This should succeed, and should use the default compiler path. + // Try building using XCBuild without specifying overrides. This should succeed, and should use the default + // compiler path. let defaultOutput = try execute(["-c", "debug", "--vv"], packagePath: fixturePath).stdout - XCTAssertMatch(defaultOutput, .contains(try UserToolchain.default.swiftCompilerPath.pathString)) + XCTAssertMatch(defaultOutput, try .contains(UserToolchain.default.swiftCompilerPath.pathString)) - // Now try building using XCBuild while specifying a faulty compiler override. This should fail. Note that we need to set the executable to use for the manifest itself to the default one, since it defaults to SWIFT_EXEC if not provided. + // Now try building using XCBuild while specifying a faulty compiler override. This should fail. Note that + // we need to set the executable to use for the manifest itself to the default one, since it defaults to + // SWIFT_EXEC if not provided. var overriddenOutput = "" XCTAssertThrowsCommandExecutionError( - try execute( + try self.execute( ["-c", "debug", "--vv"], - environment: ["SWIFT_EXEC": "/usr/bin/false", "SWIFT_EXEC_MANIFEST": UserToolchain.default.swiftCompilerPath.pathString], + environment: [ + "SWIFT_EXEC": "/usr/bin/false", + "SWIFT_EXEC_MANIFEST": UserToolchain.default.swiftCompilerPath.pathString, + ], packagePath: fixturePath ) ) { error in @@ -388,16 +479,158 @@ final class BuildToolTests: CommandsTestCase { XCTAssertMatch(output, .prefix("digraph Jobs {")) } } - + func testSwiftDriverRawOutputGetsNewlines() throws { try fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in - // Building with `-wmo` should result in a `remark: Incremental compilation has been disabled: it is not compatible with whole module optimization` message, which should have a trailing newline. Since that message won't be there at all when the legacy compiler driver is used, we gate this check on whether the remark is there in the first place. + // Building with `-wmo` should result in a `remark: Incremental compilation has been disabled: it is not + // compatible with whole module optimization` message, which should have a trailing newline. Since that + // message won't be there at all when the legacy compiler driver is used, we gate this check on whether the + // remark is there in the first place. let result = try execute(["-c", "release", "-Xswiftc", "-wmo"], packagePath: fixturePath) - if result.stdout.contains("remark: Incremental compilation has been disabled: it is not compatible with whole module optimization") { + if result.stdout.contains( + "remark: Incremental compilation has been disabled: it is not compatible with whole module optimization" + ) { XCTAssertMatch(result.stdout, .contains("optimization\n")) XCTAssertNoMatch(result.stdout, .contains("optimization[")) XCTAssertNoMatch(result.stdout, .contains("optimizationremark")) } } } + + func testSwiftGetVersion() throws { + try fixture(name: "Miscellaneous/Simple") { fixturePath in + func findSwiftGetVersionFile() throws -> AbsolutePath { + let buildArenaPath = fixturePath.appending(components: ".build", "debug") + let files = try localFileSystem.getDirectoryContents(buildArenaPath) + let filename = try XCTUnwrap(files.first { $0.hasPrefix("swift-version") }) + return buildArenaPath.appending(component: filename) + } + + let dummySwiftcPath = SwiftPM.testBinaryPath(for: "dummy-swiftc") + let swiftCompilerPath = try UserToolchain.default.swiftCompilerPath + + var environment = [ + "SWIFT_EXEC": dummySwiftcPath.pathString, + // Environment variables used by `dummy-swiftc.sh` + "SWIFT_ORIGINAL_PATH": swiftCompilerPath.pathString, + "CUSTOM_SWIFT_VERSION": "1.0", + ] + + // Build with a swiftc that returns version 1.0, we expect a successful build which compiles our one source + // file. + do { + let result = try execute(["--verbose"], environment: environment, packagePath: fixturePath) + XCTAssertTrue( + result.stdout.contains("\(dummySwiftcPath.pathString) -module-name"), + "compilation task missing from build result: \(result.stdout)" + ) + XCTAssertTrue(result.stdout.contains("Build complete!"), "unexpected build result: \(result.stdout)") + let swiftGetVersionFilePath = try findSwiftGetVersionFile() + XCTAssertEqual(try String(contentsOfFile: swiftGetVersionFilePath.pathString).spm_chomp(), "1.0") + } + + // Build again with that same version, we do not expect any compilation tasks. + do { + let result = try execute(["--verbose"], environment: environment, packagePath: fixturePath) + XCTAssertFalse( + result.stdout.contains("\(dummySwiftcPath.pathString) -module-name"), + "compilation task present in build result: \(result.stdout)" + ) + XCTAssertTrue(result.stdout.contains("Build complete!"), "unexpected build result: \(result.stdout)") + let swiftGetVersionFilePath = try findSwiftGetVersionFile() + XCTAssertEqual(try String(contentsOfFile: swiftGetVersionFilePath.pathString).spm_chomp(), "1.0") + } + + // Build again with a swiftc that returns version 2.0, we expect compilation happening once more. + do { + environment["CUSTOM_SWIFT_VERSION"] = "2.0" + let result = try execute(["--verbose"], environment: environment, packagePath: fixturePath) + XCTAssertTrue( + result.stdout.contains("\(dummySwiftcPath.pathString) -module-name"), + "compilation task missing from build result: \(result.stdout)" + ) + XCTAssertTrue(result.stdout.contains("Build complete!"), "unexpected build result: \(result.stdout)") + let swiftGetVersionFilePath = try findSwiftGetVersionFile() + XCTAssertEqual(try String(contentsOfFile: swiftGetVersionFilePath.pathString).spm_chomp(), "2.0") + } + } + } + + func testGetTaskAllowEntitlement() throws { + try fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in + #if os(macOS) + // Try building with default parameters. This should succeed. We build verbosely so we get full command + // lines. + var buildResult = try build(["-v"], packagePath: fixturePath) + + XCTAssertMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + buildResult = try self.build(["-c", "debug", "-v"], packagePath: fixturePath) + + XCTAssertMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + // Build with different combinations of the entitlement flag and debug/release build configurations. + + buildResult = try self.build( + ["--enable-get-task-allow-entitlement", "-v"], + packagePath: fixturePath, + isRelease: true + ) + + XCTAssertMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + buildResult = try self.build( + ["-c", "debug", "--enable-get-task-allow-entitlement", "-v"], + packagePath: fixturePath + ) + + XCTAssertMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + buildResult = try self.build( + ["-c", "debug", "--disable-get-task-allow-entitlement", "-v"], + packagePath: fixturePath + ) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + buildResult = try self.build( + ["--disable-get-task-allow-entitlement", "-v"], + packagePath: fixturePath, + isRelease: true + ) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + #else + var buildResult = try self.build(["-v"], packagePath: fixturePath) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + buildResult = try self.build(["-v"], packagePath: fixturePath, isRelease: true) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + + buildResult = try self.build( + ["--disable-get-task-allow-entitlement", "-v"], + packagePath: fixturePath, + isRelease: true + ) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + XCTAssertMatch(buildResult.stderr, .contains(SwiftTool.entitlementsMacOSWarning)) + + buildResult = try self.build( + ["--enable-get-task-allow-entitlement", "-v"], + packagePath: fixturePath, + isRelease: true + ) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + XCTAssertMatch(buildResult.stderr, .contains(SwiftTool.entitlementsMacOSWarning)) + #endif + + buildResult = try self.build(["-c", "release", "-v"], packagePath: fixturePath, isRelease: true) + + XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) + } + } } diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageToolTests.swift index ab16594ca7f..fafa4fb435c 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageToolTests.swift @@ -264,17 +264,35 @@ final class PackageToolTests: CommandsTestCase { // Perform an initial fetch. _ = try execute(["resolve"], packagePath: packageRoot) - var path = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot) - XCTAssertEqual(try GitRepository(path: path).getTags(), ["1.2.3"]) - // Retag the dependency, and update. - let repo = GitRepository(path: fixturePath.appending("Foo")) + do { + let checkoutPath = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot) + let checkoutRepo = GitRepository(path: checkoutPath) + XCTAssertEqual(try checkoutRepo.getTags(), ["1.2.3"]) + _ = try checkoutRepo.revision(forTag: "1.2.3") + } + + + // update and retag the dependency, and update. + let repoPath = fixturePath.appending("Foo") + let repo = GitRepository(path: repoPath) + try localFileSystem.writeFileContents(repoPath.appending("test"), string: "test") + try repo.stageEverything() + try repo.commit() try repo.tag(name: "1.2.4") + + // we will validate it is there + let revision = try repo.revision(forTag: "1.2.4") + _ = try execute(["update"], packagePath: packageRoot) - // We shouldn't assume package path will be same after an update so ask again for it. - path = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot) - XCTAssertEqual(try GitRepository(path: path).getTags(), ["1.2.3", "1.2.4"]) + do { + // We shouldn't assume package path will be same after an update so ask again for it. + let checkoutPath = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot) + let checkoutRepo = GitRepository(path: checkoutPath) + // tag may not be there, but revision should be after update + XCTAssertTrue(checkoutRepo.exists(revision: .init(identifier: revision))) + } } } @@ -479,7 +497,7 @@ final class PackageToolTests: CommandsTestCase { let arguments = withPrettyPrinting ? ["dump-symbol-graph", "--pretty-print"] : ["dump-symbol-graph"] - _ = try SwiftPM.Package.execute(arguments, packagePath: path, env: ["SWIFT_SYMBOLGRAPH_EXTRACT": symbolGraphExtractorPath.pathString]) + let result = try SwiftPM.Package.execute(arguments, packagePath: path, env: ["SWIFT_SYMBOLGRAPH_EXTRACT": symbolGraphExtractorPath.pathString]) let enumerator = try XCTUnwrap(FileManager.default.enumerator(at: URL(fileURLWithPath: path.pathString), includingPropertiesForKeys: nil), file: file, line: line) var symbolGraphURL: URL? @@ -488,7 +506,13 @@ final class PackageToolTests: CommandsTestCase { break } - let symbolGraphData = try Data(contentsOf: XCTUnwrap(symbolGraphURL, file: file, line: line)) + let symbolGraphData: Data + if let symbolGraphURL { + symbolGraphData = try Data(contentsOf: symbolGraphURL) + } else { + XCTFail("Failed to extract symbol graph: \(result.stdout)\n\(result.stderr)") + return nil + } // Double check that it's a valid JSON XCTAssertNoThrow(try JSONSerialization.jsonObject(with: symbolGraphData), file: file, line: line) @@ -502,7 +526,7 @@ final class PackageToolTests: CommandsTestCase { try fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in let compactGraphData = try XCTUnwrap(symbolGraph(atPath: fixturePath, withPrettyPrinting: false)) - let compactJSONText = try XCTUnwrap(String(data: compactGraphData, encoding: .utf8)) + let compactJSONText = String(decoding: compactGraphData, as: UTF8.self) XCTAssertEqual(compactJSONText.components(separatedBy: .newlines).count, 1) } } @@ -513,7 +537,7 @@ final class PackageToolTests: CommandsTestCase { try fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in let prettyGraphData = try XCTUnwrap(symbolGraph(atPath: fixturePath, withPrettyPrinting: true)) - let prettyJSONText = try XCTUnwrap(String(data: prettyGraphData, encoding: .utf8)) + let prettyJSONText = String(decoding: prettyGraphData, as: UTF8.self) XCTAssertGreaterThan(prettyJSONText.components(separatedBy: .newlines).count, 1) } } @@ -768,7 +792,7 @@ final class PackageToolTests: CommandsTestCase { _ = try SwiftPM.Package.execute(["edit", "baz", "--branch", "bugfix"], packagePath: fooPath) // Path to the executable. - let exec = [fooPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "foo").pathString] + let exec = [fooPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "foo").pathString] // We should see it now in packages directory. let editsPath = fooPath.appending(components: "Packages", "bar") @@ -842,7 +866,7 @@ final class PackageToolTests: CommandsTestCase { // Build it. XCTAssertBuilds(packageRoot) let buildPath = packageRoot.appending(".build") - let binFile = buildPath.appending(components: try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Bar") + let binFile = buildPath.appending(components: try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Bar") XCTAssertFileExists(binFile) XCTAssert(localFileSystem.isDirectory(buildPath)) @@ -861,7 +885,7 @@ final class PackageToolTests: CommandsTestCase { // Build it. XCTAssertBuilds(packageRoot) let buildPath = packageRoot.appending(".build") - let binFile = buildPath.appending(components: try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Bar") + let binFile = buildPath.appending(components: try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Bar") XCTAssertFileExists(binFile) XCTAssert(localFileSystem.isDirectory(buildPath)) // Clean, and check for removal of the build directory but not Packages. @@ -928,13 +952,10 @@ final class PackageToolTests: CommandsTestCase { func testPinning() throws { try fixture(name: "Miscellaneous/PackageEdit") { fixturePath in let fooPath = fixturePath.appending("foo") - func build() throws -> String { - return try SwiftPM.Build.execute(packagePath: fooPath).stdout - } - let exec = [fooPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "foo").pathString] + let exec = [fooPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "foo").pathString] // Build and check. - _ = try build() + _ = try SwiftPM.Build.execute(packagePath: fooPath) XCTAssertEqual(try TSCBasic.Process.checkNonZeroExit(arguments: exec).spm_chomp(), "\(5)") // Get path to bar checkout. @@ -943,7 +964,7 @@ final class PackageToolTests: CommandsTestCase { // Checks the content of checked out bar.swift. func checkBar(_ value: Int, file: StaticString = #file, line: UInt = #line) throws { let contents: String = try localFileSystem.readFileContents(barPath.appending(components:"Sources", "bar.swift")) - XCTAssertTrue(contents.spm_chomp().hasSuffix("\(value)"), file: file, line: line) + XCTAssertTrue(contents.spm_chomp().hasSuffix("\(value)"), "got \(contents)", file: file, line: line) } // We should see a pin file now. @@ -1842,6 +1863,16 @@ final class PackageToolTests: CommandsTestCase { } } + func testAmbiguousCommandPlugin() throws { + // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). + try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") + + try fixture(name: "Miscellaneous/Plugins/AmbiguousCommands") { fixturePath in + let (stdout, _) = try SwiftPM.Package.execute(["plugin", "--package", "A", "A"], packagePath: fixturePath) + XCTAssertMatch(stdout, .contains("Hello A!")) + } + } + func testCommandPluginNetworkingPermissions(permissionsManifestFragment: String, permissionError: String, reason: String, remedy: [String]) throws { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -2131,7 +2162,10 @@ final class PackageToolTests: CommandsTestCase { do { let (stdout, stderr) = try SwiftPM.Package.execute(["plugin", "MyPlugin", "--foo", "--help", "--version", "--verbose"], packagePath: packageDir) XCTAssertMatch(stdout, .contains("success")) - XCTAssertEqual(stderr, "") + let filteredStderr = stderr.components(separatedBy: "\n").filter { + !$0.contains("annotation implies no releases") && !$0.contains("note: add explicit") + }.joined(separator: "\n") + XCTAssertEqual(filteredStderr, "") } // Check default command arguments diff --git a/Tests/CommandsTests/TestToolTests.swift b/Tests/CommandsTests/TestToolTests.swift index 741f61a2e31..12976e3d200 100644 --- a/Tests/CommandsTests/TestToolTests.swift +++ b/Tests/CommandsTests/TestToolTests.swift @@ -128,6 +128,22 @@ final class TestToolTests: CommandsTestCase { } } + func testSwiftTestXMLOutputWhenEmpty() throws { + try fixture(name: "Miscellaneous/EmptyTestsPkg") { fixturePath in + let xUnitOutput = fixturePath.appending("result.xml") + // Run tests in parallel with verbose output. + let stdout = try SwiftPM.Test.execute(["--parallel", "--verbose", "--xunit-output", xUnitOutput.pathString], packagePath: fixturePath).stdout + // in "swift test" test output goes to stdout + XCTAssertNoMatch(stdout, .contains("passed")) + XCTAssertNoMatch(stdout, .contains("failed")) + + // Check the xUnit output. + XCTAssertFileExists(xUnitOutput) + let contents: String = try localFileSystem.readFileContents(xUnitOutput) + XCTAssertMatch(contents, .contains("tests=\"0\" failures=\"0\"")) + } + } + func testSwiftTestFilter() throws { try fixture(name: "Miscellaneous/SkipTests") { fixturePath in let (stdout, _) = try SwiftPM.Test.execute(["--filter", ".*1"], packagePath: fixturePath) @@ -255,4 +271,22 @@ final class TestToolTests: CommandsTestCase { } } } + + func testBasicSwiftTestingIntegration() throws { + try XCTSkipUnless( + nil != ProcessInfo.processInfo.environment["SWIFT_PM_SWIFT_TESTING_TESTS_ENABLED"], + "Skipping \(#function) because swift-testing tests are not explicitly enabled" + ) + + if #unavailable(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, visionOS 1.0) { + throw XCTSkip("swift-testing unavailable") + } + + try fixture(name: "Miscellaneous/TestDiscovery/SwiftTesting") { fixturePath in + do { + let (stdout, _) = try SwiftPM.Test.execute(["--enable-experimental-swift-testing", "--disable-xctest"], packagePath: fixturePath) + XCTAssertMatch(stdout, .contains(#"Test "SOME TEST FUNCTION" started"#)) + } + } + } } diff --git a/Tests/FunctionalPerformanceTests/BuildPerfTests.swift b/Tests/FunctionalPerformanceTests/BuildPerfTests.swift index 66e17b81357..4b0e395e147 100644 --- a/Tests/FunctionalPerformanceTests/BuildPerfTests.swift +++ b/Tests/FunctionalPerformanceTests/BuildPerfTests.swift @@ -63,7 +63,7 @@ class BuildPerfTests: XCTestCasePerf { try fixture(name: name) { fixturePath in let app = fixturePath.appending(components: (appString ?? "")) let triple = try UserToolchain.default.targetTriple - let product = app.appending(components: ".build", triple.platformBuildPathComponent(), "debug", productString) + let product = app.appending(components: ".build", triple.platformBuildPathComponent, "debug", productString) try self.execute(packagePath: app) measure { try! self.clean(packagePath: app) @@ -77,7 +77,7 @@ class BuildPerfTests: XCTestCasePerf { try fixture(name: name) { fixturePath in let app = fixturePath.appending(components: (appString ?? "")) let triple = try UserToolchain.default.targetTriple - let product = app.appending(components: ".build", triple.platformBuildPathComponent(), "debug", productString) + let product = app.appending(components: ".build", triple.platformBuildPathComponent, "debug", productString) try self.execute(packagePath: app) measure { try! self.execute(packagePath: app) diff --git a/Tests/FunctionalTests/CFamilyTargetTests.swift b/Tests/FunctionalTests/CFamilyTargetTests.swift index 9f0a11be217..7b748524f09 100644 --- a/Tests/FunctionalTests/CFamilyTargetTests.swift +++ b/Tests/FunctionalTests/CFamilyTargetTests.swift @@ -41,7 +41,7 @@ class CFamilyTargetTestCase: XCTestCase { func testCLibraryWithSpaces() throws { try fixture(name: "CFamilyTargets/CLibraryWithSpaces") { fixturePath in XCTAssertBuilds(fixturePath) - let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Bar.c.o") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o") } @@ -51,7 +51,7 @@ class CFamilyTargetTestCase: XCTestCase { try fixture(name: "DependencyResolution/External/CUsingCDep") { fixturePath in let packageRoot = fixturePath.appending("Bar") XCTAssertBuilds(packageRoot) - let debugPath = fixturePath.appending(components: "Bar", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let debugPath = fixturePath.appending(components: "Bar", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Sea.c.o") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o") let path = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot) @@ -62,7 +62,7 @@ class CFamilyTargetTestCase: XCTestCase { func testModuleMapGenerationCases() throws { try fixture(name: "CFamilyTargets/ModuleMapGenerationCases") { fixturePath in XCTAssertBuilds(fixturePath) - let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Jaz.c.o") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "main.swift.o") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "FlatInclude.c.o") @@ -85,7 +85,7 @@ class CFamilyTargetTestCase: XCTestCase { // Try building a fixture which needs extra flags to be able to build. try fixture(name: "CFamilyTargets/CDynamicLookup") { fixturePath in XCTAssertBuilds(fixturePath, Xld: ["-undefined", "dynamic_lookup"]) - let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o") } } @@ -97,7 +97,7 @@ class CFamilyTargetTestCase: XCTestCase { try fixture(name: "CFamilyTargets/ObjCmacOSPackage") { fixturePath in // Build the package. XCTAssertBuilds(fixturePath) - XCTAssertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug"), filename: "HelloWorldExample.m.o") + XCTAssertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug"), filename: "HelloWorldExample.m.o") // Run swift-test on package. XCTAssertSwiftTest(fixturePath) } @@ -106,7 +106,7 @@ class CFamilyTargetTestCase: XCTestCase { func testCanBuildRelativeHeaderSearchPaths() throws { try fixture(name: "CFamilyTargets/CLibraryParentSearchPath") { fixturePath in XCTAssertBuilds(fixturePath) - XCTAssertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug"), filename: "HeaderInclude.swiftmodule") + XCTAssertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug"), filename: "HeaderInclude.swiftmodule") } } } diff --git a/Tests/FunctionalTests/DependencyResolutionTests.swift b/Tests/FunctionalTests/DependencyResolutionTests.swift index ef80bea8807..105602082d8 100644 --- a/Tests/FunctionalTests/DependencyResolutionTests.swift +++ b/Tests/FunctionalTests/DependencyResolutionTests.swift @@ -23,7 +23,7 @@ class DependencyResolutionTests: XCTestCase { try fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in XCTAssertBuilds(fixturePath) - let output = try Process.checkNonZeroExit(args: fixturePath.appending(components: ".build", UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Foo").pathString) + let output = try Process.checkNonZeroExit(args: fixturePath.appending(components: ".build", UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Foo").pathString) XCTAssertEqual(output, "Foo\nBar\n") } } @@ -38,7 +38,7 @@ class DependencyResolutionTests: XCTestCase { try fixture(name: "DependencyResolution/Internal/Complex") { fixturePath in XCTAssertBuilds(fixturePath) - let output = try Process.checkNonZeroExit(args: fixturePath.appending(components: ".build", UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Foo").pathString) + let output = try Process.checkNonZeroExit(args: fixturePath.appending(components: ".build", UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Foo").pathString) XCTAssertEqual(output, "meiow Baz\n") } } @@ -54,7 +54,7 @@ class DependencyResolutionTests: XCTestCase { let packageRoot = fixturePath.appending("Bar") XCTAssertBuilds(packageRoot) - XCTAssertFileExists(fixturePath.appending(components: "Bar", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Bar")) + XCTAssertFileExists(fixturePath.appending(components: "Bar", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Bar")) let path = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot) XCTAssert(try GitRepository(path: path).getTags().contains("1.2.3")) } @@ -63,7 +63,7 @@ class DependencyResolutionTests: XCTestCase { func testExternalComplex() throws { try fixture(name: "DependencyResolution/External/Complex") { fixturePath in XCTAssertBuilds(fixturePath.appending("app")) - let output = try Process.checkNonZeroExit(args: fixturePath.appending(components: "app", ".build", UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Dealer").pathString) + let output = try Process.checkNonZeroExit(args: fixturePath.appending(components: "app", ".build", UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Dealer").pathString) XCTAssertEqual(output, "♣︎K\n♣︎Q\n♣︎J\n♣︎10\n♣︎9\n♣︎8\n♣︎7\n♣︎6\n♣︎5\n♣︎4\n") } } diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index 53e29947299..51b87f3d733 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -52,7 +52,7 @@ class MiscellaneousTestCase: XCTestCase { try fixture(name: "Miscellaneous/ExactDependencies") { fixturePath in XCTAssertBuilds(fixturePath.appending("app")) - let buildDir = fixturePath.appending(components: "app", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let buildDir = fixturePath.appending(components: "app", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertFileExists(buildDir.appending("FooExec")) XCTAssertFileExists(buildDir.appending("FooLib1.swiftmodule")) XCTAssertFileExists(buildDir.appending("FooLib2.swiftmodule")) @@ -107,7 +107,7 @@ class MiscellaneousTestCase: XCTestCase { */ func testInternalDependencyEdges() throws { try fixture(name: "Miscellaneous/DependencyEdges/Internal") { fixturePath in - let execpath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Foo").pathString + let execpath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Foo").pathString XCTAssertBuilds(fixturePath) var output = try Process.checkNonZeroExit(args: execpath) @@ -131,7 +131,7 @@ class MiscellaneousTestCase: XCTestCase { */ func testExternalDependencyEdges1() throws { try fixture(name: "DependencyResolution/External/Complex") { fixturePath in - let execpath = fixturePath.appending(components: "app", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Dealer").pathString + let execpath = fixturePath.appending(components: "app", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Dealer").pathString let packageRoot = fixturePath.appending("app") XCTAssertBuilds(packageRoot) @@ -158,7 +158,7 @@ class MiscellaneousTestCase: XCTestCase { */ func testExternalDependencyEdges2() throws { try fixture(name: "Miscellaneous/DependencyEdges/External") { fixturePath in - let execpath = [fixturePath.appending(components: "root", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "dep2").pathString] + let execpath = [fixturePath.appending(components: "root", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "dep2").pathString] let packageRoot = fixturePath.appending("root") XCTAssertBuilds(fixturePath.appending("root")) @@ -182,7 +182,7 @@ class MiscellaneousTestCase: XCTestCase { func testSpaces() throws { try fixture(name: "Miscellaneous/Spaces Fixture") { fixturePath in XCTAssertBuilds(fixturePath) - XCTAssertFileExists(fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Module_Name_1.build", "Foo.swift.o")) + XCTAssertFileExists(fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Module_Name_1.build", "Foo.swift.o")) } } @@ -239,7 +239,7 @@ class MiscellaneousTestCase: XCTestCase { let env = ["PKG_CONFIG_PATH": fixturePath.pathString] _ = try executeSwiftBuild(moduleUser, env: env) - XCTAssertFileExists(moduleUser.appending(components: ".build", triple.platformBuildPathComponent(), "debug", "SystemModuleUserClang")) + XCTAssertFileExists(moduleUser.appending(components: ".build", triple.platformBuildPathComponent, "debug", "SystemModuleUserClang")) // Clean up the build directory before re-running the build with // different arguments. @@ -247,7 +247,7 @@ class MiscellaneousTestCase: XCTestCase { _ = try executeSwiftBuild(moduleUser, extraArgs: ["--pkg-config-path", fixturePath.pathString]) - XCTAssertFileExists(moduleUser.appending(components: ".build", triple.platformBuildPathComponent(), "debug", "SystemModuleUserClang")) + XCTAssertFileExists(moduleUser.appending(components: ".build", triple.platformBuildPathComponent, "debug", "SystemModuleUserClang")) } } @@ -337,27 +337,6 @@ class MiscellaneousTestCase: XCTestCase { } } - func testInvalidRefsValidation() throws { - try fixture(name: "Miscellaneous/InvalidRefs", createGitRepo: false) { fixturePath in - do { - XCTAssertThrowsError(try SwiftPM.Build.execute(packagePath: fixturePath.appending("InvalidBranch"))) { error in - guard case SwiftPMError.executionFailure(_, _, let stderr) = error else { - return XCTFail("invalid error \(error)") - } - XCTAssert(stderr.contains("invalid branch name: "), "Didn't find expected output: \(stderr)") - } - } - do { - XCTAssertThrowsError(try SwiftPM.Build.execute(packagePath: fixturePath.appending("InvalidRevision"))) { error in - guard case SwiftPMError.executionFailure(_, _, let stderr) = error else { - return XCTFail("invalid error \(error)") - } - XCTAssert(stderr.contains("invalid revision: "), "Didn't find expected output: \(stderr)") - } - } - } - } - func testUnicode() throws { #if !os(Linux) && !os(Android) // TODO: - Linux has trouble with this and needs investigation. try fixture(name: "Miscellaneous/Unicode") { fixturePath in @@ -425,6 +404,28 @@ class MiscellaneousTestCase: XCTestCase { } } + func testTestsCanLinkAgainstAsyncExecutable() throws { + #if compiler(<5.10) + try XCTSkipIf(true, "skipping because host compiler doesn't have a fix for symbol conflicts yet") + #endif + try fixture(name: "Miscellaneous/TestableAsyncExe") { fixturePath in + let (stdout, stderr) = try executeSwiftTest(fixturePath) + // in "swift test" build output goes to stderr + XCTAssertMatch(stderr, .contains("Linking TestableAsyncExe1")) + XCTAssertMatch(stderr, .contains("Linking TestableAsyncExe2")) + XCTAssertMatch(stderr, .contains("Linking TestableAsyncExe3")) + XCTAssertMatch(stderr, .contains("Linking TestableAsyncExe4")) + XCTAssertMatch(stderr, .contains("Linking TestableAsyncExePackageTests")) + XCTAssertMatch(stderr, .contains("Build complete!")) + // in "swift test" test output goes to stdout + XCTAssertMatch(stdout, .contains("Executed 1 test")) + XCTAssertMatch(stdout, .contains("Hello, async world")) + XCTAssertMatch(stdout, .contains("Hello, async planet")) + XCTAssertMatch(stdout, .contains("Hello, async galaxy")) + XCTAssertMatch(stdout, .contains("Hello, async universe")) + } + } + func testExecutableTargetMismatch() throws { try fixture(name: "Miscellaneous/TargetMismatch") { path in do { @@ -606,6 +607,8 @@ class MiscellaneousTestCase: XCTestCase { } func testNoWarningFromRemoteDependencies() throws { + try XCTSkipIf(!UserToolchain.default.supportsSuppressWarnings(), "skipping because test environment doesn't support suppressing warnings") + try fixture(name: "Miscellaneous/DependenciesWarnings") { path in // prepare the deps as git sources let dependency1Path = path.appending("dep1") @@ -623,6 +626,8 @@ class MiscellaneousTestCase: XCTestCase { } func testNoWarningFromRemoteDependenciesWithWarningsAsErrors() throws { + try XCTSkipIf(!UserToolchain.default.supportsSuppressWarnings(), "skipping because test environment doesn't support suppressing warnings") + try fixture(name: "Miscellaneous/DependenciesWarnings2") { path in // prepare the deps as git sources let dependency1Path = path.appending("dep1") @@ -641,7 +646,7 @@ class MiscellaneousTestCase: XCTestCase { func testRootPackageWithConditionals() throws { try fixture(name: "Miscellaneous/RootPackageWithConditionals") { path in let (_, stderr) = try SwiftPM.Build.execute(packagePath: path) - let errors = stderr.components(separatedBy: .newlines).filter { !$0.contains("[logging] misuse") && !$0.isEmpty } + let errors = stderr.components(separatedBy: .newlines).filter { !$0.contains("[logging] misuse") && !$0.contains("annotation implies no releases") && !$0.contains("note: add explicit") && !$0.isEmpty } XCTAssertEqual(errors, [], "unexpected errors: \(errors)") } } diff --git a/Tests/FunctionalTests/ModuleAliasingFixtureTests.swift b/Tests/FunctionalTests/ModuleAliasingFixtureTests.swift index dd92109b21f..3e7691c9d10 100644 --- a/Tests/FunctionalTests/ModuleAliasingFixtureTests.swift +++ b/Tests/FunctionalTests/ModuleAliasingFixtureTests.swift @@ -22,7 +22,7 @@ class ModuleAliasingFixtureTests: XCTestCase { func testModuleDirectDeps1() throws { try fixture(name: "ModuleAliasing/DirectDeps1") { fixturePath in let pkgPath = fixturePath.appending(components: "AppPkg") - let buildPath = pkgPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let buildPath = pkgPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertBuilds(pkgPath, extraArgs: ["--vv"]) XCTAssertFileExists(buildPath.appending(components: "App")) XCTAssertFileExists(buildPath.appending(components: "GameUtils.swiftmodule")) @@ -34,7 +34,7 @@ class ModuleAliasingFixtureTests: XCTestCase { func testModuleDirectDeps2() throws { try fixture(name: "ModuleAliasing/DirectDeps2") { fixturePath in let pkgPath = fixturePath.appending(components: "AppPkg") - let buildPath = pkgPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug") + let buildPath = pkgPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug") XCTAssertBuilds(pkgPath, extraArgs: ["--vv"]) XCTAssertFileExists(buildPath.appending(components: "App")) XCTAssertFileExists(buildPath.appending(components: "AUtils.swiftmodule")) diff --git a/Tests/FunctionalTests/ModuleMapTests.swift b/Tests/FunctionalTests/ModuleMapTests.swift index 707179ae9fc..14e426b5b6e 100644 --- a/Tests/FunctionalTests/ModuleMapTests.swift +++ b/Tests/FunctionalTests/ModuleMapTests.swift @@ -23,7 +23,7 @@ class ModuleMapsTestCase: XCTestCase { try SPMTestSupport.fixture(name: name) { fixturePath in let input = fixturePath.appending(components: cModuleName, "C", "foo.c") let triple = try UserToolchain.default.targetTriple - let outdir = fixturePath.appending(components: rootpkg, ".build", triple.platformBuildPathComponent(), "debug") + let outdir = fixturePath.appending(components: rootpkg, ".build", triple.platformBuildPathComponent, "debug") try makeDirectories(outdir) let output = outdir.appending("libfoo\(triple.dynamicLibraryExtension)") try systemQuietly(["clang", "-shared", input.pathString, "-o", output.pathString]) @@ -43,7 +43,7 @@ class ModuleMapsTestCase: XCTestCase { XCTAssertBuilds(fixturePath.appending("App"), Xld: Xld) let triple = try UserToolchain.default.targetTriple - let targetPath = fixturePath.appending(components: "App", ".build", triple.platformBuildPathComponent()) + let targetPath = fixturePath.appending(components: "App", ".build", triple.platformBuildPathComponent) let debugout = try Process.checkNonZeroExit(args: targetPath.appending(components: "debug", "App").pathString) XCTAssertEqual(debugout, "123\n") let releaseout = try Process.checkNonZeroExit(args: targetPath.appending(components: "release", "App").pathString) @@ -58,7 +58,7 @@ class ModuleMapsTestCase: XCTestCase { func verify(_ conf: String) throws { let triple = try UserToolchain.default.targetTriple - let out = try Process.checkNonZeroExit(args: fixturePath.appending(components: "packageA", ".build", triple.platformBuildPathComponent(), conf, "packageA").pathString) + let out = try Process.checkNonZeroExit(args: fixturePath.appending(components: "packageA", ".build", triple.platformBuildPathComponent, conf, "packageA").pathString) XCTAssertEqual(out, """ calling Y.bar() Y.bar() called diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index 87ca4129256..f578e8114f2 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -169,6 +169,8 @@ class PluginTests: XCTestCase { } func testCommandPluginInvocation() throws { + try XCTSkipIf(true, "test is disabled because it isn't stable, see rdar://117870608") + // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -472,6 +474,7 @@ class PluginTests: XCTestCase { readOnlyDirectories: [package.path], allowNetworkConnections: [], pkgConfigDirectories: [], + sdkRootPath: nil, fileSystem: localFileSystem, observabilityScope: observability.topScope, callbackQueue: delegateQueue, @@ -738,6 +741,7 @@ class PluginTests: XCTestCase { readOnlyDirectories: [package.path], allowNetworkConnections: [], pkgConfigDirectories: [], + sdkRootPath: try UserToolchain.default.sdkRootPath, fileSystem: localFileSystem, observabilityScope: observability.topScope, callbackQueue: delegateQueue, diff --git a/Tests/FunctionalTests/ResourcesTests.swift b/Tests/FunctionalTests/ResourcesTests.swift index 60857fb8877..81b9ada9132 100644 --- a/Tests/FunctionalTests/ResourcesTests.swift +++ b/Tests/FunctionalTests/ResourcesTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import PackageModel import SPMTestSupport import XCTest @@ -99,21 +100,9 @@ class ResourcesTests: XCTestCase { } } - func testFoundationlessClient() throws { - try fixture(name: "Resources/FoundationlessClient") { fixturePath in - #if os(Linux) && swift(>=5.8) - let pkgPath = fixturePath.appending(components: "AppPkg") - guard let failure = XCTAssertBuildFails(pkgPath) else { - XCTFail("missing expected command execution error") - return - } - // Check that the following code expectedly doesn't compile for lack of 'import Foundation' - XCTAssertMatch(failure.stdout, .contains("print(FooUtils.foo.trimmingCharacters(in: .whitespaces))")) - #endif - } - } - func testSwiftResourceAccessorDoesNotCauseInconsistentImportWarning() throws { + try XCTSkipIf(!UserToolchain.default.supportsWarningsAsErrors(), "skipping because test environment doesn't support warnings as errors") + try fixture(name: "Resources/FoundationlessClient/UtilsWithFoundationPkg") { fixturePath in XCTAssertBuilds( fixturePath, diff --git a/Tests/FunctionalTests/ToolsVersionTests.swift b/Tests/FunctionalTests/ToolsVersionTests.swift index b007beb142d..067f6b893c8 100644 --- a/Tests/FunctionalTests/ToolsVersionTests.swift +++ b/Tests/FunctionalTests/ToolsVersionTests.swift @@ -93,7 +93,7 @@ class ToolsVersionTests: XCTestCase { // Build the primary package. _ = try SwiftPM.Build.execute(packagePath: primaryPath) - let exe = primaryPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent(), "debug", "Primary").pathString + let exe = primaryPath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug", "Primary").pathString // v1 should get selected because v1.0.1 depends on a (way) higher set of tools. XCTAssertEqual(try Process.checkNonZeroExit(args: exe).spm_chomp(), "foo@1.0") diff --git a/Tests/LLBuildManifestTests/LLBuildManifestTests.swift b/Tests/LLBuildManifestTests/LLBuildManifestTests.swift new file mode 100644 index 00000000000..ffe03d6e8db --- /dev/null +++ b/Tests/LLBuildManifestTests/LLBuildManifestTests.swift @@ -0,0 +1,222 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2023 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 struct Basics.AbsolutePath +import class Foundation.PropertyListDecoder +@testable import LLBuildManifest +import SPMTestSupport +import class TSCBasic.InMemoryFileSystem +import XCTest + + +private let testEntitlement = "test-entitlement" + +final class LLBuildManifestTests: XCTestCase { + func testEntitlementsPlist() throws { + let FileType = WriteAuxiliary.EntitlementPlist.self + let inputs = FileType.computeInputs(entitlement: testEntitlement) + XCTAssertEqual(inputs, [.virtual(FileType.name), .virtual(testEntitlement)]) + + let contents = try FileType.getFileContents(inputs: inputs) + let decoder = PropertyListDecoder() + let decodedEntitlements = try decoder.decode([String: Bool].self, from: .init(contents.utf8)) + XCTAssertEqual(decodedEntitlements, [testEntitlement: true]) + + var manifest = LLBuildManifest() + let outputPath = AbsolutePath("/test.plist") + manifest.addEntitlementPlistCommand(entitlement: testEntitlement, outputPath: outputPath) + + let commandName = outputPath.pathString + XCTAssertEqual(manifest.commands.count, 1) + + let command = try XCTUnwrap(manifest.commands[commandName]?.tool as? WriteAuxiliaryFile) + + XCTAssertEqual(command, .init(inputs: inputs, outputFilePath: outputPath)) + } + + func testBasics() throws { + var manifest = LLBuildManifest() + + let root: AbsolutePath = "/some" + + manifest.defaultTarget = "main" + manifest.addPhonyCmd( + name: "C.Foo", + inputs: [ + .file(root.appending(components: "file.c")), + .directory(root.appending(components: "dir")), + .directoryStructure(root.appending(components: "dir", "structure")), + ], + outputs: [.virtual("Foo")] + ) + + manifest.addNode(.virtual("Foo"), toTarget: "main") + + let fs = InMemoryFileSystem() + try LLBuildManifestWriter.write(manifest, at: "/manifest.yaml", fileSystem: fs) + + let contents: String = try fs.readFileContents("/manifest.yaml") + + // FIXME(#5475) - use the platform's preferred separator for directory + // indicators + XCTAssertEqual(contents.replacingOccurrences(of: "\\\\", with: "\\"), """ + client: + name: basic + file-system: device-agnostic + tools: {} + targets: + "main": [""] + default: "main" + nodes: + "\(root.appending(components: "dir", "structure"))/": + is-directory-structure: true + content-exclusion-patterns: [".git",".build"] + commands: + "C.Foo": + tool: phony + inputs: ["\(root.appending(components: "file.c"))","\(root.appending(components: "dir"))/","\(root.appending(components: "dir", "structure"))/"] + outputs: [""] + + + """) + } + + func testShellCommands() throws { + var manifest = LLBuildManifest() + + let root: AbsolutePath = .root + + manifest.defaultTarget = "main" + manifest.addShellCmd( + name: "shelley", + description: "Shelley, Keats, and Byron", + inputs: [ + .file(root.appending(components: "file.in")) + ], + outputs: [ + .file(root.appending(components: "file.out")) + ], + arguments: [ + "foo", "bar", "baz" + ], + environment: [ + "ABC": "DEF", + "G H": "I J K", + ], + workingDirectory: "/wdir", + allowMissingInputs: true + ) + + manifest.addNode(.file("/file.out"), toTarget: "main") + + let fs = InMemoryFileSystem() + try LLBuildManifestWriter.write(manifest, at: "/manifest.yaml", fileSystem: fs) + + let contents: String = try fs.readFileContents("/manifest.yaml") + + XCTAssertEqual(contents.replacingOccurrences(of: "\\\\", with: "\\"), """ + client: + name: basic + file-system: device-agnostic + tools: {} + targets: + "main": ["\(root.appending(components: "file.out"))"] + default: "main" + commands: + "shelley": + tool: shell + inputs: ["\(root.appending(components: "file.in"))"] + outputs: ["\(root.appending(components: "file.out"))"] + description: "Shelley, Keats, and Byron" + args: ["foo","bar","baz"] + env: + "ABC": "DEF" + "G H": "I J K" + working-directory: "/wdir" + allow-missing-inputs: true + + + """) + } + + func testMutatedNodes() throws { + var manifest = LLBuildManifest() + + let root: AbsolutePath = .root + + manifest.addNode(.virtual("C.mutate"), toTarget: "") + let createTimestampNode = Node.virtual("C.create.timestamp", isCommandTimestamp: true) + let mutatedNode = Node.file(root.appending(components: "file.out"), isMutated: true) + + manifest.addShellCmd( + name: "C.create", + description: "C.create", + inputs: [ + .file(root.appending(components: "file.in")) + ], + outputs: [mutatedNode, createTimestampNode], + arguments: [ + "cp", "file.in", "file.out" + ] + ) + + manifest.addShellCmd( + name: "C.mutate", + description: "C.mutate", + inputs: [ + createTimestampNode + ], + outputs: [ + .virtual("C.mutate") + ], + arguments: [ + "touch", "file.out" + ] + ) + + let fs = InMemoryFileSystem() + try LLBuildManifestWriter.write(manifest, at: "/manifest.yaml", fileSystem: fs) + + let contents: String = try fs.readFileContents("/manifest.yaml") + + XCTAssertEqual(contents.replacingOccurrences(of: "\\\\", with: "\\"), """ + client: + name: basic + file-system: device-agnostic + tools: {} + targets: + "": [""] + default: "" + nodes: + "": + is-command-timestamp: true + "\(AbsolutePath("/file.out"))": + is-mutated: true + commands: + "C.create": + tool: shell + inputs: ["\(AbsolutePath("/file.in"))"] + outputs: ["\(AbsolutePath("/file.out"))",""] + description: "C.create" + args: ["cp","file.in","file.out"] + + "C.mutate": + tool: shell + inputs: [""] + outputs: [""] + description: "C.mutate" + args: ["touch","file.out"] + + + """) + } +} diff --git a/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift b/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift index cfccc53de67..26fc46b16d9 100644 --- a/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift +++ b/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift @@ -45,13 +45,13 @@ class GitHubPackageMetadataProviderTests: XCTestCase { XCTAssertNil(GitHubPackageMetadataProvider.apiURL("bad/Hello-World.git")) } - func testGood() throws { - try testWithTemporaryDirectory { tmpPath in + func testGood() async throws { + try await testWithTemporaryDirectory { tmpPath in let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") let apiURL = URL("https://api.github.com/repos/octocat/Hello-World") let releasesURL = URL("https://api.github.com/repos/octocat/Hello-World/releases?per_page=20") - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let handler: LegacyHTTPClient.Handler = { request, _, completion in switch (request.method, request.url) { case (.get, apiURL): @@ -103,7 +103,7 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let provider = GitHubPackageMetadataProvider(configuration: configuration, httpClient: httpClient) defer { XCTAssertNoThrow(try provider.close()) } - let metadata = try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) + let metadata = try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) XCTAssertEqual(metadata.summary, "This your first repo!") XCTAssertEqual(metadata.versions.count, 2) @@ -127,8 +127,8 @@ class GitHubPackageMetadataProviderTests: XCTestCase { } } - func testRepoNotFound() throws { - try testWithTemporaryDirectory { tmpPath in + func testRepoNotFound() async throws { + try await testWithTemporaryDirectory { tmpPath in let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") let handler: LegacyHTTPClient.Handler = { _, _, completion in @@ -143,18 +143,18 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let provider = GitHubPackageMetadataProvider(configuration: configuration, httpClient: httpClient) defer { XCTAssertNoThrow(try provider.close()) } - XCTAssertThrowsError(try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in + await XCTAssertAsyncThrowsError(try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in XCTAssert(error is NotFoundError, "\(error)") } } } - func testOthersNotFound() throws { - try testWithTemporaryDirectory { tmpPath in + func testOthersNotFound() async throws { + try await testWithTemporaryDirectory { tmpPath in let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") let apiURL = URL("https://api.github.com/repos/octocat/Hello-World") - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "GitHub", "metadata.json") let data = try Data(localFileSystem.readFileContents(path).contents) let handler: LegacyHTTPClient.Handler = { request, _, completion in @@ -176,7 +176,7 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let provider = GitHubPackageMetadataProvider(configuration: configuration, httpClient: httpClient) defer { XCTAssertNoThrow(try provider.close()) } - let metadata = try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) + let metadata = try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) XCTAssertEqual(metadata.summary, "This your first repo!") XCTAssertEqual(metadata.versions, []) @@ -187,8 +187,8 @@ class GitHubPackageMetadataProviderTests: XCTestCase { } } - func testPermissionDenied() throws { - try testWithTemporaryDirectory { tmpPath in + func testPermissionDenied() async throws { + try await testWithTemporaryDirectory { tmpPath in let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") let apiURL = URL("https://api.github.com/repos/octocat/Hello-World") @@ -204,14 +204,14 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let provider = GitHubPackageMetadataProvider(configuration: configuration, httpClient: httpClient) defer { XCTAssertNoThrow(try provider.close()) } - XCTAssertThrowsError(try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in + await XCTAssertAsyncThrowsError(try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in XCTAssertEqual(error as? GitHubPackageMetadataProviderError, .permissionDenied(apiURL)) } } } - func testInvalidAuthToken() throws { - try testWithTemporaryDirectory { tmpPath in + func testInvalidAuthToken() async throws { + try await testWithTemporaryDirectory { tmpPath in let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") let apiURL = URL("https://api.github.com/repos/octocat/Hello-World") let authTokens = [AuthTokenType.github("github.com"): "foo"] @@ -234,21 +234,21 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let provider = GitHubPackageMetadataProvider(configuration: configuration, httpClient: httpClient) defer { XCTAssertNoThrow(try provider.close()) } - XCTAssertThrowsError(try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in + await XCTAssertAsyncThrowsError(try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in XCTAssertEqual(error as? GitHubPackageMetadataProviderError, .invalidAuthToken(apiURL)) } } } - func testAPILimit() throws { - try testWithTemporaryDirectory { tmpPath in + func testAPILimit() async throws { + try await testWithTemporaryDirectory { tmpPath in let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") let apiURL = URL("https://api.github.com/repos/octocat/Hello-World") let total = 5 var remaining = total - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "GitHub", "metadata.json") let data = try Data(localFileSystem.readFileContents(path).contents) let handler: LegacyHTTPClient.Handler = { request, _, completion in @@ -280,20 +280,20 @@ class GitHubPackageMetadataProviderTests: XCTestCase { for index in 0 ... total * 2 { if index >= total { - XCTAssertThrowsError(try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in + await XCTAssertAsyncThrowsError(try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString), "should throw error") { error in XCTAssertEqual(error as? GitHubPackageMetadataProviderError, .apiLimitsExceeded(apiURL, total)) } } else { - XCTAssertNoThrow(try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString)) + _ = try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) } } } } } - func testInvalidURL() throws { - try testWithTemporaryDirectory { tmpPath in - try fixture(name: "Collections", createGitRepo: false) { _ in + func testInvalidURL() async throws { + try await testWithTemporaryDirectory { tmpPath in + try await fixture(name: "Collections", createGitRepo: false) { _ in var configuration = GitHubPackageMetadataProvider.Configuration() configuration.cacheDir = tmpPath let provider = GitHubPackageMetadataProvider(configuration: configuration) @@ -301,16 +301,16 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let url = UUID().uuidString let identity = PackageIdentity(urlString: url) - XCTAssertThrowsError(try provider.syncGet(identity: identity, location: url), "should throw error") { error in + await XCTAssertAsyncThrowsError(try await provider.syncGet(identity: identity, location: url), "should throw error") { error in XCTAssertEqual(error as? GitHubPackageMetadataProviderError, .invalidSourceControlURL(url)) } } } } - func testInvalidURL2() throws { - try testWithTemporaryDirectory { tmpPath in - try fixture(name: "Collections", createGitRepo: false) { _ in + func testInvalidURL2() async throws { + try await testWithTemporaryDirectory { tmpPath in + try await fixture(name: "Collections", createGitRepo: false) { _ in var configuration = GitHubPackageMetadataProvider.Configuration() configuration.cacheDir = tmpPath let provider = GitHubPackageMetadataProvider(configuration: configuration) @@ -318,14 +318,14 @@ class GitHubPackageMetadataProviderTests: XCTestCase { let path = AbsolutePath.root let identity = PackageIdentity(path: path) - XCTAssertThrowsError(try provider.syncGet(identity: identity, location: path.pathString), "should throw error") { error in + await XCTAssertAsyncThrowsError(try await provider.syncGet(identity: identity, location: path.pathString), "should throw error") { error in XCTAssertEqual(error as? GitHubPackageMetadataProviderError, .invalidSourceControlURL(path.pathString)) } } } } - func testForRealz() throws { + func testForRealz() async throws { #if ENABLE_GITHUB_NETWORK_TEST #else try XCTSkipIf(true) @@ -347,7 +347,7 @@ class GitHubPackageMetadataProviderTests: XCTestCase { defer { XCTAssertNoThrow(try provider.close()) } for _ in 0 ... 60 { - let metadata = try provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) + let metadata = try await provider.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) XCTAssertNotNil(metadata) XCTAssert(metadata.versions.count > 0) XCTAssert(metadata.keywords!.count > 0) @@ -368,8 +368,8 @@ internal extension GitHubPackageMetadataProvider { } private extension GitHubPackageMetadataProvider { - func syncGet(identity: PackageIdentity, location: String) throws -> Model.PackageBasicMetadata { - try temp_await { callback in + func syncGet(identity: PackageIdentity, location: String) async throws -> Model.PackageBasicMetadata { + try await safe_async { callback in self.get(identity: identity, location: location) { result, _ in callback(result) } } } diff --git a/Tests/PackageCollectionsTests/JSONPackageCollectionProviderTests.swift b/Tests/PackageCollectionsTests/JSONPackageCollectionProviderTests.swift index 7d70a8ce672..994aab6729e 100644 --- a/Tests/PackageCollectionsTests/JSONPackageCollectionProviderTests.swift +++ b/Tests/PackageCollectionsTests/JSONPackageCollectionProviderTests.swift @@ -21,8 +21,8 @@ import SourceControl import SPMTestSupport class JSONPackageCollectionProviderTests: XCTestCase { - func testGood() throws { - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + func testGood() async throws { + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -47,7 +47,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -87,8 +87,8 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testLocalFile() throws { - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + func testLocalFile() async throws { + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good.json") let httpClient = LegacyHTTPClient(handler: { (_, _, _) -> Void in fatalError("should not be called") }) @@ -96,7 +96,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) let source = PackageCollectionsModel.CollectionSource(type: .json, url: path.asURL) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -135,14 +135,14 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testInvalidURL() throws { + func testInvalidURL() async throws { let url = URL("ftp://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) let httpClient = LegacyHTTPClient(handler: { (_, _, _) -> Void in fatalError("should not be called") }) httpClient.configuration.circuitBreakerStrategy = .none httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in guard case .invalidSource(let errorMessage) = error as? JSONPackageCollectionProviderError else { return XCTFail("invalid error \(error)") } @@ -150,7 +150,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { }) } - func testExceedsDownloadSizeLimitHead() throws { + func testExceedsDownloadSizeLimitHead() async throws { let maxSize: Int64 = 50 let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) @@ -167,12 +167,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let configuration = JSONPackageCollectionProvider.Configuration(maximumSizeInBytes: 10) let provider = JSONPackageCollectionProvider(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .responseTooLarge(url, maxSize * 2)) }) } - func testExceedsDownloadSizeLimitGet() throws { + func testExceedsDownloadSizeLimitGet() async throws { let maxSize: Int64 = 50 let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) @@ -196,12 +196,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let configuration = JSONPackageCollectionProvider.Configuration(maximumSizeInBytes: 10) let provider = JSONPackageCollectionProvider(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .responseTooLarge(url, maxSize * 2)) }) } - func testNoContentLengthOnGet() throws { + func testNoContentLengthOnGet() async throws { let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) @@ -216,12 +216,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let configuration = JSONPackageCollectionProvider.Configuration(maximumSizeInBytes: 10) let provider = JSONPackageCollectionProvider(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .invalidResponse(url, "Missing Content-Length header")) }) } - func testExceedsDownloadSizeLimitProgress() throws { + func testExceedsDownloadSizeLimitProgress() async throws { let maxSize: Int64 = 50 let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) @@ -248,12 +248,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let configuration = JSONPackageCollectionProvider.Configuration(maximumSizeInBytes: 10) let provider = JSONPackageCollectionProvider(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? HTTPClientError, .responseTooLarge(maxSize * 2)) }) } - func testUnsuccessfulHead_unavailable() throws { + func testUnsuccessfulHead_unavailable() async throws { let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) let statusCode = Int.random(in: 500 ... 550) // Don't use 404 because it leads to a different error message @@ -268,12 +268,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.circuitBreakerStrategy = .none httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .collectionUnavailable(url, statusCode)) }) } - func testUnsuccessfulGet_unavailable() throws { + func testUnsuccessfulGet_unavailable() async throws { let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) let statusCode = Int.random(in: 500 ... 550) // Don't use 404 because it leads to a different error message @@ -294,12 +294,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.circuitBreakerStrategy = .none httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .collectionUnavailable(url, statusCode)) }) } - func testUnsuccessfulHead_notFound() throws { + func testUnsuccessfulHead_notFound() async throws { let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) @@ -313,12 +313,12 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.circuitBreakerStrategy = .none httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .collectionNotFound(url)) }) } - func testUnsuccessfulGet_notFound() throws { + func testUnsuccessfulGet_notFound() async throws { let url = URL("https://www.test.com/collection.json") let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) @@ -338,14 +338,14 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.circuitBreakerStrategy = .none httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .collectionNotFound(url)) }) } - func testBadJSON() throws { + func testBadJSON() async throws { let url = URL("https://www.test.com/collection.json") - let data = "blah".data(using: .utf8)! + let data = Data("blah".utf8) let handler: LegacyHTTPClient.Handler = { request, _, completion in XCTAssertEqual(request.url, url, "url should match") @@ -366,15 +366,15 @@ class JSONPackageCollectionProviderTests: XCTestCase { httpClient.configuration.retryStrategy = .none let provider = JSONPackageCollectionProvider(httpClient: httpClient) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in XCTAssertEqual(error as? JSONPackageCollectionProviderError, .invalidJSON(url)) }) } - func testSignedGood() throws { + func testSignedGood() async throws { try skipIfSignatureCheckNotSupported() - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -402,7 +402,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let signatureValidator = MockCollectionSignatureValidator(["Sample Package Collection"]) let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -446,8 +446,8 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testSigned_skipSignatureCheck() throws { - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + func testSigned_skipSignatureCheck() async throws { + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -475,7 +475,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator) // Skip signature check let source = PackageCollectionsModel.CollectionSource(type: .json, url: url, skipSignatureCheck: true) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -515,10 +515,10 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testSigned_noTrustedRootCertsConfigured() throws { + func testSigned_noTrustedRootCertsConfigured() async throws { try skipIfSignatureCheckNotSupported() - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -546,7 +546,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in switch error { case PackageCollectionError.cannotVerifySignature: break @@ -557,10 +557,10 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testSignedBad() throws { + func testSignedBad() async throws { try skipIfSignatureCheckNotSupported() - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -589,7 +589,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in switch error { case PackageCollectionError.invalidSignature: break @@ -600,10 +600,10 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testSignedLocalFile() throws { + func testSignedLocalFile() async throws { try skipIfSignatureCheckNotSupported() - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let httpClient = LegacyHTTPClient(handler: { (_, _, _) -> Void in fatalError("should not be called") }) @@ -615,7 +615,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator) let source = PackageCollectionsModel.CollectionSource(type: .json, url: path.asURL) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -654,8 +654,8 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testRequiredSigningGood() throws { - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + func testRequiredSigningGood() async throws { + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -688,7 +688,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator, sourceCertPolicy: sourceCertPolicy) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -728,8 +728,8 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testRequiredSigningMultiplePoliciesGood() throws { - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + func testRequiredSigningMultiplePoliciesGood() async throws { + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good_signed.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -767,7 +767,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { let provider = JSONPackageCollectionProvider(httpClient: httpClient, signatureValidator: signatureValidator, sourceCertPolicy: sourceCertPolicy) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - let collection = try temp_await { callback in provider.get(source, callback: callback) } + let collection = try await provider.get(source) XCTAssertEqual(collection.name, "Sample Package Collection") XCTAssertEqual(collection.overview, "This is a sample package collection listing made-up packages.") @@ -807,8 +807,8 @@ class JSONPackageCollectionProviderTests: XCTestCase { } } - func testMissingRequiredSignature() throws { - try fixture(name: "Collections", createGitRepo: false) { fixturePath in + func testMissingRequiredSignature() async throws { + try await fixture(name: "Collections", createGitRepo: false) { fixturePath in let path = fixturePath.appending(components: "JSON", "good.json") let url = URL("https://www.test.com/collection.json") let data: Data = try localFileSystem.readFileContents(path) @@ -842,7 +842,7 @@ class JSONPackageCollectionProviderTests: XCTestCase { sourceCertPolicy: sourceCertPolicy) let source = PackageCollectionsModel.CollectionSource(type: .json, url: url) - XCTAssertThrowsError(try temp_await { callback in provider.get(source, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await provider.get(source), "expected error", { error in switch error { case PackageCollectionError.missingSignature: break diff --git a/Tests/PackageCollectionsTests/PackageCollectionsSourcesStorageTest.swift b/Tests/PackageCollectionsTests/PackageCollectionsSourcesStorageTest.swift index c10581cddee..43f32c69f14 100644 --- a/Tests/PackageCollectionsTests/PackageCollectionsSourcesStorageTest.swift +++ b/Tests/PackageCollectionsTests/PackageCollectionsSourcesStorageTest.swift @@ -18,24 +18,24 @@ import XCTest import class TSCBasic.InMemoryFileSystem final class PackageCollectionsSourcesStorageTest: XCTestCase { - func testHappyCase() throws { + func testHappyCase() async throws { let mockFileSystem = InMemoryFileSystem() let storage = FilePackageCollectionsSourcesStorage(fileSystem: mockFileSystem) - try assertHappyCase(storage: storage) + try await assertHappyCase(storage: storage) let buffer = try mockFileSystem.readFileContents(storage.path) XCTAssertNotEqual(buffer.count, 0, "expected file to be written") print(buffer) } - func testRealFile() throws { - try testWithTemporaryDirectory { tmpPath in + func testRealFile() async throws { + try await testWithTemporaryDirectory { tmpPath in let fileSystem = localFileSystem let path = tmpPath.appending("test.json") let storage = FilePackageCollectionsSourcesStorage(fileSystem: fileSystem, path: path) - try assertHappyCase(storage: storage) + try await assertHappyCase(storage: storage) let buffer = try fileSystem.readFileContents(storage.path) XCTAssertNotEqual(buffer.count, 0, "expected file to be written") @@ -43,78 +43,78 @@ final class PackageCollectionsSourcesStorageTest: XCTestCase { } } - func assertHappyCase(storage: PackageCollectionsSourcesStorage) throws { + func assertHappyCase(storage: PackageCollectionsSourcesStorage) async throws { let sources = makeMockSources() - try sources.forEach { source in - _ = try temp_await { callback in storage.add(source: source, order: nil, callback: callback) } + for source in sources { + _ = try await storage.add(source: source, order: nil) } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, sources.count, "sources should match") } let remove = sources.enumerated().filter { index, _ in index % 2 == 0 }.map { $1 } - try remove.forEach { source in - _ = try temp_await { callback in storage.remove(source: source, callback: callback) } + for source in remove { + _ = try await storage.remove(source: source) } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, sources.count - remove.count, "sources should match") } let remaining = sources.filter { !remove.contains($0) } - try sources.forEach { source in - XCTAssertEqual(try temp_await { callback in storage.exists(source: source, callback: callback) }, remaining.contains(source)) + for source in sources { + try await XCTAssertAsyncTrue(try await storage.exists(source: source) == remaining.contains(source)) } do { - _ = try temp_await { callback in storage.move(source: remaining.last!, to: 0, callback: callback) } - let list = try temp_await { callback in storage.list(callback: callback) } + _ = try await storage.move(source: remaining.last!, to: 0) + let list = try await storage.list() XCTAssertEqual(list.count, remaining.count, "sources should match") XCTAssertEqual(list.first, remaining.last, "item should match") } do { - _ = try temp_await { callback in storage.move(source: remaining.last!, to: remaining.count - 1, callback: callback) } - let list = try temp_await { callback in storage.list(callback: callback) } + _ = try await storage.move(source: remaining.last!, to: remaining.count - 1) + let list = try await storage.list() XCTAssertEqual(list.count, remaining.count, "sources should match") XCTAssertEqual(list.last, remaining.last, "item should match") } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() var source = list.first! source.isTrusted = !(source.isTrusted ?? false) - _ = try temp_await { callback in storage.update(source: source, callback: callback) } - let listAfter = try temp_await { callback in storage.list(callback: callback) } + _ = try await storage.update(source: source) + let listAfter = try await storage.list() XCTAssertEqual(source.isTrusted, listAfter.first!.isTrusted, "isTrusted should match") } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() var source = list.first! source.skipSignatureCheck = !source.skipSignatureCheck - _ = try temp_await { callback in storage.update(source: source, callback: callback) } - let listAfter = try temp_await { callback in storage.list(callback: callback) } + _ = try await storage.update(source: source) + let listAfter = try await storage.list() XCTAssertEqual(source.skipSignatureCheck, listAfter.first!.skipSignatureCheck, "skipSignatureCheck should match") } } - func testFileDeleted() throws { + func testFileDeleted() async throws { let mockFileSystem = InMemoryFileSystem() let storage = FilePackageCollectionsSourcesStorage(fileSystem: mockFileSystem) let sources = makeMockSources() - try sources.forEach { source in - _ = try temp_await { callback in storage.add(source: source, order: nil, callback: callback) } + for source in sources { + _ = try await storage.add(source: source, order: nil) } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, sources.count, "collections should match") } @@ -122,23 +122,23 @@ final class PackageCollectionsSourcesStorageTest: XCTestCase { XCTAssertFalse(mockFileSystem.exists(storage.path), "expected file to be deleted") do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, 0, "collections should match") } } - func testFileEmpty() throws { + func testFileEmpty() async throws { let mockFileSystem = InMemoryFileSystem() let storage = FilePackageCollectionsSourcesStorage(fileSystem: mockFileSystem) let sources = makeMockSources() - try sources.forEach { source in - _ = try temp_await { callback in storage.add(source: source, order: nil, callback: callback) } + for source in sources { + _ = try await storage.add(source: source, order: nil) } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, sources.count, "collections should match") } @@ -147,22 +147,22 @@ final class PackageCollectionsSourcesStorageTest: XCTestCase { XCTAssertEqual(buffer.count, 0, "expected file to be empty") do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, 0, "collections should match") } } - func testFileCorrupt() throws { + func testFileCorrupt() async throws { let mockFileSystem = InMemoryFileSystem() let storage = FilePackageCollectionsSourcesStorage(fileSystem: mockFileSystem) let sources = makeMockSources() - try sources.forEach { source in - _ = try temp_await { callback in storage.add(source: source, order: nil, callback: callback) } + for source in sources { + _ = try await storage.add(source: source, order: nil) } - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, sources.count, "collections should match") try mockFileSystem.writeFileContents(storage.path, string: "{") @@ -171,7 +171,7 @@ final class PackageCollectionsSourcesStorageTest: XCTestCase { XCTAssertNotEqual(buffer.count, 0, "expected file to be written") print(buffer) - XCTAssertThrowsError(try temp_await { callback in storage.list(callback: callback) }, "expected an error", { error in + await XCTAssertAsyncThrowsError(try await storage.list(), "expected an error", { error in XCTAssert(error is DecodingError, "expected error to match") }) } diff --git a/Tests/PackageCollectionsTests/PackageCollectionsStorageTests.swift b/Tests/PackageCollectionsTests/PackageCollectionsStorageTests.swift index 2dc15570b1c..462e198658c 100644 --- a/Tests/PackageCollectionsTests/PackageCollectionsStorageTests.swift +++ b/Tests/PackageCollectionsTests/PackageCollectionsStorageTests.swift @@ -17,47 +17,47 @@ import tsan_utils import XCTest class PackageCollectionsStorageTests: XCTestCase { - func testHappyCase() throws { - try testWithTemporaryDirectory { tmpPath in + func testHappyCase() async throws { + try await testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let storage = SQLitePackageCollectionsStorage(path: path) defer { XCTAssertNoThrow(try storage.close()) } let mockSources = makeMockSources() - try mockSources.forEach { source in - XCTAssertThrowsError(try temp_await { callback in storage.get(identifier: .init(from: source), callback: callback) }, "expected error", { error in + for source in mockSources { + await XCTAssertAsyncThrowsError(try await storage.get(identifier: .init(from: source)), "expected error", { error in XCTAssert(error is NotFoundError, "Expected NotFoundError") }) } let mockCollections = makeMockCollections(count: 50) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - try mockCollections.forEach { collection in - let retVal = try temp_await { callback in storage.get(identifier: collection.identifier, callback: callback) } + for collection in mockCollections { + let retVal = try await storage.get(identifier: collection.identifier) XCTAssertEqual(retVal.identifier, collection.identifier) } do { - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, mockCollections.count) } do { let count = Int.random(in: 1 ..< mockCollections.count) - let list = try temp_await { callback in storage.list(identifiers: mockCollections.prefix(count).map { $0.identifier }, callback: callback) } + let list = try await storage.list(identifiers: mockCollections.prefix(count).map { $0.identifier }) XCTAssertEqual(list.count, count) } do { - _ = try temp_await { callback in storage.remove(identifier: mockCollections.first!.identifier, callback: callback) } - let list = try temp_await { callback in storage.list(callback: callback) } + _ = try await storage.remove(identifier: mockCollections.first!.identifier) + let list = try await storage.list() XCTAssertEqual(list.count, mockCollections.count - 1) } - XCTAssertThrowsError(try temp_await { callback in storage.get(identifier: mockCollections.first!.identifier, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await storage.get(identifier: mockCollections.first!.identifier), "expected error", { error in XCTAssert(error is NotFoundError, "Expected NotFoundError") }) @@ -69,24 +69,24 @@ class PackageCollectionsStorageTests: XCTestCase { } } - func testFileDeleted() throws { + func testFileDeleted() async throws { #if os(Windows) try XCTSkipIf(true, "open files cannot be deleted on Windows") #endif try XCTSkipIf(is_tsan_enabled()) - try testWithTemporaryDirectory { tmpPath in + try await testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let storage = SQLitePackageCollectionsStorage(path: path) defer { XCTAssertNoThrow(try storage.close()) } let mockCollections = makeMockCollections(count: 3) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - try mockCollections.forEach { collection in - let retVal = try temp_await { callback in storage.get(identifier: collection.identifier, callback: callback) } + for collection in mockCollections { + let retVal = try await storage.get(identifier: collection.identifier) XCTAssertEqual(retVal.identifier, collection.identifier) } @@ -99,36 +99,36 @@ class PackageCollectionsStorageTests: XCTestCase { try storage.fileSystem.removeFileTree(storagePath) storage.resetCache() - XCTAssertThrowsError(try temp_await { callback in storage.get(identifier: mockCollections.first!.identifier, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await storage.get(identifier: mockCollections.first!.identifier), "expected error", { error in XCTAssert(error is NotFoundError, "Expected NotFoundError") }) - XCTAssertNoThrow(try temp_await { callback in storage.put(collection: mockCollections.first!, callback: callback) }) - let retVal = try temp_await { callback in storage.get(identifier: mockCollections.first!.identifier, callback: callback) } + _ = try await storage.put(collection: mockCollections.first!) + let retVal = try await storage.get(identifier: mockCollections.first!.identifier) XCTAssertEqual(retVal.identifier, mockCollections.first!.identifier) XCTAssertTrue(storage.fileSystem.exists(storagePath), "expected file to exist at \(storagePath)") } } - func testFileCorrupt() throws { + func testFileCorrupt() async throws { #if os(Windows) try XCTSkipIf(true, "open files cannot be deleted on Windows") #endif try XCTSkipIf(is_tsan_enabled()) - try testWithTemporaryDirectory { tmpPath in + try await testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let storage = SQLitePackageCollectionsStorage(path: path) defer { XCTAssertNoThrow(try storage.close()) } let mockCollections = makeMockCollections(count: 3) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - try mockCollections.forEach { collection in - let retVal = try temp_await { callback in storage.get(identifier: collection.identifier, callback: callback) } + for collection in mockCollections { + let retVal = try await storage.get(identifier: collection.identifier) XCTAssertEqual(retVal.identifier, collection.identifier) } @@ -143,17 +143,17 @@ class PackageCollectionsStorageTests: XCTestCase { let storage2 = SQLitePackageCollectionsStorage(path: path) defer { XCTAssertNoThrow(try storage2.close()) } - XCTAssertThrowsError(try temp_await { callback in storage2.get(identifier: mockCollections.first!.identifier, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await storage2.get(identifier: mockCollections.first!.identifier), "expected error", { error in XCTAssert("\(error)".contains("is not a database"), "Expected file is not a database error") }) - XCTAssertThrowsError(try temp_await { callback in storage2.put(collection: mockCollections.first!, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await storage2.put(collection: mockCollections.first!), "expected error", { error in XCTAssert("\(error)".contains("is not a database"), "Expected file is not a database error") }) } } - func testListLessThanBatch() throws { + func testListLessThanBatch() async throws { var configuration = SQLitePackageCollectionsStorage.Configuration() configuration.batchSize = 10 let storage = SQLitePackageCollectionsStorage(location: .memory, configuration: configuration) @@ -161,15 +161,15 @@ class PackageCollectionsStorageTests: XCTestCase { let count = configuration.batchSize / 2 let mockCollections = makeMockCollections(count: count) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, mockCollections.count) } - func testListNonBatching() throws { + func testListNonBatching() async throws { var configuration = SQLitePackageCollectionsStorage.Configuration() configuration.batchSize = 10 let storage = SQLitePackageCollectionsStorage(location: .memory, configuration: configuration) @@ -177,15 +177,15 @@ class PackageCollectionsStorageTests: XCTestCase { let count = Int(Double(configuration.batchSize) * 2.5) let mockCollections = makeMockCollections(count: count) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - let list = try temp_await { callback in storage.list(callback: callback) } + let list = try await storage.list() XCTAssertEqual(list.count, mockCollections.count) } - func testListBatching() throws { + func testListBatching() async throws { var configuration = SQLitePackageCollectionsStorage.Configuration() configuration.batchSize = 10 let storage = SQLitePackageCollectionsStorage(location: .memory, configuration: configuration) @@ -193,46 +193,46 @@ class PackageCollectionsStorageTests: XCTestCase { let count = Int(Double(configuration.batchSize) * 2.5) let mockCollections = makeMockCollections(count: count) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - let list = try temp_await { callback in storage.list(identifiers: mockCollections.map { $0.identifier }, callback: callback) } + let list = try await storage.list(identifiers: mockCollections.map { $0.identifier }) XCTAssertEqual(list.count, mockCollections.count) } - func testPutUpdates() throws { + func testPutUpdates() async throws { let storage = SQLitePackageCollectionsStorage(location: .memory) defer { XCTAssertNoThrow(try storage.close()) } let mockCollections = makeMockCollections(count: 3) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } - let list = try temp_await { callback in storage.list(identifiers: mockCollections.map { $0.identifier }, callback: callback) } + let list = try await storage.list(identifiers: mockCollections.map { $0.identifier }) XCTAssertEqual(list.count, mockCollections.count) - _ = try temp_await { callback in storage.put(collection: mockCollections.last!, callback: callback) } + _ = try await storage.put(collection: mockCollections.last!) XCTAssertEqual(list.count, mockCollections.count) } - func testPopulateTargetTrie() throws { - try testWithTemporaryDirectory { tmpPath in + func testPopulateTargetTrie() async throws { + try await testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let storage = SQLitePackageCollectionsStorage(path: path) defer { XCTAssertNoThrow(try storage.close()) } let mockCollections = makeMockCollections(count: 3) - try mockCollections.forEach { collection in - _ = try temp_await { callback in storage.put(collection: collection, callback: callback) } + for collection in mockCollections { + _ = try await storage.put(collection: collection) } let version = mockCollections.last!.packages.last!.versions.last! let targetName = version.defaultManifest!.targets.last!.name do { - let searchResult = try temp_await { callback in storage.searchTargets(query: targetName, type: .exactMatch, callback: callback) } + let searchResult = try await storage.searchTargets(query: targetName, type: .exactMatch) XCTAssert(searchResult.items.count > 0, "should get results") } @@ -243,9 +243,9 @@ class PackageCollectionsStorageTests: XCTestCase { // populateTargetTrie is called in `.init`; call it again explicitly so we know when it's finished do { - try temp_await { callback in storage2.populateTargetTrie(callback: callback) } + try await storage2.populateTargetTrie() - let searchResult = try temp_await { callback in storage2.searchTargets(query: targetName, type: .exactMatch, callback: callback) } + let searchResult = try await storage2.searchTargets(query: targetName, type: .exactMatch) XCTAssert(searchResult.items.count > 0, "should get results") } catch { // It's possible that some platforms don't have support FTS diff --git a/Tests/PackageCollectionsTests/PackageCollectionsTests.swift b/Tests/PackageCollectionsTests/PackageCollectionsTests.swift index 64657b591de..e1d3ce14fed 100644 --- a/Tests/PackageCollectionsTests/PackageCollectionsTests.swift +++ b/Tests/PackageCollectionsTests/PackageCollectionsTests.swift @@ -12,6 +12,7 @@ import Foundation import XCTest +import SPMTestSupport import Basics @testable import PackageCollections @@ -21,7 +22,7 @@ import SourceControl import struct TSCUtility.Version final class PackageCollectionsTests: XCTestCase { - func testUpdateAuthTokens() throws { + func testUpdateAuthTokens() async throws { let authTokens = ThreadSafeKeyValueStore() let configuration = PackageCollections.Configuration(authTokens: { authTokens.get() }) @@ -56,7 +57,7 @@ final class PackageCollectionsTests: XCTestCase { } } - func testBasicRegistration() throws { + func testBasicRegistration() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -69,21 +70,21 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list, mockCollections, "list count should match") } } - func testAddDuplicates() throws { + func testAddDuplicates() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -97,21 +98,21 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } - _ = try temp_await { callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) } + _ = try await packageCollections.addCollection(mockCollection.source, order: nil) + _ = try await packageCollections.addCollection(mockCollection.source, order: nil) + _ = try await packageCollections.addCollection(mockCollection.source, order: nil) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 1, "list count should match") } } - func testAddUnsigned() throws { + func testAddUnsigned() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -125,36 +126,35 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } // User trusted - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[0].source, order: nil, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + _ = try await packageCollections.addCollection(mockCollections[0].source, order: nil, trustConfirmationProvider: { _, cb in cb(true) }) // User untrusted - XCTAssertThrowsError( - try temp_await { callback in - packageCollections.addCollection(mockCollections[1].source, order: nil, trustConfirmationProvider: { _, cb in cb(false) }, callback: callback) - }) { error in + await XCTAssertAsyncThrowsError( + try await packageCollections.addCollection(mockCollections[1].source, order: nil, trustConfirmationProvider: { _, cb in cb(false) }) + ) { error in guard case PackageCollectionError.untrusted = error else { return XCTFail("Expected PackageCollectionError.untrusted") } } // User preference unknown - XCTAssertThrowsError( - try temp_await { callback in packageCollections.addCollection(mockCollections[2].source, order: nil, trustConfirmationProvider: nil, callback: callback) }) { error in + await XCTAssertAsyncThrowsError( + try await packageCollections.addCollection(mockCollections[2].source, order: nil, trustConfirmationProvider: nil)) { error in guard case PackageCollectionError.trustConfirmationRequired = error else { return XCTFail("Expected PackageCollectionError.trustConfirmationRequired") } } do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 1, "list count should match") } } - func testInvalidCollectionNotAdded() throws { + func testInvalidCollectionNotAdded() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -170,29 +170,28 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") - let sources = try temp_await { callback in storage.sources.list(callback: callback) } + let sources = try await storage.sources.list() XCTAssertEqual(sources.count, 0, "sources should be empty") } // add fails because collection is not found - guard case .failure(let error) = temp_await({ callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) }), - error is NotFoundError else { - return XCTFail("expected error") + await XCTAssertAsyncThrowsError(try await packageCollections.addCollection(mockCollection.source, order: nil)) { error in + XCTAssert(error is NotFoundError) } do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list count should match") - let sources = try temp_await { callback in storage.sources.list(callback: callback) } + let sources = try await storage.sources.list() XCTAssertEqual(sources.count, 0, "sources should be empty") } } - func testCollectionPendingTrustConfirmIsKeptOnAdd() throws { + func testCollectionPendingTrustConfirmIsKeptOnAdd() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -208,29 +207,28 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") - let sources = try temp_await { callback in storage.sources.list(callback: callback) } + let sources = try await storage.sources.list() XCTAssertEqual(sources.count, 0, "sources should be empty") } // add fails because collection requires trust confirmation - guard case .failure(let error) = temp_await({ callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) }), - case PackageCollectionError.trustConfirmationRequired = error else { - return XCTFail("expected error") + await XCTAssertAsyncThrowsError(try await packageCollections.addCollection(mockCollection.source, order: nil)) { error in + XCTAssert(error as? PackageCollectionError == PackageCollectionError.trustConfirmationRequired) } do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list count should match") - let sources = try temp_await { callback in storage.sources.list(callback: callback) } + let sources = try await storage.sources.list() XCTAssertEqual(sources.count, 1, "sources should match") } } - func testCollectionWithInvalidSignatureNotAdded() throws { + func testCollectionWithInvalidSignatureNotAdded() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -246,29 +244,28 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") - let sources = try temp_await { callback in storage.sources.list(callback: callback) } + let sources = try await storage.sources.list() XCTAssertEqual(sources.count, 0, "sources should be empty") } // add fails because collection's signature is invalid - guard case .failure(let error) = temp_await({ callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) }), - case PackageCollectionError.invalidSignature = error else { - return XCTFail("expected PackageCollectionError.invalidSignature") + await XCTAssertAsyncThrowsError(try await packageCollections.addCollection(mockCollection.source, order: nil)) { error in + XCTAssert((error as? PackageCollectionError) == PackageCollectionError.invalidSignature) } do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list count should match") - let sources = try temp_await { callback in storage.sources.list(callback: callback) } + let sources = try await storage.sources.list() XCTAssertEqual(sources.count, 0, "sources should be empty") } } - func testDelete() throws { + func testDelete() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -281,45 +278,45 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list, mockCollections, "list count should match") } do { - _ = try temp_await { callback in packageCollections.removeCollection(mockCollections.first!.source, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.removeCollection(mockCollections.first!.source) + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count - 1, "list count should match") } do { - _ = try temp_await { callback in packageCollections.removeCollection(mockCollections.first!.source, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.removeCollection(mockCollections.first!.source) + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count - 1, "list count should match") } do { - _ = try temp_await { callback in packageCollections.removeCollection(mockCollections[mockCollections.count - 1].source, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.removeCollection(mockCollections[mockCollections.count - 1].source) + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count - 2, "list count should match") } do { let unknownSource = makeMockSources(count: 1).first! - _ = try temp_await { callback in packageCollections.removeCollection(unknownSource, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.removeCollection(unknownSource) + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count - 2, "list should be empty") } } - func testDeleteFromBothStorages() throws { + func testDeleteFromBothStorages() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -333,28 +330,28 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } - _ = try temp_await { callback in packageCollections.addCollection(mockCollection.source, order: nil, callback: callback) } + _ = try await packageCollections.addCollection(mockCollection.source, order: nil) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 1, "list count should match") } do { - _ = try temp_await { callback in packageCollections.removeCollection(mockCollection.source, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.removeCollection(mockCollection.source) + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list count should match") // check if exists in storage - XCTAssertThrowsError(try temp_await { callback in storage.collections.get(identifier: mockCollection.identifier, callback: callback) }, "expected error") + await XCTAssertAsyncThrowsError(try await storage.collections.get(identifier: mockCollection.identifier), "expected error") } } - func testOrdering() throws { + func testOrdering() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -367,18 +364,18 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[0].source, order: 0, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[1].source, order: 1, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[2].source, order: 2, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[3].source, order: Int.min, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[4].source, order: Int.max, callback: callback) } + _ = try await packageCollections.addCollection(mockCollections[0].source, order: 0) + _ = try await packageCollections.addCollection(mockCollections[1].source, order: 1) + _ = try await packageCollections.addCollection(mockCollections[2].source, order: 2) + _ = try await packageCollections.addCollection(mockCollections[3].source, order: Int.min) + _ = try await packageCollections.addCollection(mockCollections[4].source, order: Int.max) - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 5, "list count should match") let expectedOrder = [ @@ -398,12 +395,12 @@ final class PackageCollectionsTests: XCTestCase { // bump the order do { - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[5].source, order: 2, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[6].source, order: 2, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[7].source, order: 0, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[8].source, order: -1, callback: callback) } + _ = try await packageCollections.addCollection(mockCollections[5].source, order: 2) + _ = try await packageCollections.addCollection(mockCollections[6].source, order: 2) + _ = try await packageCollections.addCollection(mockCollections[7].source, order: 0) + _ = try await packageCollections.addCollection(mockCollections[8].source, order: -1) - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 9, "list count should match") let expectedOrder = [ @@ -425,7 +422,7 @@ final class PackageCollectionsTests: XCTestCase { } } - func testReorder() throws { + func testReorder() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -438,16 +435,16 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[0].source, order: 0, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[1].source, order: 1, callback: callback) } - _ = try temp_await { callback in packageCollections.addCollection(mockCollections[2].source, order: 2, callback: callback) } + _ = try await packageCollections.addCollection(mockCollections[0].source, order: 0) + _ = try await packageCollections.addCollection(mockCollections[1].source, order: 1) + _ = try await packageCollections.addCollection(mockCollections[2].source, order: 2) - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 3, "list count should match") let expectedOrder = [ @@ -463,8 +460,8 @@ final class PackageCollectionsTests: XCTestCase { } do { - _ = try temp_await { callback in packageCollections.moveCollection(mockCollections[2].source, to: -1, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.moveCollection(mockCollections[2].source, to: -1) + let list = try await packageCollections.listCollections() let expectedOrder = [ mockCollections[0].identifier: 0, @@ -479,8 +476,8 @@ final class PackageCollectionsTests: XCTestCase { } do { - _ = try temp_await { callback in packageCollections.moveCollection(mockCollections[2].source, to: Int.max, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.moveCollection(mockCollections[2].source, to: Int.max) + let list = try await packageCollections.listCollections() let expectedOrder = [ mockCollections[0].identifier: 0, @@ -495,8 +492,8 @@ final class PackageCollectionsTests: XCTestCase { } do { - _ = try temp_await { callback in packageCollections.moveCollection(mockCollections[2].source, to: 0, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.moveCollection(mockCollections[2].source, to: 0) + let list = try await packageCollections.listCollections() let expectedOrder = [ mockCollections[0].identifier: 1, @@ -511,8 +508,8 @@ final class PackageCollectionsTests: XCTestCase { } do { - _ = try temp_await { callback in packageCollections.moveCollection(mockCollections[2].source, to: 1, callback: callback) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + try await packageCollections.moveCollection(mockCollections[2].source, to: 1) + let list = try await packageCollections.listCollections() let expectedOrder = [ mockCollections[0].identifier: 0, @@ -527,7 +524,7 @@ final class PackageCollectionsTests: XCTestCase { } } - func testUpdateTrust() throws { + func testUpdateTrust() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -541,15 +538,15 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } // User preference unknown - collection not saved to storage - _ = try? temp_await { callback in packageCollections.addCollection(mockCollections.first!.source, order: nil, trustConfirmationProvider: nil, callback: callback) } + _ = try? await packageCollections.addCollection(mockCollections.first!.source, order: nil, trustConfirmationProvider: nil) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } @@ -557,28 +554,28 @@ final class PackageCollectionsTests: XCTestCase { // Update to trust the source. It will trigger a collection refresh which will save collection to storage. source.isTrusted = true - _ = try temp_await { callback in packageCollections.updateCollection(source, callback: callback) } + _ = try await packageCollections.updateCollection(source) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 1, "list count should match") } // Update to untrust the source. It will trigger a collection refresh which will remove collection from storage. source.isTrusted = false - XCTAssertThrowsError(try temp_await { callback in packageCollections.updateCollection(source, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await packageCollections.updateCollection(source)) { error in guard case PackageCollectionError.untrusted = error else { return XCTFail("Expected PackageCollectionError.untrusted") } } do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } } - func testList() throws { + func testList() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -592,15 +589,15 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([mockPackage.identity: mockMetadata]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - func testListSubset() throws { + func testListSubset() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -614,16 +611,16 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([mockPackage.identity: mockMetadata]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } let expectedCollections = Set([mockCollections.first!.identifier, mockCollections.last!.identifier]) - let list = try temp_await { callback in packageCollections.listCollections(identifiers: expectedCollections, callback: callback) } + let list = try await packageCollections.listCollections(identifiers: expectedCollections) XCTAssertEqual(list.count, expectedCollections.count, "list count should match") } - func testListPerformance() throws { + func testListPerformance() async throws { #if ENABLE_COLLECTION_PERF_TESTS #else try XCTSkipIf(true) @@ -642,23 +639,18 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([mockPackage.identity: mockMetadata]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - let sync = DispatchGroup() - mockCollections.forEach { collection in - sync.enter() - packageCollections.addCollection(collection.source, order: nil) { _ in - sync.leave() - } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - sync.wait() let start = Date() - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") let delta = Date().timeIntervalSince(start) XCTAssert(delta < 1.0, "should list quickly, took \(delta)") } - func testPackageSearch() throws { + func testPackageSearch() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -732,67 +724,67 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } do { // search by package name - let searchResult = try temp_await { callback in packageCollections.findPackages(mockManifest.packageName, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockManifest.packageName) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "list count should match") } do { // search by package description/summary - let searchResult = try temp_await { callback in packageCollections.findPackages(mockPackage.summary!, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockPackage.summary!) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "list count should match") } do { // search by package keywords - let searchResult = try temp_await { callback in packageCollections.findPackages(mockPackage.keywords!.first!, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockPackage.keywords!.first!) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "list count should match") } do { // search by package repository url - let searchResult = try temp_await { callback in packageCollections.findPackages(mockPackage.location, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockPackage.location) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") } do { // search by package identity - let searchResult = try temp_await { callback in packageCollections.findPackages(mockPackage.identity.description, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockPackage.identity.description) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") } do { // search by product name - let searchResult = try temp_await { callback in packageCollections.findPackages(mockProducts.first!.name, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockProducts.first!.name) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "list count should match") } do { // search by target name - let searchResult = try temp_await { callback in packageCollections.findPackages(mockTargets.first!.name, callback: callback) } + let searchResult = try await packageCollections.findPackages(mockTargets.first!.name) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") } do { // empty search - let searchResult = try temp_await { callback in packageCollections.findPackages(UUID().uuidString, callback: callback) } + let searchResult = try await packageCollections.findPackages(UUID().uuidString) XCTAssertEqual(searchResult.items.count, 0, "list count should match") } } - func testPackageSearchPerformance() throws { + func testPackageSearchPerformance() async throws { #if ENABLE_COLLECTION_PERF_TESTS #else try XCTSkipIf(true) @@ -809,25 +801,20 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - let sync = DispatchGroup() - mockCollections.forEach { collection in - sync.enter() - packageCollections.addCollection(collection.source, order: nil) { _ in - sync.leave() - } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - sync.wait() // search by package name let start = Date() let repoName = mockCollections.last!.packages.last!.identity.description - let searchResult = try temp_await { callback in packageCollections.findPackages(repoName, callback: callback) } + let searchResult = try await packageCollections.findPackages(repoName) XCTAssert(searchResult.items.count > 0, "should get results") let delta = Date().timeIntervalSince(start) XCTAssert(delta < 1.0, "should search quickly, took \(delta)") } - func testTargetsSearch() throws { + func testTargetsSearch() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -901,13 +888,13 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } do { // search by exact target name - let searchResult = try temp_await { callback in packageCollections.findTargets(mockTargets.first!.name, searchType: .exactMatch, callback: callback) } + let searchResult = try await packageCollections.findTargets(mockTargets.first!.name, searchType: .exactMatch) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.packages.map { $0.identity }, [mockPackage.identity], "packages should match") XCTAssertEqual(searchResult.items.first?.packages.flatMap { $0.collections }.sorted(), expectedCollectionsIdentifiers, "collections should match") @@ -915,7 +902,7 @@ final class PackageCollectionsTests: XCTestCase { do { // search by prefix target name - let searchResult = try temp_await { callback in packageCollections.findTargets(String(mockTargets.first!.name.prefix(mockTargets.first!.name.count - 1)), searchType: .prefix, callback: callback) } + let searchResult = try await packageCollections.findTargets(String(mockTargets.first!.name.prefix(mockTargets.first!.name.count - 1)), searchType: .prefix) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.packages.map { $0.identity }, [mockPackage.identity], "packages should match") XCTAssertEqual(searchResult.items.first?.packages.flatMap { $0.collections }.sorted(), expectedCollectionsIdentifiers, "collections should match") @@ -923,12 +910,12 @@ final class PackageCollectionsTests: XCTestCase { do { // empty search - let searchResult = try temp_await { callback in packageCollections.findTargets(UUID().uuidString, searchType: .exactMatch, callback: callback) } + let searchResult = try await packageCollections.findTargets(UUID().uuidString, searchType: .exactMatch) XCTAssertEqual(searchResult.items.count, 0, "list count should match") } } - func testTargetsSearchPerformance() throws { + func testTargetsSearchPerformance() async throws { #if ENABLE_COLLECTION_PERF_TESTS #else try XCTSkipIf(true) @@ -945,25 +932,20 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - let sync = DispatchGroup() - mockCollections.forEach { collection in - sync.enter() - packageCollections.addCollection(collection.source, order: nil) { _ in - sync.leave() - } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - sync.wait() // search by target name let start = Date() let targetName = mockCollections.last!.packages.last!.versions.last!.defaultManifest!.targets.last!.name - let searchResult = try temp_await { callback in packageCollections.findTargets(targetName, searchType: .exactMatch, callback: callback) } + let searchResult = try await packageCollections.findTargets(targetName, searchType: .exactMatch) XCTAssert(searchResult.items.count > 0, "should get results") let delta = Date().timeIntervalSince(start) XCTAssert(delta < 1.0, "should search quickly, took \(delta)") } - func testHappyRefresh() throws { + func testHappyRefresh() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -975,17 +957,17 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in + for collection in mockCollections { // save directly to storage to circumvent refresh on add - _ = try temp_await { callback in storage.sources.add(source: collection.source, order: nil, callback: callback) } + try await storage.sources.add(source: collection.source, order: nil) } - _ = try temp_await { callback in packageCollections.refreshCollections(callback: callback) } + _ = try await packageCollections.refreshCollections() - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - func testBrokenRefresh() throws { + func testBrokenRefresh() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() struct BrokenProvider: PackageCollectionProvider { @@ -1030,20 +1012,20 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - XCTAssertThrowsError(try temp_await { callback in packageCollections.addCollection(brokenSources.first!, order: nil, callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await packageCollections.addCollection(brokenSources.first!), "expected error") { error in XCTAssertEqual(error as? MyError, expectedError, "expected error to match") - }) + } // save directly to storage to circumvent refresh on add - try goodSources.forEach { source in - _ = try temp_await { callback in storage.sources.add(source: source, order: nil, callback: callback) } + for source in goodSources { + try await storage.sources.add(source: source, order: nil) } - try brokenSources.forEach { source in - _ = try temp_await { callback in storage.sources.add(source: source, order: nil, callback: callback) } + for source in brokenSources { + try await storage.sources.add(source: source, order: nil) } - _ = try temp_await { callback in storage.sources.add(source: .init(type: .json, url: "https://feed-\(UUID().uuidString)"), order: nil, callback: callback) } + try await storage.sources.add(source: .init(type: .json, url: "https://feed-\(UUID().uuidString)"), order: nil) - XCTAssertThrowsError(try temp_await { callback in packageCollections.refreshCollections(callback: callback) }, "expected error", { error in + await XCTAssertAsyncThrowsError(try await packageCollections.refreshCollections(), "expected error") { error in if let error = error as? MultipleErrors { XCTAssertEqual(error.errors.count, brokenSources.count, "expected error to match") error.errors.forEach { error in @@ -1052,14 +1034,14 @@ final class PackageCollectionsTests: XCTestCase { } else { XCTFail("expected error to match") } - }) + } // test isolation - broken feeds does not impact good ones - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, goodSources.count + 1, "list count should match") } - func testRefreshOne() throws { + func testRefreshOne() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1071,17 +1053,17 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in + for collection in mockCollections { // save directly to storage to circumvent refresh on add - _ = try temp_await { callback in storage.sources.add(source: collection.source, order: nil, callback: callback) } + try await storage.sources.add(source: collection.source, order: nil) } - _ = try temp_await { callback in packageCollections.refreshCollection(mockCollections.first!.source, callback: callback) } + _ = try await packageCollections.refreshCollection(mockCollections.first!.source) - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - func testRefreshOneTrustedUnsigned() throws { + func testRefreshOneTrustedUnsigned() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1094,14 +1076,14 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) // User trusted - let collection = try temp_await { callback in packageCollections.addCollection(mockCollections[0].source, order: nil, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + let collection = try await packageCollections.addCollection(mockCollections[0].source, order: nil, trustConfirmationProvider: { _, cb in cb(true) }) XCTAssertEqual(true, collection.source.isTrusted) // isTrusted is nil-able // `isTrusted` should be true so refreshCollection should succeed - XCTAssertNoThrow(try temp_await { callback in packageCollections.refreshCollection(collection.source, callback: callback) }) + _ = try await packageCollections.refreshCollection(collection.source) } - func testRefreshOneNotFound() throws { + func testRefreshOneNotFound() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1114,12 +1096,12 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) // Don't add collection so it's not found in the config - XCTAssertThrowsError(try temp_await { callback in packageCollections.refreshCollection(mockCollections[0].source, callback: callback) }, "expected error") { error in + await XCTAssertAsyncThrowsError(try await packageCollections.refreshCollection(mockCollections[0].source), "expected error") { error in XCTAssert(error is NotFoundError) } } - func testListTargets() throws { + func testListTargets() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1132,19 +1114,19 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let targetsList = try temp_await { callback in packageCollections.listTargets(callback: callback) } + let targetsList = try await packageCollections.listTargets() let expectedTargets = Set(mockCollections.flatMap { $0.packages.flatMap { $0.versions.flatMap { $0.defaultManifest!.targets.map { $0.name } } } }) XCTAssertEqual(Set(targetsList.map { $0.target.name }), expectedTargets, "targets should match") @@ -1157,7 +1139,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertEqual(targetsCollectionsList, expectedCollections, "collections should match") } - func testFetchMetadataHappy() throws { + func testFetchMetadataHappy() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1172,19 +1154,19 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let metadata = try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } + let metadata = try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) let expectedCollections = Set(mockCollections.filter { $0.packages.map { $0.identity }.contains(mockPackage.identity) }.map { $0.identifier }) XCTAssertEqual(Set(metadata.collections), expectedCollections, "collections should match") @@ -1195,7 +1177,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertNil(metadata.provider, "provider should be nil") } - func testFetchMetadataInOrder() throws { + func testFetchMetadataInOrder() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1209,19 +1191,19 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let metadata = try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } + let metadata = try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) let expectedCollections = Set(mockCollections.filter { $0.packages.map { $0.identity }.contains(mockPackage.identity) }.map { $0.identifier }) XCTAssertEqual(Set(metadata.collections), expectedCollections, "collections should match") @@ -1233,7 +1215,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertNil(metadata.provider, "provider should be nil") } - func testFetchMetadataInCollections() throws { + func testFetchMetadataInCollections() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1247,20 +1229,20 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } let collectionIdentifiers: Set = [mockCollections.last!.identifier] - let metadata = try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, collections: collectionIdentifiers, callback: callback) } + let metadata = try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, collections: collectionIdentifiers) XCTAssertEqual(Set(metadata.collections), collectionIdentifiers, "collections should match") let expectedMetadata = PackageCollections.mergedPackageMetadata(package: mockPackage, basicMetadata: nil) @@ -1270,7 +1252,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertNil(metadata.provider, "provider should be nil") } - func testMergedPackageMetadata() throws { + func testMergedPackageMetadata() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let packageId = UUID().uuidString @@ -1363,7 +1345,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertEqual(metadata.languages, mockMetadata.languages, "languages should match") } - func testFetchMetadataNotFoundInCollections() throws { + func testFetchMetadataNotFoundInCollections() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1377,16 +1359,16 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } - XCTAssertThrowsError(try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) }, "expected error") { error in + await XCTAssertAsyncThrowsError(try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location), "expected error") { error in XCTAssert(error is NotFoundError) } } - func testFetchMetadataNotFoundByProvider() throws { + func testFetchMetadataNotFoundByProvider() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1400,19 +1382,19 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let metadata = try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } + let metadata = try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) let expectedCollections = Set(mockCollections.filter { $0.packages.map { $0.identity }.contains(mockPackage.identity) }.map { $0.identifier }) XCTAssertEqual(Set(metadata.collections), expectedCollections, "collections should match") @@ -1424,7 +1406,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertNil(metadata.provider, "provider should be nil") } - func testFetchMetadataProviderError() throws { + func testFetchMetadataProviderError() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() struct BrokenMetadataProvider: PackageMetadataProvider { @@ -1452,20 +1434,20 @@ final class PackageCollectionsTests: XCTestCase { let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) do { - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in packageCollections.listCollections(callback: callback) } + let list = try await packageCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } // Despite metadata provider error we should still get back data from storage - let metadata = try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } + let metadata = try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) let expectedMetadata = PackageCollections.mergedPackageMetadata(package: mockPackage, basicMetadata: nil) XCTAssertEqual(metadata.package, expectedMetadata, "package should match") @@ -1473,7 +1455,7 @@ final class PackageCollectionsTests: XCTestCase { XCTAssertNil(metadata.provider, "provider should be nil") } - func testFetchMetadataPerformance() throws { + func testFetchMetadataPerformance() async throws { #if ENABLE_COLLECTION_PERF_TESTS #else try XCTSkipIf(true) @@ -1492,23 +1474,18 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([mockPackage.identity: mockMetadata]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - let sync = DispatchGroup() - mockCollections.forEach { collection in - sync.enter() - packageCollections.addCollection(collection.source, order: nil) { _ in - sync.leave() - } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, order: nil) } - sync.wait() let start = Date() - let metadata = try temp_await { callback in packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } + let metadata = try await packageCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) XCTAssertNotNil(metadata) let delta = Date().timeIntervalSince(start) XCTAssert(delta < 1.0, "should fetch quickly, took \(delta)") } - func testListPackages() throws { + func testListPackages() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let configuration = PackageCollections.Configuration() @@ -1580,8 +1557,8 @@ final class PackageCollectionsTests: XCTestCase { let metadataProvider = MockMetadataProvider([:]) let packageCollections = PackageCollections(configuration: configuration, storage: storage, collectionProviders: collectionProviders, metadataProvider: metadataProvider) - try mockCollections.forEach { collection in - _ = try temp_await { callback in packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await packageCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } do { @@ -1589,7 +1566,7 @@ final class PackageCollectionsTests: XCTestCase { let expectedPackages = Set(mockCollections.flatMap { $0.packages.map { $0.identity } } + [mockPackage.identity]) let expectedCollections = Set([mockCollection.identifier, mockCollection2.identifier]) - let searchResult = try temp_await { callback in packageCollections.listPackages(collections: fetchCollections, callback: callback) } + let searchResult = try await packageCollections.listPackages(collections: fetchCollections) XCTAssertEqual(searchResult.items.count, expectedPackages.count, "list count should match") XCTAssertEqual(Set(searchResult.items.map { $0.package.identity }), expectedPackages, "items should match") XCTAssertEqual(Set(searchResult.items.first(where: { $0.package.identity == mockPackage.identity })?.collections ?? []), expectedCollections, "collections should match") @@ -1601,7 +1578,7 @@ final class PackageCollectionsTests: XCTestCase { let expectedPackages = Set(mockCollections[0].packages.map { $0.identity } + [mockPackage.identity]) let expectedCollections = Set([mockCollection.identifier, mockCollection2.identifier]) - let searchResult = try temp_await { callback in packageCollections.listPackages(collections: fetchCollections, callback: callback) } + let searchResult = try await packageCollections.listPackages(collections: fetchCollections) XCTAssertEqual(searchResult.items.count, expectedPackages.count, "list count should match") XCTAssertEqual(Set(searchResult.items.map { $0.package.identity }), expectedPackages, "items should match") XCTAssertEqual(Set(searchResult.items.first(where: { $0.package.identity == mockPackage.identity })?.collections ?? []), expectedCollections, "collections should match") diff --git a/Tests/PackageCollectionsTests/PackageIndexAndCollectionsTests.swift b/Tests/PackageCollectionsTests/PackageIndexAndCollectionsTests.swift index 8afab195fc5..0631fdc4360 100644 --- a/Tests/PackageCollectionsTests/PackageIndexAndCollectionsTests.swift +++ b/Tests/PackageCollectionsTests/PackageIndexAndCollectionsTests.swift @@ -20,7 +20,7 @@ import XCTest import struct TSCUtility.Version class PackageIndexAndCollectionsTests: XCTestCase { - func testCollectionAddRemoveGetList() throws { + func testCollectionAddRemoveGetList() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -33,31 +33,31 @@ class PackageIndexAndCollectionsTests: XCTestCase { defer { XCTAssertNoThrow(try indexAndCollections.close()) } do { - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list, mockCollections, "list count should match") } do { - let collection = try temp_await { callback in indexAndCollections.getCollection(mockCollections.first!.source, callback: callback) } + let collection = try await indexAndCollections.getCollection(mockCollections.first!.source) XCTAssertEqual(collection, mockCollections.first, "collection should match") } do { - _ = try temp_await { callback in indexAndCollections.removeCollection(mockCollections.first!.source, callback: callback) } - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + try await indexAndCollections.removeCollection(mockCollections.first!.source) + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count - 1, "list count should match") } } - func testRefreshCollections() throws { + func testRefreshCollections() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -69,17 +69,17 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - try mockCollections.forEach { collection in + for collection in mockCollections { // save directly to storage to circumvent refresh on add - _ = try temp_await { callback in storage.sources.add(source: collection.source, order: nil, callback: callback) } + try await storage.sources.add(source: collection.source, order: nil) } - _ = try temp_await { callback in indexAndCollections.refreshCollections(callback: callback) } + _ = try await indexAndCollections.refreshCollections() - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - func testRefreshCollection() throws { + func testRefreshCollection() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -91,17 +91,17 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - try mockCollections.forEach { collection in + for collection in mockCollections { // save directly to storage to circumvent refresh on add - _ = try temp_await { callback in storage.sources.add(source: collection.source, order: nil, callback: callback) } + try await storage.sources.add(source: collection.source, order: nil) } - _ = try temp_await { callback in indexAndCollections.refreshCollection(mockCollections.first!.source, callback: callback) } + _ = try await indexAndCollections.refreshCollection(mockCollections.first!.source) - let collection = try temp_await { callback in indexAndCollections.getCollection(mockCollections.first!.source, callback: callback) } + let collection = try await indexAndCollections.getCollection(mockCollections.first!.source) XCTAssertEqual(collection, mockCollections.first, "collection should match") } - func testListPackages() throws { + func testListPackages() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -174,8 +174,8 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } do { @@ -183,7 +183,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { let expectedPackages = Set(mockCollections.flatMap { $0.packages.map { $0.identity } } + [mockPackage.identity]) let expectedCollections = Set([mockCollection.identifier, mockCollection2.identifier]) - let searchResult = try temp_await { callback in indexAndCollections.listPackages(collections: fetchCollections, callback: callback) } + let searchResult = try await indexAndCollections.listPackages(collections: fetchCollections) XCTAssertEqual(searchResult.items.count, expectedPackages.count, "list count should match") XCTAssertEqual(Set(searchResult.items.map { $0.package.identity }), expectedPackages, "items should match") XCTAssertEqual(Set(searchResult.items.first(where: { $0.package.identity == mockPackage.identity })?.collections ?? []), expectedCollections, "collections should match") @@ -195,14 +195,14 @@ class PackageIndexAndCollectionsTests: XCTestCase { let expectedPackages = Set(mockCollections[0].packages.map { $0.identity } + [mockPackage.identity]) let expectedCollections = Set([mockCollection.identifier, mockCollection2.identifier]) - let searchResult = try temp_await { callback in indexAndCollections.listPackages(collections: fetchCollections, callback: callback) } + let searchResult = try await indexAndCollections.listPackages(collections: fetchCollections) XCTAssertEqual(searchResult.items.count, expectedPackages.count, "list count should match") XCTAssertEqual(Set(searchResult.items.map { $0.package.identity }), expectedPackages, "items should match") XCTAssertEqual(Set(searchResult.items.first(where: { $0.package.identity == mockPackage.identity })?.collections ?? []), expectedCollections, "collections should match") } } - func testListTargets() throws { + func testListTargets() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -215,19 +215,19 @@ class PackageIndexAndCollectionsTests: XCTestCase { defer { XCTAssertNoThrow(try indexAndCollections.close()) } do { - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let targetsList = try temp_await { callback in indexAndCollections.listTargets(callback: callback) } + let targetsList = try await indexAndCollections.listTargets() let expectedTargets = Set(mockCollections.flatMap { $0.packages.flatMap { $0.versions.flatMap { $0.defaultManifest!.targets.map { $0.name } } } }) XCTAssertEqual(Set(targetsList.map { $0.target.name }), expectedTargets, "targets should match") @@ -240,7 +240,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { XCTAssertEqual(targetsCollectionsList, expectedCollections, "collections should match") } - func testFindTargets() throws { + func testFindTargets() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -315,13 +315,13 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } do { // search by exact target name - let searchResult = try temp_await { callback in indexAndCollections.findTargets(mockTargets.first!.name, searchType: .exactMatch, callback: callback) } + let searchResult = try await indexAndCollections.findTargets(mockTargets.first!.name, searchType: .exactMatch) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.packages.map { $0.identity }, [mockPackage.identity], "packages should match") XCTAssertEqual(searchResult.items.first?.packages.flatMap { $0.collections }.sorted(), expectedCollectionsIdentifiers, "collections should match") @@ -329,7 +329,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { do { // search by prefix target name - let searchResult = try temp_await { callback in indexAndCollections.findTargets(String(mockTargets.first!.name.prefix(mockTargets.first!.name.count - 1)), searchType: .prefix, callback: callback) } + let searchResult = try await indexAndCollections.findTargets(String(mockTargets.first!.name.prefix(mockTargets.first!.name.count - 1)), searchType: .prefix) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.packages.map { $0.identity }, [mockPackage.identity], "packages should match") XCTAssertEqual(searchResult.items.first?.packages.flatMap { $0.collections }.sorted(), expectedCollectionsIdentifiers, "collections should match") @@ -337,12 +337,12 @@ class PackageIndexAndCollectionsTests: XCTestCase { do { // empty search - let searchResult = try temp_await { callback in indexAndCollections.findTargets(UUID().uuidString, searchType: .exactMatch, callback: callback) } + let searchResult = try await indexAndCollections.findTargets(UUID().uuidString, searchType: .exactMatch) XCTAssertEqual(searchResult.items.count, 0, "list count should match") } } - func testListPackagesInIndex() throws { + func testListPackagesInIndex() async throws { let storage = makeMockStorage() defer { XCTAssertNoThrow(try storage.close()) } let packageCollections = makePackageCollections(mockCollections: [], storage: storage) @@ -354,11 +354,11 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - let result = try temp_await { callback in indexAndCollections.listPackagesInIndex(offset: 1, limit: 5, callback: callback) } + let result = try await indexAndCollections.listPackagesInIndex(offset: 1, limit: 5) XCTAssertFalse(result.items.isEmpty) } - func testGetPackageMetadata() throws { + func testGetPackageMetadata() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -374,19 +374,19 @@ class PackageIndexAndCollectionsTests: XCTestCase { defer { XCTAssertNoThrow(try indexAndCollections.close()) } do { - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let metadata = try temp_await { callback in indexAndCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } + let metadata = try await indexAndCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) let expectedCollections = Set(mockCollections.filter { $0.packages.map { $0.identity }.contains(mockPackage.identity) }.map { $0.identifier }) XCTAssertEqual(Set(metadata.collections), expectedCollections, "collections should match") @@ -397,7 +397,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { XCTAssertEqual(metadata.provider?.name, "package index") } - func testGetPackageMetadata_brokenIndex() throws { + func testGetPackageMetadata_brokenIndex() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -413,20 +413,20 @@ class PackageIndexAndCollectionsTests: XCTestCase { defer { XCTAssertNoThrow(try indexAndCollections.close()) } do { - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, 0, "list should be empty") } do { - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, order: nil, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, order: nil) } - let list = try temp_await { callback in indexAndCollections.listCollections(callback: callback) } + let list = try await indexAndCollections.listCollections() XCTAssertEqual(list.count, mockCollections.count, "list count should match") } - let metadata = try temp_await { callback in indexAndCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) } - + let metadata = try await indexAndCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location) + let expectedCollections = Set(mockCollections.filter { $0.packages.map { $0.identity }.contains(mockPackage.identity) }.map { $0.identifier }) XCTAssertEqual(Set(metadata.collections), expectedCollections, "collections should match") @@ -437,7 +437,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { XCTAssertNil(metadata.provider) } - func testGetPackageMetadata_indexAndCollectionError() throws { + func testGetPackageMetadata_indexAndCollectionError() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -450,7 +450,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { let mockPackage = makeMockPackage(id: "test-package") // Package not found in collections; index is broken - XCTAssertThrowsError(try temp_await { callback in indexAndCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await indexAndCollections.getPackageMetadata(identity: mockPackage.identity, location: mockPackage.location)) { error in // Index error is returned guard let _ = error as? BrokenPackageIndex.TerribleThing else { return XCTFail("Expected BrokenPackageIndex.TerribleThing") @@ -458,7 +458,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { } } - func testFindPackages() throws { + func testFindPackages() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -532,13 +532,13 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } // both index and collections do { - let searchResult = try temp_await { callback in indexAndCollections.findPackages(mockPackage.identity.description, in: .both(collections: nil), callback: callback) } + let searchResult = try await indexAndCollections.findPackages(mockPackage.identity.description, in: .both(collections: nil)) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") XCTAssertEqual(searchResult.items.first?.indexes, [packageIndex.url], "indexes should match") @@ -546,7 +546,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { // index only do { - let searchResult = try temp_await { callback in indexAndCollections.findPackages(mockPackage.identity.description, in: .index, callback: callback) } + let searchResult = try await indexAndCollections.findPackages(mockPackage.identity.description, in: .index) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertTrue(searchResult.items.first?.collections.isEmpty ?? true, "collections should match") XCTAssertEqual(searchResult.items.first?.indexes, [packageIndex.url], "indexes should match") @@ -554,14 +554,14 @@ class PackageIndexAndCollectionsTests: XCTestCase { // collections only do { - let searchResult = try temp_await { callback in indexAndCollections.findPackages(mockPackage.identity.description, in: .collections(nil), callback: callback) } + let searchResult = try await indexAndCollections.findPackages(mockPackage.identity.description, in: .collections(nil)) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") XCTAssertTrue(searchResult.items.first?.indexes.isEmpty ?? true, "indexes should match") } } - func testFindPackages_brokenIndex() throws { + func testFindPackages_brokenIndex() async throws { try PackageCollectionsTests_skipIfUnsupportedPlatform() let storage = makeMockStorage() @@ -635,13 +635,13 @@ class PackageIndexAndCollectionsTests: XCTestCase { let indexAndCollections = PackageIndexAndCollections(index: packageIndex, collections: packageCollections, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try indexAndCollections.close()) } - try mockCollections.forEach { collection in - _ = try temp_await { callback in indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }, callback: callback) } + for collection in mockCollections { + _ = try await indexAndCollections.addCollection(collection.source, trustConfirmationProvider: { _, cb in cb(true) }) } // both index and collections do { - let searchResult = try temp_await { callback in indexAndCollections.findPackages(mockPackage.identity.description, in: .both(collections: nil), callback: callback) } + let searchResult = try await indexAndCollections.findPackages(mockPackage.identity.description, in: .both(collections: nil)) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") // Results come from collections since index is broken @@ -650,7 +650,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { // index only do { - XCTAssertThrowsError(try temp_await { callback in indexAndCollections.findPackages(mockPackage.identity.description, in: .index, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await indexAndCollections.findPackages(mockPackage.identity.description, in: .index)) { error in guard error is BrokenPackageIndex.TerribleThing else { return XCTFail("invalid error \(error)") } @@ -659,7 +659,7 @@ class PackageIndexAndCollectionsTests: XCTestCase { // collections only do { - let searchResult = try temp_await { callback in indexAndCollections.findPackages(mockPackage.identity.description, in: .collections(nil), callback: callback) } + let searchResult = try await indexAndCollections.findPackages(mockPackage.identity.description, in: .collections(nil)) XCTAssertEqual(searchResult.items.count, 1, "list count should match") XCTAssertEqual(searchResult.items.first?.collections.sorted(), expectedCollectionsIdentifiers, "collections should match") // Not searching in index so should not be impacted by its error diff --git a/Tests/PackageCollectionsTests/PackageIndexTests.swift b/Tests/PackageCollectionsTests/PackageIndexTests.swift index fdaceb5d271..9fb820e17af 100644 --- a/Tests/PackageCollectionsTests/PackageIndexTests.swift +++ b/Tests/PackageCollectionsTests/PackageIndexTests.swift @@ -18,7 +18,7 @@ import SPMTestSupport import XCTest class PackageIndexTests: XCTestCase { - func testGetPackageMetadata() throws { + func testGetPackageMetadata() async throws { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = true @@ -45,13 +45,13 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, customHTTPClient: httpClient, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - let metadata = try temp_await { callback in index.getPackageMetadata(identity: .init(url: repoURL), location: repoURL.absoluteString, callback: callback) } + let metadata = try await index.getPackageMetadata(identity: .init(url: repoURL), location: repoURL.absoluteString) XCTAssertEqual(metadata.package.identity, package.identity) XCTAssert(metadata.collections.isEmpty) XCTAssertNotNil(metadata.provider) } - func testGetPackageMetadata_featureDisabled() { + func testGetPackageMetadata_featureDisabled() async { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = false @@ -60,12 +60,12 @@ class PackageIndexTests: XCTestCase { defer { XCTAssertNoThrow(try index.close()) } let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") - XCTAssertThrowsError(try temp_await { callback in index.getPackageMetadata(identity: .init(url: repoURL), location: repoURL.absoluteString, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await index.getPackageMetadata(identity: .init(url: repoURL), location: repoURL.absoluteString)) { error in XCTAssertEqual(error as? PackageIndexError, .featureDisabled) } } - func testGetPackageMetadata_notConfigured() { + func testGetPackageMetadata_notConfigured() async { var configuration = PackageIndexConfiguration(url: nil, disableCache: true) configuration.enabled = true @@ -73,12 +73,12 @@ class PackageIndexTests: XCTestCase { defer { XCTAssertNoThrow(try index.close()) } let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") - XCTAssertThrowsError(try temp_await { callback in index.getPackageMetadata(identity: .init(url: repoURL), location: repoURL.absoluteString, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await index.getPackageMetadata(identity: .init(url: repoURL), location: repoURL.absoluteString)) { error in XCTAssertEqual(error as? PackageIndexError, .notConfigured) } } - func testFindPackages() throws { + func testFindPackages() async throws { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, searchResultMaxItemsCount: 10, disableCache: true) configuration.enabled = true @@ -106,7 +106,7 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, customHTTPClient: httpClient, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - let result = try temp_await { callback in index.findPackages(query, callback: callback) } + let result = try await index.findPackages(query) XCTAssertEqual(result.items.count, packages.count) for (i, item) in result.items.enumerated() { XCTAssertEqual(item.package.identity, packages[i].identity) @@ -115,7 +115,7 @@ class PackageIndexTests: XCTestCase { } } - func testFindPackages_resultsLimit() throws { + func testFindPackages_resultsLimit() async throws { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, searchResultMaxItemsCount: 3, disableCache: true) configuration.enabled = true @@ -144,7 +144,7 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, customHTTPClient: httpClient, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - let result = try temp_await { callback in index.findPackages(query, callback: callback) } + let result = try await index.findPackages(query) XCTAssertEqual(result.items.count, configuration.searchResultMaxItemsCount) for (i, item) in result.items.enumerated() { XCTAssertEqual(item.package.identity, packages[i].identity) @@ -153,7 +153,7 @@ class PackageIndexTests: XCTestCase { } } - func testFindPackages_featureDisabled() { + func testFindPackages_featureDisabled() async { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = false @@ -161,24 +161,24 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - XCTAssertThrowsError(try temp_await { callback in index.findPackages("foobar", callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await index.findPackages("foobar")) { error in XCTAssertEqual(error as? PackageIndexError, .featureDisabled) } } - func testFindPackages_notConfigured() { + func testFindPackages_notConfigured() async { var configuration = PackageIndexConfiguration(url: nil, disableCache: true) configuration.enabled = true let index = PackageIndex(configuration: configuration, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - XCTAssertThrowsError(try temp_await { callback in index.findPackages("foobar", callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await index.findPackages("foobar")) { error in XCTAssertEqual(error as? PackageIndexError, .notConfigured) } } - func testListPackages() throws { + func testListPackages() async throws { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = true @@ -209,14 +209,14 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, customHTTPClient: httpClient, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - let result = try temp_await { callback in index.listPackages(offset: offset, limit: limit, callback: callback) } + let result = try await index.listPackages(offset: offset, limit: limit) XCTAssertEqual(result.items.count, packages.count) XCTAssertEqual(result.offset, offset) XCTAssertEqual(result.limit, limit) XCTAssertEqual(result.total, total) } - func testListPackages_featureDisabled() { + func testListPackages_featureDisabled() async { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = false @@ -224,24 +224,24 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - XCTAssertThrowsError(try temp_await { callback in index.listPackages(offset: 0, limit: 10, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await index.listPackages(offset: 0, limit: 10)) { error in XCTAssertEqual(error as? PackageIndexError, .featureDisabled) } } - func testListPackages_notConfigured() { + func testListPackages_notConfigured() async { var configuration = PackageIndexConfiguration(url: nil, disableCache: true) configuration.enabled = true let index = PackageIndex(configuration: configuration, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - XCTAssertThrowsError(try temp_await { callback in index.listPackages(offset: 0, limit: 10, callback: callback) }) { error in + await XCTAssertAsyncThrowsError(try await index.listPackages(offset: 0, limit: 10)) { error in XCTAssertEqual(error as? PackageIndexError, .notConfigured) } } - func testAsPackageMetadataProvider() throws { + func testAsPackageMetadataProvider() async throws { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = true @@ -268,11 +268,11 @@ class PackageIndexTests: XCTestCase { let index = PackageIndex(configuration: configuration, customHTTPClient: httpClient, callbackQueue: .sharedConcurrent, observabilityScope: ObservabilitySystem.NOOP) defer { XCTAssertNoThrow(try index.close()) } - let metadata = try index.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) + let metadata = try await index.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString) XCTAssertEqual(metadata.summary, package.summary) } - func testAsGetPackageMetadataProvider_featureDisabled() { + func testAsGetPackageMetadataProvider_featureDisabled() async { let url = URL("https://package-index.test") var configuration = PackageIndexConfiguration(url: url, disableCache: true) configuration.enabled = false @@ -281,12 +281,12 @@ class PackageIndexTests: XCTestCase { defer { XCTAssertNoThrow(try index.close()) } let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") - XCTAssertThrowsError(try index.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString)) { error in + await XCTAssertAsyncThrowsError(try await index.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString)) { error in XCTAssertEqual(error as? PackageIndexError, .featureDisabled) } } - func testAsGetPackageMetadataProvider_notConfigured() { + func testAsGetPackageMetadataProvider_notConfigured() async { var configuration = PackageIndexConfiguration(url: nil, disableCache: true) configuration.enabled = true @@ -294,15 +294,15 @@ class PackageIndexTests: XCTestCase { defer { XCTAssertNoThrow(try index.close()) } let repoURL = SourceControlURL("https://github.com/octocat/Hello-World.git") - XCTAssertThrowsError(try index.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString)) { error in + await XCTAssertAsyncThrowsError(try await index.syncGet(identity: .init(url: repoURL), location: repoURL.absoluteString)) { error in XCTAssertEqual(error as? PackageIndexError, .notConfigured) } } } private extension PackageIndex { - func syncGet(identity: PackageIdentity, location: String) throws -> Model.PackageBasicMetadata { - try temp_await { callback in + func syncGet(identity: PackageIdentity, location: String) async throws -> Model.PackageBasicMetadata { + try await safe_async { callback in self.get(identity: identity, location: location) { result, _ in callback(result) } } } diff --git a/Tests/PackageFingerprintTests/FilePackageFingerprintStorageTests.swift b/Tests/PackageFingerprintTests/FilePackageFingerprintStorageTests.swift index 549693552ee..40941151672 100644 --- a/Tests/PackageFingerprintTests/FilePackageFingerprintStorageTests.swift +++ b/Tests/PackageFingerprintTests/FilePackageFingerprintStorageTests.swift @@ -60,10 +60,10 @@ final class FilePackageFingerprintStorageTests: XCTestCase { ) // A checksum file should have been created for each package - XCTAssertTrue(mockFileSystem.exists(storage.directoryPath.appending(component: package.fingerprintsFilename()))) + XCTAssertTrue(mockFileSystem.exists(storage.directoryPath.appending(component: package.fingerprintsFilename))) XCTAssertTrue( mockFileSystem - .exists(storage.directoryPath.appending(component: otherPackage.fingerprintsFilename())) + .exists(storage.directoryPath.appending(component: otherPackage.fingerprintsFilename)) ) // Fingerprints should be saved @@ -248,16 +248,16 @@ final class FilePackageFingerprintStorageTests: XCTestCase { fingerprint: .init(origin: .sourceControl(barURL), value: "abcde-bar", contentType: .sourceCode) ) - XCTAssertNotEqual(try fooRef.fingerprintsFilename(), try barRef.fingerprintsFilename()) + XCTAssertNotEqual(try fooRef.fingerprintsFilename, try barRef.fingerprintsFilename) // A checksum file should have been created for each package XCTAssertTrue( mockFileSystem - .exists(storage.directoryPath.appending(component: try fooRef.fingerprintsFilename())) + .exists(storage.directoryPath.appending(component: try fooRef.fingerprintsFilename)) ) XCTAssertTrue( mockFileSystem - .exists(storage.directoryPath.appending(component: try barRef.fingerprintsFilename())) + .exists(storage.directoryPath.appending(component: try barRef.fingerprintsFilename)) ) // This should fail because fingerprint for 1.0.0 already exists and it's different @@ -291,7 +291,7 @@ final class FilePackageFingerprintStorageTests: XCTestCase { let sourceControlURL = SourceControlURL("https://example.com/mona/LinkedList.git") let package = PackageIdentity.plain("mona.LinkedList") - let fingerprintsPath = directoryPath.appending(package.fingerprintsFilename()) + let fingerprintsPath = directoryPath.appending(package.fingerprintsFilename) let v1Fingerprints = """ { "versionFingerprints" : { diff --git a/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift b/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift index 6af1668fa63..9dfb2040ea3 100644 --- a/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift @@ -69,7 +69,7 @@ class DependencyResolverRealWorldPerfTests: XCTestCasePerf { switch resolver.solve(constraints: graph.constraints) { case .success(let result): let result: [(container: PackageReference, version: Version)] = result.compactMap { - guard case .version(let version) = $0.binding else { + guard case .version(let version) = $0.boundVersion else { XCTFail("Unexpected result") return nil } diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index e9a92586f38..9c7c6856094 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -79,7 +79,7 @@ class PackageGraphPerfTests: XCTestCasePerf { measure { let observability = ObservabilitySystem.makeForTesting() let g = try! PackageGraph.load( - root: PackageGraphRoot(input: PackageGraphRootInput(packages: [rootManifest.path]), manifests: [rootManifest.path: rootManifest]), + root: PackageGraphRoot(input: PackageGraphRootInput(packages: [rootManifest.path]), manifests: [rootManifest.path: rootManifest], observabilityScope: observability.topScope), identityResolver: identityResolver, externalManifests: externalManifests, binaryArtifacts: [:], diff --git a/Tests/PackageGraphTests/PackageGraphTests.swift b/Tests/PackageGraphTests/PackageGraphTests.swift index 157ec1eb20b..33debc92f08 100644 --- a/Tests/PackageGraphTests/PackageGraphTests.swift +++ b/Tests/PackageGraphTests/PackageGraphTests.swift @@ -337,7 +337,7 @@ class PackageGraphTests: XCTestCase { ) testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: "multiple targets named 'Bar' in: 'bar', 'foo'; consider using the `moduleAliases` parameter in manifest to provide unique names", severity: .error) + result.check(diagnostic: "multiple targets named 'Bar' in: 'bar', 'foo'", severity: .error) } } @@ -396,7 +396,7 @@ class PackageGraphTests: XCTestCase { ) testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: "multiple targets named 'First' in: 'first', 'fourth', 'second', 'third'; consider using the `moduleAliases` parameter in manifest to provide unique names", severity: .error) + result.check(diagnostic: "multiple targets named 'First' in: 'first', 'fourth', 'second', 'third'", severity: .error) } } @@ -466,8 +466,8 @@ class PackageGraphTests: XCTestCase { ) testDiagnostics(observability.diagnostics) { result in - result.checkUnordered(diagnostic: "multiple targets named 'Bar' in: 'fourth', 'third'; consider using the `moduleAliases` parameter in manifest to provide unique names", severity: .error) - result.checkUnordered(diagnostic: "multiple targets named 'Foo' in: 'first', 'second'; consider using the `moduleAliases` parameter in manifest to provide unique names", severity: .error) + result.checkUnordered(diagnostic: "multiple targets named 'Bar' in: 'fourth', 'third'", severity: .error) + result.checkUnordered(diagnostic: "multiple targets named 'Foo' in: 'first', 'second'", severity: .error) } } @@ -535,7 +535,7 @@ class PackageGraphTests: XCTestCase { ) testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: "multiple targets named 'First' in: 'first', 'fourth'; consider using the `moduleAliases` parameter in manifest to provide unique names", severity: .error) + result.check(diagnostic: "multiple targets named 'First' in: 'first', 'fourth'", severity: .error) } } @@ -786,6 +786,74 @@ class PackageGraphTests: XCTestCase { } } + func testProductDependencyWithSimilarName() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Foo/Sources/Foo/foo.swift", + "/Bar/Sources/Bar/bar.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + _ = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + targets: [ + TargetDescription(name: "Foo", dependencies: ["Barx"]), + ]), + Manifest.createRootManifest( + displayName: "Bar", + path: "/Bar", + targets: [ + TargetDescription(name: "Bar") + ]), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'Barx' required by package 'foo' target 'Foo' not found. Did you mean 'Bar'?", + severity: .error + ) + } + } + + func testProductDependencyWithNonSimilarName() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Foo/Sources/Foo/foo.swift", + "/Bar/Sources/Bar/bar.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + _ = try loadPackageGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + targets: [ + TargetDescription(name: "Foo", dependencies: ["Qux"]), + ]), + Manifest.createRootManifest( + displayName: "Bar", + path: "/Bar", + targets: [ + TargetDescription(name: "Bar") + ]), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'Qux' required by package 'foo' target 'Foo' not found.", + severity: .error + ) + } + } + func testProductDependencyDeclaredInSamePackage() throws { let fs = InMemoryFileSystem(emptyFiles: "/Foo/Sources/FooTarget/src.swift", @@ -1187,7 +1255,7 @@ class PackageGraphTests: XCTestCase { ) testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: "multiple targets named 'Foo' in: 'dep2', 'start'; consider using the `moduleAliases` parameter in manifest to provide unique names", severity: .error) + result.check(diagnostic: "multiple targets named 'Foo' in: 'dep2', 'start'", severity: .error) } } @@ -1197,6 +1265,9 @@ class PackageGraphTests: XCTestCase { "/Bar/Sources/Bar/bar.swift", "/Baz/Sources/Baz/baz.swift" ) + let fooPkg: AbsolutePath = "/Foo" + let barPkg: AbsolutePath = "/Bar" + let bazPkg: AbsolutePath = "/Baz" let observability = ObservabilitySystem.makeForTesting() XCTAssertThrowsError(try loadPackageGraph( @@ -1204,17 +1275,17 @@ class PackageGraphTests: XCTestCase { manifests: [ Manifest.createRootManifest( displayName: "Foo", - path: "/Foo", + path: fooPkg, dependencies: [ - .localSourceControl(path: "/Bar", requirement: .upToNextMajor(from: "1.0.0")), - .localSourceControl(path: "/Baz", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: barPkg, requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: bazPkg, requirement: .upToNextMajor(from: "1.0.0")), ], targets: [ TargetDescription(name: "Foo", dependencies: ["Bar"]), ]), Manifest.createFileSystemManifest( displayName: "Bar", - path: "/Bar", + path: barPkg, products: [ ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]) ], @@ -1223,7 +1294,7 @@ class PackageGraphTests: XCTestCase { ]), Manifest.createFileSystemManifest( displayName: "Baz", - path: "/Baz", + path: bazPkg, products: [ ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Baz"]) ], @@ -1233,7 +1304,8 @@ class PackageGraphTests: XCTestCase { ], observabilityScope: observability.topScope )) { error in - XCTAssertEqual((error as? PackageGraphError)?.description, "multiple products named 'Bar' in: 'bar' (at '/Bar'), 'baz' (at '/Baz')") + XCTAssertEqual((error as? PackageGraphError)?.description, + "multiple products named 'Bar' in: 'bar' (at '\(barPkg)'), 'baz' (at '\(bazPkg)')") } } @@ -1506,7 +1578,7 @@ class PackageGraphTests: XCTestCase { let pinsFile = AbsolutePath("/pins") try fs.writeFileContents(pinsFile, string: json) - XCTAssertThrows(StringError("/pins file is corrupted or malformed; fix or delete the file to continue: duplicated entry for package \"yams\""), { + XCTAssertThrows(StringError("\(pinsFile) file is corrupted or malformed; fix or delete the file to continue: duplicated entry for package \"yams\""), { _ = try PinsStore(pinsFile: pinsFile, workingDirectory: .root, fileSystem: fs, mirrors: .init()) }) } @@ -2148,8 +2220,8 @@ class PackageGraphTests: XCTestCase { "linux": "0.0", "macos": "10.13", "maccatalyst": "13.0", - "ios": "11.0", - "tvos": "11.0", + "ios": "12.0", + "tvos": "12.0", "driverkit": "19.0", "watchos": "4.0", "visionos": "1.0", @@ -2161,8 +2233,8 @@ class PackageGraphTests: XCTestCase { let customXCTestMinimumDeploymentTargets = [ PackageModel.Platform.macOS: PlatformVersion("10.15"), - PackageModel.Platform.iOS: PlatformVersion("11.0"), - PackageModel.Platform.tvOS: PlatformVersion("10.0"), + PackageModel.Platform.iOS: PlatformVersion("12.0"), + PackageModel.Platform.tvOS: PlatformVersion("12.0"), PackageModel.Platform.watchOS: PlatformVersion("4.0"), PackageModel.Platform.visionOS: PlatformVersion("1.0"), ] @@ -2399,8 +2471,8 @@ class PackageGraphTests: XCTestCase { "linux": "0.0", "macos": "10.13", "maccatalyst": "13.0", - "ios": "11.0", - "tvos": "11.0", + "ios": "12.0", + "tvos": "12.0", "driverkit": "19.0", "watchos": "4.0", "visionos": "1.0", diff --git a/Tests/PackageGraphTests/PubgrubTests.swift b/Tests/PackageGraphTests/PubgrubTests.swift index 11e12cb33e5..93b718eeb38 100644 --- a/Tests/PackageGraphTests/PubgrubTests.swift +++ b/Tests/PackageGraphTests/PubgrubTests.swift @@ -1460,34 +1460,6 @@ final class PubgrubTests: XCTestCase { ]) } - func testMissingPin() throws { - // This checks that we can drop pins that are no longer available but still keep the ones - // which fit the constraints. - try builder.serve("a", at: v1, with: ["a": ["b": (.versionSet(v1Range), .specific(["b"]))]]) - try builder.serve("a", at: v1_1) - try builder.serve("b", at: v1) - try builder.serve("b", at: v1_1) - - let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(v1Range), .specific(["a"])), - ]) - - // Here c is pinned to v1.1, but it is no longer available, so the resolver should fall back - // to v1. - let pinsStore = try builder.create(pinsStore: [ - "a": (.version(v1), .specific(["a"])), - "b": (.version("1.2.0"), .specific(["b"])), - ]) - - let resolver = builder.create(pins: pinsStore.pins) - let result = resolver.solve(constraints: dependencies) - - AssertResult(result, [ - ("a", .version(v1)), - ("b", .version(v1_1)), - ]) - } - func testBranchedBasedPin() throws { // This test ensures that we get the SHA listed in Package.resolved for branch-based // dependencies. @@ -1592,8 +1564,8 @@ final class PubgrubTests: XCTestCase { func failedToResolve(incompatibility: Incompatibility) {} - func solved(result: [DependencyResolver.Binding]) { - let decisions = result.sorted(by: { $0.package.identity < $1.package.identity }).map { "'\($0.package.identity)' at '\($0.binding)'" } + func solved(result: [DependencyResolverBinding]) { + let decisions = result.sorted(by: { $0.package.identity < $1.package.identity }).map { "'\($0.package.identity)' at '\($0.boundVersion)'" } self.lock.withLock { self.events.append("solved: \(decisions.joined(separator: ", "))") } @@ -2899,7 +2871,7 @@ fileprivate extension PinsStore.PinState { /// Asserts that the listed packages are present in the bindings with their /// specified versions. private func AssertBindings( - _ bindings: [DependencyResolver.Binding], + _ bindings: [DependencyResolverBinding], _ packages: [(identity: PackageIdentity, version: BoundVersion)], file: StaticString = #file, line: UInt = #line @@ -2921,15 +2893,15 @@ private func AssertBindings( continue } - if binding.binding != package.version { - XCTFail("Expected \(package.version) for \(package.identity), found \(binding.binding) instead.", file: file, line: line) + if binding.boundVersion != package.version { + XCTFail("Expected \(package.version) for \(package.identity), found \(binding.boundVersion) instead.", file: file, line: line) } } } /// Asserts that a result succeeded and contains the specified bindings. private func AssertResult( - _ result: Result<[DependencyResolver.Binding], Error>, + _ result: Result<[DependencyResolverBinding], Error>, _ packages: [(identifier: String, version: BoundVersion)], file: StaticString = #file, line: UInt = #line @@ -2944,14 +2916,14 @@ private func AssertResult( /// Asserts that a result failed with specified error. private func AssertError( - _ result: Result<[DependencyResolver.Binding], Error>, + _ result: Result<[DependencyResolverBinding], Error>, _ expectedError: Error, file: StaticString = #file, line: UInt = #line ) { switch result { case .success(let bindings): - let bindingsDesc = bindings.map { "\($0.package)@\($0.binding)" }.joined(separator: ", ") + let bindingsDesc = bindings.map { "\($0.package)@\($0.boundVersion)" }.joined(separator: ", ") XCTFail("Expected unresolvable graph, found bindings instead: \(bindingsDesc)", file: file, line: line) case .failure(let foundError): XCTAssertEqual(String(describing: foundError), String(describing: expectedError), file: file, line: line) @@ -3319,7 +3291,7 @@ extension PackageReference: ExpressibleByStringLiteral { self = ref } } -extension Result where Success == [DependencyResolver.Binding] { +extension Result where Success == [DependencyResolverBinding] { var errorMsg: String? { switch self { case .failure(let error): diff --git a/Tests/PackageGraphTests/TargetTests.swift b/Tests/PackageGraphTests/TargetTests.swift index 08c1eec8daf..c8c1e4a2f05 100644 --- a/Tests/PackageGraphTests/TargetTests.swift +++ b/Tests/PackageGraphTests/TargetTests.swift @@ -30,7 +30,7 @@ private extension ResolvedTarget { ), dependencies: deps.map { .target($0, conditions: []) }, defaultLocalization: nil, - platforms: .init(declared: [], derived: []) + platforms: .init(declared: [], derivedXCTestPlatformProvider: .none) ) } } diff --git a/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift b/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift new file mode 100644 index 00000000000..dcf2c4f24de --- /dev/null +++ b/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift @@ -0,0 +1,579 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2020-2023 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 +// +//===----------------------------------------------------------------------===// + +@testable import Basics +@testable import PackageLoading +import PackageModel +import SPMTestSupport +import XCTest + +import class TSCBasic.InMemoryFileSystem +import enum TSCBasic.ProcessEnv +import func TSCTestSupport.withCustomEnv + +class ManifestLoaderCacheTests: XCTestCase { + func testDBCaching() throws { + try testWithTemporaryDirectory { path in + let fileSystem = localFileSystem + let observability = ObservabilitySystem.makeForTesting() + + let manifestPath = path.appending(components: "pkg", "Package.swift") + try fileSystem.createDirectory(manifestPath.parentDirectory, recursive: true) + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + let package = Package( + name: "Trivial", + targets: [ + .target( + name: "foo", + dependencies: []), + ] + ) + """ + ) + + let delegate = ManifestTestDelegate() + + let manifestLoader = ManifestLoader( + toolchain: try UserToolchain.default, + useInMemoryCache: false, + cacheDir: path, + delegate: delegate + ) + + func check(loader: ManifestLoader, expectCached: Bool) throws { + delegate.clear() + delegate.prepare(expectParsing: !expectCached) + + let manifest = try XCTUnwrap(loader.load( + manifestPath: manifestPath, + packageKind: .root(manifestPath.parentDirectory), + toolsVersion: .current, + fileSystem: fileSystem, + observabilityScope: observability.topScope + )) + + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath]) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1) + XCTAssertEqual(manifest.displayName, "Trivial") + XCTAssertEqual(manifest.targets[0].name, "foo") + } + + try check(loader: manifestLoader, expectCached: false) + for _ in 0..<2 { + try check(loader: manifestLoader, expectCached: true) + } + + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + + let package = Package( + name: "Trivial", + targets: [ + .target( + name: "foo", + dependencies: [ ]), + ] + ) + + """ + ) + + try check(loader: manifestLoader, expectCached: false) + for _ in 0..<2 { + try check(loader: manifestLoader, expectCached: true) + } + + let noCacheLoader = ManifestLoader( + toolchain: try UserToolchain.default, + useInMemoryCache: false, + cacheDir: .none, + delegate: delegate + ) + for _ in 0..<2 { + try check(loader: noCacheLoader, expectCached: false) + } + + // Resetting the cache should allow us to remove the cache + // directory without triggering assertions in sqlite. + manifestLoader.purgeCache(observabilityScope: observability.topScope) + XCTAssertNoDiagnostics(observability.diagnostics) + try fileSystem.removeFileTree(path) + } + } + + func testInMemoryCaching() throws { + let fileSystem = InMemoryFileSystem() + let observability = ObservabilitySystem.makeForTesting() + + let manifestPath = AbsolutePath.root.appending(components: "pkg", "Package.swift") + try fileSystem.createDirectory(manifestPath.parentDirectory, recursive: true) + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + let package = Package( + name: "Trivial", + targets: [ + .target( + name: "foo", + dependencies: []), + ] + ) + """ + ) + + let delegate = ManifestTestDelegate() + + let manifestLoader = ManifestLoader( + toolchain: try UserToolchain.default, + useInMemoryCache: true, + cacheDir: .none, + delegate: delegate + ) + + func check(loader: ManifestLoader, expectCached: Bool) throws { + delegate.clear() + delegate.prepare(expectParsing: !expectCached) + + let manifest = try XCTUnwrap(loader.load( + manifestPath: manifestPath, + packageKind: .root(manifestPath.parentDirectory), + toolsVersion: .current, + fileSystem: fileSystem, + observabilityScope: observability.topScope + )) + + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath]) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1) + XCTAssertEqual(manifest.displayName, "Trivial") + XCTAssertEqual(manifest.targets[0].name, "foo") + } + + try check(loader: manifestLoader, expectCached: false) + for _ in 0..<2 { + try check(loader: manifestLoader, expectCached: true) + } + + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + + let package = Package( + name: "Trivial", + targets: [ + .target( + name: "foo", + dependencies: [ ]), + ] + ) + + """ + ) + + try check(loader: manifestLoader, expectCached: false) + for _ in 0..<2 { + try check(loader: manifestLoader, expectCached: true) + } + + let noCacheLoader = ManifestLoader( + toolchain: try UserToolchain.default, + useInMemoryCache: false, + cacheDir: .none, + delegate: delegate + ) + for _ in 0..<2 { + try check(loader: noCacheLoader, expectCached: false) + } + + manifestLoader.purgeCache(observabilityScope: observability.topScope) + XCTAssertNoDiagnostics(observability.diagnostics) + } + + func testContentBasedCaching() throws { + try testWithTemporaryDirectory { path in + let manifest = """ + import PackageDescription + let package = Package( + name: "Trivial", + targets: [ + .target(name: "foo"), + ] + ) + """ + + let delegate = ManifestTestDelegate() + + let manifestLoader = ManifestLoader( + toolchain: try UserToolchain.default, + cacheDir: path, + delegate: delegate + ) + + func check(loader: ManifestLoader, manifest: String) throws { + let fileSystem = InMemoryFileSystem() + let observability = ObservabilitySystem.makeForTesting() + + let manifestPath = AbsolutePath.root.appending(component: Manifest.filename) + try fileSystem.writeFileContents(manifestPath, string: manifest) + + let m = try manifestLoader.load( + manifestPath: manifestPath, + packageKind: .root(.root), + toolsVersion: .current, + fileSystem: fileSystem, + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(m.displayName, "Trivial") + } + + do { + delegate.prepare() + try check(loader: manifestLoader, manifest: manifest) + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, 1) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, 1) + } + + do { + delegate.prepare(expectParsing: false) + try check(loader: manifestLoader, manifest: manifest) + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, 2) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, 1) + } + + do { + delegate.prepare() + try check(loader: manifestLoader, manifest: manifest + "\n\n") + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, 3) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, 2) + } + } + } + + func testCacheInvalidationOnEnv() throws { + try testWithTemporaryDirectory { path in + let fileSystem = InMemoryFileSystem() + let observability = ObservabilitySystem.makeForTesting() + + let manifestPath = path.appending(components: "pkg", "Package.swift") + try fileSystem.createDirectory(manifestPath.parentDirectory, recursive: true) + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + let package = Package( + name: "Trivial", + targets: [ + .target( + name: "foo", + dependencies: []), + ] + ) + """ + ) + + let delegate = ManifestTestDelegate() + + let manifestLoader = ManifestLoader( + toolchain: try UserToolchain.default, + cacheDir: path, + delegate: delegate + ) + + try check(loader: manifestLoader, expectCached: false) + try check(loader: manifestLoader, expectCached: true) + + try withCustomEnv(["SWIFTPM_MANIFEST_CACHE_TEST": "1"]) { + try check(loader: manifestLoader, expectCached: false) + try check(loader: manifestLoader, expectCached: true) + } + + try withCustomEnv(["SWIFTPM_MANIFEST_CACHE_TEST": "2"]) { + try check(loader: manifestLoader, expectCached: false) + try check(loader: manifestLoader, expectCached: true) + } + + try check(loader: manifestLoader, expectCached: true) + + func check(loader: ManifestLoader, expectCached: Bool) throws { + delegate.clear() + delegate.prepare(expectParsing: !expectCached) + + let manifest = try XCTUnwrap(loader.load( + manifestPath: manifestPath, + packageKind: .root(manifestPath.parentDirectory), + toolsVersion: .current, + fileSystem: fileSystem, + observabilityScope: observability.topScope + )) + + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath]) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1) + XCTAssertEqual(manifest.displayName, "Trivial") + XCTAssertEqual(manifest.targets[0].name, "foo") + } + } + } + + func testCacheDoNotInvalidationExpectedEnv() throws { + try testWithTemporaryDirectory { path in + let fileSystem = InMemoryFileSystem() + let observability = ObservabilitySystem.makeForTesting() + + let manifestPath = path.appending(components: "pkg", "Package.swift") + try fileSystem.createDirectory(manifestPath.parentDirectory, recursive: true) + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + let package = Package( + name: "Trivial", + targets: [ + .target( + name: "foo", + dependencies: []), + ] + ) + """ + ) + + let delegate = ManifestTestDelegate() + + let manifestLoader = ManifestLoader( + toolchain: try UserToolchain.default, + cacheDir: path, + delegate: delegate + ) + + func check(loader: ManifestLoader, expectCached: Bool) throws { + delegate.clear() + delegate.prepare(expectParsing: !expectCached) + + let manifest = try XCTUnwrap(loader.load( + manifestPath: manifestPath, + packageKind: .root(manifestPath.parentDirectory), + toolsVersion: .current, + fileSystem: fileSystem, + observabilityScope: observability.topScope + )) + + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath]) + XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1) + XCTAssertEqual(manifest.displayName, "Trivial") + XCTAssertEqual(manifest.targets[0].name, "foo") + } + + try check(loader: manifestLoader, expectCached: false) + try check(loader: manifestLoader, expectCached: true) + + for key in EnvironmentVariables.nonCachableKeys { + try withCustomEnv([key: UUID().uuidString]) { + try check(loader: manifestLoader, expectCached: true) + } + } + + try check(loader: manifestLoader, expectCached: true) + } + } + + func testSQLiteCacheHappyCase() throws { + try testWithTemporaryDirectory { tmpPath in + let path = tmpPath.appending("test.db") + let storage = SQLiteBackedCache(tableName: "manifests", path: path) + defer { XCTAssertNoThrow(try storage.close()) } + + let mockManifests = try makeMockManifests(fileSystem: localFileSystem, rootPath: tmpPath) + try mockManifests.forEach { key, manifest in + _ = try storage.put(key: key.sha256Checksum, value: manifest) + } + + try mockManifests.forEach { key, manifest in + let result = try storage.get(key: key.sha256Checksum) + XCTAssertEqual(result?.manifestJSON, manifest.manifestJSON) + } + + guard case .path(let storagePath) = storage.location else { + return XCTFail("invalid location \(storage.location)") + } + + XCTAssertTrue(storage.fileSystem.exists(storagePath), "expected file to be written") + } + } + + func testInMemoryCacheHappyCase() throws { + let content = """ + import PackageDescription + let package = Package( + name: "Root", + dependencies: [ + .package(url: "https://scm.com/foo", from: "1.0.0"), + .package(url: "https://scm.com/bar", from: "2.1.0") + ] + ) + """ + + let manifestLoader = ManifestLoader( + toolchain: try UserToolchain.default, + cacheDir: .none, + delegate: .none + ) + + let packageURL = "https://scm.com/\(UUID().uuidString)/foo" + + do { + let observability = ObservabilitySystem.makeForTesting() + let (manifest, validationDiagnostics) = try PackageDescriptionLoadingTests.loadAndValidateManifest( + content, + toolsVersion: .current, + packageKind: .remoteSourceControl(.init(packageURL)), + manifestLoader: manifestLoader, + observabilityScope: observability.topScope + ) + + // first time should not come from cache + testDiagnostics(observability.diagnostics, problemsOnly: false) { result in + result.check( + diagnostic: .regex("evaluating manifest for .*"), + severity: .debug + ) + } + XCTAssertNoDiagnostics(validationDiagnostics) + + let deps = Dictionary(uniqueKeysWithValues: manifest.dependencies.map{ ($0.identity.description, $0) }) + XCTAssertEqual(deps["foo"], .remoteSourceControl(url: "https://scm.com/foo", requirement: .upToNextMajor(from: "1.0.0"))) + XCTAssertEqual(deps["bar"], .remoteSourceControl(url: "https://scm.com/bar", requirement: .upToNextMajor(from: "2.1.0"))) + } + + // second time should come from in-memory cache + do { + let observability = ObservabilitySystem.makeForTesting() + let (manifest, validationDiagnostics) = try PackageDescriptionLoadingTests.loadAndValidateManifest( + content, + toolsVersion: .current, + packageKind: .remoteSourceControl(.init(packageURL)), + manifestLoader: manifestLoader, + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics, problemsOnly: false) { result in + result.check( + diagnostic: .regex("loading manifest .* from memory cache"), + severity: .debug + ) + } + XCTAssertNoDiagnostics(validationDiagnostics) + + let deps = Dictionary(uniqueKeysWithValues: manifest.dependencies.map{ ($0.identity.description, $0) }) + XCTAssertEqual(deps["foo"], .remoteSourceControl(url: "https://scm.com/foo", requirement: .upToNextMajor(from: "1.0.0"))) + XCTAssertEqual(deps["bar"], .remoteSourceControl(url: "https://scm.com/bar", requirement: .upToNextMajor(from: "2.1.0"))) + } + + // change location and make sure not coming from cache (rdar://73462555) + let newPackageURL = "https://scm.com/\(UUID().uuidString)/foo" + do { + let observability = ObservabilitySystem.makeForTesting() + let (manifest, validationDiagnostics) = try PackageDescriptionLoadingTests.loadAndValidateManifest( + content, + toolsVersion: .current, + packageKind: .remoteSourceControl(.init(newPackageURL)), + manifestLoader: manifestLoader, + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics, problemsOnly: false) { result in + result.check( + diagnostic: .regex("evaluating manifest for .*"), + severity: .debug + ) + } + XCTAssertNoDiagnostics(validationDiagnostics) + + let deps = Dictionary(uniqueKeysWithValues: manifest.dependencies.map{ ($0.identity.description, $0) }) + XCTAssertEqual(deps["foo"], .remoteSourceControl(url: "https://scm.com/foo", requirement: .upToNextMajor(from: "1.0.0"))) + XCTAssertEqual(deps["bar"], .remoteSourceControl(url: "https://scm.com/bar", requirement: .upToNextMajor(from: "2.1.0"))) + } + + // second time should come from in-memory cache + do { + let observability = ObservabilitySystem.makeForTesting() + let (manifest, validationDiagnostics) = try PackageDescriptionLoadingTests.loadAndValidateManifest( + content, + toolsVersion: .current, + packageKind: .remoteSourceControl(.init(newPackageURL)), + manifestLoader: manifestLoader, + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics, problemsOnly: false) { result in + result.check( + diagnostic: .regex("loading manifest .* from memory cache"), + severity: .debug + ) + } + XCTAssertNoDiagnostics(validationDiagnostics) + + let deps = Dictionary(uniqueKeysWithValues: manifest.dependencies.map{ ($0.identity.description, $0) }) + XCTAssertEqual(deps["foo"], .remoteSourceControl(url: "https://scm.com/foo", requirement: .upToNextMajor(from: "1.0.0"))) + XCTAssertEqual(deps["bar"], .remoteSourceControl(url: "https://scm.com/bar", requirement: .upToNextMajor(from: "2.1.0"))) + } + } +} + +private func makeMockManifests(fileSystem: FileSystem, rootPath: AbsolutePath, count: Int = Int.random(in: 50 ..< 100)) throws -> [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult] { + var manifests = [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult]() + for index in 0 ..< count { + let packagePath = rootPath.appending("\(index)") + let manifestPath = packagePath.appending("Package.swift") + + try fileSystem.createDirectory(packagePath, recursive: true) + try fileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + let package = Package( + name: "Trivial-\(index)", + targets: [ + .target( + name: "foo-\(index)", + dependencies: []), + + ) + """ + ) + let key = try ManifestLoader.CacheKey( + packageIdentity: PackageIdentity(path: packagePath), + packageLocation: packagePath.pathString, + manifestPath: manifestPath, + toolsVersion: ToolsVersion.current, + env: [:], + swiftpmVersion: SwiftVersion.current.displayString, + fileSystem: fileSystem + ) + manifests[key] = ManifestLoader.EvaluationResult( + compilerOutput: "mock-output-\(index)", + manifestJSON: "{ 'name': 'mock-manifest-\(index)' }" + ) + } + + return manifests +} diff --git a/Tests/PackageLoadingTests/ManifestLoaderSQLiteCacheTests.swift b/Tests/PackageLoadingTests/ManifestLoaderSQLiteCacheTests.swift deleted file mode 100644 index 97b2d292b8c..00000000000 --- a/Tests/PackageLoadingTests/ManifestLoaderSQLiteCacheTests.swift +++ /dev/null @@ -1,76 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2020-2021 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 Basics -@testable import PackageLoading -import PackageModel -import SPMTestSupport -import XCTest - -class ManifestLoaderSQLiteCacheTests: XCTestCase { - func testHappyCase() throws { - try testWithTemporaryDirectory { tmpPath in - let path = tmpPath.appending("test.db") - let storage = SQLiteBackedCache(tableName: "manifests", path: path) - defer { XCTAssertNoThrow(try storage.close()) } - - let mockManifests = try makeMockManifests(fileSystem: localFileSystem, rootPath: tmpPath) - try mockManifests.forEach { key, manifest in - _ = try storage.put(key: key.sha256Checksum, value: manifest) - } - - try mockManifests.forEach { key, manifest in - let result = try storage.get(key: key.sha256Checksum) - XCTAssertEqual(result?.manifestJSON, manifest.manifestJSON) - } - - guard case .path(let storagePath) = storage.location else { - return XCTFail("invalid location \(storage.location)") - } - - XCTAssertTrue(storage.fileSystem.exists(storagePath), "expected file to be written") - } - } -} - -private func makeMockManifests(fileSystem: FileSystem, rootPath: AbsolutePath, count: Int = Int.random(in: 50 ..< 100)) throws -> [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult] { - var manifests = [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult]() - for index in 0 ..< count { - let manifestPath = rootPath.appending(components: "\(index)", "Package.swift") - - try fileSystem.createDirectory(manifestPath.parentDirectory, recursive: true) - try fileSystem.writeFileContents( - manifestPath, - string: """ - import PackageDescription - let package = Package( - name: "Trivial-\(index)", - targets: [ - .target( - name: "foo-\(index)", - dependencies: []), - - ) - """ - ) - let key = try ManifestLoader.CacheKey(packageIdentity: PackageIdentity(path: manifestPath), - manifestPath: manifestPath, - toolsVersion: ToolsVersion.current, - env: [:], - swiftpmVersion: SwiftVersion.current.displayString, - fileSystem: fileSystem) - manifests[key] = ManifestLoader.EvaluationResult(compilerOutput: "mock-output-\(index)", - manifestJSON: "{ 'name': 'mock-manifest-\(index)' }") - } - - return manifests -} diff --git a/Tests/PackageLoadingTests/PDLoadingTests.swift b/Tests/PackageLoadingTests/PDLoadingTests.swift index e1a21a7c847..37a46c17a3c 100644 --- a/Tests/PackageLoadingTests/PDLoadingTests.swift +++ b/Tests/PackageLoadingTests/PDLoadingTests.swift @@ -21,12 +21,37 @@ import class TSCBasic.InMemoryFileSystem class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { lazy var manifestLoader = ManifestLoader(toolchain: try! UserToolchain.default, delegate: self) var parsedManifest = ThreadSafeBox() - - public func willLoad(manifest: AbsolutePath) { + + func willLoad(packageIdentity: PackageModel.PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // noop + } + + func didLoad(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { + // noop + } + + func willParse(packageIdentity: PackageIdentity, packageLocation: String) { + // noop + } + + func didParse(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + // noop } - - public func willParse(manifest: AbsolutePath) { - parsedManifest.put(manifest) + + func willCompile(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // noop + } + + func didCompile(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { + // noop + } + + func willEvaluate(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // noop + } + + func didEvaluate(packageIdentity: PackageModel.PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { + parsedManifest.put(manifestPath) } var toolsVersion: ToolsVersion { @@ -42,7 +67,26 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { file: StaticString = #file, line: UInt = #line ) throws -> (manifest: Manifest, diagnostics: [Basics.Diagnostic]) { - let packageKind = packageKind ?? .fileSystem(.root) + try Self.loadAndValidateManifest( + content, + toolsVersion: toolsVersion ?? self.toolsVersion, + packageKind: packageKind ?? .fileSystem(.root), + manifestLoader: customManifestLoader ?? self.manifestLoader, + observabilityScope: observabilityScope, + file: file, + line: line + ) + } + + static func loadAndValidateManifest( + _ content: String, + toolsVersion: ToolsVersion, + packageKind: PackageReference.Kind, + manifestLoader: ManifestLoader, + observabilityScope: ObservabilityScope, + file: StaticString = #file, + line: UInt = #line + ) throws -> (manifest: Manifest, diagnostics: [Basics.Diagnostic]) { let packagePath: AbsolutePath switch packageKind { case .root(let path): @@ -52,14 +96,14 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { case .localSourceControl(let path): packagePath = path case .remoteSourceControl, .registry: - throw InternalError("invalid package kind \(packageKind)") + packagePath = .root } - let toolsVersion = toolsVersion ?? self.toolsVersion + let toolsVersion = toolsVersion let fileSystem = InMemoryFileSystem() let manifestPath = packagePath.appending(component: Manifest.filename) try fileSystem.writeFileContents(manifestPath, string: content) - let manifest = try (customManifestLoader ?? manifestLoader).load( + let manifest = try manifestLoader.load( manifestPath: manifestPath, packageKind: packageKind, toolsVersion: toolsVersion, @@ -77,12 +121,76 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { } } -fileprivate struct NOOPManifestSourceControlValidator: ManifestSourceControlValidator { - func isValidRefFormat(_ revision: String) -> Bool { - true +final class ManifestTestDelegate: ManifestLoaderDelegate { + private let loaded = ThreadSafeArrayStore() + private let parsed = ThreadSafeArrayStore() + private let loadingGroup = DispatchGroup() + private let parsingGroup = DispatchGroup() + + func prepare(expectParsing: Bool = true) { + self.loadingGroup.enter() + if expectParsing { + self.parsingGroup.enter() + } } - func isValidDirectory(_ path: AbsolutePath) -> Bool { + func willLoad(packageIdentity: PackageModel.PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // noop + } + + func didLoad(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { + self.loaded.append(manifestPath) + self.loadingGroup.leave() + } + + func willParse(packageIdentity: PackageIdentity, packageLocation: String) { + // noop + } + + func didParse(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + // noop + } + + func willCompile(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // noop + } + + func didCompile(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { + // noop + } + + func willEvaluate(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { + // noop + } + + func didEvaluate(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { + self.parsed.append(manifestPath) + self.parsingGroup.leave() + } + + + func clear() { + self.loaded.clear() + self.parsed.clear() + } + + func loaded(timeout: DispatchTime) throws -> [AbsolutePath] { + guard case .success = self.loadingGroup.wait(timeout: timeout) else { + throw StringError("timeout waiting for loading") + } + return self.loaded.get() + } + + func parsed(timeout: DispatchTime) throws -> [AbsolutePath] { + guard case .success = self.parsingGroup.wait(timeout: timeout) else { + throw StringError("timeout waiting for parsing") + } + return self.parsed.get() + } +} + +fileprivate struct NOOPManifestSourceControlValidator: ManifestSourceControlValidator { + func isValidDirectory(_ path: AbsolutePath) throws -> Bool { true } } diff --git a/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift b/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift index e9fe155ea90..435c8025a27 100644 --- a/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift @@ -137,12 +137,12 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { let package = Package( name: "Foo", dependencies: [ - .package(url: "\(AbsolutePath("/foo1").escapedPathString())", from: "1.0.0"), - .package(url: "\(AbsolutePath("/foo2").escapedPathString())", .upToNextMajor(from: "1.0.0")), - .package(url: "\(AbsolutePath("/foo3").escapedPathString())", .upToNextMinor(from: "1.0.0")), - .package(url: "\(AbsolutePath("/foo4").escapedPathString())", .exact("1.0.0")), - .package(url: "\(AbsolutePath("/foo5").escapedPathString())", .branch("main")), - .package(url: "\(AbsolutePath("/foo6").escapedPathString())", .revision("58e9de4e7b79e67c72a46e164158e3542e570ab6")), + .package(url: "\(AbsolutePath("/foo1").escapedPathString)", from: "1.0.0"), + .package(url: "\(AbsolutePath("/foo2").escapedPathString)", .upToNextMajor(from: "1.0.0")), + .package(url: "\(AbsolutePath("/foo3").escapedPathString)", .upToNextMinor(from: "1.0.0")), + .package(url: "\(AbsolutePath("/foo4").escapedPathString)", .exact("1.0.0")), + .package(url: "\(AbsolutePath("/foo5").escapedPathString)", .branch("main")), + .package(url: "\(AbsolutePath("/foo6").escapedPathString)", .revision("58e9de4e7b79e67c72a46e164158e3542e570ab6")), ] ) """ diff --git a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift index eb2c13bc5fc..e00141d8479 100644 --- a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift @@ -17,12 +17,13 @@ import PackageModel import SPMTestSupport import XCTest -import class TSCBasic.DiagnosticsEngine import class TSCBasic.InMemoryFileSystem import enum TSCBasic.PathValidationError import func TSCTestSupport.withCustomEnv +import struct TSCUtility.Version + class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v4_2 @@ -38,7 +39,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { .library(name: "Foo", targets: ["foo"]), ], dependencies: [ - .package(url: "\(AbsolutePath("/foo1").escapedPathString())", from: "1.0.0"), + .package(url: "\(AbsolutePath("/foo1").escapedPathString)", from: "1.0.0"), ], targets: [ .target( @@ -253,15 +254,15 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { let package = Package( name: "Foo", dependencies: [ - .package(url: "\(AbsolutePath("/foo1").escapedPathString())", from: "1.0.0"), - .package(url: "\(AbsolutePath("/foo2").escapedPathString())", .revision("58e9de4e7b79e67c72a46e164158e3542e570ab6")), + .package(url: "\(AbsolutePath("/foo1").escapedPathString)", from: "1.0.0"), + .package(url: "\(AbsolutePath("/foo2").escapedPathString)", .revision("58e9de4e7b79e67c72a46e164158e3542e570ab6")), .package(path: "../foo3"), - .package(path: "\(AbsolutePath("/path/to/foo4").escapedPathString())"), - .package(url: "\(AbsolutePath("/foo5").escapedPathString())", .exact("1.2.3")), - .package(url: "\(AbsolutePath("/foo6").escapedPathString())", "1.2.3"..<"2.0.0"), - .package(url: "\(AbsolutePath("/foo7").escapedPathString())", .branch("master")), - .package(url: "\(AbsolutePath("/foo8").escapedPathString())", .upToNextMinor(from: "1.3.4")), - .package(url: "\(AbsolutePath("/foo9").escapedPathString())", .upToNextMajor(from: "1.3.4")), + .package(path: "\(AbsolutePath("/path/to/foo4").escapedPathString)"), + .package(url: "\(AbsolutePath("/foo5").escapedPathString)", .exact("1.2.3")), + .package(url: "\(AbsolutePath("/foo6").escapedPathString)", "1.2.3"..<"2.0.0"), + .package(url: "\(AbsolutePath("/foo7").escapedPathString)", .branch("master")), + .package(url: "\(AbsolutePath("/foo8").escapedPathString)", .upToNextMinor(from: "1.3.4")), + .package(url: "\(AbsolutePath("/foo9").escapedPathString)", .upToNextMajor(from: "1.3.4")), .package(path: "~/path/to/foo10"), .package(path: "~foo11"), .package(path: "~/path/to/~/foo12"), @@ -575,212 +576,6 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testCacheInvalidationOnEnv() throws { - try testWithTemporaryDirectory { path in - let fs = localFileSystem - let observability = ObservabilitySystem.makeForTesting() - - let manifestPath = path.appending(components: "pkg", "Package.swift") - try fs.createDirectory(manifestPath.parentDirectory, recursive: true) - try fs.writeFileContents( - manifestPath, - string: """ - import PackageDescription - let package = Package( - name: "Trivial", - targets: [ - .target( - name: "foo", - dependencies: []), - ] - ) - """ - ) - - let delegate = ManifestTestDelegate() - - let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, cacheDir: path, delegate: delegate) - - func check(loader: ManifestLoader, expectCached: Bool) throws { - delegate.clear() - delegate.prepare(expectParsing: !expectCached) - - let manifest = try XCTUnwrap(loader.load( - manifestPath: manifestPath, - packageKind: .fileSystem(manifestPath.parentDirectory), - toolsVersion: .v4_2, - fileSystem: fs, - observabilityScope: observability.topScope - )) - - XCTAssertNoDiagnostics(observability.diagnostics) - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath]) - XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1) - XCTAssertEqual(manifest.displayName, "Trivial") - XCTAssertEqual(manifest.targets[0].name, "foo") - } - - try check(loader: manifestLoader, expectCached: false) - try check(loader: manifestLoader, expectCached: true) - - try withCustomEnv(["SWIFTPM_MANIFEST_CACHE_TEST": "1"]) { - try check(loader: manifestLoader, expectCached: false) - try check(loader: manifestLoader, expectCached: true) - } - - try withCustomEnv(["SWIFTPM_MANIFEST_CACHE_TEST": "2"]) { - try check(loader: manifestLoader, expectCached: false) - try check(loader: manifestLoader, expectCached: true) - } - - try check(loader: manifestLoader, expectCached: true) - } - } - - func testCaching() throws { - try testWithTemporaryDirectory { path in - let fs = localFileSystem - let observability = ObservabilitySystem.makeForTesting() - - let manifestPath = path.appending(components: "pkg", "Package.swift") - try fs.createDirectory(manifestPath.parentDirectory, recursive: true) - try fs.writeFileContents( - manifestPath, - string: """ - import PackageDescription - let package = Package( - name: "Trivial", - targets: [ - .target( - name: "foo", - dependencies: []), - ] - ) - """ - ) - - let delegate = ManifestTestDelegate() - - let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, cacheDir: path, delegate: delegate) - - func check(loader: ManifestLoader, expectCached: Bool) throws { - delegate.clear() - delegate.prepare(expectParsing: !expectCached) - - let manifest = try XCTUnwrap(loader.load( - manifestPath: manifestPath, - packageKind: .fileSystem(manifestPath.parentDirectory), - toolsVersion: .v4_2, - fileSystem: fs, - observabilityScope: observability.topScope - )) - - XCTAssertNoDiagnostics(observability.diagnostics) - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath]) - XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1) - XCTAssertEqual(manifest.displayName, "Trivial") - XCTAssertEqual(manifest.targets[0].name, "foo") - } - - try check(loader: manifestLoader, expectCached: false) - for _ in 0..<2 { - try check(loader: manifestLoader, expectCached: true) - } - - try fs.writeFileContents( - manifestPath, - string: """ - import PackageDescription - - let package = Package( - - name: "Trivial", - targets: [ - .target( - name: "foo", - dependencies: [ ]), - ] - ) - - """ - ) - - try check(loader: manifestLoader, expectCached: false) - for _ in 0..<2 { - try check(loader: manifestLoader, expectCached: true) - } - - let noCacheLoader = ManifestLoader(toolchain: try UserToolchain.default, delegate: delegate) - for _ in 0..<2 { - try check(loader: noCacheLoader, expectCached: false) - } - - // Resetting the cache should allow us to remove the cache - // directory without triggering assertions in sqlite. - manifestLoader.purgeCache(observabilityScope: observability.topScope) - XCTAssertNoDiagnostics(observability.diagnostics) - try localFileSystem.removeFileTree(path) - } - } - - func testContentBasedCaching() throws { - try testWithTemporaryDirectory { path in - let manifest = """ - import PackageDescription - let package = Package( - name: "Trivial", - targets: [ - .target(name: "foo"), - ] - ) - """ - - let delegate = ManifestTestDelegate() - - let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, cacheDir: path, delegate: delegate) - - func check(loader: ManifestLoader, manifest: String) throws { - let fs = InMemoryFileSystem() - let observability = ObservabilitySystem.makeForTesting() - - let manifestPath = AbsolutePath.root.appending(component: Manifest.filename) - try fs.writeFileContents(manifestPath, string: manifest) - - let m = try manifestLoader.load( - manifestPath: manifestPath, - packageKind: .root(.root), - toolsVersion: .v4_2, - fileSystem: fs, - observabilityScope: observability.topScope - ) - - XCTAssertNoDiagnostics(observability.diagnostics) - XCTAssertEqual(m.displayName, "Trivial") - } - - do { - delegate.prepare() - try check(loader: manifestLoader, manifest: manifest) - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, 1) - XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, 1) - } - - do { - delegate.prepare(expectParsing: false) - try check(loader: manifestLoader, manifest: manifest) - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, 2) - XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, 1) - } - - do { - delegate.prepare() - try check(loader: manifestLoader, manifest: manifest + "\n\n") - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, 3) - XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, 2) - } - } - } - func testProductTargetNotFound() throws { let content = """ import PackageDescription @@ -831,6 +626,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { let delegate = ManifestTestDelegate() let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, cacheDir: path, delegate: delegate) let identityResolver = DefaultIdentityResolver() + let dependencyMapper = DefaultDependencyMapper(identityResolver: identityResolver) // warm up caches delegate.prepare() @@ -843,6 +639,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { packageLocation: manifestPath.pathString, packageVersion: nil, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: localFileSystem, observabilityScope: observability.topScope, delegateQueue: .sharedConcurrent, @@ -867,6 +664,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { packageLocation: manifestPath.pathString, packageVersion: nil, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: localFileSystem, observabilityScope: observability.topScope, delegateQueue: .sharedConcurrent, @@ -912,6 +710,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { let delegate = ManifestTestDelegate() let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, cacheDir: path, delegate: delegate) let identityResolver = DefaultIdentityResolver() + let dependencyMapper = DefaultDependencyMapper(identityResolver: identityResolver) let sync = DispatchGroup() for _ in 0 ..< total { @@ -945,6 +744,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { packageLocation: manifestPath.pathString, packageVersion: nil, identityResolver: identityResolver, + dependencyMapper: dependencyMapper, fileSystem: localFileSystem, observabilityScope: observability.topScope, delegateQueue: .sharedConcurrent, @@ -974,53 +774,4 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } #endif } - - final class ManifestTestDelegate: ManifestLoaderDelegate { - private let loaded = ThreadSafeArrayStore() - private let parsed = ThreadSafeArrayStore() - private let loadingGroup = DispatchGroup() - private let parsingGroup = DispatchGroup() - - func prepare(expectParsing: Bool = true) { - self.loadingGroup.enter() - if expectParsing { - self.parsingGroup.enter() - } - } - - func willLoad(manifest: AbsolutePath) { - self.loaded.append(manifest) - self.loadingGroup.leave() - } - - func willParse(manifest: AbsolutePath) { - self.parsed.append(manifest) - self.parsingGroup.leave() - } - - func clear() { - self.loaded.clear() - self.parsed.clear() - } - - func loaded(timeout: DispatchTime) throws -> [AbsolutePath] { - guard case .success = self.loadingGroup.wait(timeout: timeout) else { - throw StringError("timeout waiting for loading") - } - return self.loaded.get() - } - - func parsed(timeout: DispatchTime) throws -> [AbsolutePath] { - guard case .success = self.parsingGroup.wait(timeout: timeout) else { - throw StringError("timeout waiting for parsing") - } - return self.parsed.get() - } - } -} - -extension DiagnosticsEngine { - public var hasWarnings: Bool { - return diagnostics.contains(where: { $0.message.behavior == .warning }) - } } diff --git a/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift index 934d7393cf0..436448b46c3 100644 --- a/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift @@ -218,8 +218,10 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in - result.checkUnordered(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'foo' (at '/foo'), 'bar' (at '/bar')", severity: .error) - result.checkUnordered(diagnostic: "unknown dependency 'foos' in target 'Target2'; valid dependencies are: 'foo' (at '/foo'), 'bar' (at '/bar')", severity: .error) + let fooPkg: AbsolutePath = "/foo" + let barPkg: AbsolutePath = "/bar" + result.checkUnordered(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'foo' (at '\(fooPkg)'), 'bar' (at '\(barPkg)')", severity: .error) + result.checkUnordered(diagnostic: "unknown dependency 'foos' in target 'Target2'; valid dependencies are: 'foo' (at '\(fooPkg)'), 'bar' (at '\(barPkg)')", severity: .error) } } @@ -248,8 +250,10 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in - result.checkUnordered(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'Foo' (at '/foo1'), 'Bar' (at '/bar1')", severity: .error) - result.checkUnordered(diagnostic: "unknown dependency 'foos' in target 'Target2'; valid dependencies are: 'Foo' (at '/foo1'), 'Bar' (at '/bar1')", severity: .error) + let foo1Pkg: AbsolutePath = "/foo1" + let bar1Pkg: AbsolutePath = "/bar1" + result.checkUnordered(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'Foo' (at '\(foo1Pkg)'), 'Bar' (at '\(bar1Pkg)')", severity: .error) + result.checkUnordered(diagnostic: "unknown dependency 'foos' in target 'Target2'; valid dependencies are: 'Foo' (at '\(foo1Pkg)'), 'Bar' (at '\(bar1Pkg)')", severity: .error) } } } diff --git a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift index 9efa3481184..77a67485b3e 100644 --- a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift @@ -208,7 +208,9 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) - let name = parsedManifest.parentDirectory?.pathString ?? "" + XCTAssertNotNil(parsedManifest) + XCTAssertNotNil(parsedManifest.parentDirectory) + let name = try XCTUnwrap(parsedManifest.parentDirectory).pathString XCTAssertEqual(manifest.displayName, name) } diff --git a/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift index 2591e82733e..a21d9ff5ed4 100644 --- a/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift @@ -180,7 +180,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, restrictImports: (.v5_7, [])) + let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, importRestrictions: (.v5_7, [])) XCTAssertThrowsError(try loadAndValidateManifest(content, customManifestLoader: manifestLoader, observabilityScope: observability.topScope)) { error in if case ManifestParseError.importsRestrictedModules(let modules) = error { XCTAssertEqual(modules.sorted(), ["BestModule", "Foundation"]) diff --git a/Tests/PackageLoadingTests/PkgConfigAllowlistTests.swift b/Tests/PackageLoadingTests/PkgConfigAllowlistTests.swift index 38a51d4e9f3..cddac43b194 100644 --- a/Tests/PackageLoadingTests/PkgConfigAllowlistTests.swift +++ b/Tests/PackageLoadingTests/PkgConfigAllowlistTests.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import Basics import PackageLoading import XCTest @@ -42,4 +43,12 @@ final class PkgConfigAllowlistTests: XCTestCase { XCTAssertEqual(result.0, ["-I", "/usr/include/Cellar/gtk+3/3.18.9/include/gtk-3.0", "-L/hello"]) XCTAssertEqual(result.1, ["-L/usr/lib/Cellar/gtk+3/3.18.9/lib", "-lgtk-3", "-module-name", "-lcool", "ok", "name"]) } + + func testPathSDKPaths() throws { + let flags = ["-I/opt/homebrew/Cellar/cairo/1.16.0_5/include/cairo", "-I/Library/Developer/CommandLineTools/SDKs/MacOSX13.sdk/usr/include/ffi"] + let sdk = AbsolutePath("/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk") + let result = try patchSDKPaths(in: flags, to: sdk) + + XCTAssertEqual(result, ["-I/opt/homebrew/Cellar/cairo/1.16.0_5/include/cairo", "-I\(sdk)/usr/include/ffi"]) + } } diff --git a/Tests/PackageLoadingTests/ToolsVersionParserTests.swift b/Tests/PackageLoadingTests/ToolsVersionParserTests.swift index 564659c266b..1cbae7794f4 100644 --- a/Tests/PackageLoadingTests/ToolsVersionParserTests.swift +++ b/Tests/PackageLoadingTests/ToolsVersionParserTests.swift @@ -764,4 +764,19 @@ class ToolsVersionParserTests: XCTestCase { } } + func testVersionSpecificManifestMostCompatibleIfLower() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/pkg/foo" + ) + let root = AbsolutePath("/pkg") + + try fs.writeFileContents(root.appending("Package.swift"), string: "// swift-tools-version:6.0.0\n") + try fs.writeFileContents(root.appending("Package@swift-5.0.swift"), string: "// swift-tools-version:5.0.0\n") + + let currentToolsVersion = ToolsVersion(version: "5.5.0") + let manifestPath = try ManifestLoader.findManifest(packagePath: root, fileSystem: fs, currentToolsVersion: currentToolsVersion) + let version = try ToolsVersionParser.parse(manifestPath: manifestPath, fileSystem: fs) + try version.validateToolsVersion(currentToolsVersion, packageIdentity: .plain("lunch")) + XCTAssertEqual(version.description, "5.0.0") + } } diff --git a/Tests/PackageModelTests/CanonicalPackageLocationTests.swift b/Tests/PackageModelTests/CanonicalPackageLocationTests.swift index ace25ccadc1..f53d765d0b0 100644 --- a/Tests/PackageModelTests/CanonicalPackageLocationTests.swift +++ b/Tests/PackageModelTests/CanonicalPackageLocationTests.swift @@ -324,4 +324,19 @@ final class CanonicalPackageLocationTests: XCTestCase { "example.com/mona/linked:list" ) } + + func testScheme() { + XCTAssertEqual(CanonicalPackageURL("https://example.com/mona/LinkedList").scheme, "https") + XCTAssertEqual(CanonicalPackageURL("git@example.com/mona/LinkedList").scheme, "ssh") + XCTAssertEqual(CanonicalPackageURL("git@example.com:mona/LinkedList.git ").scheme, "ssh") + XCTAssertEqual(CanonicalPackageURL("ssh://mona@example.com/~/LinkedList.git").scheme, "ssh") + XCTAssertEqual(CanonicalPackageURL("file:///Users/mona/LinkedList").scheme, "file") + XCTAssertEqual(CanonicalPackageURL("example.com:443/mona/LinkedList").scheme, nil) + XCTAssertEqual(CanonicalPackageURL("example.com/mona/%F0%9F%94%97List").scheme, nil) + XCTAssertEqual(CanonicalPackageURL("example.com/mona/LinkedList.git").scheme, nil) + XCTAssertEqual(CanonicalPackageURL("example.com/mona/LinkedList/").scheme, nil) + XCTAssertEqual(CanonicalPackageURL("example.com/mona/LinkedList#installation").scheme, nil) + XCTAssertEqual(CanonicalPackageURL("example.com/mona/LinkedList?utm_source=forums.swift.org").scheme, nil) + XCTAssertEqual(CanonicalPackageURL("user:sw0rdf1sh!@example.com:/mona/Linked:List.git").scheme, nil) + } } diff --git a/Tests/PackageModelTests/InstalledSwiftPMConfigurationTests.swift b/Tests/PackageModelTests/InstalledSwiftPMConfigurationTests.swift new file mode 100644 index 00000000000..2caa3459a99 --- /dev/null +++ b/Tests/PackageModelTests/InstalledSwiftPMConfigurationTests.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2020 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 XCTest + +@testable import PackageModel + +final class InstalledSwiftPMConfigurationTests: XCTestCase { + func testVersionDescription() { + do { + let version = InstalledSwiftPMConfiguration.Version(major: 509, minor: 0, patch: 0) + XCTAssertEqual(version.description, "509.0.0") + } + do { + let version = InstalledSwiftPMConfiguration.Version(major: 509, minor: 0, patch: 0, prereleaseIdentifier: "alpha1") + XCTAssertEqual(version.description, "509.0.0-alpha1") + } + do { + let version = InstalledSwiftPMConfiguration.Version(major: 509, minor: 0, patch: 0, prereleaseIdentifier: "beta.1") + XCTAssertEqual(version.description, "509.0.0-beta.1") + } + } +} diff --git a/Tests/PackageModelTests/PackageModelTests.swift b/Tests/PackageModelTests/PackageModelTests.swift index d509109fe9b..3fbfc13e8c7 100644 --- a/Tests/PackageModelTests/PackageModelTests.swift +++ b/Tests/PackageModelTests/PackageModelTests.swift @@ -61,14 +61,14 @@ class PackageModelTests: XCTestCase { let sdkDir = AbsolutePath("/some/path/to/an/SDK.sdk") let toolchainPath = AbsolutePath("/some/path/to/a/toolchain.xctoolchain") - let destination = SwiftSDK( + let swiftSDK = SwiftSDK( targetTriple: triple, toolset: .init(toolchainBinDir: toolchainPath.appending(components: "usr", "bin"), buildFlags: .init()), pathsConfiguration: .init(sdkRootPath: sdkDir) ) XCTAssertEqual( - try UserToolchain.deriveSwiftCFlags(triple: triple, swiftSDK: destination, environment: .process()), + try UserToolchain.deriveSwiftCFlags(triple: triple, swiftSDK: swiftSDK, environment: .process()), [ // Needed when cross‐compiling for Android. 2020‐03‐01 "-sdk", sdkDir.pathString, diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index f94185fc906..55439f4be68 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -import PackageModel +@testable import PackageModel import SPMTestSupport import XCTest @@ -21,27 +21,58 @@ import class TSCBasic.InMemoryFileSystem private let testArtifactID = "test-artifact" -private func generateInfoJSON(artifacts: [MockArtifact]) -> SerializedJSON { - """ - { - "artifacts" : { - \(artifacts.map { - """ - "\($0.id)" : { - "type" : "swiftSDK", - "version" : "0.0.1", - "variants" : [ - { - "path" : "\($0.id)/aarch64-unknown-linux", - "supportedTriples" : \($0.supportedTriples.map(\.tripleString)) +private let targetTriple = try! Triple("aarch64-unknown-linux") + +private let jsonEncoder = JSONEncoder() + +private func generateBundleFiles(bundle: MockBundle) throws -> [(String, ByteString)] { + try [ + ( + "\(bundle.path)/info.json", + ByteString(json: """ + { + "artifacts" : { + \(bundle.artifacts.map { + """ + "\($0.id)" : { + "type" : "swiftSDK", + "version" : "0.0.1", + "variants" : [ + { + "path" : "\($0.id)/\(targetTriple.triple)", + "supportedTriples" : \($0.supportedTriples.map(\.tripleString)) + } + ] } - ] - } - """ - }.joined(separator: ",\n") - ) - }, - "schemaVersion" : "1.0" + """ + }.joined(separator: ",\n") + ) + }, + "schemaVersion" : "1.0" + } + """) + ), + + ] + bundle.artifacts.map { + ( + "\(bundle.path)/\($0.id)/\(targetTriple.tripleString)/swift-sdk.json", + ByteString(json: try generateSwiftSDKMetadata(jsonEncoder)) + ) + } +} + +private func generateSwiftSDKMetadata(_ encoder: JSONEncoder) throws -> SerializedJSON { + try """ + { + "schemaVersion": "4.0", + "targetTriples": \( + String( + bytes: encoder.encode([ + targetTriple.tripleString: SwiftSDKMetadataV4.TripleProperties(sdkRootPath: "sdk") + ]), + encoding: .utf8 + )! + ) } """ } @@ -59,19 +90,17 @@ private struct MockArtifact { private func generateTestFileSystem(bundleArtifacts: [MockArtifact]) throws -> (some FileSystem, [MockBundle], AbsolutePath) { let bundles = bundleArtifacts.enumerated().map { (i, artifacts) in - let bundleName = "test\(i).artifactbundle" - return MockBundle(name: "test\(i).artifactbundle", path: "/\(bundleName)", artifacts: [artifacts]) + let bundleName = "test\(i).\(artifactBundleExtension)" + return MockBundle(name: "test\(i).\(artifactBundleExtension)", path: "/\(bundleName)", artifacts: [artifacts]) } - let fileSystem = InMemoryFileSystem( - files: Dictionary(uniqueKeysWithValues: bundles.map { - ( - "\($0.path)/info.json", - ByteString( - json: generateInfoJSON(artifacts: $0.artifacts) - ) - ) - }) + + let fileSystem = try InMemoryFileSystem( + files: Dictionary( + uniqueKeysWithValues: bundles.flatMap { + try generateBundleFiles(bundle: $0) + } + ) ) let swiftSDKsDirectory = try AbsolutePath(validating: "/sdks") @@ -82,9 +111,60 @@ private func generateTestFileSystem(bundleArtifacts: [MockArtifact]) throws -> ( } private let arm64Triple = try! Triple("arm64-apple-macosx13.0") -let i686Triple = try! Triple("i686-apple-macosx13.0") +private let i686Triple = try! Triple("i686-apple-macosx13.0") + +private let fixtureArchivePath = try! AbsolutePath(validating: #file) + .parentDirectory + .parentDirectory + .parentDirectory + .appending(components: ["Fixtures", "SwiftSDKs", "test-sdk.artifactbundle.tar.gz"]) final class SwiftSDKBundleTests: XCTestCase { + func testInstallRemote() async throws { + #if canImport(Darwin) && !os(macOS) + try XCTSkipIf(true, "skipping test because process launching is not available") + #endif + + let system = ObservabilitySystem.makeForTesting() + var output = [SwiftSDKBundleStore.Output]() + let observabilityScope = system.topScope + let cancellator = Cancellator(observabilityScope: observabilityScope) + let archiver = UniversalArchiver(localFileSystem, cancellator) + + let httpClient = HTTPClient { request, _ in + guard case let .download(_, downloadPath) = request.kind else { + XCTFail("Unexpected HTTPClient.Request.Kind") + return .init(statusCode: 400) + } + try localFileSystem.copy(from: fixtureArchivePath, to: downloadPath) + return .init(statusCode: 200) + } + + try await withTemporaryDirectory(fileSystem: localFileSystem, removeTreeOnDeinit: true) { tmpDir in + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: tmpDir, + fileSystem: localFileSystem, + observabilityScope: observabilityScope, + outputHandler: { + output.append($0) + } + ) + let bundleURLString = "https://localhost/archive?test=foo" + try await store.install(bundlePathOrURL: bundleURLString, archiver, httpClient) + + let bundleURL = URL(string: bundleURLString)! + XCTAssertEqual(output, [ + .downloadStarted(bundleURL), + .downloadFinishedSuccessfully(bundleURL), + .unpackingArchive(bundlePathOrURL: bundleURLString), + .installationSuccessful( + bundlePathOrURL: bundleURLString, + bundleName: "test-sdk.artifactbundle" + ), + ]) + }.value + } + func testInstall() async throws { let system = ObservabilitySystem.makeForTesting() @@ -97,23 +177,23 @@ final class SwiftSDKBundleTests: XCTestCase { let archiver = MockArchiver() - try await SwiftSDKBundle.install( - bundlePathOrURL: bundles[0].path, + var output = [SwiftSDKBundleStore.Output]() + let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, - fileSystem, - archiver, - system.topScope + fileSystem: fileSystem, + observabilityScope: system.topScope, + outputHandler: { + output.append($0) + } ) + // Expected to be successful: + try await store.install(bundlePathOrURL: bundles[0].path, archiver) + + // Expected to fail: let invalidPath = "foobar" do { - try await SwiftSDKBundle.install( - bundlePathOrURL: "foobar", - swiftSDKsDirectory: swiftSDKsDirectory, - fileSystem, - archiver, - system.topScope - ) + try await store.install(bundlePathOrURL: invalidPath, archiver) XCTFail("Function expected to throw") } catch { @@ -123,21 +203,15 @@ final class SwiftSDKBundleTests: XCTestCase { } switch error { - case .invalidBundleName(let bundleName): - XCTAssertEqual(bundleName, invalidPath) + case let .invalidBundleArchive(archivePath): + XCTAssertEqual(archivePath, AbsolutePath.root.appending(invalidPath)) default: XCTFail("Unexpected error value") } } do { - try await SwiftSDKBundle.install( - bundlePathOrURL: bundles[0].path, - swiftSDKsDirectory: swiftSDKsDirectory, - fileSystem, - archiver, - system.topScope - ) + try await store.install(bundlePathOrURL: bundles[0].path, archiver) XCTFail("Function expected to throw") } catch { @@ -147,21 +221,17 @@ final class SwiftSDKBundleTests: XCTestCase { } switch error { - case .swiftSDKBundleAlreadyInstalled(let installedBundleName): + case let .swiftSDKArtifactAlreadyInstalled(installedBundleName, newBundleName, artifactID): XCTAssertEqual(bundles[0].name, installedBundleName) + XCTAssertEqual(newBundleName, "test0.\(artifactBundleExtension)") + XCTAssertEqual(artifactID, testArtifactID) default: XCTFail("Unexpected error value") } } do { - try await SwiftSDKBundle.install( - bundlePathOrURL: bundles[1].path, - swiftSDKsDirectory: swiftSDKsDirectory, - fileSystem, - archiver, - system.topScope - ) + try await store.install(bundlePathOrURL: bundles[1].path, archiver) XCTFail("Function expected to throw") } catch { @@ -179,33 +249,96 @@ final class SwiftSDKBundleTests: XCTestCase { XCTFail("Unexpected error value") } } + + XCTAssertEqual(output, [ + .installationSuccessful( + bundlePathOrURL: bundles[0].path, + bundleName: AbsolutePath(bundles[0].path).components.last! + ), + .unpackingArchive(bundlePathOrURL: invalidPath), + ]) } func testList() async throws { let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem( bundleArtifacts: [ + .init(id: "\(testArtifactID)2", supportedTriples: [i686Triple]), .init(id: "\(testArtifactID)1", supportedTriples: [arm64Triple]), - .init(id: "\(testArtifactID)2", supportedTriples: [i686Triple]) ] ) let system = ObservabilitySystem.makeForTesting() + let archiver = MockArchiver() + + var output = [SwiftSDKBundleStore.Output]() + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem: fileSystem, + observabilityScope: system.topScope, + outputHandler: { + output.append($0) + } + ) for bundle in bundles { - try await SwiftSDKBundle.install( - bundlePathOrURL: bundle.path, - swiftSDKsDirectory: swiftSDKsDirectory, - fileSystem, - MockArchiver(), - system.topScope - ) + try await store.install(bundlePathOrURL: bundle.path, archiver) } - let validBundles = try SwiftSDKBundle.getAllValidBundles( + let validBundles = try store.allValidBundles + + XCTAssertEqual(validBundles.count, bundles.count) + + XCTAssertEqual(validBundles.sortedArtifactIDs, ["\(testArtifactID)1", "\(testArtifactID)2"]) + XCTAssertEqual(output, [ + .installationSuccessful( + bundlePathOrURL: bundles[0].path, + bundleName: AbsolutePath(bundles[0].path).components.last! + ), + .installationSuccessful( + bundlePathOrURL: bundles[1].path, + bundleName: AbsolutePath(bundles[1].path).components.last! + ), + ]) + } + + func testBundleSelection() async throws { + let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem( + bundleArtifacts: [ + .init(id: "\(testArtifactID)1", supportedTriples: [arm64Triple]), + .init(id: "\(testArtifactID)2", supportedTriples: [i686Triple]) + ] + ) + let system = ObservabilitySystem.makeForTesting() + + var output = [SwiftSDKBundleStore.Output]() + let store = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, fileSystem: fileSystem, - observabilityScope: system.topScope + observabilityScope: system.topScope, + outputHandler: { + output.append($0) + } ) - XCTAssertEqual(validBundles.count, bundles.count) + let archiver = MockArchiver() + for bundle in bundles { + try await store.install(bundlePathOrURL: bundle.path, archiver) + } + + let sdk = try store.selectBundle( + matching: "\(testArtifactID)1", + hostTriple: Triple("arm64-apple-macosx14.0") + ) + + XCTAssertEqual(sdk.targetTriple, targetTriple) + XCTAssertEqual(output, [ + .installationSuccessful( + bundlePathOrURL: bundles[0].path, + bundleName: AbsolutePath(bundles[0].path).components.last! + ), + .installationSuccessful( + bundlePathOrURL: bundles[1].path, + bundleName: AbsolutePath(bundles[1].path).components.last! + ), + ]) } } diff --git a/Tests/PackageModelTests/SwiftSDKTests.swift b/Tests/PackageModelTests/SwiftSDKTests.swift index 562a595ec76..889906b76a5 100644 --- a/Tests/PackageModelTests/SwiftSDKTests.swift +++ b/Tests/PackageModelTests/SwiftSDKTests.swift @@ -408,12 +408,13 @@ final class DestinationTests: XCTestCase { fileSystem: fs, observabilityScope: observability )) { + let toolsetDefinition: AbsolutePath = "/tools/asdf.json" XCTAssertEqual( $0 as? StringError, StringError( """ - Couldn't parse toolset configuration at `/tools/asdf.json`: /tools/asdf.json doesn't exist in file \ - system + Couldn't parse toolset configuration at `\(toolsetDefinition)`: \ + \(toolsetDefinition) doesn't exist in file system """ ) ) @@ -429,9 +430,10 @@ final class DestinationTests: XCTestCase { fileSystem: fs, observabilityScope: observability )) { + let toolsetDefinition: AbsolutePath = "/tools/invalidToolset.json" XCTAssertTrue( ($0 as? StringError)?.description - .hasPrefix("Couldn't parse toolset configuration at `/tools/invalidToolset.json`: ") ?? false + .hasPrefix("Couldn't parse toolset configuration at `\(toolsetDefinition)`: ") ?? false ) } @@ -456,12 +458,13 @@ final class DestinationTests: XCTestCase { fileSystem: fs, observabilityScope: observability )) { + let toolsetDefinition: AbsolutePath = "/tools/asdf.json" XCTAssertEqual( $0 as? StringError, StringError( """ - Couldn't parse toolset configuration at `/tools/asdf.json`: /tools/asdf.json doesn't exist in file \ - system + Couldn't parse toolset configuration at `\(toolsetDefinition)`: \ + \(toolsetDefinition) doesn't exist in file system """ ) ) @@ -477,9 +480,10 @@ final class DestinationTests: XCTestCase { fileSystem: fs, observabilityScope: observability )) { + let toolsetDefinition: AbsolutePath = "/tools/invalidToolset.json" XCTAssertTrue( ($0 as? StringError)?.description - .hasPrefix("Couldn't parse toolset configuration at `/tools/invalidToolset.json`: ") ?? false + .hasPrefix("Couldn't parse toolset configuration at `\(toolsetDefinition)`: ") ?? false ) } } @@ -523,7 +527,7 @@ final class DestinationTests: XCTestCase { let system = ObservabilitySystem.makeForTesting() XCTAssertEqual( - bundles.selectDestination( + bundles.selectSwiftSDK( matching: "id1", hostTriple: hostTriple, observabilityScope: system.topScope @@ -534,7 +538,7 @@ final class DestinationTests: XCTestCase { // Expecting `nil` because no host triple is specified for this destination // in the fake destination bundle. XCTAssertNil( - bundles.selectDestination( + bundles.selectSwiftSDK( matching: "id2", hostTriple: hostTriple, observabilityScope: system.topScope @@ -542,7 +546,7 @@ final class DestinationTests: XCTestCase { ) XCTAssertEqual( - bundles.selectDestination( + bundles.selectSwiftSDK( matching: "id3", hostTriple: hostTriple, observabilityScope: system.topScope diff --git a/Tests/PackageRegistryTests/PackageSigningEntityTOFUTests.swift b/Tests/PackageRegistryTests/PackageSigningEntityTOFUTests.swift index 5df3496626f..2e4c9b34578 100644 --- a/Tests/PackageRegistryTests/PackageSigningEntityTOFUTests.swift +++ b/Tests/PackageRegistryTests/PackageSigningEntityTOFUTests.swift @@ -22,7 +22,7 @@ import XCTest import struct TSCUtility.Version final class PackageSigningEntityTOFUTests: XCTestCase { - func testSigningEntitySeenForTheFirstTime() throws { + func testSigningEntitySeenForTheFirstTime() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -43,30 +43,25 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Package doesn't have any recorded signer. // It should be ok to assign one. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity ) // `signingEntity` meets requirement to be used for TOFU // (i.e., it's .recognized), so it should be saved to storage. - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[signingEntity]?.versions, [version]) } - func testNilSigningEntityShouldNotBeSaved() throws { + func testNilSigningEntityShouldNotBeSaved() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -81,28 +76,23 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Package doesn't have any recorded signer. // It should be ok to continue not to have one. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: .none - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: .none ) // `signingEntity` is nil, so it should not be saved to storage. - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertTrue(packageSigners.isEmpty) } - func testUnrecognizedSigningEntityShouldNotBeSaved() throws { + func testUnrecognizedSigningEntityShouldNotBeSaved() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -122,28 +112,23 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Package doesn't have any recorded signer. // It should be ok to continue not to have one. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity ) // `signingEntity` is not .recognized, so it should not be saved to storage. - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertTrue(packageSigners.isEmpty) } - func testSigningEntityMatchesStorageForSameVersion() throws { + func testSigningEntityMatchesStorageForSameVersion() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -173,17 +158,15 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Appleseed" as signer for package version. // Signer remaining the same should be ok. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity ) } - func testSigningEntityDoesNotMatchStorageForSameVersion_strictMode() throws { + func testSigningEntityDoesNotMatchStorageForSameVersion_strictMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -219,8 +202,8 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Smith" as signer for package version. // The given signer "J. Appleseed" is different so it should fail. - XCTAssertThrowsError( - try tofu.validate( + await XCTAssertAsyncThrowsError( + try await tofu.validate( registry: registry, package: package, version: version, @@ -235,19 +218,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, [version]) } - func testSigningEntityDoesNotMatchStorageForSameVersion_warnMode() throws { + func testSigningEntityDoesNotMatchStorageForSameVersion_warnMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -286,14 +266,12 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Smith" as signer for package version. // The given signer "J. Appleseed" is different, but because // of .warn mode, no error is thrown. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity, - observabilityScope: observability.topScope - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity, + observabilityScope: observability.topScope ) // But there should be a warning @@ -302,19 +280,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, [version]) } - func testPackageVersionLosingSigningEntity_strictMode() throws { + func testPackageVersionLosingSigningEntity_strictMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -344,8 +319,8 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Smith" as signer for package version. // The given signer is nil which is different so it should fail. - XCTAssertThrowsError( - try tofu.validate( + await XCTAssertAsyncThrowsError( + try await tofu.validate( registry: registry, package: package, version: version, @@ -360,19 +335,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, [version]) } - func testSigningEntityMatchesStorageForDifferentVersion() throws { + func testSigningEntityMatchesStorageForDifferentVersion() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -403,29 +375,24 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Appleseed" as signer for package v2.0.0. // Signer remaining the same should be ok. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity ) // Storage should be updated with version 1.1.1 added - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[signingEntity]?.versions, [existingVersion, version]) } - func testSigningEntityDoesNotMatchStorageForDifferentVersion_strictMode() throws { + func testSigningEntityDoesNotMatchStorageForDifferentVersion_strictMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -462,8 +429,8 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Smith" as signer for package v2.0.0. // The given signer "J. Appleseed" is different so it should fail. - XCTAssertThrowsError( - try tofu.validate( + await XCTAssertAsyncThrowsError( + try await tofu.validate( registry: registry, package: package, version: version, @@ -486,19 +453,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, [existingVersion]) } - func testSigningEntityDoesNotMatchStorageForDifferentVersion_warnMode() throws { + func testSigningEntityDoesNotMatchStorageForDifferentVersion_warnMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -538,14 +502,12 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has "J. Smith" as signer for package v2.0.0. // The given signer "J. Appleseed" is different, but because // of .warn mode, no error is thrown. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity, - observabilityScope: observability.topScope - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity, + observabilityScope: observability.topScope ) // But there should be a warning @@ -554,19 +516,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, [existingVersion]) } - func testNilSigningEntityWhenStorageHasNewerSignedVersions() throws { + func testNilSigningEntityWhenStorageHasNewerSignedVersions() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -598,29 +557,24 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has versions 1.5.0 and 2.0.0 signed. The given version 1.1.1 is // "older" than both, and we allow nil signer in this case, assuming // this is before package started being signed. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: .none - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: .none ) // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, existingVersions) } - func testNilSigningEntityWhenStorageHasOlderSignedVersions_strictMode() throws { + func testNilSigningEntityWhenStorageHasOlderSignedVersions_strictMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.6.1") @@ -652,8 +606,8 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Storage has versions 1.5.0 and 2.0.0 signed. The given version 1.6.1 is // "newer" than 1.5.0, which we don't allow, because we assume from 1.5.0 // onwards all versions are signed. - XCTAssertThrowsError( - try tofu.validate( + await XCTAssertAsyncThrowsError( + try await tofu.validate( registry: registry, package: package, version: version, @@ -676,19 +630,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, existingVersions) } - func testNilSigningEntityWhenStorageHasOlderSignedVersions_warnMode() throws { + func testNilSigningEntityWhenStorageHasOlderSignedVersions_warnMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.6.1") @@ -723,14 +674,12 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // "newer" than 1.5.0, which we don't allow, because we assume from 1.5.0 // onwards all versions are signed. However, because of .warn mode, // no error is thrown. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: .none, - observabilityScope: observability.topScope - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: .none, + observabilityScope: observability.topScope ) // But there should be a warning @@ -739,19 +688,16 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, existingVersions) } - func testNilSigningEntityWhenStorageHasOlderSignedVersionsInDifferentMajorVersion() throws { + func testNilSigningEntityWhenStorageHasOlderSignedVersionsInDifferentMajorVersion() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("2.0.0") @@ -785,29 +731,24 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // We allow this with the assumption that package signing might not have // begun until a later 2.x version, so until we encounter a signed 2.x version, // we assume none of them is signed. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: .none - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: .none ) // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[existingSigningEntity]?.versions, existingVersions) } - func testSigningEntityOfNewerVersionMatchesExpectedSigner() throws { + func testSigningEntityOfNewerVersionMatchesExpectedSigner() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("2.0.0") @@ -839,29 +780,24 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // Package has expected signer starting from v1.5.0. // The given v2.0.0 is newer than v1.5.0, and signer // matches the expected signer. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: expectedSigningEntity - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: expectedSigningEntity ) // Storage should be updated with v2.0.0 added - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[expectedSigningEntity]?.versions, [expectedFromVersion, version]) } - func testSigningEntityOfNewerVersionDoesNotMatchExpectedSignerButOlderThanExisting() throws { + func testSigningEntityOfNewerVersionDoesNotMatchExpectedSignerButOlderThanExisting() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("2.0.0") @@ -908,30 +844,25 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // the given signer was recorded previously for v2.2.0. // The given v2.0.0 is before v2.2.0, and we allow the same // signer for older versions. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity ) // Storage should be updated with v2.0.0 added - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 2) XCTAssertEqual(packageSigners.signers[expectedSigningEntity]?.versions, [expectedFromVersion]) XCTAssertEqual(packageSigners.signers[signingEntity]?.versions, [existingVersion, version]) } - func testSigningEntityOfNewerVersionDoesNotMatchExpectedSignerAndNewerThanExisting() throws { + func testSigningEntityOfNewerVersionDoesNotMatchExpectedSignerAndNewerThanExisting() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("2.3.0") @@ -978,8 +909,8 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // the given signer was recorded previously for v2.2.0, but // the given v2.3.0 is after v2.2.0, which we don't allow // because we assume the signer has "stopped" signing at v2.2.0. - XCTAssertThrowsError( - try tofu.validate( + await XCTAssertAsyncThrowsError( + try await tofu.validate( registry: registry, package: package, version: version, @@ -1002,20 +933,17 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } // Storage should not be updated - let packageSigners = try temp_await { callback in - signingEntityStorage.get( - package: package.underlying, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: callback - ) - } + let packageSigners = try await signingEntityStorage.get( + package: package.underlying, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) XCTAssertEqual(packageSigners.signers.count, 2) XCTAssertEqual(packageSigners.signers[expectedSigningEntity]?.versions, [expectedFromVersion]) XCTAssertEqual(packageSigners.signers[signingEntity]?.versions, [existingVersion]) } - func testWriteConflictsWithStorage_strictMode() throws { + func testWriteConflictsWithStorage_strictMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -1035,8 +963,8 @@ final class PackageSigningEntityTOFUTests: XCTestCase { ) // This triggers a storage write conflict - XCTAssertThrowsError( - try tofu.validate( + await XCTAssertAsyncThrowsError( + try await tofu.validate( registry: registry, package: package, version: version, @@ -1049,7 +977,7 @@ final class PackageSigningEntityTOFUTests: XCTestCase { } } - func testWriteConflictsWithStorage_warnMode() throws { + func testWriteConflictsWithStorage_warnMode() async throws { let registry = Registry(url: URL("https://packages.example.com"), supportsAvailability: false) let package = PackageIdentity.plain("mona.LinkedList").registry! let version = Version("1.1.1") @@ -1072,14 +1000,12 @@ final class PackageSigningEntityTOFUTests: XCTestCase { // This triggers a storage write conflict, but // because of .warn mode, no error is thrown. - XCTAssertNoThrow( - try tofu.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity, - observabilityScope: observability.topScope - ) + _ = try await tofu.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity, + observabilityScope: observability.topScope ) // But there should be a warning @@ -1096,18 +1022,15 @@ extension PackageSigningEntityTOFU { version: Version, signingEntity: SigningEntity?, observabilityScope: ObservabilityScope? = nil - ) throws { - try temp_await { - self.validate( - registry: registry, - package: package, - version: version, - signingEntity: signingEntity, - observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws { + try await self.validate( + registry: registry, + package: package, + version: version, + signingEntity: signingEntity, + observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } } diff --git a/Tests/PackageRegistryTests/PackageVersionChecksumTOFUTests.swift b/Tests/PackageRegistryTests/PackageVersionChecksumTOFUTests.swift index faad39ead06..028444a9274 100644 --- a/Tests/PackageRegistryTests/PackageVersionChecksumTOFUTests.swift +++ b/Tests/PackageRegistryTests/PackageVersionChecksumTOFUTests.swift @@ -21,7 +21,7 @@ import XCTest import struct TSCUtility.Version final class PackageVersionChecksumTOFUTests: XCTestCase { - func testSourceArchiveChecksumSeenForTheFirstTime() throws { + func testSourceArchiveChecksumSeenForTheFirstTime() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -93,17 +93,15 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // Checksum for package version not found in storage, // so we fetch metadata to get the expected checksum, // then save it to storage for future reference. - XCTAssertNoThrow( - try tofu.validateSourceArchive( - registry: registry, - package: package, - version: version, - checksum: checksum - ) + try await tofu.validateSourceArchive( + registry: registry, + package: package, + version: version, + checksum: checksum ) // Checksum should have been saved to storage - let fingerprint = try temp_await { callback in + let fingerprint = try await safe_async { fingerprintStorage.get( package: identity, version: version, @@ -111,14 +109,14 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { contentType: .sourceCode, observabilityScope: ObservabilitySystem.NOOP, callbackQueue: .sharedConcurrent, - callback: callback + callback: $0 ) } XCTAssertEqual(SourceControlURL(registryURL), fingerprint.origin.url) XCTAssertEqual(checksum, fingerprint.value) } - func testSourceArchiveMetadataChecksumConflictsWithStorage_strictMode() throws { + func testSourceArchiveMetadataChecksumConflictsWithStorage_strictMode() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -189,8 +187,8 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // We get expected checksum from metadata but it's different // from value in storage, and because of .strict mode, // an error is thrown. - XCTAssertThrowsError( - try tofu.validateSourceArchive( + await XCTAssertAsyncThrowsError( + try await tofu.validateSourceArchive( registry: registry, package: package, version: version, @@ -203,7 +201,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testSourceArchiveMetadataChecksumConflictsWithStorage_warnMode() throws { + func testSourceArchiveMetadataChecksumConflictsWithStorage_warnMode() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -276,14 +274,12 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // We get expected checksum from metadata and it's different // from value in storage, but because of .warn mode, // no error is thrown. - XCTAssertNoThrow( - try tofu.validateSourceArchive( - registry: registry, - package: package, - version: version, - checksum: checksum, - observabilityScope: observability.topScope - ) + try await tofu.validateSourceArchive( + registry: registry, + package: package, + version: version, + checksum: checksum, + observabilityScope: observability.topScope ) // But there should be a warning @@ -292,7 +288,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testFetchSourceArchiveMetadataChecksum_404() throws { + func testFetchSourceArchiveMetadataChecksum_404() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -331,8 +327,8 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { versionMetadataProvider: registryClient.getPackageVersionMetadata ) - XCTAssertThrowsError( - try tofu.validateSourceArchive( + await XCTAssertAsyncThrowsError( + try await tofu.validateSourceArchive( registry: registry, package: package, version: version, @@ -345,7 +341,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testFetchSourceArchiveMetadataChecksum_ServerError() throws { + func testFetchSourceArchiveMetadataChecksum_ServerError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -384,8 +380,8 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { versionMetadataProvider: registryClient.getPackageVersionMetadata ) - XCTAssertThrowsError( - try tofu.validateSourceArchive( + await XCTAssertAsyncThrowsError( + try await tofu.validateSourceArchive( registry: registry, package: package, version: version, @@ -398,7 +394,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testFetchSourceArchiveMetadataChecksum_RegistryNotAvailable() throws { + func testFetchSourceArchiveMetadataChecksum_RegistryNotAvailable() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -431,8 +427,8 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { versionMetadataProvider: registryClient.getPackageVersionMetadata ) - XCTAssertThrowsError( - try tofu.validateSourceArchive( + await XCTAssertAsyncThrowsError( + try await tofu.validateSourceArchive( registry: registry, package: package, version: version, @@ -445,7 +441,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testSourceArchiveChecksumMatchingStorage() throws { + func testSourceArchiveChecksumMatchingStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -495,17 +491,15 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // Checksum for package version found in storage, // so we just compare that with the given checksum. - XCTAssertNoThrow( - try tofu.validateSourceArchive( - registry: registry, - package: package, - version: version, - checksum: checksum - ) + try await tofu.validateSourceArchive( + registry: registry, + package: package, + version: version, + checksum: checksum ) } - func testSourceArchiveChecksumDoesNotMatchExpectedFromStorage_strictMode() throws { + func testSourceArchiveChecksumDoesNotMatchExpectedFromStorage_strictMode() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -557,8 +551,8 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // so we just compare that with the given checksum. // Since the checksums don't match, and because of // .strict mode, an error is thrown. - XCTAssertThrowsError( - try tofu.validateSourceArchive( + await XCTAssertAsyncThrowsError( + try await tofu.validateSourceArchive( registry: registry, package: package, version: version, @@ -571,7 +565,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testSourceArchiveChecksumDoesNotMatchExpectedFromStorage_warnMode() throws { + func testSourceArchiveChecksumDoesNotMatchExpectedFromStorage_warnMode() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -625,14 +619,12 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // so we just compare that with the given checksum. // The checksums don't match, but because of // .warn mode, no error is thrown. - XCTAssertNoThrow( - try tofu.validateSourceArchive( - registry: registry, - package: package, - version: version, - checksum: checksum, - observabilityScope: observability.topScope - ) + try await tofu.validateSourceArchive( + registry: registry, + package: package, + version: version, + checksum: checksum, + observabilityScope: observability.topScope ) // But there should be a warning @@ -641,7 +633,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testManifestChecksumSeenForTheFirstTime() throws { + func testManifestChecksumSeenForTheFirstTime() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -678,28 +670,25 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // Checksum for package version not found in storage, // so we save it to storage for future reference. - XCTAssertNoThrow( - try tofu.validateManifest( - registry: registry, - package: package, - version: version, - toolsVersion: .v5_6, // Version specific manifest - checksum: "Package@swift-5.6.swift checksum" - ) + try await tofu.validateManifest( + registry: registry, + package: package, + version: version, + toolsVersion: .v5_6, // Version specific manifest + checksum: "Package@swift-5.6.swift checksum" ) - XCTAssertNoThrow( - try tofu.validateManifest( - registry: registry, - package: package, - version: version, - toolsVersion: .none, // default manifest - checksum: "Package.swift checksum" - ) + + try await tofu.validateManifest( + registry: registry, + package: package, + version: version, + toolsVersion: .none, // default manifest + checksum: "Package.swift checksum" ) // Checksums should have been saved to storage do { - let fingerprint = try temp_await { callback in + let fingerprint = try await safe_async { fingerprintStorage.get( package: identity, version: version, @@ -707,14 +696,14 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { contentType: .manifest(.none), observabilityScope: ObservabilitySystem.NOOP, callbackQueue: .sharedConcurrent, - callback: callback + callback: $0 ) } XCTAssertEqual(SourceControlURL(registryURL), fingerprint.origin.url) XCTAssertEqual("Package.swift checksum", fingerprint.value) } do { - let fingerprint = try temp_await { callback in + let fingerprint = try await safe_async { fingerprintStorage.get( package: identity, version: version, @@ -722,7 +711,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { contentType: .manifest(.v5_6), observabilityScope: ObservabilitySystem.NOOP, callbackQueue: .sharedConcurrent, - callback: callback + callback: $0 ) } XCTAssertEqual(SourceControlURL(registryURL), fingerprint.origin.url) @@ -730,7 +719,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testManifestChecksumMatchingStorage() throws { + func testManifestChecksumMatchingStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -781,18 +770,16 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // Checksum for package version found in storage, // so we just compare that with the given checksum. - XCTAssertNoThrow( - try tofu.validateManifest( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - checksum: checksum - ) + try await tofu.validateManifest( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + checksum: checksum ) } - func testManifestChecksumDoesNotMatchExpectedFromStorage_strictMode() throws { + func testManifestChecksumDoesNotMatchExpectedFromStorage_strictMode() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -845,8 +832,8 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // so we just compare that with the given checksum. // Since the checksums don't match, and because of // .strict mode, an error is thrown. - XCTAssertThrowsError( - try tofu.validateManifest( + await XCTAssertAsyncThrowsError( + try await tofu.validateManifest( registry: registry, package: package, version: version, @@ -860,7 +847,7 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { } } - func testManifestChecksumDoesNotMatchExpectedFromStorage_warnMode() throws { + func testManifestChecksumDoesNotMatchExpectedFromStorage_warnMode() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -915,15 +902,13 @@ final class PackageVersionChecksumTOFUTests: XCTestCase { // so we just compare that with the given checksum. // The checksums don't match, but because of // .warn mode, no error is thrown. - XCTAssertNoThrow( - try tofu.validateManifest( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - checksum: checksum, - observabilityScope: observability.topScope - ) + try await tofu.validateManifest( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + checksum: checksum, + observabilityScope: observability.topScope ) // But there should be a warning @@ -940,19 +925,16 @@ extension PackageVersionChecksumTOFU { version: Version, checksum: String, observabilityScope: ObservabilityScope? = nil - ) throws { - try temp_await { - self.validateSourceArchive( - registry: registry, - package: package, - version: version, - checksum: checksum, - timeout: nil, - observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws { + try await self.validateSourceArchive( + registry: registry, + package: package, + version: version, + checksum: checksum, + timeout: nil, + observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } fileprivate func validateManifest( @@ -962,20 +944,17 @@ extension PackageVersionChecksumTOFU { toolsVersion: ToolsVersion?, checksum: String, observabilityScope: ObservabilityScope? = nil - ) throws { - try temp_await { - self.validateManifest( - registry: registry, - package: package, - version: version, - toolsVersion: toolsVersion, - checksum: checksum, - timeout: nil, - observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws { + try await self.validateManifest( + registry: registry, + package: package, + version: version, + toolsVersion: toolsVersion, + checksum: checksum, + timeout: nil, + observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } } diff --git a/Tests/PackageRegistryTests/RegistryClientTests.swift b/Tests/PackageRegistryTests/RegistryClientTests.swift index 8cff2d77f66..db027bb4a00 100644 --- a/Tests/PackageRegistryTests/RegistryClientTests.swift +++ b/Tests/PackageRegistryTests/RegistryClientTests.swift @@ -26,7 +26,7 @@ import class TSCBasic.InMemoryFileSystem import struct TSCUtility.Version final class RegistryClientTests: XCTestCase { - func testGetPackageMetadata() throws { + func testGetPackageMetadata() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let releasesURL = URL("\(registryURL)/\(identity.registry!.scope)/\(identity.registry!.name)") @@ -87,7 +87,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let metadata = try registryClient.getPackageMetadata(package: identity) + let metadata = try await registryClient.getPackageMetadata(package: identity) XCTAssertEqual(metadata.versions, ["1.1.1", "1.0.0"]) XCTAssertEqual(metadata.alternateLocations!, [ SourceControlURL("https://github.com/mona/LinkedList"), @@ -97,7 +97,7 @@ final class RegistryClientTests: XCTestCase { ]) } - func testGetPackageMetadata_NotFound() throws { + func testGetPackageMetadata_NotFound() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let releasesURL = URL("\(registryURL)/\(identity.registry!.scope)/\(identity.registry!.name)") @@ -117,7 +117,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.getPackageMetadata(package: identity)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.getPackageMetadata(package: identity)) { error in guard case RegistryError.failedRetrievingReleases( registry: configuration.defaultRegistry!, package: identity, @@ -128,7 +128,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetPackageMetadata_ServerError() throws { + func testGetPackageMetadata_ServerError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let releasesURL = URL("\(registryURL)/\(identity.registry!.scope)/\(identity.registry!.name)") @@ -148,7 +148,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.getPackageMetadata(package: identity)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.getPackageMetadata(package: identity)) { error in guard case RegistryError .failedRetrievingReleases( registry: configuration.defaultRegistry!, @@ -164,7 +164,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetPackageMetadata_RegistryNotAvailable() throws { + func testGetPackageMetadata_RegistryNotAvailable() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") @@ -179,7 +179,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = registry let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.getPackageMetadata(package: identity)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.getPackageMetadata(package: identity)) { error in guard case RegistryError.registryNotAvailable(registry) = error else { return XCTFail("unexpected error: '\(error)'") @@ -187,7 +187,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetPackageVersionMetadata() throws { + func testGetPackageVersionMetadata() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -246,7 +246,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let metadata = try registryClient.getPackageVersionMetadata(package: identity, version: version) + let metadata = try await registryClient.getPackageVersionMetadata(package: identity, version: version) XCTAssertEqual(metadata.resources.count, 1) XCTAssertEqual(metadata.resources[0].name, "source-archive") XCTAssertEqual(metadata.resources[0].type, "application/zip") @@ -264,7 +264,7 @@ final class RegistryClientTests: XCTestCase { ]) } - func testGetPackageVersionMetadata_404() throws { + func testGetPackageVersionMetadata_404() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -285,8 +285,8 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError( - try registryClient + await XCTAssertAsyncThrowsError( + try await registryClient .getPackageVersionMetadata(package: identity, version: version) ) { error in guard case RegistryError @@ -302,7 +302,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetPackageVersionMetadata_ServerError() throws { + func testGetPackageVersionMetadata_ServerError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -323,8 +323,8 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError( - try registryClient + await XCTAssertAsyncThrowsError( + try await registryClient .getPackageVersionMetadata(package: identity, version: version) ) { error in guard case RegistryError @@ -343,7 +343,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetPackageVersionMetadata_RegistryNotAvailable() throws { + func testGetPackageVersionMetadata_RegistryNotAvailable() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -359,8 +359,8 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = registry let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError( - try registryClient + await XCTAssertAsyncThrowsError( + try await registryClient .getPackageVersionMetadata(package: identity, version: version) ) { error in guard case RegistryError.registryNotAvailable(registry) = error @@ -370,7 +370,7 @@ final class RegistryClientTests: XCTestCase { } } - func testAvailableManifests() throws { + func testAvailableManifests() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -439,7 +439,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let defaultManifestData = defaultManifest.data(using: .utf8)! + let defaultManifestData = Data(defaultManifest.utf8) let links = """ ; rel="alternate"; filename="Package@swift-4.swift"; swift-tools-version="4.0", @@ -475,7 +475,7 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient, checksumAlgorithm: checksumAlgorithm ) - let availableManifests = try registryClient.getAvailableManifests( + let availableManifests = try await registryClient.getAvailableManifests( package: identity, version: version ) @@ -490,7 +490,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(availableManifests["Package@swift-5.3.swift"]?.content, .none) } - func testAvailableManifests_matchingChecksumInStorage() throws { + func testAvailableManifests_matchingChecksumInStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -559,7 +559,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let defaultManifestData = defaultManifest.data(using: .utf8)! + let defaultManifestData = Data(defaultManifest.utf8) let links = """ ; rel="alternate"; filename="Package@swift-4.swift"; swift-tools-version="4.0", @@ -591,7 +591,7 @@ final class RegistryClientTests: XCTestCase { configuration.security = .testDefault let contentType = Fingerprint.ContentType.manifest(.none) - let manifestChecksum = checksumAlgorithm.hash(.init(defaultManifest.data(using: .utf8)!)) + let manifestChecksum = checksumAlgorithm.hash(.init(Data(defaultManifest.utf8))) .hexadecimalRepresentation let fingerprintStorage = MockPackageFingerprintStorage([ identity: [ @@ -614,7 +614,7 @@ final class RegistryClientTests: XCTestCase { fingerprintCheckingMode: .strict, checksumAlgorithm: checksumAlgorithm ) - let availableManifests = try registryClient.getAvailableManifests( + let availableManifests = try await registryClient.getAvailableManifests( package: identity, version: version ) @@ -629,7 +629,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(availableManifests["Package@swift-5.3.swift"]?.content, .none) } - func testAvailableManifests_nonMatchingChecksumInStorage_strict() throws { + func testAvailableManifests_nonMatchingChecksumInStorage_strict() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -698,7 +698,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let defaultManifestData = defaultManifest.data(using: .utf8)! + let defaultManifestData = Data(defaultManifest.utf8) let links = """ ; rel="alternate"; filename="Package@swift-4.swift"; swift-tools-version="4.0", @@ -752,8 +752,8 @@ final class RegistryClientTests: XCTestCase { checksumAlgorithm: checksumAlgorithm ) - XCTAssertThrowsError( - try registryClient.getAvailableManifests( + await XCTAssertAsyncThrowsError( + try await registryClient.getAvailableManifests( package: identity, version: version ) @@ -764,7 +764,7 @@ final class RegistryClientTests: XCTestCase { } } - func testAvailableManifests_nonMatchingChecksumInStorage_warn() throws { + func testAvailableManifests_nonMatchingChecksumInStorage_warn() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -833,7 +833,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let defaultManifestData = defaultManifest.data(using: .utf8)! + let defaultManifestData = Data(defaultManifest.utf8) let links = """ ; rel="alternate"; filename="Package@swift-4.swift"; swift-tools-version="4.0", @@ -890,7 +890,7 @@ final class RegistryClientTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() // The checksum differs from that in storage, but error is not thrown // because fingerprintCheckingMode=.warn - let availableManifests = try registryClient.getAvailableManifests( + let availableManifests = try await registryClient.getAvailableManifests( package: identity, version: version, observabilityScope: observability.topScope @@ -911,7 +911,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(availableManifests["Package@swift-5.3.swift"]?.content, .none) } - func testAvailableManifests_404() throws { + func testAvailableManifests_404() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -960,7 +960,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.getAvailableManifests(package: identity, version: version)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.getAvailableManifests(package: identity, version: version)) { error in guard case RegistryError .failedRetrievingManifest( registry: configuration.defaultRegistry!, @@ -974,7 +974,7 @@ final class RegistryClientTests: XCTestCase { } } - func testAvailableManifests_ServerError() throws { + func testAvailableManifests_ServerError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1023,7 +1023,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.getAvailableManifests(package: identity, version: version)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.getAvailableManifests(package: identity, version: version)) { error in guard case RegistryError .failedRetrievingManifest( registry: configuration.defaultRegistry!, @@ -1038,7 +1038,7 @@ final class RegistryClientTests: XCTestCase { } } - func testAvailableManifests_RegistryNotAvailable() throws { + func testAvailableManifests_RegistryNotAvailable() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1054,7 +1054,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = registry let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.getAvailableManifests(package: identity, version: version)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.getAvailableManifests(package: identity, version: version)) { error in guard case RegistryError.registryNotAvailable(registry) = error else { return XCTFail("unexpected error: '\(error)'") @@ -1062,7 +1062,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent() throws { + func testGetManifestContent() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1157,7 +1157,7 @@ final class RegistryClientTests: XCTestCase { ) do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: nil @@ -1167,7 +1167,7 @@ final class RegistryClientTests: XCTestCase { } do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: .v5_3 @@ -1177,7 +1177,7 @@ final class RegistryClientTests: XCTestCase { } do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: .v4 @@ -1187,7 +1187,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_optionalContentVersion() throws { + func testGetManifestContent_optionalContentVersion() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1282,7 +1282,7 @@ final class RegistryClientTests: XCTestCase { ) do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: nil @@ -1292,7 +1292,7 @@ final class RegistryClientTests: XCTestCase { } do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: .v5_3 @@ -1302,7 +1302,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_matchingChecksumInStorage() throws { + func testGetManifestContent_matchingChecksumInStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1360,7 +1360,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let data = manifestContent(toolsVersion: toolsVersion).data(using: .utf8)! + let data = Data(manifestContent(toolsVersion: toolsVersion).utf8) completion(.success(.init( statusCode: 200, @@ -1385,9 +1385,9 @@ final class RegistryClientTests: XCTestCase { configuration.security = .testDefault let defaultManifestChecksum = checksumAlgorithm - .hash(.init(manifestContent(toolsVersion: .none).data(using: .utf8)!)).hexadecimalRepresentation + .hash(.init(Data(manifestContent(toolsVersion: .none).utf8))).hexadecimalRepresentation let versionManifestChecksum = checksumAlgorithm - .hash(.init(manifestContent(toolsVersion: .v5_3).data(using: .utf8)!)).hexadecimalRepresentation + .hash(.init(Data(manifestContent(toolsVersion: .v5_3).utf8))).hexadecimalRepresentation let fingerprintStorage = MockPackageFingerprintStorage([ identity: [ version: [ @@ -1416,7 +1416,7 @@ final class RegistryClientTests: XCTestCase { ) do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: nil @@ -1426,7 +1426,7 @@ final class RegistryClientTests: XCTestCase { } do { - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: .v5_3 @@ -1436,7 +1436,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_nonMatchingChecksumInStorage_strict() throws { + func testGetManifestContent_nonMatchingChecksumInStorage_strict() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1494,7 +1494,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let data = manifestContent(toolsVersion: toolsVersion).data(using: .utf8)! + let data = Data(manifestContent(toolsVersion: toolsVersion).utf8) completion(.success(.init( statusCode: 200, @@ -1545,8 +1545,8 @@ final class RegistryClientTests: XCTestCase { checksumAlgorithm: checksumAlgorithm ) - XCTAssertThrowsError( - try registryClient.getManifestContent( + await XCTAssertAsyncThrowsError( + try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: nil @@ -1557,8 +1557,8 @@ final class RegistryClientTests: XCTestCase { } } - XCTAssertThrowsError( - try registryClient.getManifestContent( + await XCTAssertAsyncThrowsError( + try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: .v5_3 @@ -1570,7 +1570,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_matchingChecksumInStorage_warn() throws { + func testGetManifestContent_matchingChecksumInStorage_warn() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1628,7 +1628,7 @@ final class RegistryClientTests: XCTestCase { case (.get, manifestURL): XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+swift") - let data = manifestContent(toolsVersion: toolsVersion).data(using: .utf8)! + let data = Data(manifestContent(toolsVersion: toolsVersion).utf8) completion(.success(.init( statusCode: 200, @@ -1683,7 +1683,7 @@ final class RegistryClientTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() // The checksum differs from that in storage, but error is not thrown // because fingerprintCheckingMode=.warn - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: nil, @@ -1703,7 +1703,7 @@ final class RegistryClientTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() // The checksum differs from that in storage, but error is not thrown // because fingerprintCheckingMode=.warn - let manifest = try registryClient.getManifestContent( + let manifest = try await registryClient.getManifestContent( package: identity, version: version, customToolsVersion: .v5_3, @@ -1720,7 +1720,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_404() throws { + func testGetManifestContent_404() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1769,8 +1769,8 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError( - try registryClient + await XCTAssertAsyncThrowsError( + try await registryClient .getManifestContent(package: identity, version: version, customToolsVersion: nil) ) { error in guard case RegistryError @@ -1786,7 +1786,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_ServerError() throws { + func testGetManifestContent_ServerError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1835,8 +1835,8 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError( - try registryClient + await XCTAssertAsyncThrowsError( + try await registryClient .getManifestContent(package: identity, version: version, customToolsVersion: nil) ) { error in guard case RegistryError @@ -1853,7 +1853,7 @@ final class RegistryClientTests: XCTestCase { } } - func testGetManifestContent_RegistryNotAvailable() throws { + func testGetManifestContent_RegistryNotAvailable() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -1869,8 +1869,8 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = registry let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError( - try registryClient + await XCTAssertAsyncThrowsError( + try await registryClient .getManifestContent(package: identity, version: version, customToolsVersion: nil) ) { error in guard case RegistryError @@ -1881,7 +1881,7 @@ final class RegistryClientTests: XCTestCase { } } - func testDownloadSourceArchive() throws { + func testDownloadSourceArchive() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.registry("mona.LinkedList") let version = Version("1.1.1") @@ -1996,7 +1996,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = try! AbsolutePath(validating: "/\(identity)-\(version)") - try registryClient.downloadSourceArchive( + try await registryClient.downloadSourceArchive( package: identity.underlying, version: version, fileSystem: fileSystem, @@ -2017,7 +2017,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(storedMetadata.metadata.scmRepositoryURLs, repositoryURLs) } - func testDownloadSourceArchive_matchingChecksumInStorage() throws { + func testDownloadSourceArchive_matchingChecksumInStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2137,7 +2137,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - try registryClient.downloadSourceArchive( + try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2148,7 +2148,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(contents.sorted(), [RegistryReleaseMetadataStorage.fileName, "Package.swift"].sorted()) } - func testDownloadSourceArchive_nonMatchingChecksumInStorage() throws { + func testDownloadSourceArchive_nonMatchingChecksumInStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2268,8 +2268,8 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - XCTAssertThrowsError( - try registryClient.downloadSourceArchive( + await XCTAssertAsyncThrowsError( + try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2285,7 +2285,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertFalse(fileSystem.exists(path)) } - func testDownloadSourceArchive_nonMatchingChecksumInStorage_fingerprintChecking_warn() throws { + func testDownloadSourceArchive_nonMatchingChecksumInStorage_fingerprintChecking_warn() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2408,7 +2408,7 @@ final class RegistryClientTests: XCTestCase { // The checksum differs from that in storage, but error is not thrown // because fingerprintCheckingMode=.warn - try registryClient.downloadSourceArchive( + try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2425,7 +2425,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(contents.sorted(), [RegistryReleaseMetadataStorage.fileName, "Package.swift"].sorted()) } - func testDownloadSourceArchive_checksumNotInStorage() throws { + func testDownloadSourceArchive_checksumNotInStorage() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2527,7 +2527,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - try registryClient.downloadSourceArchive( + try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2538,7 +2538,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(contents.sorted(), [RegistryReleaseMetadataStorage.fileName, "Package.swift"].sorted()) // Expected checksum is not found in storage so the metadata API will be called - let fingerprint = try temp_await { callback in + let fingerprint = try await safe_async { fingerprintStorage.get( package: identity, version: version, @@ -2547,14 +2547,14 @@ final class RegistryClientTests: XCTestCase { observabilityScope: ObservabilitySystem .NOOP, callbackQueue: .sharedConcurrent, - callback: callback + callback: $0 ) } XCTAssertEqual(SourceControlURL(registryURL), fingerprint.origin.url) XCTAssertEqual(checksum, fingerprint.value) } - func testDownloadSourceArchive_optionalContentVersion() throws { + func testDownloadSourceArchive_optionalContentVersion() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2656,7 +2656,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - try registryClient.downloadSourceArchive( + try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2668,7 +2668,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(contents.sorted(), [RegistryReleaseMetadataStorage.fileName, "Package.swift"].sorted()) } - func testDownloadSourceArchive_404() throws { + func testDownloadSourceArchive_404() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2730,7 +2730,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - XCTAssertThrowsError(try registryClient.downloadSourceArchive( + await XCTAssertAsyncThrowsError(try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2749,7 +2749,7 @@ final class RegistryClientTests: XCTestCase { } } - func testDownloadSourceArchive_ServerError() throws { + func testDownloadSourceArchive_ServerError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2811,7 +2811,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - XCTAssertThrowsError(try registryClient.downloadSourceArchive( + await XCTAssertAsyncThrowsError(try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2831,7 +2831,7 @@ final class RegistryClientTests: XCTestCase { } } - func testDownloadSourceArchive_RegistryNotAvailable() throws { + func testDownloadSourceArchive_RegistryNotAvailable() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -2861,7 +2861,7 @@ final class RegistryClientTests: XCTestCase { let fileSystem = InMemoryFileSystem() let path = AbsolutePath("/LinkedList-1.1.1") - XCTAssertThrowsError(try registryClient.downloadSourceArchive( + await XCTAssertAsyncThrowsError(try await registryClient.downloadSourceArchive( package: identity, version: version, fileSystem: fileSystem, @@ -2875,7 +2875,7 @@ final class RegistryClientTests: XCTestCase { } } - func testLookupIdentities() throws { + func testLookupIdentities() async throws { let registryURL = URL("https://packages.example.com") let packageURL = SourceControlURL("https://example.com/mona/LinkedList") let identifiersURL = URL("\(registryURL)/identifiers?url=\(packageURL.absoluteString)") @@ -2915,11 +2915,11 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let identities = try registryClient.lookupIdentities(scmURL: packageURL) + let identities = try await registryClient.lookupIdentities(scmURL: packageURL) XCTAssertEqual([PackageIdentity.plain("mona.LinkedList")], identities) } - func testLookupIdentities404() throws { + func testLookupIdentities404() async throws { let registryURL = URL("https://packages.example.com") let packageURL = SourceControlURL("https://example.com/mona/LinkedList") let identifiersURL = URL("\(registryURL)/identifiers?url=\(packageURL.absoluteString)") @@ -2942,11 +2942,11 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let identities = try registryClient.lookupIdentities(scmURL: packageURL) + let identities = try await registryClient.lookupIdentities(scmURL: packageURL) XCTAssertEqual([], identities) } - func testLookupIdentities_ServerError() throws { + func testLookupIdentities_ServerError() async throws { let registryURL = URL("https://packages.example.com") let packageURL = SourceControlURL("https://example.com/mona/LinkedList") let identifiersURL = URL("\(registryURL)/identifiers?url=\(packageURL.absoluteString)") @@ -2966,7 +2966,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.lookupIdentities(scmURL: packageURL)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.lookupIdentities(scmURL: packageURL)) { error in guard case RegistryError .failedIdentityLookup( registry: configuration.defaultRegistry!, @@ -2980,7 +2980,7 @@ final class RegistryClientTests: XCTestCase { } } - func testRequestAuthorization_token() throws { + func testRequestAuthorization_token() async throws { let registryURL = URL("https://packages.example.com") let packageURL = SourceControlURL("https://example.com/mona/LinkedList") let identifiersURL = URL("\(registryURL)/identifiers?url=\(packageURL.absoluteString)") @@ -3030,11 +3030,11 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient, authorizationProvider: authorizationProvider ) - let identities = try registryClient.lookupIdentities(scmURL: packageURL) + let identities = try await registryClient.lookupIdentities(scmURL: packageURL) XCTAssertEqual([PackageIdentity.plain("mona.LinkedList")], identities) } - func testRequestAuthorization_basic() throws { + func testRequestAuthorization_basic() async throws { let registryURL = URL("https://packages.example.com") let packageURL = SourceControlURL("https://example.com/mona/LinkedList") let identifiersURL = URL("\(registryURL)/identifiers?url=\(packageURL.absoluteString)") @@ -3047,7 +3047,7 @@ final class RegistryClientTests: XCTestCase { case (.get, identifiersURL): XCTAssertEqual( request.headers.get("Authorization").first, - "Basic \("\(user):\(password)".data(using: .utf8)!.base64EncodedString())" + "Basic \(Data("\(user):\(password)".utf8).base64EncodedString())" ) XCTAssertEqual(request.headers.get("Accept").first, "application/vnd.swift.registry.v1+json") @@ -3088,11 +3088,11 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient, authorizationProvider: authorizationProvider ) - let identities = try registryClient.lookupIdentities(scmURL: packageURL) + let identities = try await registryClient.lookupIdentities(scmURL: packageURL) XCTAssertEqual([PackageIdentity.plain("mona.LinkedList")], identities) } - func testLogin() throws { + func testLogin() async throws { let registryURL = URL("https://packages.example.com") let loginURL = URL("\(registryURL)/login") @@ -3129,10 +3129,10 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient, authorizationProvider: authorizationProvider ) - XCTAssertNoThrow(try registryClient.login(loginURL: loginURL)) + try await registryClient.login(loginURL: loginURL) } - func testLogin_missingCredentials() throws { + func testLogin_missingCredentials() async throws { let registryURL = URL("https://packages.example.com") let loginURL = URL("\(registryURL)/login") @@ -3164,14 +3164,14 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient ) - XCTAssertThrowsError(try registryClient.login(loginURL: loginURL)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.login(loginURL: loginURL)) { error in guard case RegistryError.loginFailed(_, _) = error else { return XCTFail("Expected RegistryError.unauthorized, got \(error)") } } } - func testLogin_authenticationMethodNotSupported() throws { + func testLogin_authenticationMethodNotSupported() async throws { let registryURL = URL("https://packages.example.com") let loginURL = URL("\(registryURL)/login") @@ -3209,14 +3209,14 @@ final class RegistryClientTests: XCTestCase { authorizationProvider: authorizationProvider ) - XCTAssertThrowsError(try registryClient.login(loginURL: loginURL)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.login(loginURL: loginURL)) { error in guard case RegistryError.loginFailed = error else { return XCTFail("Expected RegistryError.authenticationMethodNotSupported, got \(error)") } } } - func testRegistryPublishSync() throws { + func testRegistryPublishSync() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -3234,7 +3234,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertNil(request.headers.get("X-Swift-Package-Signature-Format").first) // TODO: implement multipart form parsing - let body = String(data: request.body!, encoding: .utf8) + let body = String(decoding: request.body!, as: UTF8.self) XCTAssertMatch(body, .contains(archiveContent)) XCTAssertMatch(body, .contains(metadataContent)) @@ -3251,7 +3251,7 @@ final class RegistryClientTests: XCTestCase { } } - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in let archivePath = temporaryDirectory.appending("\(identity)-\(version).zip") try localFileSystem.writeFileContents(archivePath, string: archiveContent) @@ -3266,7 +3266,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let result = try registryClient.publish( + let result = try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3282,7 +3282,7 @@ final class RegistryClientTests: XCTestCase { } } - func testRegistryPublishAsync() throws { + func testRegistryPublishAsync() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -3301,7 +3301,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertNil(request.headers.get("X-Swift-Package-Signature-Format").first) // TODO: implement multipart form parsing - let body = String(data: request.body!, encoding: .utf8) + let body = String(decoding: request.body!, as: UTF8.self) XCTAssertMatch(body, .contains(archiveContent)) XCTAssertMatch(body, .contains(metadataContent)) @@ -3319,7 +3319,7 @@ final class RegistryClientTests: XCTestCase { } } - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in let archivePath = temporaryDirectory.appending("\(identity)-\(version).zip") try localFileSystem.writeFileContents(archivePath, string: archiveContent) @@ -3334,7 +3334,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let result = try registryClient.publish( + let result = try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3350,7 +3350,7 @@ final class RegistryClientTests: XCTestCase { } } - func testRegistryPublishWithSignature() throws { + func testRegistryPublishWithSignature() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let version = Version("1.1.1") @@ -3371,7 +3371,7 @@ final class RegistryClientTests: XCTestCase { XCTAssertEqual(request.headers.get("X-Swift-Package-Signature-Format").first, signatureFormat.rawValue) // TODO: implement multipart form parsing - let body = String(data: request.body!, encoding: .utf8) + let body = String(decoding: request.body!, as: UTF8.self) XCTAssertMatch(body, .contains(archiveContent)) XCTAssertMatch(body, .contains(metadataContent)) XCTAssertMatch(body, .contains(signature)) @@ -3390,7 +3390,7 @@ final class RegistryClientTests: XCTestCase { } } - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in let archivePath = temporaryDirectory.appending(component: "\(identity)-\(version).zip") try localFileSystem.writeFileContents(archivePath, string: archiveContent) @@ -3405,7 +3405,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - let result = try registryClient.publish( + let result = try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3450,7 +3450,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.publish( + await XCTAssertAsyncThrowsError(try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3497,7 +3497,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.publish( + await XCTAssertAsyncThrowsError(try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3544,7 +3544,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.publish( + await XCTAssertAsyncThrowsError(try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3590,7 +3590,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.publish( + await XCTAssertAsyncThrowsError(try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3639,7 +3639,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.publish( + await XCTAssertAsyncThrowsError(try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3680,7 +3680,7 @@ final class RegistryClientTests: XCTestCase { configuration.defaultRegistry = Registry(url: registryURL, supportsAvailability: false) let registryClient = makeRegistryClient(configuration: configuration, httpClient: httpClient) - XCTAssertThrowsError(try registryClient.publish( + await XCTAssertAsyncThrowsError(try await registryClient.publish( registryURL: registryURL, packageIdentity: identity, packageVersion: version, @@ -3698,7 +3698,7 @@ final class RegistryClientTests: XCTestCase { } } - func testRegistryAvailability() throws { + func testRegistryAvailability() async throws { let registryURL = URL("https://packages.example.com") let availabilityURL = URL("\(registryURL)/availability") @@ -3722,11 +3722,11 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient ) - let status = try registryClient.checkAvailability(registry: registry) + let status = try await registryClient.checkAvailability(registry: registry) XCTAssertEqual(status, .available) } - func testRegistryAvailability_NotAvailable() throws { + func testRegistryAvailability_NotAvailable() async throws { let registryURL = URL("https://packages.example.com") let availabilityURL = URL("\(registryURL)/availability") @@ -3751,12 +3751,12 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient ) - let status = try registryClient.checkAvailability(registry: registry) + let status = try await registryClient.checkAvailability(registry: registry) XCTAssertEqual(status, .unavailable) } } - func testRegistryAvailability_ServerError() throws { + func testRegistryAvailability_ServerError() async throws { let registryURL = URL("https://packages.example.com") let availabilityURL = URL("\(registryURL)/availability") @@ -3780,11 +3780,11 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient ) - let status = try registryClient.checkAvailability(registry: registry) + let status = try await registryClient.checkAvailability(registry: registry) XCTAssertEqual(status, .error("unknown server error (500)")) } - func testRegistryAvailability_NotSupported() throws { + func testRegistryAvailability_NotSupported() async throws { let registryURL = URL("https://packages.example.com") let availabilityURL = URL("\(registryURL)/availability") @@ -3808,7 +3808,7 @@ final class RegistryClientTests: XCTestCase { httpClient: httpClient ) - XCTAssertThrowsError(try registryClient.checkAvailability(registry: registry)) { error in + await XCTAssertAsyncThrowsError(try await registryClient.checkAvailability(registry: registry)) { error in XCTAssertEqual( error as? StringError, StringError("registry \(registry.url) does not support availability checks.") @@ -3820,57 +3820,57 @@ final class RegistryClientTests: XCTestCase { // MARK: - Sugar extension RegistryClient { - fileprivate func getPackageMetadata(package: PackageIdentity) throws -> RegistryClient.PackageMetadata { - try temp_await { - self.getPackageMetadata( - package: package, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + fileprivate func getPackageMetadata(package: PackageIdentity) async throws -> RegistryClient.PackageMetadata { + try await self.getPackageMetadata( + package: package, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } func getPackageVersionMetadata( package: PackageIdentity, version: Version + ) async throws -> PackageVersionMetadata { + try await self.getPackageVersionMetadata( + package: package, + version: version, + fileSystem: InMemoryFileSystem(), + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) + } + + func getPackageVersionMetadata( + package: PackageIdentity.RegistryIdentity, + version: Version ) throws -> PackageVersionMetadata { - try temp_await { + // TODO: Finish removing this temp_await + // It can't currently be removed because it is passed to + // PackageVersionChecksumTOFU which expects a non async method + return try temp_await { completion in self.getPackageVersionMetadata( - package: package, + package: package.underlying, version: version, fileSystem: InMemoryFileSystem(), observabilityScope: ObservabilitySystem.NOOP, callbackQueue: .sharedConcurrent, - completion: $0 + completion: completion ) } } - func getPackageVersionMetadata( - package: PackageIdentity.RegistryIdentity, - version: Version - ) throws -> PackageVersionMetadata { - try self.getPackageVersionMetadata( - package: package.underlying, - version: version - ) - } - fileprivate func getAvailableManifests( package: PackageIdentity, version: Version, observabilityScope: ObservabilityScope = ObservabilitySystem.NOOP - ) throws -> [String: (toolsVersion: ToolsVersion, content: String?)] { - try temp_await { - self.getAvailableManifests( - package: package, - version: version, - observabilityScope: observabilityScope, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws -> [String: (toolsVersion: ToolsVersion, content: String?)] { + try await self.getAvailableManifests( + package: package, + version: version, + observabilityScope: observabilityScope, + callbackQueue: .sharedConcurrent + ) } fileprivate func getManifestContent( @@ -3878,17 +3878,14 @@ extension RegistryClient { version: Version, customToolsVersion: ToolsVersion?, observabilityScope: ObservabilityScope = ObservabilitySystem.NOOP - ) throws -> String { - try temp_await { - self.getManifestContent( - package: package, - version: version, - customToolsVersion: customToolsVersion, - observabilityScope: observabilityScope, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws -> String { + try await self.getManifestContent( + package: package, + version: version, + customToolsVersion: customToolsVersion, + observabilityScope: observabilityScope, + callbackQueue: .sharedConcurrent + ) } fileprivate func downloadSourceArchive( @@ -3897,41 +3894,32 @@ extension RegistryClient { fileSystem: FileSystem, destinationPath: AbsolutePath, observabilityScope: ObservabilityScope = ObservabilitySystem.NOOP - ) throws { - try temp_await { - self.downloadSourceArchive( - package: package, - version: version, - destinationPath: destinationPath, - progressHandler: .none, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws { + try await self.downloadSourceArchive( + package: package, + version: version, + destinationPath: destinationPath, + progressHandler: .none, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + callbackQueue: .sharedConcurrent + ) } - fileprivate func lookupIdentities(scmURL: SourceControlURL) throws -> Set { - try temp_await { - self.lookupIdentities( - scmURL: scmURL, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + fileprivate func lookupIdentities(scmURL: SourceControlURL) async throws -> Set { + try await self.lookupIdentities( + scmURL: scmURL, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } - fileprivate func login(loginURL: URL) throws { - try temp_await { - self.login( - loginURL: loginURL, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + fileprivate func login(loginURL: URL) async throws { + try await self.login( + loginURL: loginURL, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } func publish( @@ -3944,34 +3932,28 @@ extension RegistryClient { metadataSignature: [UInt8]?, signatureFormat: SignatureFormat?, fileSystem: FileSystem - ) throws -> RegistryClient.PublishResult { - try temp_await { - self.publish( - registryURL: registryURL, - packageIdentity: packageIdentity, - packageVersion: packageVersion, - packageArchive: packageArchive, - packageMetadata: packageMetadata, - signature: signature, - metadataSignature: metadataSignature, - signatureFormat: signatureFormat, - fileSystem: fileSystem, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws -> RegistryClient.PublishResult { + try await self.publish( + registryURL: registryURL, + packageIdentity: packageIdentity, + packageVersion: packageVersion, + packageArchive: packageArchive, + packageMetadata: packageMetadata, + signature: signature, + metadataSignature: metadataSignature, + signatureFormat: signatureFormat, + fileSystem: fileSystem, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } - func checkAvailability(registry: Registry) throws -> AvailabilityStatus { - try temp_await { - self.checkAvailability( - registry: registry, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + func checkAvailability(registry: Registry) async throws -> AvailabilityStatus { + try await self.checkAvailability( + registry: registry, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } } diff --git a/Tests/PackageRegistryTests/RegistryDownloadsManagerTests.swift b/Tests/PackageRegistryTests/RegistryDownloadsManagerTests.swift index 3d09927deeb..b5ffd16a3c3 100644 --- a/Tests/PackageRegistryTests/RegistryDownloadsManagerTests.swift +++ b/Tests/PackageRegistryTests/RegistryDownloadsManagerTests.swift @@ -22,7 +22,7 @@ import class TSCBasic.InMemoryFileSystem import struct TSCUtility.Version class RegistryDownloadsManagerTests: XCTestCase { - func testNoCache() throws { + func testNoCache() async throws { let observability = ObservabilitySystem.makeForTesting() let fs = InMemoryFileSystem() @@ -59,7 +59,7 @@ class RegistryDownloadsManagerTests: XCTestCase { do { delegate.prepare(fetchExpected: true) - let path = try manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) + let path = try await manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(path, try downloadsPath.appending(package.downloadPath(version: packageVersion))) XCTAssertTrue(fs.isDirectory(path)) @@ -81,7 +81,7 @@ class RegistryDownloadsManagerTests: XCTestCase { do { delegate.prepare(fetchExpected: true) - XCTAssertThrowsError(try manager.lookup(package: unknownPackage, version: unknownPackageVersion, observabilityScope: observability.topScope)) { error in + await XCTAssertAsyncThrowsError(try await manager.lookup(package: unknownPackage, version: unknownPackageVersion, observabilityScope: observability.topScope)) { error in XCTAssertNotNil(error as? RegistryError) } @@ -104,7 +104,7 @@ class RegistryDownloadsManagerTests: XCTestCase { do { delegate.prepare(fetchExpected: false) - let path = try manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) + let path = try await manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(path, try downloadsPath.appending(package.downloadPath(version: packageVersion))) XCTAssertTrue(fs.isDirectory(path)) @@ -130,7 +130,7 @@ class RegistryDownloadsManagerTests: XCTestCase { try manager.remove(package: package) delegate.prepare(fetchExpected: true) - let path = try manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) + let path = try await manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(path, try downloadsPath.appending(package.downloadPath(version: packageVersion))) XCTAssertTrue(fs.isDirectory(path)) @@ -153,7 +153,7 @@ class RegistryDownloadsManagerTests: XCTestCase { } } - func testCache() throws { + func testCache() async throws { let observability = ObservabilitySystem.makeForTesting() let fs = InMemoryFileSystem() @@ -191,7 +191,7 @@ class RegistryDownloadsManagerTests: XCTestCase { do { delegate.prepare(fetchExpected: true) - let path = try manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) + let path = try await manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(path, try downloadsPath.appending(package.downloadPath(version: packageVersion))) XCTAssertTrue(fs.isDirectory(path)) @@ -214,7 +214,7 @@ class RegistryDownloadsManagerTests: XCTestCase { try manager.remove(package: package) delegate.prepare(fetchExpected: true) - let path = try manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) + let path = try await manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(path, try downloadsPath.appending(package.downloadPath(version: packageVersion))) XCTAssertTrue(fs.isDirectory(path)) @@ -237,7 +237,7 @@ class RegistryDownloadsManagerTests: XCTestCase { manager.purgeCache(observabilityScope: observability.topScope) delegate.prepare(fetchExpected: true) - let path = try manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) + let path = try await manager.lookup(package: package, version: packageVersion, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(path, try downloadsPath.appending(package.downloadPath(version: packageVersion))) XCTAssertTrue(fs.isDirectory(path)) @@ -419,16 +419,14 @@ private class MockRegistryDownloadsManagerDelegate: RegistryDownloadsManagerDele } extension RegistryDownloadsManager { - fileprivate func lookup(package: PackageIdentity, version: Version, observabilityScope: ObservabilityScope) throws -> AbsolutePath { - return try temp_await { - self.lookup( - package: package, - version: version, - observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, completion: $0 - ) - } + fileprivate func lookup(package: PackageIdentity, version: Version, observabilityScope: ObservabilityScope) async throws -> AbsolutePath { + try await self.lookup( + package: package, + version: version, + observabilityScope: observabilityScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) } } diff --git a/Tests/PackageRegistryTests/SignatureValidationTests.swift b/Tests/PackageRegistryTests/SignatureValidationTests.swift index 3aeee90812c..dad052ba09f 100644 --- a/Tests/PackageRegistryTests/SignatureValidationTests.swift +++ b/Tests/PackageRegistryTests/SignatureValidationTests.swift @@ -33,7 +33,7 @@ final class SignatureValidationTests: XCTestCase { ) """ - func testUnsignedPackage_shouldError() throws { + func testUnsignedPackage_shouldError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -82,8 +82,8 @@ final class SignatureValidationTests: XCTestCase { // Package is not signed. With onUnsigned = .error, // an error gets thrown. - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -97,7 +97,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testUnsignedPackage_shouldWarn() throws { + func testUnsignedPackage_shouldWarn() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -148,15 +148,13 @@ final class SignatureValidationTests: XCTestCase { // Package is not signed. With onUnsigned = .warn, // no error gets thrown but there should be a warning - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - content: Data(emptyZipFile.contents), - configuration: configuration.signing(for: package, registry: registry), - observabilityScope: observability.topScope - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + content: Data(emptyZipFile.contents), + configuration: configuration.signing(for: package, registry: registry), + observabilityScope: observability.topScope ) testDiagnostics(observability.diagnostics) { result in @@ -165,7 +163,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testUnsignedPackage_shouldPrompt() throws { + func testUnsignedPackage_shouldPrompt() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -216,8 +214,8 @@ final class SignatureValidationTests: XCTestCase { // Package is not signed. With onUnsigned = .error, // an error gets thrown. - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -242,7 +240,7 @@ final class SignatureValidationTests: XCTestCase { ) // Package is not signed, signingEntity should be nil - let signingEntity = try signatureValidation.validate( + let signingEntity = try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -253,7 +251,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testFailedToFetchSignature_shouldError() throws { + func testFailedToFetchSignature_shouldError() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -302,8 +300,8 @@ final class SignatureValidationTests: XCTestCase { ) // Failed to fetch package metadata / signature - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -317,7 +315,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testUnsignedArchiveAndManifest_shouldPrompt() throws { + func testUnsignedArchiveAndManifest_shouldPrompt() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -367,8 +365,8 @@ final class SignatureValidationTests: XCTestCase { ) // Package is not signed. With onUnsigned = .prompt, prompt to continue. - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -394,7 +392,7 @@ final class SignatureValidationTests: XCTestCase { ) // Package is not signed, signingEntity should be nil - let signingEntity = try signatureValidation.validate( + let signingEntity = try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -406,7 +404,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testUnsignedArchiveAndManifest_nonPrompt() throws { + func testUnsignedArchiveAndManifest_nonPrompt() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -457,16 +455,14 @@ final class SignatureValidationTests: XCTestCase { // Package is not signed. // With the exception of .prompt, we log then continue. - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - manifestContent: Self.unsignedManifest, - configuration: configuration.signing(for: package, registry: registry), - observabilityScope: observability.topScope - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + manifestContent: Self.unsignedManifest, + configuration: configuration.signing(for: package, registry: registry), + observabilityScope: observability.topScope ) testDiagnostics(observability.diagnostics, problemsOnly: false) { result in @@ -475,7 +471,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testFailedToFetchArchiveSignatureToValidateManifest_diagnostics() throws { + func testFailedToFetchArchiveSignatureToValidateManifest_diagnostics() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -519,16 +515,14 @@ final class SignatureValidationTests: XCTestCase { // Failed to fetch package metadata / signature. // This error is not thrown for manifest but there should be diagnostics. - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - manifestContent: Self.unsignedManifest, - configuration: configuration.signing(for: package, registry: registry), - observabilityScope: observability.topScope - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + manifestContent: Self.unsignedManifest, + configuration: configuration.signing(for: package, registry: registry), + observabilityScope: observability.topScope ) testDiagnostics(observability.diagnostics, problemsOnly: false) { result in @@ -549,7 +543,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -604,8 +598,8 @@ final class SignatureValidationTests: XCTestCase { ) // Archive is signed, but manifest is not signed - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -629,7 +623,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -694,8 +688,8 @@ final class SignatureValidationTests: XCTestCase { ) // Archive is signed, but manifest signature format is bad - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -718,7 +712,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -778,8 +772,8 @@ final class SignatureValidationTests: XCTestCase { ) // Archive is signed, but manifest signature is malformed - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -804,7 +798,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -832,7 +826,7 @@ final class SignatureValidationTests: XCTestCase { var configuration = RegistryConfiguration() configuration.defaultRegistry = registry - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in // Write test root to trust roots directory let trustRootsDirectoryPath = temporaryDirectory.appending(component: "trust-roots") try localFileSystem.createDirectory(trustRootsDirectoryPath) @@ -874,19 +868,17 @@ final class SignatureValidationTests: XCTestCase { ) // Package signature is valid - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - content: Data(emptyZipFile.contents), - configuration: configuration.signing(for: package, registry: registry) - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + content: Data(emptyZipFile.contents), + configuration: configuration.signing(for: package, registry: registry) ) } } - func testSignedPackage_badSignature() throws { + func testSignedPackage_badSignature() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -937,8 +929,8 @@ final class SignatureValidationTests: XCTestCase { ) // Package signature can't be parsed so it is invalid - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -952,7 +944,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testSignedPackage_badSignature_skipSignatureValidation() throws { + func testSignedPackage_badSignature_skipSignatureValidation() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -997,14 +989,12 @@ final class SignatureValidationTests: XCTestCase { // Signature is bad, but we are skipping signature // validation, so no error is thrown. - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - content: Data(emptyZipFile.contents), - configuration: configuration.signing(for: package, registry: registry) - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + content: Data(emptyZipFile.contents), + configuration: configuration.signing(for: package, registry: registry) ) } @@ -1016,7 +1006,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1044,7 +1034,7 @@ final class SignatureValidationTests: XCTestCase { var configuration = RegistryConfiguration() configuration.defaultRegistry = registry - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in // Write test root to trust roots directory let trustRootsDirectoryPath = temporaryDirectory.appending(component: "trust-roots") try localFileSystem.createDirectory(trustRootsDirectoryPath) @@ -1086,8 +1076,8 @@ final class SignatureValidationTests: XCTestCase { ) // Package signature doesn't match content so it's invalid - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1110,7 +1100,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1172,8 +1162,8 @@ final class SignatureValidationTests: XCTestCase { ) // Test root not trusted; onUntrustedCertificate is set to .error - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1195,7 +1185,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1259,8 +1249,8 @@ final class SignatureValidationTests: XCTestCase { ) // Test root not trusted; onUntrustedCertificate is set to .prompt - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1285,7 +1275,7 @@ final class SignatureValidationTests: XCTestCase { ) // Package signer is untrusted, signingEntity should be nil - let signingEntity = try signatureValidation.validate( + let signingEntity = try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1304,7 +1294,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1368,15 +1358,13 @@ final class SignatureValidationTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() // Test root not trusted but onUntrustedCertificate is set to .warn - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - content: Data(emptyZipFile.contents), - configuration: configuration.signing(for: package, registry: registry), - observabilityScope: observability.topScope - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + content: Data(emptyZipFile.contents), + configuration: configuration.signing(for: package, registry: registry), + observabilityScope: observability.topScope ) testDiagnostics(observability.diagnostics) { result in @@ -1393,7 +1381,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1431,7 +1419,7 @@ final class SignatureValidationTests: XCTestCase { var configuration = RegistryConfiguration() configuration.defaultRegistry = registry - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in // Write test root to trust roots directory let trustRootsDirectoryPath = temporaryDirectory.appending(component: "trust-roots") try localFileSystem.createDirectory(trustRootsDirectoryPath) @@ -1473,20 +1461,18 @@ final class SignatureValidationTests: XCTestCase { ) // Manifest signature is valid - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - manifestContent: manifestContent, - configuration: configuration.signing(for: package, registry: registry) - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + manifestContent: manifestContent, + configuration: configuration.signing(for: package, registry: registry) ) } } - func testSignedManifest_badSignature() throws { + func testSignedManifest_badSignature() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -1494,7 +1480,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1552,8 +1538,8 @@ final class SignatureValidationTests: XCTestCase { ) // Manifest signature can't be parsed so it is invalid - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1568,7 +1554,7 @@ final class SignatureValidationTests: XCTestCase { } } - func testSignedManifest_badSignature_skipSignatureValidation() throws { + func testSignedManifest_badSignature_skipSignatureValidation() async throws { let registryURL = URL("https://packages.example.com") let identity = PackageIdentity.plain("mona.LinkedList") let package = identity.registry! @@ -1576,7 +1562,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1635,15 +1621,13 @@ final class SignatureValidationTests: XCTestCase { // Manifest signature is bad, but we are skipping signature // validation, so no error is thrown. - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - manifestContent: manifestContent, - configuration: configuration.signing(for: package, registry: registry) - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + manifestContent: manifestContent, + configuration: configuration.signing(for: package, registry: registry) ) } @@ -1655,7 +1639,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1693,7 +1677,7 @@ final class SignatureValidationTests: XCTestCase { var configuration = RegistryConfiguration() configuration.defaultRegistry = registry - try withTemporaryDirectory { temporaryDirectory in + try await withTemporaryDirectory { temporaryDirectory in // Write test root to trust roots directory let trustRootsDirectoryPath = temporaryDirectory.appending(component: "trust-roots") try localFileSystem.createDirectory(trustRootsDirectoryPath) @@ -1735,8 +1719,8 @@ final class SignatureValidationTests: XCTestCase { ) // Manifest signature doesn't match content so it's invalid - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1760,7 +1744,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1834,8 +1818,8 @@ final class SignatureValidationTests: XCTestCase { ) // Test root not trusted; onUntrustedCertificate is set to .prompt - XCTAssertThrowsError( - try signatureValidation.validate( + await XCTAssertAsyncThrowsError( + try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1861,7 +1845,7 @@ final class SignatureValidationTests: XCTestCase { ) // Package signer is not trusted, signingEntity should be nil - let signingEntity = try signatureValidation.validate( + let signingEntity = try await signatureValidation.validate( registry: registry, package: package, version: version, @@ -1881,7 +1865,7 @@ final class SignatureValidationTests: XCTestCase { let metadataURL = URL("\(registryURL)/\(package.scope)/\(package.name)/\(version)") let checksum = "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812" - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = try SwiftSigningIdentity( derEncodedCertificate: keyAndCertChain.leafCertificate, derEncodedPrivateKey: keyAndCertChain.privateKey, @@ -1956,16 +1940,14 @@ final class SignatureValidationTests: XCTestCase { // Test root not trusted. // With the exception of .prompt, we log then continue. - XCTAssertNoThrow( - try signatureValidation.validate( - registry: registry, - package: package, - version: version, - toolsVersion: .none, - manifestContent: manifestContent, - configuration: configuration.signing(for: package, registry: registry), - observabilityScope: observability.topScope - ) + _ = try await signatureValidation.validate( + registry: registry, + package: package, + version: version, + toolsVersion: .none, + manifestContent: manifestContent, + configuration: configuration.signing(for: package, registry: registry), + observabilityScope: observability.topScope ) testDiagnostics(observability.diagnostics, problemsOnly: false) { result in @@ -1991,23 +1973,19 @@ final class SignatureValidationTests: XCTestCase { ) } - private func ecSelfSignedTestKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try localFileSystem.readFileContents( - fixturePath.appending(components: "Certificates", "Test_ec_self_signed_key.p8") - ).contents - let certificate = try localFileSystem.readFileContents( - fixturePath.appending(components: "Certificates", "Test_ec_self_signed.cer") - ).contents - - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ))) - } - } catch { - callback(.failure(error)) + private func ecSelfSignedTestKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try localFileSystem.readFileContents( + fixturePath.appending(components: "Certificates", "Test_ec_self_signed_key.p8") + ).contents + let certificate = try localFileSystem.readFileContents( + fixturePath.appending(components: "Certificates", "Test_ec_self_signed.cer") + ).contents + + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) } } @@ -2040,21 +2018,18 @@ extension SignatureValidation { content: Data, configuration: RegistryConfiguration.Security.Signing, observabilityScope: ObservabilityScope? = nil - ) throws -> SigningEntity? { - try temp_await { - self.validate( - registry: registry, - package: package, - version: version, - content: content, - configuration: configuration, - timeout: nil, - fileSystem: localFileSystem, - observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws -> SigningEntity? { + try await self.validate( + registry: registry, + package: package, + version: version, + content: content, + configuration: configuration, + timeout: nil, + fileSystem: localFileSystem, + observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } fileprivate func validate( @@ -2065,22 +2040,19 @@ extension SignatureValidation { manifestContent: String, configuration: RegistryConfiguration.Security.Signing, observabilityScope: ObservabilityScope? = nil - ) throws -> SigningEntity? { - try temp_await { - self.validate( - registry: registry, - package: package, - version: version, - toolsVersion: toolsVersion, - manifestContent: manifestContent, - configuration: configuration, - timeout: nil, - fileSystem: localFileSystem, - observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + ) async throws -> SigningEntity? { + try await self.validate( + registry: registry, + package: package, + version: version, + toolsVersion: toolsVersion, + manifestContent: manifestContent, + configuration: configuration, + timeout: nil, + fileSystem: localFileSystem, + observabilityScope: observabilityScope ?? ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } } @@ -2125,15 +2097,12 @@ private struct AcceptingSignatureValidationDelegate: SignatureValidation.Delegat } extension PackageSigningEntityStorage { - fileprivate func get(package: PackageIdentity) throws -> PackageSigners { - try temp_await { - self.get( - package: package, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: $0 - ) - } + fileprivate func get(package: PackageIdentity) async throws -> PackageSigners { + try await self.get( + package: package, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } } diff --git a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift index 936c805e8d8..b5805f2e23a 100644 --- a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift +++ b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift @@ -23,7 +23,7 @@ import class TSCBasic.InMemoryFileSystem import struct TSCUtility.Version final class FilePackageSigningEntityStorageTests: XCTestCase { - func testHappyCase() throws { + func testHappyCase() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -42,19 +42,19 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit 2", organization: "SwiftPM Test" ) - try storage.put( + try await storage.put( package: package, version: Version("1.0.0"), signingEntity: davinci, origin: .registry(URL("http://foo.com")) ) - try storage.put( + try await storage.put( package: package, version: Version("1.1.0"), signingEntity: davinci, origin: .registry(URL("http://bar.com")) ) - try storage.put( + try await storage.put( package: package, version: Version("2.0.0"), signingEntity: appleseed, @@ -62,7 +62,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Record signing entity for another package let otherPackage = PackageIdentity.plain("other.LinkedList") - try storage.put( + try await storage.put( package: otherPackage, version: Version("1.0.0"), signingEntity: appleseed, @@ -78,7 +78,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { // Signed versions should be saved do { - let packageSigners = try storage.get(package: package) + let packageSigners = try await storage.get(package: package) XCTAssertNil(packageSigners.expectedSigner) XCTAssertEqual(packageSigners.signers.count, 2) XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0"), Version("1.1.0")]) @@ -91,7 +91,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } do { - let packageSigners = try storage.get(package: otherPackage) + let packageSigners = try await storage.get(package: otherPackage) XCTAssertNil(packageSigners.expectedSigner) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) @@ -99,7 +99,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } } - func testPutDifferentSigningEntityShouldConflict() throws { + func testPutDifferentSigningEntityShouldConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -118,7 +118,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organization: "SwiftPM Test" ) let version = Version("1.0.0") - try storage.put( + try await storage.put( package: package, version: version, signingEntity: davinci, @@ -126,7 +126,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Writing different signing entities for the same version should fail - XCTAssertThrowsError(try storage.put( + await XCTAssertAsyncThrowsError(try await storage.put( package: package, version: version, signingEntity: appleseed, @@ -138,7 +138,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } } - func testPutSameSigningEntityShouldNotConflict() throws { + func testPutSameSigningEntityShouldNotConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -151,7 +151,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organization: "SwiftPM Test" ) let version = Version("1.0.0") - try storage.put( + try await storage.put( package: package, version: version, signingEntity: appleseed, @@ -159,14 +159,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Writing same signing entity for version should be ok - XCTAssertNoThrow(try storage.put( + try await storage.put( package: package, version: version, signingEntity: appleseed, origin: .registry(URL("http://bar.com")) // origin is different and should be added - )) + ) - let packageSigners = try storage.get(package: package) + let packageSigners = try await storage.get(package: package) XCTAssertNil(packageSigners.expectedSigner) XCTAssertEqual(packageSigners.signers.count, 1) XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) @@ -176,7 +176,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) } - func testPutUnrecognizedSigningEntityShouldError() throws { + func testPutUnrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -185,7 +185,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { let appleseed = SigningEntity.unrecognized(name: "J. Appleseed", organizationalUnit: nil, organization: nil) let version = Version("1.0.0") - XCTAssertThrowsError(try storage.put( + await XCTAssertAsyncThrowsError(try await storage.put( package: package, version: version, signingEntity: appleseed, @@ -197,7 +197,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } } - func testAddDifferentSigningEntityShouldNotConflict() throws { + func testAddDifferentSigningEntityShouldNotConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -216,7 +216,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organization: "SwiftPM Test" ) let version = Version("1.0.0") - try storage.put( + try await storage.put( package: package, version: version, signingEntity: davinci, @@ -224,14 +224,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Adding different signing entity for the same version should not fail - XCTAssertNoThrow(try storage.add( + try await storage.add( package: package, version: version, signingEntity: appleseed, origin: .registry(URL("http://bar.com")) - )) + ) - let packageSigners = try storage.get(package: package) + let packageSigners = try await storage.get(package: package) XCTAssertNil(packageSigners.expectedSigner) XCTAssertEqual(packageSigners.signers.count, 2) XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) @@ -241,7 +241,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { XCTAssertEqual(packageSigners.signingEntities(of: Version("1.0.0")), [appleseed, davinci]) } - func testAddUnrecognizedSigningEntityShouldError() throws { + func testAddUnrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -255,14 +255,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organization: "SwiftPM Test" ) let version = Version("1.0.0") - try storage.put( + try await storage.put( package: package, version: version, signingEntity: davinci, origin: .registry(URL("http://foo.com")) ) - XCTAssertThrowsError(try storage.add( + await XCTAssertAsyncThrowsError(try await storage.add( package: package, version: version, signingEntity: appleseed, @@ -274,7 +274,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } } - func testChangeSigningEntityFromVersion() throws { + func testChangeSigningEntityFromVersion() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -292,7 +292,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit 2", organization: "SwiftPM Test" ) - try storage.put( + try await storage.put( package: package, version: Version("1.0.0"), signingEntity: davinci, @@ -300,14 +300,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Sets package's expectedSigner and add package version signer - XCTAssertNoThrow(try storage.changeSigningEntityFromVersion( + try await storage.changeSigningEntityFromVersion( package: package, version: Version("1.5.0"), signingEntity: appleseed, origin: .registry(URL("http://bar.com")) - )) + ) - let packageSigners = try storage.get(package: package) + let packageSigners = try await storage.get(package: package) XCTAssertEqual(packageSigners.expectedSigner?.signingEntity, appleseed) XCTAssertEqual(packageSigners.expectedSigner?.fromVersion, Version("1.5.0")) XCTAssertEqual(packageSigners.signers.count, 2) @@ -317,7 +317,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { XCTAssertEqual(packageSigners.signers[davinci]?.origins, [.registry(URL("http://foo.com"))]) } - func testChangeSigningEntityFromVersion_unrecognizedSigningEntityShouldError() throws { + func testChangeSigningEntityFromVersion_unrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -330,14 +330,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit", organization: "SwiftPM Test" ) - try storage.put( + try await storage.put( package: package, version: Version("1.0.0"), signingEntity: davinci, origin: .registry(URL("http://foo.com")) ) - XCTAssertThrowsError(try storage.changeSigningEntityFromVersion( + await XCTAssertAsyncThrowsError(try await storage.changeSigningEntityFromVersion( package: package, version: Version("1.5.0"), signingEntity: appleseed, @@ -349,7 +349,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } } - func testChangeSigningEntityForAllVersions() throws { + func testChangeSigningEntityForAllVersions() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -367,13 +367,13 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit 2", organization: "SwiftPM Test" ) - try storage.put( + try await storage.put( package: package, version: Version("1.0.0"), signingEntity: davinci, origin: .registry(URL("http://foo.com")) ) - try storage.put( + try await storage.put( package: package, version: Version("2.0.0"), signingEntity: appleseed, @@ -381,14 +381,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Sets package's expectedSigner and remove all other signers - XCTAssertNoThrow(try storage.changeSigningEntityForAllVersions( + try await storage.changeSigningEntityForAllVersions( package: package, version: Version("1.5.0"), signingEntity: appleseed, origin: .registry(URL("http://bar.com")) - )) + ) - let packageSigners = try storage.get(package: package) + let packageSigners = try await storage.get(package: package) XCTAssertEqual(packageSigners.expectedSigner?.signingEntity, appleseed) XCTAssertEqual(packageSigners.expectedSigner?.fromVersion, Version("1.5.0")) XCTAssertEqual(packageSigners.signers.count, 1) @@ -396,7 +396,7 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) } - func testChangeSigningEntityForAllVersions_unrecognizedSigningEntityShouldError() throws { + func testChangeSigningEntityForAllVersions_unrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -409,14 +409,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit", organization: "SwiftPM Test" ) - try storage.put( + try await storage.put( package: package, version: Version("1.0.0"), signingEntity: davinci, origin: .registry(URL("http://foo.com")) ) - XCTAssertThrowsError(try storage.changeSigningEntityForAllVersions( + await XCTAssertAsyncThrowsError(try await storage.changeSigningEntityForAllVersions( package: package, version: Version("1.5.0"), signingEntity: appleseed, @@ -430,15 +430,12 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { } extension PackageSigningEntityStorage { - fileprivate func get(package: PackageIdentity) throws -> PackageSigners { - try temp_await { - self.get( - package: package, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: $0 - ) - } + fileprivate func get(package: PackageIdentity) async throws -> PackageSigners { + try await self.get( + package: package, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } fileprivate func put( @@ -446,18 +443,15 @@ extension PackageSigningEntityStorage { version: Version, signingEntity: SigningEntity, origin: SigningEntity.Origin - ) throws { - try temp_await { - self.put( + ) async throws { + try await self.put( package: package, version: version, signingEntity: signingEntity, origin: origin, observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: $0 - ) - } + callbackQueue: .sharedConcurrent + ) } fileprivate func add( @@ -465,18 +459,15 @@ extension PackageSigningEntityStorage { version: Version, signingEntity: SigningEntity, origin: SigningEntity.Origin - ) throws { - try temp_await { - self.add( - package: package, - version: version, - signingEntity: signingEntity, - origin: origin, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: $0 - ) - } + ) async throws { + try await self.add( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } fileprivate func changeSigningEntityFromVersion( @@ -484,18 +475,16 @@ extension PackageSigningEntityStorage { version: Version, signingEntity: SigningEntity, origin: SigningEntity.Origin - ) throws { - try temp_await { - self.changeSigningEntityFromVersion( - package: package, - version: version, - signingEntity: signingEntity, - origin: origin, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: $0 - ) - } + ) async throws { + try await self.changeSigningEntityFromVersion( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) + } fileprivate func changeSigningEntityForAllVersions( @@ -503,17 +492,14 @@ extension PackageSigningEntityStorage { version: Version, signingEntity: SigningEntity, origin: SigningEntity.Origin - ) throws { - try temp_await { - self.changeSigningEntityForAllVersions( - package: package, - version: version, - signingEntity: signingEntity, - origin: origin, - observabilityScope: ObservabilitySystem.NOOP, - callbackQueue: .sharedConcurrent, - callback: $0 - ) - } + ) async throws { + try await self.changeSigningEntityForAllVersions( + package: package, + version: version, + signingEntity: signingEntity, + origin: origin, + observabilityScope: ObservabilitySystem.NOOP, + callbackQueue: .sharedConcurrent + ) } } diff --git a/Tests/PackageSigningTests/SigningTests.swift b/Tests/PackageSigningTests/SigningTests.swift index e71fddc61a5..3b090ba448a 100644 --- a/Tests/PackageSigningTests/SigningTests.swift +++ b/Tests/PackageSigningTests/SigningTests.swift @@ -22,7 +22,7 @@ import XCTest final class SigningTests: XCTestCase { func testCMS1_0_0EndToEnd() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -66,7 +66,7 @@ final class SigningTests: XCTestCase { } func testCMSEndToEndWithECSigningIdentity() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -108,7 +108,7 @@ final class SigningTests: XCTestCase { } func testCMSEndToEndWithRSASigningIdentity() async throws { - let keyAndCertChain = try temp_await { self.rsaTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.rsaTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -150,7 +150,7 @@ final class SigningTests: XCTestCase { } func testCMSWrongKeyTypeForSignatureAlgorithm() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -177,7 +177,7 @@ final class SigningTests: XCTestCase { } func testCMS1_0_0EndToEndWithSelfSignedCertificate() async throws { - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -221,7 +221,7 @@ final class SigningTests: XCTestCase { } func testCMSEndToEndWithSelfSignedECSigningIdentity() async throws { - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -263,7 +263,7 @@ final class SigningTests: XCTestCase { } func testCMSEndToEndWithSelfSignedRSASigningIdentity() async throws { - let keyAndCertChain = try temp_await { self.rsaSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.rsaSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -322,7 +322,7 @@ final class SigningTests: XCTestCase { } func testCMSInvalidSignature() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -359,7 +359,7 @@ final class SigningTests: XCTestCase { } func testCMSUntrustedCertificate() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -395,7 +395,7 @@ final class SigningTests: XCTestCase { } func testCMSCheckCertificateValidityPeriod() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -517,8 +517,8 @@ final class SigningTests: XCTestCase { responses: [OCSPSingleResponse( certID: singleRequest.certID, certStatus: .unknown, - thisUpdate: try .init(validationTime - .days(1)), - nextUpdate: try .init(validationTime + .days(1)) + thisUpdate: try GeneralizedTime(validationTime - .days(1)), + nextUpdate: try GeneralizedTime(validationTime + .days(1)) )], privateKey: intermediatePrivateKey, responseExtensions: { nonce } @@ -589,7 +589,7 @@ final class SigningTests: XCTestCase { try XCTSkipIf(true) #endif - let keyAndCertChain = try temp_await { rsaADPKeyAndCertChain(callback: $0) } + let keyAndCertChain = try rsaADPKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -623,25 +623,21 @@ final class SigningTests: XCTestCase { return XCTFail("Expected signature status to be .valid but got \(status)") } - func rsaADPKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "development_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "development.cer" - ) + func rsaADPKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "development_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "development.cer" + ) - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ))) - } - } catch { - callback(.failure(error)) + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) } } } @@ -652,7 +648,7 @@ final class SigningTests: XCTestCase { try XCTSkipIf(true) #endif - let keyAndCertChain = try temp_await { ecADPKeyAndCertChain(callback: $0) } + let keyAndCertChain = try ecADPKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -686,25 +682,21 @@ final class SigningTests: XCTestCase { return XCTFail("Expected signature status to be .valid but got \(status)") } - func ecADPKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "swift_package_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "swift_package.cer" - ) + func ecADPKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "swift_package_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "swift_package.cer" + ) - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ))) - } - } catch { - callback(.failure(error)) + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) } } } @@ -880,7 +872,7 @@ final class SigningTests: XCTestCase { #endif func testCMS1_0_0ExtractSigningEntity() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -919,7 +911,7 @@ final class SigningTests: XCTestCase { } func testCMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { - let keyAndCertChain = try temp_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -958,7 +950,7 @@ final class SigningTests: XCTestCase { } func testCMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { - let keyAndCertChain = try temp_await { self.ecTestKeyAndCertChain(callback: $0) } + let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), privateKey: try Certificate @@ -996,107 +988,91 @@ final class SigningTests: XCTestCase { } } - private func ecTestKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_ec_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_ec.cer" - ) - let intermediateCA = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "TestIntermediateCA.cer" - ) - let rootCA = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "TestRootCA.cer" - ) + private func ecTestKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_ec_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_ec.cer" + ) + let intermediateCA = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "TestIntermediateCA.cer" + ) + let rootCA = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "TestRootCA.cer" + ) - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate, intermediateCA, rootCA] - ))) - } - } catch { - callback(.failure(error)) + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate, intermediateCA, rootCA] + ) } } - private func ecSelfSignedTestKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_ec_self_signed_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_ec_self_signed.cer" - ) + private func ecSelfSignedTestKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_ec_self_signed_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_ec_self_signed.cer" + ) - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ))) - } - } catch { - callback(.failure(error)) + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) } } - private func rsaTestKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_rsa_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_rsa.cer" - ) - let intermediateCA = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "TestIntermediateCA.cer" - ) - let rootCA = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "TestRootCA.cer" - ) + private func rsaTestKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_rsa_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_rsa.cer" + ) + let intermediateCA = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "TestIntermediateCA.cer" + ) + let rootCA = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "TestRootCA.cer" + ) - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate, intermediateCA, rootCA] - ))) - } - } catch { - callback(.failure(error)) + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate, intermediateCA, rootCA] + ) } } - private func rsaSelfSignedTestKeyAndCertChain(callback: (Result) -> Void) { - do { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let privateKey = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_rsa_self_signed_key.p8" - ) - let certificate = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", "Test_rsa_self_signed.cer" - ) + private func rsaSelfSignedTestKeyAndCertChain() throws -> KeyAndCertChain { + try fixture(name: "Signing", createGitRepo: false) { fixturePath in + let privateKey = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_rsa_self_signed_key.p8" + ) + let certificate = try readFileContents( + in: fixturePath, + pathComponents: "Certificates", "Test_rsa_self_signed.cer" + ) - callback(.success(KeyAndCertChain( - privateKey: privateKey, - certificateChain: [certificate] - ))) - } - } catch { - callback(.failure(error)) + return KeyAndCertChain( + privateKey: privateKey, + certificateChain: [certificate] + ) } } @@ -1150,7 +1126,7 @@ enum OCSPTestHelper { } if isCodeSigning { Critical( - ExtendedKeyUsage([ExtendedKeyUsage.Usage.codeSigning]) + try ExtendedKeyUsage([ExtendedKeyUsage.Usage.codeSigning]) ) } if let ocspServer { diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index 507afe3cc45..83cf4fca585 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -195,6 +195,7 @@ class PluginInvocationTests: XCTestCase { buildEnvironment: BuildEnvironment(platform: .macOS, configuration: .debug), toolSearchDirectories: [UserToolchain.default.swiftCompilerPath.parentDirectory], pkgConfigDirectories: [], + sdkRootPath: UserToolchain.default.sdkRootPath, pluginScriptRunner: pluginRunner, observabilityScope: observability.topScope, fileSystem: fileSystem @@ -495,6 +496,11 @@ class PluginInvocationTests: XCTestCase { } """) + // NTFS does not have nanosecond granularity (nor is this is a guaranteed file + // system feature on all file systems). Add a sleep before the execution to ensure that we have sufficient + // precision to read a difference. + Thread.sleep(forTimeInterval: 1) + // Recompile the plugin again. let thirdExecModTime: Date do { @@ -895,6 +901,7 @@ class PluginInvocationTests: XCTestCase { buildEnvironment: BuildEnvironment(platform: .macOS, configuration: .debug), toolSearchDirectories: [UserToolchain.default.swiftCompilerPath.parentDirectory], pkgConfigDirectories: [], + sdkRootPath: UserToolchain.default.sdkRootPath, pluginScriptRunner: pluginScriptRunner, observabilityScope: observability.topScope, fileSystem: localFileSystem @@ -909,11 +916,11 @@ class PluginInvocationTests: XCTestCase { } } - func testScanImportsInPluginTargets() throws { + func testScanImportsInPluginTargets() async throws { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") - try testWithTemporaryDirectory { tmpPath in + try await testWithTemporaryDirectory { tmpPath in // Create a sample package with a library target and a plugin. let packageDir = tmpPath.appending(components: "MyPackage") try localFileSystem.createDirectory(packageDir, recursive: true) @@ -1051,39 +1058,35 @@ class PluginInvocationTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") let graph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) - workspace.loadPluginImports(packageGraph: graph) { (result: Result<[PackageIdentity : [String : [String]]], Error>) in - - var count = 0 - if let dict = try? result.get() { - for (pkg, entry) in dict { - if pkg.description == "mypackage" { - XCTAssertNotNil(entry["XPlugin"]) - let XPluginPossibleImports1 = ["PackagePlugin", "XcodeProjectPlugin"] - let XPluginPossibleImports2 = ["PackagePlugin", "XcodeProjectPlugin", "_SwiftConcurrencyShims"] - XCTAssertTrue(entry["XPlugin"] == XPluginPossibleImports1 || - entry["XPlugin"] == XPluginPossibleImports2) - - let YPluginPossibleImports1 = ["PackagePlugin", "Foundation"] - let YPluginPossibleImports2 = ["PackagePlugin", "Foundation", "_SwiftConcurrencyShims"] - XCTAssertTrue(entry["YPlugin"] == YPluginPossibleImports1 || - entry["YPlugin"] == YPluginPossibleImports2) - count += 1 - } else if pkg.description == "otherpackage" { - XCTAssertNotNil(dict[pkg]?["QPlugin"]) - - let possibleImports1 = ["PackagePlugin", "XcodeProjectPlugin", "ModuleFoundViaExtraSearchPaths"] - let possibleImports2 = ["PackagePlugin", "XcodeProjectPlugin", "ModuleFoundViaExtraSearchPaths", "_SwiftConcurrencyShims"] - XCTAssertTrue(entry["QPlugin"] == possibleImports1 || - entry["QPlugin"] == possibleImports2) - count += 1 - } - } - } else { - XCTFail("Scanned import list should not be empty") + let dict = try await workspace.loadPluginImports(packageGraph: graph) + + var count = 0 + for (pkg, entry) in dict { + if pkg.description == "mypackage" { + XCTAssertNotNil(entry["XPlugin"]) + let XPluginPossibleImports1 = ["PackagePlugin", "XcodeProjectPlugin"] + let XPluginPossibleImports2 = ["PackagePlugin", "XcodeProjectPlugin", "_SwiftConcurrencyShims"] + XCTAssertTrue(entry["XPlugin"] == XPluginPossibleImports1 || + entry["XPlugin"] == XPluginPossibleImports2) + + let YPluginPossibleImports1 = ["PackagePlugin", "Foundation"] + let YPluginPossibleImports2 = ["PackagePlugin", "Foundation", "_SwiftConcurrencyShims"] + XCTAssertTrue(entry["YPlugin"] == YPluginPossibleImports1 || + entry["YPlugin"] == YPluginPossibleImports2) + count += 1 + } else if pkg.description == "otherpackage" { + XCTAssertNotNil(dict[pkg]?["QPlugin"]) + + let possibleImports1 = ["PackagePlugin", "XcodeProjectPlugin", "ModuleFoundViaExtraSearchPaths"] + let possibleImports2 = ["PackagePlugin", "XcodeProjectPlugin", "ModuleFoundViaExtraSearchPaths", "_SwiftConcurrencyShims"] + XCTAssertTrue(entry["QPlugin"] == possibleImports1 || + entry["QPlugin"] == possibleImports2) + count += 1 } - - XCTAssertEqual(count, 2) } + + + XCTAssertEqual(count, 2) } } @@ -1118,7 +1121,7 @@ class PluginInvocationTests: XCTestCase { ), .binaryTarget( name: "LocalBinaryTool", - path: "Binaries/LocalBinaryTool.artifactbundle" + path: "Binaries/LocalBinaryTool.\(artifactBundleExtension)" ), ] ) @@ -1238,6 +1241,7 @@ class PluginInvocationTests: XCTestCase { buildEnvironment: BuildEnvironment(platform: .macOS, configuration: .debug), toolSearchDirectories: [UserToolchain.default.swiftCompilerPath.parentDirectory], pkgConfigDirectories: [], + sdkRootPath: UserToolchain.default.sdkRootPath, pluginScriptRunner: pluginScriptRunner, observabilityScope: observability.topScope, fileSystem: localFileSystem diff --git a/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift b/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift index ccd2669d53a..1aa7f427d0e 100644 --- a/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift +++ b/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift @@ -37,6 +37,21 @@ final class XCFrameworkMetadataTests: XCTestCase { SupportedPlatform macos + + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + MyFramework.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + CFBundlePackageType XFWK @@ -55,7 +70,16 @@ final class XCFrameworkMetadataTests: XCTestCase { libraryPath: "MyFramework.framework", headersPath: nil, platform: "macos", - architectures: ["x86_64"] + architectures: ["x86_64"], + variant: nil + ), + XCFrameworkMetadata.Library( + libraryIdentifier: "ios-arm64_x86_64-simulator", + libraryPath: "MyFramework.framework", + headersPath: nil, + platform: "ios", + architectures: ["arm64", "x86_64"], + variant: "simulator" ), ])) } @@ -102,7 +126,8 @@ final class XCFrameworkMetadataTests: XCTestCase { libraryPath: "MyLibrary.a", headersPath: "Headers", platform: "macos", - architectures: ["x86_64"] + architectures: ["x86_64"], + variant: nil ), ])) } diff --git a/Tests/SourceControlTests/GitRepositoryTests.swift b/Tests/SourceControlTests/GitRepositoryTests.swift index 02035c66302..85f9ffbca0d 100644 --- a/Tests/SourceControlTests/GitRepositoryTests.swift +++ b/Tests/SourceControlTests/GitRepositoryTests.swift @@ -682,11 +682,14 @@ class GitRepositoryTests: XCTestCase { let checkoutRepo = try provider.createWorkingCopy(repository: repoSpec, sourcePath: testClonePath, at: checkoutPath, editable: false) // The object store should be valid. - XCTAssertTrue(checkoutRepo.isAlternateObjectStoreValid()) + XCTAssertTrue(checkoutRepo.isAlternateObjectStoreValid(expected: testClonePath)) + + // Wrong path + XCTAssertFalse(checkoutRepo.isAlternateObjectStoreValid(expected: testClonePath.appending(UUID().uuidString))) // Delete the clone (alternative object store). try localFileSystem.removeFileTree(testClonePath) - XCTAssertFalse(checkoutRepo.isAlternateObjectStoreValid()) + XCTAssertFalse(checkoutRepo.isAlternateObjectStoreValid(expected: testClonePath)) } } diff --git a/Tests/SourceControlTests/RepositoryManagerTests.swift b/Tests/SourceControlTests/RepositoryManagerTests.swift index d33f7528487..0b73a926661 100644 --- a/Tests/SourceControlTests/RepositoryManagerTests.swift +++ b/Tests/SourceControlTests/RepositoryManagerTests.swift @@ -328,8 +328,6 @@ class RepositoryManagerTests: XCTestCase { let variants: [RepositorySpecifier] = [ .init(url: "https://scm.com/org/foo"), .init(url: "https://scm.com/org/foo.git"), - .init(url: "http://scm.com/org/foo"), - .init(url: "http://scm.com/org/foo.git") ] for variant in variants { @@ -559,11 +557,11 @@ class RepositoryManagerTests: XCTestCase { fatalError("should not be called") } - func isValidDirectory(_ directory: AbsolutePath) -> Bool { + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { fatalError("should not be called") } - func isValidRefFormat(_ ref: String) -> Bool { + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { fatalError("should not be called") } @@ -572,6 +570,125 @@ class RepositoryManagerTests: XCTestCase { } } } + + func testInvalidRepositoryOnDisk() throws { + let fileSystem = localFileSystem + let observability = ObservabilitySystem.makeForTesting() + + try testWithTemporaryDirectory { path in + let repositoriesDirectory = path.appending("repositories") + try fileSystem.createDirectory(repositoriesDirectory, recursive: true) + + let testRepository = RepositorySpecifier(url: .init("test-\(UUID().uuidString)")) + let provider = MockRepositoryProvider(repository: testRepository) + + let manager = RepositoryManager( + fileSystem: fileSystem, + path: repositoriesDirectory, + provider: provider, + delegate: nil + ) + + _ = try manager.lookup(repository: testRepository, observabilityScope: observability.topScope) + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: .contains("is not valid git repository for '\(testRepository)', will fetch again"), + severity: .warning + ) + } + } + + class MockRepositoryProvider: RepositoryProvider { + let repository: RepositorySpecifier + var fetch: Int = 0 + + init(repository: RepositorySpecifier) { + self.repository = repository + } + + func fetch(repository: RepositorySpecifier, to path: AbsolutePath, progressHandler: ((FetchProgress) -> Void)?) throws { + assert(repository == self.repository) + self.fetch += 1 + } + + func repositoryExists(at path: AbsolutePath) throws -> Bool { + // the directory exists + return true + } + + func open(repository: RepositorySpecifier, at path: AbsolutePath) throws -> Repository { + return MockRepository() + } + + func createWorkingCopy(repository: RepositorySpecifier, sourcePath: AbsolutePath, at destinationPath: AbsolutePath, editable: Bool) throws -> WorkingCheckout { + fatalError("should not be called") + } + + func workingCopyExists(at path: AbsolutePath) throws -> Bool { + fatalError("should not be called") + } + + func openWorkingCopy(at path: AbsolutePath) throws -> WorkingCheckout { + fatalError("should not be called") + } + + func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws { + fatalError("should not be called") + } + + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { + fatalError("should not be called") + } + + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + assert(repository == self.repository) + // the directory is not valid + return false + } + + func cancel(deadline: DispatchTime) throws { + fatalError("should not be called") + } + } + + class MockRepository: Repository { + func getTags() throws -> [String] { + fatalError("unexpected API call") + } + + func resolveRevision(tag: String) throws -> Revision { + fatalError("unexpected API call") + } + + func resolveRevision(identifier: String) throws -> Revision { + fatalError("unexpected API call") + } + + func exists(revision: Revision) -> Bool { + fatalError("unexpected API call") + } + + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { + fatalError("unexpected API call") + } + + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + fatalError("unexpected API call") + } + + func fetch() throws { + // noop + } + + func openFileView(revision: Revision) throws -> FileSystem { + fatalError("unexpected API call") + } + + public func openFileView(tag: String) throws -> FileSystem { + fatalError("unexpected API call") + } + } + } } extension RepositoryManager { @@ -619,50 +736,6 @@ private enum DummyError: Swift.Error { case invalidRepository } -private class DummyRepository: Repository { - unowned let provider: DummyRepositoryProvider - - init(provider: DummyRepositoryProvider) { - self.provider = provider - } - - func getTags() throws -> [String] { - ["1.0.0"] - } - - func resolveRevision(tag: String) throws -> Revision { - fatalError("unexpected API call") - } - - func resolveRevision(identifier: String) throws -> Revision { - fatalError("unexpected API call") - } - - func exists(revision: Revision) -> Bool { - fatalError("unexpected API call") - } - - func isValidDirectory(_ directory: AbsolutePath) -> Bool { - fatalError("unexpected API call") - } - - func isValidRefFormat(_ ref: String) -> Bool { - fatalError("unexpected API call") - } - - func fetch() throws { - self.provider.increaseFetchCount() - } - - func openFileView(revision: Revision) throws -> FileSystem { - fatalError("unexpected API call") - } - - public func openFileView(tag: String) throws -> FileSystem { - fatalError("unexpected API call") - } -} - private class DummyRepositoryProvider: RepositoryProvider { private let fileSystem: FileSystem @@ -726,11 +799,11 @@ private class DummyRepositoryProvider: RepositoryProvider { return DummyWorkingCheckout(at: path) } - func isValidDirectory(_ directory: AbsolutePath) -> Bool { + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { return true } - func isValidRefFormat(_ ref: String) -> Bool { + func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { return true } @@ -799,7 +872,7 @@ private class DummyRepositoryProvider: RepositoryProvider { fatalError("not implemented") } - func isAlternateObjectStoreValid() -> Bool { + func isAlternateObjectStoreValid(expected: AbsolutePath) -> Bool { fatalError("not implemented") } @@ -809,7 +882,7 @@ private class DummyRepositoryProvider: RepositoryProvider { } } -private class DummyRepositoryManagerDelegate: RepositoryManager.Delegate { +fileprivate class DummyRepositoryManagerDelegate: RepositoryManager.Delegate { private var _willFetch = ThreadSafeArrayStore<(repository: RepositorySpecifier, details: RepositoryManager.FetchDetails)>() private var _didFetch = ThreadSafeArrayStore<(repository: RepositorySpecifier, result: Result)>() private var _willUpdate = ThreadSafeArrayStore() @@ -884,3 +957,47 @@ private class DummyRepositoryManagerDelegate: RepositoryManager.Delegate { self.group.leave() } } + +fileprivate class DummyRepository: Repository { + unowned let provider: DummyRepositoryProvider + + init(provider: DummyRepositoryProvider) { + self.provider = provider + } + + func getTags() throws -> [String] { + ["1.0.0"] + } + + func resolveRevision(tag: String) throws -> Revision { + fatalError("unexpected API call") + } + + func resolveRevision(identifier: String) throws -> Revision { + fatalError("unexpected API call") + } + + func exists(revision: Revision) -> Bool { + fatalError("unexpected API call") + } + + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { + fatalError("unexpected API call") + } + + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + fatalError("unexpected API call") + } + + func fetch() throws { + self.provider.increaseFetchCount() + } + + func openFileView(revision: Revision) throws -> FileSystem { + fatalError("unexpected API call") + } + + public func openFileView(tag: String) throws -> FileSystem { + fatalError("unexpected API call") + } +} diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index a8108f0ef07..51d816f8dfc 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -88,7 +88,7 @@ class InitTests: XCTestCase { XCTAssertEqual(try fs.getDirectoryContents(path.appending("Sources")), ["main.swift"]) XCTAssertBuilds(path) let triple = try UserToolchain.default.targetTriple - let binPath = path.appending(components: ".build", triple.platformBuildPathComponent(), "debug") + let binPath = path.appending(components: ".build", triple.platformBuildPathComponent, "debug") #if os(Windows) XCTAssertFileExists(binPath.appending("Foo.exe")) #else @@ -145,7 +145,7 @@ class InitTests: XCTestCase { // Try building it XCTAssertBuilds(path) let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent(), "debug", "Foo.swiftmodule")) + XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Foo.swiftmodule")) } } @@ -243,7 +243,7 @@ class InitTests: XCTestCase { // Try building it. XCTAssertBuilds(packageRoot) let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(packageRoot.appending(components: ".build", triple.platformBuildPathComponent(), "debug", "some_package.swiftmodule")) + XCTAssertFileExists(packageRoot.appending(components: ".build", triple.platformBuildPathComponent, "debug", "some_package.swiftmodule")) } } @@ -286,6 +286,7 @@ class InitTests: XCTestCase { name: "Foo", options: options, destinationPath: packageRoot, + installedSwiftPMConfiguration: .default, fileSystem: localFileSystem ) try initPackage.writePackageStructure() diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index 46c031a27c1..88652873d23 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -43,8 +43,8 @@ class ManifestSourceGenerationTests: XCTestCase { toolsVersionHeaderComment: String? = .none, additionalImportModuleNames: [String] = [], fs: FileSystem = localFileSystem - ) throws -> String { - try withTemporaryDirectory { packageDir in + ) async throws -> String { + try await withTemporaryDirectory { packageDir in let observability = ObservabilitySystem.makeForTesting() // Write the original manifest file contents, and load it. @@ -52,22 +52,21 @@ class ManifestSourceGenerationTests: XCTestCase { try fs.writeFileContents(manifestPath, string: manifestContents) let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default) let identityResolver = DefaultIdentityResolver() - let manifest = try temp_await { - manifestLoader.load( - manifestPath: manifestPath, - manifestToolsVersion: toolsVersion, - packageIdentity: .plain("Root"), - packageKind: .root(packageDir), - packageLocation: packageDir.pathString, - packageVersion: nil, - identityResolver: identityResolver, - fileSystem: fs, - observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + let dependencyMapper = DefaultDependencyMapper(identityResolver: identityResolver) + let manifest = try await manifestLoader.load( + manifestPath: manifestPath, + manifestToolsVersion: toolsVersion, + packageIdentity: .plain("Root"), + packageKind: .root(packageDir), + packageLocation: packageDir.pathString, + packageVersion: nil, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fs, + observabilityScope: observability.topScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -83,22 +82,20 @@ class ManifestSourceGenerationTests: XCTestCase { // Write out the generated manifest to replace the old manifest file contents, and load it again. try fs.writeFileContents(manifestPath, string: newContents) - let newManifest = try temp_await { - manifestLoader.load( - manifestPath: manifestPath, - manifestToolsVersion: toolsVersion, - packageIdentity: .plain("Root"), - packageKind: .root(packageDir), - packageLocation: packageDir.pathString, - packageVersion: nil, - identityResolver: identityResolver, - fileSystem: fs, - observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + let newManifest = try await manifestLoader.load( + manifestPath: manifestPath, + manifestToolsVersion: toolsVersion, + packageIdentity: .plain("Root"), + packageKind: .root(packageDir), + packageLocation: packageDir.pathString, + packageVersion: nil, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fs, + observabilityScope: observability.topScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -122,7 +119,7 @@ class ManifestSourceGenerationTests: XCTestCase { } } - func testBasics() throws { + func testBasics() async throws { let manifestContents = """ // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -157,10 +154,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) } - func testCustomPlatform() throws { + func testCustomPlatform() async throws { let manifestContents = """ // swift-tools-version:5.6 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -194,10 +191,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_6) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_6) } - func testAdvancedFeatures() throws { + func testAdvancedFeatures() async throws { let manifestContents = """ // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -243,10 +240,10 @@ class ManifestSourceGenerationTests: XCTestCase { cxxLanguageStandard: .cxx11 ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) } - func testPackageDependencyVariations() throws { + func testPackageDependencyVariations() async throws { let manifestContents = """ // swift-tools-version:5.4 import PackageDescription @@ -278,7 +275,7 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - let newContents = try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) + let newContents = try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) // Check some things about the contents of the manifest. XCTAssertTrue(newContents.contains("url: \"\("../MyPkg10".nativePathString(escaped: true))\""), newContents) @@ -286,7 +283,7 @@ class ManifestSourceGenerationTests: XCTestCase { XCTAssertTrue(newContents.contains("path: \"\("packages/path/to/MyPkg12".nativePathString(escaped: true))"), newContents) } - func testResources() throws { + func testResources() async throws { let manifestContents = """ // swift-tools-version:5.3 import PackageDescription @@ -317,10 +314,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) } - func testBuildSettings() throws { + func testBuildSettings() async throws { let manifestContents = """ // swift-tools-version:5.3 import PackageDescription @@ -353,10 +350,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_3) } - func testPluginTargets() throws { + func testPluginTargets() async throws { let manifestContents = """ // swift-tools-version:5.5 import PackageDescription @@ -375,10 +372,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_5) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_5) } - func testCustomToolsVersionHeaderComment() throws { + func testCustomToolsVersionHeaderComment() async throws { let manifestContents = """ // swift-tools-version:5.5 import PackageDescription @@ -397,12 +394,12 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - let newContents = try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_5, toolsVersionHeaderComment: "a comment") + let newContents = try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_5, toolsVersionHeaderComment: "a comment") XCTAssertTrue(newContents.hasPrefix("// swift-tools-version: 5.5; a comment\n"), "contents: \(newContents)") } - func testAdditionalModuleImports() throws { + func testAdditionalModuleImports() async throws { let manifestContents = """ // swift-tools-version:5.5 import PackageDescription @@ -417,12 +414,12 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - let newContents = try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_5, additionalImportModuleNames: ["Foundation"]) + let newContents = try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_5, additionalImportModuleNames: ["Foundation"]) XCTAssertTrue(newContents.contains("import Foundation\n"), "contents: \(newContents)") } - func testLatestPlatformVersions() throws { + func testLatestPlatformVersions() async throws { let manifestContents = """ // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -444,10 +441,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_9) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_9) } - func testTargetPlatformConditions() throws { + func testTargetPlatformConditions() async throws { let manifestContents = """ // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. @@ -472,7 +469,7 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_9) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_9) } func testCustomProductSourceGeneration() throws { @@ -514,7 +511,7 @@ class ManifestSourceGenerationTests: XCTestCase { XCTAssertTrue(contents.contains(".library(name: \"Foo\", targets: [\"Bar\"], type: .static)"), "contents: \(contents)") } - func testModuleAliasGeneration() throws { + func testModuleAliasGeneration() async throws { let manifest = Manifest.createRootManifest( displayName: "thisPkg", path: "/thisPkg", @@ -555,10 +552,10 @@ class ManifestSourceGenerationTests: XCTestCase { let isContained = trimmedParts.allSatisfy(trimmedContents.contains(_:)) XCTAssertTrue(isContained) - try testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v5_8) + try await testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v5_8) } - func testUpcomingAndExperimentalFeatures() throws { + func testUpcomingAndExperimentalFeatures() async throws { let manifestContents = """ // swift-tools-version:5.8 import PackageDescription @@ -577,10 +574,10 @@ class ManifestSourceGenerationTests: XCTestCase { ] ) """ - try testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_8) + try await testManifestWritingRoundTrip(manifestContents: manifestContents, toolsVersion: .v5_8) } - func testPluginNetworkingPermissionGeneration() throws { + func testPluginNetworkingPermissionGeneration() async throws { let manifest = Manifest.createRootManifest( displayName: "thisPkg", path: "/thisPkg", @@ -590,6 +587,6 @@ class ManifestSourceGenerationTests: XCTestCase { try TargetDescription(name: "MyPlugin", type: .plugin, pluginCapability: .command(intent: .custom(verb: "foo", description: "bar"), permissions: [.allowNetworkConnections(scope: .all(ports: [23, 42, 443, 8080]), reason: "internet good")])) ]) let contents = try manifest.generateManifestFileContents(packageDirectory: manifest.path.parentDirectory) - try testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v5_9) + try await testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v5_9) } } diff --git a/Tests/WorkspaceTests/MirrorsConfigurationTests.swift b/Tests/WorkspaceTests/MirrorsConfigurationTests.swift index 9a5050708cb..84a1fec495d 100644 --- a/Tests/WorkspaceTests/MirrorsConfigurationTests.swift +++ b/Tests/WorkspaceTests/MirrorsConfigurationTests.swift @@ -73,7 +73,7 @@ final class MirrorsConfigurationTests: XCTestCase { let mirrorURL = "https://github.com/mona/swift-argument-parser.git" try config.apply{ mirrors in - mirrors.set(mirror: mirrorURL, for: originalURL) + try mirrors.set(mirror: mirrorURL, for: originalURL) } XCTAssertTrue(fs.exists(configFile)) @@ -96,7 +96,7 @@ final class MirrorsConfigurationTests: XCTestCase { let mirrorURL = "https://github.com/mona/swift-argument-parser.git" try config.apply{ mirrors in - mirrors.set(mirror: mirrorURL, for: originalURL) + try mirrors.set(mirror: mirrorURL, for: originalURL) } XCTAssertTrue(fs.exists(configFile)) @@ -124,7 +124,7 @@ final class MirrorsConfigurationTests: XCTestCase { let mirror1URL = "https://github.com/mona/swift-argument-parser.git" try config.applyShared { mirrors in - mirrors.set(mirror: mirror1URL, for: original1URL) + try mirrors.set(mirror: mirror1URL, for: original1URL) } XCTAssertEqual(config.mirrors.count, 1) @@ -137,7 +137,7 @@ final class MirrorsConfigurationTests: XCTestCase { let mirror2URL = "https://github.com/mona/swift-nio.git" try config.applyLocal { mirrors in - mirrors.set(mirror: mirror2URL, for: original2URL) + try mirrors.set(mirror: mirror2URL, for: original2URL) } XCTAssertEqual(config.mirrors.count, 1) diff --git a/Tests/WorkspaceTests/PinsStoreTests.swift b/Tests/WorkspaceTests/PinsStoreTests.swift index c9d5a4fbd33..e32c14aa77a 100644 --- a/Tests/WorkspaceTests/PinsStoreTests.swift +++ b/Tests/WorkspaceTests/PinsStoreTests.swift @@ -348,9 +348,9 @@ final class PinsStoreTests: XCTestCase { let bazURL = SourceControlURL("https://github.com/cool/baz.git") let bazIdentity = PackageIdentity(url: bazURL) - let mirrors = DependencyMirrors() - mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL.absoluteString) - mirrors.set(mirror: barMirroredURL.absoluteString, for: barURL.absoluteString) + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL.absoluteString) + try mirrors.set(mirror: barMirroredURL.absoluteString, for: barURL.absoluteString) let fileSystem = InMemoryFileSystem() let pinsFile = AbsolutePath("/pins.txt") @@ -398,11 +398,11 @@ final class PinsStoreTests: XCTestCase { let fooURL4 = SourceControlURL("https://github.com/old-corporate/foo.git") let fooMirroredURL = SourceControlURL("https://github.corporate.com/team/foo") - let mirrors = DependencyMirrors() - mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL1.absoluteString) - mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL2.absoluteString) - mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL3.absoluteString) - mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL4.absoluteString) + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL1.absoluteString) + try mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL2.absoluteString) + try mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL3.absoluteString) + try mirrors.set(mirror: fooMirroredURL.absoluteString, for: fooURL4.absoluteString) let fileSystem = InMemoryFileSystem() let pinsFile = AbsolutePath("/pins.txt") @@ -445,7 +445,7 @@ final class PinsStoreTests: XCTestCase { let mirroredURL = URL("https://github.corporate.com/team/foo") do { - let mirrors = DependencyMirrors([ + let mirrors = try DependencyMirrors([ URL1.absoluteString: mirroredURL.absoluteString, URL2.absoluteString: mirroredURL.absoluteString, URL3.absoluteString: mirroredURL.absoluteString, @@ -458,7 +458,7 @@ final class PinsStoreTests: XCTestCase { } do { - let mirrors = DependencyMirrors([ + let mirrors = try DependencyMirrors([ URL1.absoluteString: mirroredURL.absoluteString, URL2.absoluteString: mirroredURL.absoluteString, URL3.absoluteString: mirroredURL.absoluteString, @@ -471,7 +471,7 @@ final class PinsStoreTests: XCTestCase { } do { - let mirrors = DependencyMirrors([ + let mirrors = try DependencyMirrors([ URL1.absoluteString: mirroredURL.absoluteString, URL2.absoluteString: mirroredURL.absoluteString, URL3.absoluteString: mirroredURL.absoluteString, @@ -485,7 +485,7 @@ final class PinsStoreTests: XCTestCase { } do { - let mirrors = DependencyMirrors([ + let mirrors = try DependencyMirrors([ URL1.absoluteString: mirroredURL.absoluteString, URL2.absoluteString: mirroredURL.absoluteString, URL3.absoluteString: mirroredURL.absoluteString, @@ -499,7 +499,7 @@ final class PinsStoreTests: XCTestCase { } do { - let mirrors = DependencyMirrors([ + let mirrors = try DependencyMirrors([ URL1.absoluteString: mirroredURL.absoluteString, URL2.absoluteString: mirroredURL.absoluteString, URL3.absoluteString: mirroredURL.absoluteString, diff --git a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift index 7435f8d5cc2..ea669b6cff5 100644 --- a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift +++ b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift @@ -26,7 +26,7 @@ import struct TSCUtility.Version class RegistryPackageContainerTests: XCTestCase { - func testToolsVersionCompatibleVersions() throws { + func testToolsVersionCompatibleVersions() async throws { let fs = InMemoryFileSystem() let packageIdentity = PackageIdentity.plain("org.foo") @@ -80,7 +80,7 @@ class RegistryPackageContainerTests: XCTestCase { "Content-Version": "1", "Content-Type": "text/x-swift" ], - body: "// swift-tools-version:\(toolsVersion)".data(using: .utf8) + body: Data("// swift-tools-version:\(toolsVersion)".utf8) ) )) } @@ -98,7 +98,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v4) let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, ["1.0.1"]) } @@ -106,7 +106,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v4_2) let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, ["1.0.2", "1.0.1"]) } @@ -114,13 +114,13 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_4) let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, ["1.0.3", "1.0.2", "1.0.1"]) } } - func testAlternateManifests() throws { + func testAlternateManifests() async throws { let fs = InMemoryFileSystem() let packageIdentity = PackageIdentity.plain("org.foo") @@ -145,7 +145,7 @@ class RegistryPackageContainerTests: XCTestCase { \(self.manifestLink(packageIdentity, .v5_5)), """ ], - body: "// swift-tools-version:\(ToolsVersion.v5_3)".data(using: .utf8) + body: Data("// swift-tools-version:\(ToolsVersion.v5_3)".utf8) ) )) } @@ -163,7 +163,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_2) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) XCTAssertEqual(try container.toolsVersion(for: packageVersion), .v5_3) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, []) @@ -172,7 +172,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_3) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) XCTAssertEqual(try container.toolsVersion(for: packageVersion), .v5_3) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, [packageVersion]) @@ -181,7 +181,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_4) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) XCTAssertEqual(try container.toolsVersion(for: packageVersion), .v5_4) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, [packageVersion]) @@ -190,7 +190,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_5) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) XCTAssertEqual(try container.toolsVersion(for: packageVersion), .v5_5) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, [packageVersion]) @@ -199,14 +199,14 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_6) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) XCTAssertEqual(try container.toolsVersion(for: packageVersion), .v5_5) let versions = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(versions, [packageVersion]) } } - func testLoadManifest() throws { + func testLoadManifest() async throws { let fs = InMemoryFileSystem() let packageIdentity = PackageIdentity.plain("org.foo") @@ -238,7 +238,7 @@ class RegistryPackageContainerTests: XCTestCase { self.manifestLink(packageIdentity, $0) }.joined(separator: ",\n") ], - body: "// swift-tools-version:\(requestedVersion)".data(using: .utf8) + body: Data("// swift-tools-version:\(requestedVersion)".utf8) ) )) } @@ -260,6 +260,7 @@ class RegistryPackageContainerTests: XCTestCase { packageLocation: String, packageVersion: (version: Version?, revision: String?)?, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, @@ -285,7 +286,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_3) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) as! RegistryPackageContainer + let container = try await provider.getContainer(for: ref) as! RegistryPackageContainer let manifest = try container.loadManifest(version: packageVersion) XCTAssertEqual(manifest.toolsVersion, .v5_3) } @@ -293,7 +294,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(v5_3_3) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) as! RegistryPackageContainer + let container = try await provider.getContainer(for: ref) as! RegistryPackageContainer let manifest = try container.loadManifest(version: packageVersion) XCTAssertEqual(manifest.toolsVersion, v5_3_3) } @@ -301,7 +302,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_4) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) as! RegistryPackageContainer + let container = try await provider.getContainer(for: ref) as! RegistryPackageContainer let manifest = try container.loadManifest(version: packageVersion) XCTAssertEqual(manifest.toolsVersion, .v5_4) } @@ -309,7 +310,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_5) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) as! RegistryPackageContainer + let container = try await provider.getContainer(for: ref) as! RegistryPackageContainer let manifest = try container.loadManifest(version: packageVersion) XCTAssertEqual(manifest.toolsVersion, .v5_5) } @@ -317,7 +318,7 @@ class RegistryPackageContainerTests: XCTestCase { do { let provider = try createProvider(.v5_6) // the version of the alternate let ref = PackageReference.registry(identity: packageIdentity) - let container = try provider.getContainer(for: ref) as! RegistryPackageContainer + let container = try await provider.getContainer(for: ref) as! RegistryPackageContainer let manifest = try container.loadManifest(version: packageVersion) XCTAssertEqual(manifest.toolsVersion, .v5_5) } @@ -399,7 +400,7 @@ class RegistryPackageContainerTests: XCTestCase { "Content-Version": "1", "Content-Type": "text/x-swift" ], - body: "// swift-tools-version:\(ToolsVersion.current)".data(using: .utf8) + body: Data("// swift-tools-version:\(ToolsVersion.current)".utf8) ) )) } @@ -419,7 +420,7 @@ class RegistryPackageContainerTests: XCTestCase { "Content-Version": "1", "Content-Type": "application/zip" ], - body: "".data(using: .utf8) + body: Data("".utf8) ) )) } @@ -485,15 +486,12 @@ class RegistryPackageContainerTests: XCTestCase { } extension PackageContainerProvider { - fileprivate func getContainer(for package: PackageReference, updateStrategy: ContainerUpdateStrategy = .always) throws -> PackageContainer { - try temp_await { - self.getContainer( - for: package, - updateStrategy: updateStrategy, - observabilityScope: ObservabilitySystem.NOOP, - on: .global(), - completion: $0 - ) - } + fileprivate func getContainer(for package: PackageReference, updateStrategy: ContainerUpdateStrategy = .always) async throws -> PackageContainer { + try await self.getContainer( + for: package, + updateStrategy: updateStrategy, + observabilityScope: ObservabilitySystem.NOOP, + on: .global() + ) } } diff --git a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift index 411f04c3645..199b1041bb9 100644 --- a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift +++ b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift @@ -132,7 +132,10 @@ private class MockRepositories: RepositoryProvider { } func open(repository: RepositorySpecifier, at path: AbsolutePath) throws -> Repository { - return self.repositories[repository.location]! + guard let repository = self.repositories[repository.location] else { + throw InternalError("unknown repository at \(repository.location)") + } + return repository } func createWorkingCopy(repository: RepositorySpecifier, sourcePath: AbsolutePath, at destinationPath: AbsolutePath, editable: Bool) throws -> WorkingCheckout { @@ -143,11 +146,11 @@ private class MockRepositories: RepositoryProvider { fatalError("unexpected API call") } - func isValidDirectory(_ directory: AbsolutePath) -> Bool { + func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { return true } - func isValidRefFormat(_ ref: String) -> Bool { + public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { return true } @@ -190,7 +193,7 @@ private let v2: Version = "2.0.0" private let v1Range: VersionSetSpecifier = .range("1.0.0" ..< "2.0.0") class SourceControlPackageContainerTests: XCTestCase { - func testVprefixVersions() throws { + func testVprefixVersions() async throws { let fs = InMemoryFileSystem() let repoPath = AbsolutePath.root @@ -227,12 +230,12 @@ class SourceControlPackageContainerTests: XCTestCase { ) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let v = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(v, ["2.0.3", "1.0.3", "1.0.2", "1.0.1", "1.0.0"]) } - func testVersions() throws { + func testVersions() async throws { let fs = InMemoryFileSystem() let repoPath = AbsolutePath.root @@ -284,7 +287,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { let provider = try createProvider(ToolsVersion(version: "4.0.0")) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let v = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(v, ["1.0.1"]) } @@ -292,7 +295,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { let provider = try createProvider(ToolsVersion(version: "4.2.0")) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) as! SourceControlPackageContainer + let container = try await provider.getContainer(for: ref) as! SourceControlPackageContainer XCTAssertTrue(container.validToolsVersionsCache.isEmpty) let v = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(container.validToolsVersionsCache["1.0.0"], false) @@ -305,7 +308,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { let provider = try createProvider(ToolsVersion(version: "3.0.0")) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let v = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(v, []) } @@ -314,7 +317,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { let provider = try createProvider(ToolsVersion(version: "4.0.0")) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) as! SourceControlPackageContainer + let container = try await provider.getContainer(for: ref) as! SourceControlPackageContainer let revision = try container.getRevision(forTag: "1.0.0") do { _ = try container.getDependencies(at: revision.identifier, productFilter: .nothing) @@ -325,7 +328,7 @@ class SourceControlPackageContainerTests: XCTestCase { } } - func testPreReleaseVersions() throws { + func testPreReleaseVersions() async throws { let fs = InMemoryFileSystem() let repoPath = AbsolutePath.root @@ -364,12 +367,12 @@ class SourceControlPackageContainerTests: XCTestCase { ) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let v = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(v, ["1.0.4-alpha", "1.0.2-dev.2", "1.0.2-dev", "1.0.1", "1.0.0", "1.0.0-beta.1", "1.0.0-alpha.1"]) } - func testSimultaneousVersions() throws { + func testSimultaneousVersions() async throws { let fs = InMemoryFileSystem() let repoPath = AbsolutePath.root @@ -412,7 +415,7 @@ class SourceControlPackageContainerTests: XCTestCase { customRepositoryManager: repositoryManager ) let ref = PackageReference.localSourceControl(identity: PackageIdentity(path: repoPath), path: repoPath) - let container = try provider.getContainer(for: ref) + let container = try await provider.getContainer(for: ref) let v = try container.toolsVersionsAppropriateVersionsDescending() XCTAssertEqual(v, ["2.0.1", "1.3.0", "1.2.0", "1.1.0", "1.0.4", "1.0.2", "1.0.1", "1.0.0"]) } @@ -446,7 +449,7 @@ class SourceControlPackageContainerTests: XCTestCase { ] let v5Constraints = try dependencies.map { PackageContainerConstraint( - package: $0.createPackageRef(), + package: $0.packageRef, requirement: try $0.toConstraintRequirement(), products: v5ProductMapping[$0.identity.description]! ) @@ -458,7 +461,7 @@ class SourceControlPackageContainerTests: XCTestCase { ] let v5_2Constraints = try dependencies.map { PackageContainerConstraint( - package: $0.createPackageRef(), + package: $0.packageRef, requirement: try $0.toConstraintRequirement(), products: v5_2ProductMapping[$0.identity.description]! ) @@ -552,8 +555,8 @@ class SourceControlPackageContainerTests: XCTestCase { } } - func testMissingBranchDiagnostics() throws { - try testWithTemporaryDirectory { tmpDir in + func testMissingBranchDiagnostics() async throws { + try await testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") try localFileSystem.createDirectory(packageDir) @@ -598,7 +601,7 @@ class SourceControlPackageContainerTests: XCTestCase { // Get a hold of the container for the test package. let packageRef = PackageReference.localSourceControl(identity: PackageIdentity(path: packageDir), path: packageDir) - let container = try containerProvider.getContainer(for: packageRef) as! SourceControlPackageContainer + let container = try await containerProvider.getContainer(for: packageRef) as! SourceControlPackageContainer // Simulate accessing a fictitious dependency on the `master` branch, and check that we get back the expected error. do { _ = try container.getDependencies(at: "master", productFilter: .everything) } @@ -620,8 +623,8 @@ class SourceControlPackageContainerTests: XCTestCase { } } - func testRepositoryContainerUpdateStrategy() throws { - try testWithTemporaryDirectory { temporaryDirectory in + func testRepositoryContainerUpdateStrategy() async throws { + try await testWithTemporaryDirectory { temporaryDirectory in let packageDirectory = temporaryDirectory.appending("MyPackage") let package = PackageReference.localSourceControl(identity: PackageIdentity(path: packageDirectory), path: packageDirectory) @@ -661,7 +664,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { repositoryManagerDelegate.reset() XCTAssertEqual(repositoryManagerDelegate.updated.count, 0) - _ = try containerProvider.getContainer( + _ = try await containerProvider.getContainer( for: package, updateStrategy: .never ) @@ -671,7 +674,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { repositoryManagerDelegate.reset() XCTAssertEqual(repositoryManagerDelegate.updated.count, 0) - _ = try containerProvider.getContainer( + _ = try await containerProvider.getContainer( for: package, updateStrategy: .always ) @@ -685,7 +688,7 @@ class SourceControlPackageContainerTests: XCTestCase { repositoryManagerDelegate.reset() XCTAssertEqual(repositoryManagerDelegate.updated.count, 0) - _ = try containerProvider.getContainer( + _ = try await containerProvider.getContainer( for: package, updateStrategy: .ifNeeded(revision: revision.identifier) ) @@ -695,7 +698,7 @@ class SourceControlPackageContainerTests: XCTestCase { do { repositoryManagerDelegate.reset() XCTAssertEqual(repositoryManagerDelegate.updated.count, 0) - _ = try containerProvider.getContainer( + _ = try await containerProvider.getContainer( for: package, updateStrategy: .ifNeeded(revision: UUID().uuidString) ) @@ -708,8 +711,8 @@ class SourceControlPackageContainerTests: XCTestCase { // RepositoryPackageContainer used to erroneously cache dependencies based only on version, // storing the result of the first product filter and then continually returning it for other filters too. // This lead to corrupt graph states. - func testRepositoryPackageContainerCache() throws { - try testWithTemporaryDirectory { temporaryDirectory in + func testRepositoryPackageContainerCache() async throws { + try await testWithTemporaryDirectory { temporaryDirectory in let packageDirectory = temporaryDirectory.appending("Package") try localFileSystem.createDirectory(packageDirectory) initGitRepo(packageDirectory) @@ -762,7 +765,7 @@ class SourceControlPackageContainerTests: XCTestCase { ) let packageReference = PackageReference.localSourceControl(identity: PackageIdentity(path: packageDirectory), path: packageDirectory) - let container = try containerProvider.getContainer(for: packageReference) + let container = try await containerProvider.getContainer(for: packageReference) let forNothing = try container.getDependencies(at: version, productFilter: .specific([])) let forProduct = try container.getDependencies(at: version, productFilter: .specific(["Product"])) @@ -778,8 +781,8 @@ extension PackageContainerProvider { fileprivate func getContainer( for package: PackageReference, updateStrategy: ContainerUpdateStrategy = .always - ) throws -> PackageContainer { - try temp_await { + ) async throws -> PackageContainer { + try await safe_async { self.getContainer( for: package, updateStrategy: updateStrategy, diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 4aad9557214..3ba2b38690a 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -26,7 +26,6 @@ import XCTest import struct TSCBasic.ByteString import class TSCBasic.InMemoryFileSystem -import enum TSCUtility.Diagnostics import struct TSCUtility.Version final class WorkspaceTests: XCTestCase { @@ -220,10 +219,10 @@ final class WorkspaceTests: XCTestCase { } } - func testManifestParseError() throws { + func testManifestParseError() async throws { let observability = ObservabilitySystem.makeForTesting() - try testWithTemporaryDirectory { path in + try await testWithTemporaryDirectory { path in let pkgDir = path.appending("MyPkg") try localFileSystem.createDirectory(pkgDir) try localFileSystem.writeFileContents( @@ -244,13 +243,10 @@ final class WorkspaceTests: XCTestCase { delegate: MockWorkspaceDelegate() ) let rootInput = PackageGraphRootInput(packages: [pkgDir], dependencies: []) - let rootManifests = try temp_await { - workspace.loadRootManifests( - packages: rootInput.packages, - observabilityScope: observability.topScope, - completion: $0 - ) - } + let rootManifests = try await workspace.loadRootManifests( + packages: rootInput.packages, + observabilityScope: observability.topScope + ) XCTAssert(rootManifests.count == 0, "\(rootManifests)") @@ -1882,7 +1878,7 @@ final class WorkspaceTests: XCTestCase { // Check that we can compute missing dependencies. try workspace.loadDependencyManifests(roots: ["Root1", "Root2"]) { manifests, diagnostics in XCTAssertEqual( - try! manifests.missingPackages().map(\.locationString).sorted(), + try! manifests.missingPackages.map(\.locationString).sorted(), [ sandbox.appending(components: "pkgs", "Bar").pathString, sandbox.appending(components: "pkgs", "Foo").pathString, @@ -1902,7 +1898,7 @@ final class WorkspaceTests: XCTestCase { // Check that we compute the correct missing dependencies. try workspace.loadDependencyManifests(roots: ["Root1", "Root2"]) { manifests, diagnostics in XCTAssertEqual( - try! manifests.missingPackages().map(\.locationString).sorted(), + try! manifests.missingPackages.map(\.locationString).sorted(), [sandbox.appending(components: "pkgs", "Bar").pathString] ) XCTAssertNoDiagnostics(diagnostics) @@ -1918,7 +1914,7 @@ final class WorkspaceTests: XCTestCase { // Check that we compute the correct missing dependencies. try workspace.loadDependencyManifests(roots: ["Root1", "Root2"]) { manifests, diagnostics in - XCTAssertEqual(try! manifests.missingPackages().map(\.locationString).sorted(), []) + XCTAssertEqual(try! manifests.missingPackages.map(\.locationString).sorted(), []) XCTAssertNoDiagnostics(diagnostics) } } @@ -1985,7 +1981,7 @@ final class WorkspaceTests: XCTestCase { try workspace.loadDependencyManifests(roots: ["Root1"]) { manifests, diagnostics in // Ensure that the order of the manifests is stable. XCTAssertEqual( - manifests.allDependencyManifests().map(\.value.manifest.displayName), + manifests.allDependencyManifests.map(\.value.manifest.displayName), ["Foo", "Baz", "Bam", "Bar"] ) XCTAssertNoDiagnostics(diagnostics) @@ -2359,7 +2355,7 @@ final class WorkspaceTests: XCTestCase { XCTAssertTrue(fs.exists(fooPath)) try workspace.loadDependencyManifests(roots: ["Root"]) { manifests, diagnostics in - let editedPackages = manifests.editedPackagesConstraints() + let editedPackages = manifests.editedPackagesConstraints XCTAssertEqual(editedPackages.map(\.package.locationString), [fooPath.pathString]) XCTAssertNoDiagnostics(diagnostics) } @@ -4279,12 +4275,12 @@ final class WorkspaceTests: XCTestCase { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let mirrors = DependencyMirrors() - mirrors.set( + let mirrors = try DependencyMirrors() + try mirrors.set( mirror: sandbox.appending(components: "pkgs", "BarMirror").pathString, for: sandbox.appending(components: "pkgs", "Bar").pathString ) - mirrors.set( + try mirrors.set( mirror: sandbox.appending(components: "pkgs", "BazMirror").pathString, for: sandbox.appending(components: "pkgs", "Baz").pathString ) @@ -4371,12 +4367,12 @@ final class WorkspaceTests: XCTestCase { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let mirrors = DependencyMirrors() - mirrors.set( + let mirrors = try DependencyMirrors() + try mirrors.set( mirror: sandbox.appending(components: "pkgs", "BarMirror").pathString, for: sandbox.appending(components: "pkgs", "Bar").pathString ) - mirrors.set( + try mirrors.set( mirror: sandbox.appending(components: "pkgs", "BarMirror").pathString, for: sandbox.appending(components: "pkgs", "Baz").pathString ) @@ -4474,9 +4470,9 @@ final class WorkspaceTests: XCTestCase { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let mirrors = DependencyMirrors() - mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "https://scm.com/org/bar") - mirrors.set(mirror: "https://scm.com/org/baz-mirror", for: "https://scm.com/org/baz") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "https://scm.com/org/bar") + try mirrors.set(mirror: "https://scm.com/org/baz-mirror", for: "https://scm.com/org/baz") let workspace = try MockWorkspace( sandbox: sandbox, @@ -4563,9 +4559,9 @@ final class WorkspaceTests: XCTestCase { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let mirrors = DependencyMirrors() - mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "https://scm.com/org/bar") - mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "https://scm.com/org/baz") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "https://scm.com/org/bar") + try mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "https://scm.com/org/baz") let workspace = try MockWorkspace( sandbox: sandbox, @@ -4668,8 +4664,8 @@ final class WorkspaceTests: XCTestCase { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let mirrors = DependencyMirrors() - mirrors.set(mirror: "org.bar-mirror", for: "https://scm.com/org/bar") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "org.bar-mirror", for: "https://scm.com/org/bar") let workspace = try MockWorkspace( sandbox: sandbox, @@ -4738,8 +4734,8 @@ final class WorkspaceTests: XCTestCase { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let mirrors = DependencyMirrors() - mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "org.bar") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "org.bar") let workspace = try MockWorkspace( sandbox: sandbox, @@ -5018,7 +5014,7 @@ final class WorkspaceTests: XCTestCase { do { let ws = try workspace.getOrCreateWorkspace() let pinsStore = try ws.pinsStore.load() - let fooPin = pinsStore.pins.values.first(where: { $0.packageRef.identity.description == "foo" })! + let fooPin = try XCTUnwrap(pinsStore.pins.values.first(where: { $0.packageRef.identity.description == "foo" })) let fooRepo = workspace.repositoryProvider .specifierMap[RepositorySpecifier(path: try AbsolutePath( @@ -5226,9 +5222,48 @@ final class WorkspaceTests: XCTestCase { } } + func testForceResolveToResolvedVersionsLocalPackageInAdditionalDependencies() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget(name: "Root"), + ], + products: [], + dependencies: [] + ), + ], + packages: [ + MockPackage( + name: "Foo", + targets: [ + MockTarget(name: "Foo"), + ], + products: [ + MockProduct(name: "Foo", targets: ["Foo"]), + ], + versions: [nil] + ), + ] + ) + + try workspace.checkPackageGraph(roots: ["Root"], dependencies: [.fileSystem(path: workspace.packagesDir.appending(component: "Foo"))], forceResolvedVersions: true) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + } + workspace.checkManagedDependencies { result in + result.check(dependency: "foo", at: .local) + } + } + // This verifies that the simplest possible loading APIs are available for package clients. - func testSimpleAPI() throws { - try testWithTemporaryDirectory { path in + func testSimpleAPI() async throws { + try await testWithTemporaryDirectory { path in // Create a temporary package as a test case. let packagePath = path.appending("MyPkg") let initPackage = try InitPackage( @@ -5247,23 +5282,17 @@ final class WorkspaceTests: XCTestCase { ) // From here the API should be simple and straightforward: - let manifest = try temp_await { - workspace.loadRootManifest( - at: packagePath, - observabilityScope: observability.topScope, - completion: $0 - ) - } + let manifest = try await workspace.loadRootManifest( + at: packagePath, + observabilityScope: observability.topScope + ) XCTAssertFalse(observability.hasWarningDiagnostics, observability.diagnostics.description) XCTAssertFalse(observability.hasErrorDiagnostics, observability.diagnostics.description) - let package = try temp_await { - workspace.loadRootPackage( - at: packagePath, - observabilityScope: observability.topScope, - completion: $0 - ) - } + let package = try await workspace.loadRootPackage( + at: packagePath, + observabilityScope: observability.topScope + ) XCTAssertFalse(observability.hasWarningDiagnostics, observability.diagnostics.description) XCTAssertFalse(observability.hasErrorDiagnostics, observability.diagnostics.description) @@ -5278,14 +5307,11 @@ final class WorkspaceTests: XCTestCase { XCTAssertEqual(package.identity, .plain(manifest.displayName)) XCTAssert(graph.reachableProducts.contains(where: { $0.name == "MyPkg" })) - let reloadedPackage = try temp_await { - workspace.loadPackage( - with: package.identity, - packageGraph: graph, - observabilityScope: observability.topScope, - completion: $0 - ) - } + let reloadedPackage = try await workspace.loadPackage( + with: package.identity, + packageGraph: graph, + observabilityScope: observability.topScope + ) XCTAssertEqual(package.identity, reloadedPackage.identity) XCTAssertEqual(package.manifest.displayName, reloadedPackage.manifest.displayName) @@ -6842,7 +6868,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false // disable cache ) ) @@ -7065,7 +7092,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ) ) @@ -7300,7 +7328,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ) ) @@ -7780,6 +7809,7 @@ final class WorkspaceTests: XCTestCase { authorizationProvider: .none, hostToolchain: UserToolchain(swiftSDK: .hostSwiftSDK()), checksumAlgorithm: checksumAlgorithm, + cachePath: .none, customHTTPClient: .none, customArchiver: .none, delegate: .none @@ -7989,7 +8019,6 @@ final class WorkspaceTests: XCTestCase { func testArtifactDownloadAddsAcceptHeader() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() - let downloads = ThreadSafeKeyValueStore() var acceptHeaders: [String] = [] // returns a dummy zipfile for the requested artifact @@ -8014,7 +8043,6 @@ final class WorkspaceTests: XCTestCase { atomically: true ) - downloads[request.url] = destination completion(.success(.okay())) } catch { completion(.failure(error)) @@ -8068,6 +8096,207 @@ final class WorkspaceTests: XCTestCase { } } + func testDownloadedArtifactNoCache() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + var downloads = 0 + + // returns a dummy zipfile for the requested artifact + let httpClient = LegacyHTTPClient(handler: { request, _, completion in + do { + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + let contents: [UInt8] + switch request.url.lastPathComponent { + case "a1.zip": + contents = [0xA1] + default: + throw StringError("unexpected url \(request.url)") + } + + try fileSystem.writeFileContents( + destination, + bytes: ByteString(contents), + atomically: true + ) + + downloads += 1 + completion(.success(.okay())) + } catch { + completion(.failure(error)) + } + }) + + // create a dummy xcframework directory from the request archive + let archiver = MockArchiver(handler: { archiver, archivePath, destinationPath, completion in + do { + switch archivePath.basename { + case "a1.zip": + try createDummyXCFramework(fileSystem: fs, path: destinationPath, name: "A1") + default: + throw StringError("unexpected archivePath \(archivePath)") + } + archiver.extractions + .append(MockArchiver.Extraction(archivePath: archivePath, destinationPath: destinationPath)) + completion(.success(())) + } catch { + completion(.failure(error)) + } + }) + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget( + name: "A1", + type: .binary, + url: "https://a.com/a1.zip", + checksum: "a1" + ), + ] + ), + ], + binaryArtifactsManager: .init( + httpClient: httpClient, + archiver: archiver, + useCache: false + ) + ) + + // should not come from cache + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 1) + } + + // state is there, should not come from local cache + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 1) + } + + // reseting state, should not come from global cache + try workspace.resetState() + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 2) + } + } + + func testDownloadedArtifactCache() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + var downloads = 0 + + // returns a dummy zipfile for the requested artifact + let httpClient = LegacyHTTPClient(handler: { request, _, completion in + do { + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + let contents: [UInt8] + switch request.url.lastPathComponent { + case "a1.zip": + contents = [0xA1] + default: + throw StringError("unexpected url \(request.url)") + } + + try fileSystem.writeFileContents( + destination, + bytes: ByteString(contents), + atomically: true + ) + + downloads += 1 + completion(.success(.okay())) + } catch { + completion(.failure(error)) + } + }) + + // create a dummy xcframework directory from the request archive + let archiver = MockArchiver(handler: { archiver, archivePath, destinationPath, completion in + do { + switch archivePath.basename { + case "a1.zip": + try createDummyXCFramework(fileSystem: fs, path: destinationPath, name: "A1") + default: + throw StringError("unexpected archivePath \(archivePath)") + } + archiver.extractions + .append(MockArchiver.Extraction(archivePath: archivePath, destinationPath: destinationPath)) + completion(.success(())) + } catch { + completion(.failure(error)) + } + }) + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget( + name: "A1", + type: .binary, + url: "https://a.com/a1.zip", + checksum: "a1" + ), + ] + ), + ], + binaryArtifactsManager: .init( + httpClient: httpClient, + archiver: archiver, + useCache: true + ) + ) + + // should not come from cache + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 1) + } + + // state is there, should not come from local cache + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 1) + } + + // reseting state, should come from global cache + try workspace.resetState() + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 1) + } + + // delete global cache, should download again + try workspace.resetState() + try fs.removeFileTree(fs.swiftPMCacheDirectory) + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 2) + } + + // reseting state, should come from global cache again + try workspace.resetState() + try workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + XCTAssertEqual(downloads, 2) + } + } + func testDownloadedArtifactTransitive() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8206,7 +8435,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ) ) @@ -8325,7 +8555,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ) ) @@ -8580,7 +8811,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ) ) @@ -8720,7 +8952,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ) ) @@ -8766,7 +8999,7 @@ final class WorkspaceTests: XCTestCase { } } - func testLoadRootPackageWithBinaryDependencies() throws { + func testLoadRootPackageWithBinaryDependencies() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8803,13 +9036,10 @@ final class WorkspaceTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() let wks = try workspace.getOrCreateWorkspace() - XCTAssertNoThrow(try temp_await { - wks.loadRootPackage( - at: workspace.rootsDir.appending("Root"), - observabilityScope: observability.topScope, - completion: $0 - ) - }) + _ = try await wks.loadRootPackage( + at: workspace.rootsDir.appending("Root"), + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) } @@ -8979,7 +9209,8 @@ final class WorkspaceTests: XCTestCase { ], binaryArtifactsManager: .init( httpClient: httpClient, - archiver: archiver + archiver: archiver, + useCache: false ), checksumAlgorithm: checksumAlgorithm ) @@ -11027,6 +11258,719 @@ final class WorkspaceTests: XCTestCase { } } + func testDeterministicURLPreference() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "BarProduct", package: "bar"), + .product(name: "BazProduct", package: "baz"), + .product(name: "QuxProduct", package: "qux"), + ]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/bar.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/baz.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/qux.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + ] + ), + ], + packages: [ + MockPackage( + name: "BarPackage", + url: "https://github.com/org/bar.git", + targets: [ + MockTarget(name: "BarTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BarProduct", targets: ["BarTarget"]), + ], + dependencies: [ + .sourceControl( + url: "http://github.com/org/foo", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "FooPackage", + url: "http://github.com/org/foo", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "BazPackage", + url: "https://github.com/org/baz.git", + targets: [ + MockTarget(name: "BazTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BazProduct", targets: ["BazTarget"]), + ], + dependencies: [ + .sourceControl( + url: "git@github.com:org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "FooPackage", + url: "git@github.com:org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "QuxPackage", + url: "https://github.com/org/qux.git", + targets: [ + MockTarget(name: "QuxTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "QuxProduct", targets: ["QuxTarget"]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "FooPackage", + url: "https://github.com/org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"] + ), + ] + ) + + try workspace.checkPackageGraph(roots: ["Root"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "BarPackage", "BazPackage", "FooPackage", "Root", "QuxPackage") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "https://github.com/org/foo.git") + + } + testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in + result.checkUnordered( + diagnostic: "similar variants of package 'foo' found at 'git@github.com:org/foo.git' and 'https://github.com/org/foo.git'. using preferred variant 'https://github.com/org/foo.git'", + severity: .debug + ) + result.checkUnordered( + diagnostic: "similar variants of package 'foo' found at 'http://github.com/org/foo' and 'https://github.com/org/foo.git'. using preferred variant 'https://github.com/org/foo.git'", + severity: .debug + ) + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "https://github.com/org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "https://github.com/org/foo.git") + } + } + + func testDeterministicURLPreferenceWithRoot() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + .product(name: "BarProduct", package: "bar"), + .product(name: "BazProduct", package: "baz"), + ]), + ], + dependencies: [ + .sourceControl( + url: "git@github.com:org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/bar.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/baz.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + ] + ), + ], + packages: [ + MockPackage( + name: "FooPackage", + url: "git@github.com:org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "BarPackage", + url: "https://github.com/org/bar.git", + targets: [ + MockTarget(name: "BarTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BarProduct", targets: ["BarTarget"]), + ], + dependencies: [ + .sourceControl( + url: "http://github.com/org/foo", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "FooPackage", + url: "http://github.com/org/foo", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "BazPackage", + url: "https://github.com/org/baz.git", + targets: [ + MockTarget(name: "BazTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BazProduct", targets: ["BazTarget"]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "FooPackage", + url: "https://github.com/org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"] + ), + ] + ) + + try workspace.checkPackageGraph(roots: ["Root"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "BarPackage", "BazPackage", "FooPackage", "Root") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "git@github.com:org/foo.git") + + } + testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in + result.checkUnordered( + diagnostic: "similar variants of package 'foo' found at 'https://github.com/org/foo.git' and 'git@github.com:org/foo.git'. using preferred root variant 'git@github.com:org/foo.git'", + severity: .debug + ) + result.checkUnordered( + diagnostic: "similar variants of package 'foo' found at 'http://github.com/org/foo' and 'git@github.com:org/foo.git'. using preferred root variant 'git@github.com:org/foo.git'", + severity: .debug + ) + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + } + + func testCanonicalURLWithPreviousManagedState() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "BarProduct", package: "bar"), + ]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/bar.git", + requirement: .upToNextMajor(from: "1.0.0") + ) + ] + ), + MockPackage( + name: "Root2", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "BarProduct", package: "bar"), + .product(name: "BazProduct", package: "baz"), + ]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/bar.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/baz.git", + requirement: .upToNextMajor(from: "1.0.0") + ) + ] + ) + ], + packages: [ + MockPackage( + name: "FooPackage", + url: "https://github.com/org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0", "1.1.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + MockPackage( + name: "FooPackage", + url: "git@github.com:org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0", "1.1.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + MockPackage( + name: "BarPackage", + url: "https://github.com/org/bar.git", + targets: [ + MockTarget(name: "BarTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BarProduct", targets: ["BarTarget"]), + ], + dependencies: [ + .sourceControl( + url: "git@github.com:org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0", "1.1.0", "1.2.0"], + revisionProvider: { _ in "bar" } // we need this to be consistent for fingerprints check to work + ), + MockPackage( + name: "BazPackage", + url: "https://github.com/org/baz.git", + targets: [ + MockTarget(name: "BazTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BazProduct", targets: ["BazTarget"]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/foo", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0", "1.1.0", "1.2.0"], + revisionProvider: { _ in "baz" } // we need this to be consistent for fingerprints check to work + ) + ] + ) + + // resolve to set previous state + + try workspace.checkPackageGraph(roots: ["Root"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "BarPackage", "FooPackage", "Root") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "git@github.com:org/foo.git") + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + + // update to a different url via transitive dependencies + + try workspace.checkPackageGraph(roots: ["Root2"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "BarPackage", "BazPackage", "FooPackage", "Root2") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "git@github.com:org/foo.git") + } + testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in + result.checkUnordered( + diagnostic: "required dependency 'foo' from 'https://github.com/org/foo' was not found in managed dependencies, using alternative location 'git@github.com:org/foo.git' instead", + severity: .info + ) + } + } + + workspace.checkManagedDependencies { result in + // we expect the managed dependency to carry the old state + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "https://github.com/org/foo") + } + } + + func testCanonicalURLChanges() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "FooProduct", package: "foo") + ]) + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ) + ] + ), + MockPackage( + name: "Root2", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "FooProduct", package: "foo") + ]) + ], + dependencies: [ + .sourceControl( + url: "git@github.com:org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ) + ] + ) + ], + packages: [ + MockPackage( + name: "FooPackage", + url: "https://github.com/org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + MockPackage( + name: "FooPackage", + url: "git@github.com:org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + ] + ) + + // check usage of canonical URL + + try workspace.checkPackageGraph(roots: ["Root"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "FooPackage", "Root") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "https://github.com/org/foo.git") + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "https://github.com/org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "https://github.com/org/foo.git") + } + + // update URL to one with different scheme + + try workspace.checkPackageGraph(roots: ["Root2"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "FooPackage", "Root2") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "git@github.com:org/foo.git") + + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + } + + func testCanonicalURLChangesWithTransitiveDependencies() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Root", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + .product(name: "BarProduct", package: "bar"), + ]), + ], + dependencies: [ + .sourceControl( + url: "https://github.com/org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/bar.git", + requirement: .upToNextMajor(from: "1.0.0") + ) + ] + ), + MockPackage( + name: "Root2", + targets: [ + MockTarget(name: "RootTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + .product(name: "BarProduct", package: "bar"), + ]), + ], + dependencies: [ + .sourceControl( + url: "git@github.com:org/foo.git", + requirement: .upToNextMajor(from: "1.0.0") + ), + .sourceControl( + url: "https://github.com/org/bar.git", + requirement: .upToNextMajor(from: "1.0.0") + ) + ] + ) + ], + packages: [ + MockPackage( + name: "FooPackage", + url: "https://github.com/org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + MockPackage( + name: "BarPackage", + url: "https://github.com/org/bar.git", + targets: [ + MockTarget(name: "BarTarget", dependencies: [ + .product(name: "FooProduct", package: "foo"), + ]), + ], + products: [ + MockProduct(name: "BarProduct", targets: ["BarTarget"]), + ], + dependencies: [ + .sourceControl( + url: "http://github.com/org/foo", + requirement: .upToNextMajor(from: "1.0.0") + ), + ], + versions: ["1.0.0"] + ), + MockPackage( + name: "FooPackage", + url: "http://github.com/org/foo", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + MockPackage( + name: "FooPackage", + url: "git@github.com:org/foo.git", + targets: [ + MockTarget(name: "FooTarget"), + ], + products: [ + MockProduct(name: "FooProduct", targets: ["FooTarget"]), + ], + versions: ["1.0.0"], + revisionProvider: { _ in "foo" } // we need this to be consistent for fingerprints check to work + ), + ] + ) + + // check usage of canonical URL + + try workspace.checkPackageGraph(roots: ["Root"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "BarPackage", "FooPackage", "Root") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "https://github.com/org/foo.git") + + } + testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in + result.checkUnordered( + diagnostic: "similar variants of package 'foo' found at 'http://github.com/org/foo' and 'https://github.com/org/foo.git'. using preferred root variant 'https://github.com/org/foo.git'", + severity: .debug + ) + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "https://github.com/org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "https://github.com/org/foo.git") + } + + // update URL to one with different scheme + + try workspace.checkPackageGraph(roots: ["Root2"]) { graph, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "BarPackage", "FooPackage", "Root2") + let package = result.find(package: "foo") + XCTAssertEqual(package?.manifest.packageLocation, "git@github.com:org/foo.git") + + } + testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in + result.checkUnordered( + diagnostic: "similar variants of package 'foo' found at 'http://github.com/org/foo' and 'git@github.com:org/foo.git'. using preferred root variant 'git@github.com:org/foo.git'", + severity: .debug + ) + } + } + + workspace.checkManagedDependencies { result in + XCTAssertEqual(result.managedDependencies["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + + workspace.checkResolved { result in + XCTAssertEqual(result.store.pins["foo"]?.packageRef.locationString, "git@github.com:org/foo.git") + } + } + func testCycleRoot() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11140,7 +12084,11 @@ final class WorkspaceTests: XCTestCase { severity: .error ) result.check( - diagnostic: "exhausted attempts to resolve the dependencies graph, with 'bar remoteSourceControl http://scm.com/org/bar', 'foo remoteSourceControl http://scm.com/org/foo' unresolved.", + diagnostic: """ + exhausted attempts to resolve the dependencies graph, with the following dependencies unresolved: + * 'bar' from http://scm.com/org/bar + * 'foo' from http://scm.com/org/foo + """, severity: .error ) } @@ -11273,6 +12221,7 @@ final class WorkspaceTests: XCTestCase { packageLocation: String, packageVersion: (version: Version?, revision: String?)?, identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, @@ -14232,8 +15181,8 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirroredSignedCorrectly() throws { - let mirrors = DependencyMirrors() - mirrors.set(mirror: "ecorp.bar", for: "org.bar") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "ecorp.bar", for: "org.bar") let actualMetadata = RegistryReleaseMetadata.createWithSigningEntity(.recognized( type: "adp", @@ -14256,8 +15205,8 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirrorSignedIncorrectly() throws { - let mirrors = DependencyMirrors() - mirrors.set(mirror: "ecorp.bar", for: "org.bar") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "ecorp.bar", for: "org.bar") let actualMetadata = RegistryReleaseMetadata.createWithSigningEntity(.recognized( type: "adp", @@ -14288,8 +15237,8 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirroredUnsigned() throws { - let mirrors = DependencyMirrors() - mirrors.set(mirror: "ecorp.bar", for: "org.bar") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "ecorp.bar", for: "org.bar") let expectedSigningEntity: RegistryReleaseMetadata.SigningEntity = .recognized( type: "adp", @@ -14313,8 +15262,8 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirroredToSCM() throws { - let mirrors = DependencyMirrors() - mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "org.bar") + let mirrors = try DependencyMirrors() + try mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "org.bar") let expectedSigningEntity: RegistryReleaseMetadata.SigningEntity = .recognized( type: "adp", @@ -14423,7 +15372,7 @@ final class WorkspaceTests: XCTestCase { "Content-Version": "1", "Content-Type": "text/x-swift", ], - body: "// swift-tools-version:\(ToolsVersion.current)".data(using: .utf8) + body: Data("// swift-tools-version:\(ToolsVersion.current)".utf8) ) )) } @@ -14445,7 +15394,7 @@ final class WorkspaceTests: XCTestCase { "Content-Version": "1", "Content-Type": "application/zip", ], - body: "".data(using: .utf8) + body: Data("".utf8) ) )) } diff --git a/Tests/XCBuildSupportTests/PIFBuilderTests.swift b/Tests/XCBuildSupportTests/PIFBuilderTests.swift index 3e6b0ce5667..dcabed69073 100644 --- a/Tests/XCBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/XCBuildSupportTests/PIFBuilderTests.swift @@ -189,7 +189,7 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.ENTITLEMENTS_REQUIRED], "NO") XCTAssertEqual(settings[.GCC_OPTIMIZATION_LEVEL], "0") XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "SWIFT_PACKAGE", "DEBUG=1"]) - XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], "11.0") + XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET, for: .macCatalyst], "13.0") XCTAssertEqual(settings[.KEEP_PRIVATE_EXTERNS], "NO") XCTAssertEqual(settings[.MACOSX_DEPLOYMENT_TARGET], "10.13") @@ -204,7 +204,7 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Onone") - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "11.0") + XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.USE_HEADERMAP], "NO") XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], "4.0") XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], "1.0") @@ -236,7 +236,7 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.ENTITLEMENTS_REQUIRED], "NO") XCTAssertEqual(settings[.GCC_OPTIMIZATION_LEVEL], "s") XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "SWIFT_PACKAGE"]) - XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], "11.0") + XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET, for: .macCatalyst], "13.0") XCTAssertEqual(settings[.KEEP_PRIVATE_EXTERNS], "NO") XCTAssertEqual(settings[.MACOSX_DEPLOYMENT_TARGET], "10.13") @@ -250,7 +250,7 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Owholemodule") - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "11.0") + XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.USE_HEADERMAP], "NO") XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], "4.0") XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], "1.0") @@ -308,7 +308,7 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Onone") - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "11.0") + XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.USE_HEADERMAP], "NO") XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], "6.0") XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], "1.0") @@ -354,7 +354,7 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Owholemodule") - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "11.0") + XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.USE_HEADERMAP], "NO") XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], "6.0") XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], "1.0") @@ -2626,7 +2626,8 @@ extension PIFBuilderParameters { enableTestability: false, shouldCreateDylibForDynamicProducts: shouldCreateDylibForDynamicProducts, toolchainLibDir: "/toolchain/lib", - pkgConfigDirectories: ["/pkg-config"] + pkgConfigDirectories: ["/pkg-config"], + sdkRootPath: "/some.sdk" ) } } diff --git a/Tests/XCBuildSupportTests/PIFTests.swift b/Tests/XCBuildSupportTests/PIFTests.swift index f43385fe89e..ec9abb37eaa 100644 --- a/Tests/XCBuildSupportTests/PIFTests.swift +++ b/Tests/XCBuildSupportTests/PIFTests.swift @@ -231,8 +231,8 @@ class PIFTests: XCTestCase { let originalPIF = try encoder.encode(workspace) let decodedPIF = try encoder.encode(decodedWorkspace) - let originalString = String(data: originalPIF, encoding: .utf8)! - let decodedString = String(data: decodedPIF, encoding: .utf8)! + let originalString = String(decoding: originalPIF, as: UTF8.self) + let decodedString = String(decoding: decodedPIF, as: UTF8.self) XCTAssertEqual(originalString, decodedString) #endif diff --git a/Utilities/InstalledSwiftPMConfiguration/Package.swift b/Utilities/InstalledSwiftPMConfiguration/Package.swift new file mode 100644 index 00000000000..4b230cc0737 --- /dev/null +++ b/Utilities/InstalledSwiftPMConfiguration/Package.swift @@ -0,0 +1,12 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "InstalledSwiftPMConfiguration", + targets: [ + .executableTarget( + name: "InstalledSwiftPMConfiguration" + ), + ] +) diff --git a/Utilities/InstalledSwiftPMConfiguration/Sources/InstalledSwiftPMConfiguration.swift b/Utilities/InstalledSwiftPMConfiguration/Sources/InstalledSwiftPMConfiguration.swift new file mode 120000 index 00000000000..6c398435dea --- /dev/null +++ b/Utilities/InstalledSwiftPMConfiguration/Sources/InstalledSwiftPMConfiguration.swift @@ -0,0 +1 @@ +../../../Sources/PackageModel/InstalledSwiftPMConfiguration.swift \ No newline at end of file diff --git a/Utilities/InstalledSwiftPMConfiguration/Sources/exec.swift b/Utilities/InstalledSwiftPMConfiguration/Sources/exec.swift new file mode 100644 index 00000000000..44a912f5085 --- /dev/null +++ b/Utilities/InstalledSwiftPMConfiguration/Sources/exec.swift @@ -0,0 +1,10 @@ +import Foundation + +@main +struct Exec { + static func main() throws { + let config = InstalledSwiftPMConfiguration(version: 1, swiftSyntaxVersionForMacroTemplate: .init(major: 509, minor: 0, patch: 0)) + let data = try JSONEncoder().encode(config) + try data.write(to: URL(fileURLWithPath: "config.json")) + } +} diff --git a/Utilities/bootstrap b/Utilities/bootstrap index e5bb4cfb6fb..5d8ab14b85b 100755 --- a/Utilities/bootstrap +++ b/Utilities/bootstrap @@ -395,6 +395,8 @@ def install(args): # Install swiftpm content in all of the passed prefixes. for prefix in args.install_prefixes: install_swiftpm(prefix, args) + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json") + install_file(args, config_path, os.path.join(os.path.join(prefix, "share"), "pm")) # Install libSwiftPM if an install directory was provided. if args.libswiftpm_install_dir: @@ -461,9 +463,11 @@ def install_dylib(args, library_name, install_dir, module_names): # Helper function that installs a single built artifact to a particular directory. The source may be either a file or a directory. def install_binary(args, binary, destination, destination_is_directory=True, ignored_patterns=[]): src = os.path.join(args.bin_dir, binary) + install_file(args, src, destination, destination_is_directory=destination_is_directory, ignored_patterns=ignored_patterns) +def install_file(args, src, destination, destination_is_directory=True, ignored_patterns=[]): if destination_is_directory: - dest = os.path.join(destination, binary) + dest = os.path.join(destination, os.path.basename(src)) mkdir_p(os.path.dirname(dest)) else: dest = destination @@ -765,6 +769,9 @@ def get_swiftpm_flags(args): "--configuration", "release", ]) + if not '-macosx' in args.build_target and args.command == 'install': + build_flags.append("--disable-local-rpath") + if args.verbose: build_flags.append("--verbose") @@ -805,8 +812,9 @@ def get_swiftpm_flags(args): for modifier in ["-Xswiftc", "-Xbuild-tools-swiftc"]: build_flags.extend([modifier, "-module-cache-path", modifier, local_module_cache_path]) + # Disabled, enable this again when it works # Enforce explicit target dependencies - build_flags.extend(["--explicit-target-dependency-import-check", "error"]) + # build_flags.extend(["--explicit-target-dependency-import-check", "error"]) return build_flags diff --git a/Utilities/build_ubuntu_cross_compilation_toolchain b/Utilities/build_ubuntu_cross_compilation_toolchain deleted file mode 100755 index cd96e9d8211..00000000000 --- a/Utilities/build_ubuntu_cross_compilation_toolchain +++ /dev/null @@ -1,272 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the Swift open source project -## -## Copyright (c) 2014-2022 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 -## -##===----------------------------------------------------------------------===## - -set -eu - -PATH="/bin:/usr/bin:$(brew --prefix)/bin" -export PATH -VERSION=${VERSION:-5.7-RELEASE} -if [[ -z "${VERSION##*RELEASE*}" ]]; then - branch=swift-${VERSION%%RELEASE}release -elif [[ -z "${VERSION##DEVELOPMENT-SNAPSHOT*}" ]]; then - branch=development -else - branch=swift-${VERSION%%DEV*}branch -fi - -function usage() { - echo >&2 "Usage: $0 TMP-DIR SWIFT-FOR-MACOS.pkg SWIFT-FOR-LINUX.tar.gz" - echo >&2 - echo >&2 "Example: $0 /tmp/ ~/Downloads/swift-${VERSION}-osx.pkg ~/Downloads/swift-${VERSION}-ubuntu22.04.tar.gz" - echo >&2 - echo >&2 "Complete example:" - echo >&2 " # Download the Swift binaries for Ubuntu and macOS" - echo >&2 " curl -o ~/Downloads/swift-${VERSION}-ubuntu22.04.tar.gz https://swift.org/builds/${branch}/ubuntu2204/swift-${VERSION}/swift-${VERSION}-ubuntu22.04.tar.gz" - echo >&2 " curl -o ~/Downloads/swift-${VERSION}-osx.pkg https://swift.org/builds/${branch}/xcode/swift-${VERSION}/swift-${VERSION}-osx.pkg" - echo >&2 " # Compile the SDK and toolchain from that" - echo >&2 " $0 /tmp/ ~/Downloads/swift-${VERSION}-osx.pkg ~/Downloads/swift-${VERSION}-ubuntu22.04.tar.gz" - echo >&2 " # Create a test application" - echo >&2 " mkdir my-test-app" - echo >&2 " cd my-test-app" - echo >&2 " swift package init --type=executable" - echo >&2 " # Build it for Ubuntu" - echo >&2 " swift build --destination /tmp/cross-toolchain/ubuntu-jammy-destination.json" -} - -if [[ $# -ne 3 ]]; then - usage - exit 1 -fi - -function realpath() { - if [[ "${1:0:1}" = / ]]; then - echo "$1" - else - ( - cd "$(dirname "$1")" - echo "$(pwd)/$(basename "$1")" - ) - fi -} - -function fix_glibc_modulemap() { - local glc_mm - local tmp - local inc_dir - - glc_mm="$1" - echo "glibc.modulemap at '$glc_mm'" - test -f "$glc_mm" - - tmp=$(mktemp "$glc_mm"_orig_XXXXXX) - inc_dir="$(dirname "$glc_mm")/private_includes" - cat "$glc_mm" >> "$tmp" - echo "Paths:" - echo " - original glibc.modulemap: $tmp" - echo " - new glibc.modulemap: $glc_mm" - echo " - private includes dir : $inc_dir" - echo -n > "$glc_mm" - rm -rf "$inc_dir" - mkdir "$inc_dir" - cat "$tmp" | while IFS='' read line; do - if [[ "$line" =~ ^(\ *header\ )\"\/+usr\/include\/(x86_64-linux-gnu\/)?([^\"]+)\" ]]; then - local orig_inc - local rel_repl_inc - local repl_inc - - orig_inc="${BASH_REMATCH[3]}" - rel_repl_inc="$(echo "$orig_inc" | tr / _)" - repl_inc="$inc_dir/$rel_repl_inc" - echo "${BASH_REMATCH[1]} \"$(basename "$inc_dir")/$rel_repl_inc\"" >> "$glc_mm" - if [[ "$orig_inc" == "uuid/uuid.h" ]]; then - # no idea why ;) - echo "#include " >> "$repl_inc" - else - echo "#include <$orig_inc>" >> "$repl_inc" - fi - true - else - echo "$line" >> "$glc_mm" - fi - done -} - -# set -xv -# where to get stuff from -dest=$(realpath "$1") -macos_swift_pkg=$(realpath "$2") -linux_swift_pkg=$(realpath "$3") -test -f "$macos_swift_pkg" -test -f "$linux_swift_pkg" - -# config -blocks_h_url="https://raw.githubusercontent.com/apple/swift-corelibs-libdispatch/main/src/BlocksRuntime/Block.h" -xc_tc_name="swift.xctoolchain" -linux_sdk_name="ubuntu-jammy.sdk" -cross_tc_basename="cross-toolchain" -clang_package_url="https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.1/clang+llvm-13.0.1-x86_64-apple-darwin.tar.xz" -ubuntu_mirror="http://gb.archive.ubuntu.com/ubuntu" -packages_file="$ubuntu_mirror/dists/jammy/main/binary-amd64/Packages.gz" -pkg_names=( libc6-dev linux-libc-dev libicu70 libgcc-12-dev libicu-dev libc6 libgcc-s1 libstdc++-12-dev libstdc++6 zlib1g-dev ) -pkgs=() - -# url -function download_stdout() { - curl --fail -s "$1" -} - -# url, key -function download_with_cache() { - mkdir -p "$dest/cache" - local out - out="$dest/cache/$2" - if [[ ! -f "$out" ]]; then - # Download with curl, also follow redirects. - curl -L --fail -s -o "$out" "$1" - fi - echo "$out" -} - -# dst, file -function unpack_deb() { - local tmp - tmp=$(mktemp -d /tmp/.unpack_deb_XXXXXX) - ( - cd "$tmp" - ar -x "$2" - tar -C "$1" -xf data.tar.* - ) - rm -rf "$tmp" -} - -# dst, file -function unpack_pkg() { - local tmp - tmp=$(mktemp -d /tmp/.unpack_pkg_XXXXXX) - ( - cd "$tmp" - xar -xf "$2" - ) - ( - cd "$1" - cat "$tmp"/*.pkg/Payload | gunzip -dc | cpio -i - ) - rm -rf "$tmp" -} - -# dst, file -function unpack() { - ext=${2##*.} - "unpack_$ext" "$@" -} - -cd "$dest" - -rm -rf $cross_tc_basename -mkdir -p "$cross_tc_basename/$linux_sdk_name" - -# oopsie, this is slow but seemingly fast enough :) -while read -r line; do - for pkg_name in "${pkg_names[@]}"; do - if [[ "$line" =~ ^Filename:\ (.*\/([^/_]+)_.*$) ]]; then - # echo "${BASH_REMATCH[2]}" - if [[ "${BASH_REMATCH[2]}" == "$pkg_name" ]]; then - new_pkg="$ubuntu_mirror/${BASH_REMATCH[1]}" - pkgs+=( "$new_pkg" ) - echo "- will download $new_pkg" - fi - fi - done -done < <(download_stdout "$packages_file" | gunzip -d -c | grep ^Filename:) - -tmp=$(mktemp -d "$dest/tmp_pkgs_XXXXXX") -( -cd "$tmp" -for f in "${pkgs[@]}"; do - name="$(basename "$f")" - archive="$(download_with_cache "$f" "$name")" - unpack "$dest/$cross_tc_basename/$linux_sdk_name" "$archive" -done -) -rm -rf "$tmp" -( -cd $cross_tc_basename -mkdir -p "$xc_tc_name/usr/bin" - -clang_txz="$(download_with_cache "$clang_package_url" clang.tar.xz)" -tmp=$(mktemp -d "$dest/tmp_pkgs_XXXXXX") -( -cd "$tmp" -tar --strip-components=1 -zxf "$clang_txz" -) -cp "$tmp/bin/lld" "$xc_tc_name/usr/bin/ld.lld" -rm -rf "$tmp" - -# fix absolute symlinks -find "$linux_sdk_name" -type l | while read -r line; do - dst=$(readlink "$line") - if [[ "${dst:0:1}" = / ]]; then - rm "$line" - fixedlink=$(echo "./$(dirname "${line#${linux_sdk_name}/}")" | sed 's:/[^/]*:/..:g')"${dst}" - echo ln -s "${fixedlink#./}" "${line#./}" - ln -s "${fixedlink#./}" "${line#./}" - fi -done -ln -s 5 "$linux_sdk_name/usr/lib/gcc/x86_64-linux-gnu/9" - -tmp=$(mktemp -d "$dest/tmp_pkgs_XXXXXX") -unpack "$tmp" "$macos_swift_pkg" -rsync -a "$tmp/" "$xc_tc_name" -rm -rf "$tmp" - -tmp=$(mktemp -d "$dest/tmp_pkgs_XXXXXX") -tar -C "$tmp" --strip-components 1 -xf "$linux_swift_pkg" -rsync -a "$tmp/usr/lib/swift/linux" "$xc_tc_name/usr/lib/swift/" -rsync -a "$tmp/usr/lib/swift_static/linux" "$xc_tc_name/usr/lib/swift_static/" -rsync -a "$tmp/usr/lib/swift/dispatch" "$linux_sdk_name/usr/include/" -rsync -a "$tmp/usr/lib/swift/os" "$linux_sdk_name/usr/include/" -rsync -a "$tmp/usr/lib/swift/CoreFoundation" "$linux_sdk_name/usr/include/" -rm -rf "$tmp" -curl --fail -s -o "$linux_sdk_name/usr/include/Block.h" "$blocks_h_url" -if [ ! -e "$xc_tc_name/usr/bin/swift-autolink-extract" ]; then - ln -s swift "$xc_tc_name/usr/bin/swift-autolink-extract" -fi -) -# fix up glibc modulemap -fix_glibc_modulemap "$cross_tc_basename/$xc_tc_name/usr/lib/swift/linux/x86_64/glibc.modulemap" - -cat > "$cross_tc_basename/ubuntu-jammy-destination.json" < Checking format... \n" -git diff --name-only | grep ".swift" | while read changed_file; do +git diff --name-only | grep -v ".swiftpm" | grep ".swift" | while read changed_file; do printf " * checking ${changed_file}... " before=$(cat ${changed_file}) swiftformat $changed_file > /dev/null 2>&1 @@ -64,14 +66,14 @@ for language in swift-or-c bash python; do matching_files=( -name '*' ) case "$language" in swift-or-c) - exceptions=( - -name "Package.swift" + exceptions=( + -name "Package.swift" -o -path "./Sources/PackageSigning/embedded_resources.swift" - -o -path "./Examples/*" - -o -path "./Fixtures/*" - -o -path "./IntegrationTests/*" - -o -path "./Tests/ExtraTests/*" - -o -path "./Tests/PackageLoadingTests/Inputs/*" + -o -path "./Examples/*" + -o -path "./Fixtures/*" + -o -path "./IntegrationTests/*" + -o -path "./Tests/ExtraTests/*" + -o -path "./Tests/PackageLoadingTests/Inputs/*" ) matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" diff --git a/xcode/SwiftPM-Package.xctestplan b/xcode/SwiftPM-Package.xctestplan index 2e7ceb5f47e..faa80d41fb5 100644 --- a/xcode/SwiftPM-Package.xctestplan +++ b/xcode/SwiftPM-Package.xctestplan @@ -191,6 +191,14 @@ "identifier" : "XCBuildSupportTests", "name" : "XCBuildSupportTests" } + }, + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:", + "identifier" : "LLBuildManifestTests", + "name" : "LLBuildManifestTests" + } } ], "version" : 1 From ba37f14a963bfd9dce5dac160b615925af17c6ad Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 5 Dec 2023 23:41:26 -0500 Subject: [PATCH 169/178] Fix issues remaining after merges --- CMakeLists.txt | 2 -- .../SwiftTargetBuildDescription.swift | 2 +- .../Build/BuildManifest/LLBuildManifestBuilder.swift | 12 ------------ Sources/PackageLoading/PackageBuilder.swift | 1 - 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9810efcb29..77fecb6e41c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,8 +37,6 @@ option(USE_CMAKE_INSTALL add_compile_options(-DUSE_IMPL_ONLY_IMPORTS) -add_compile_options(-DUSE_IMPL_ONLY_IMPORTS) - if(FIND_PM_DEPS) find_package(SwiftSystem CONFIG REQUIRED) find_package(TSC CONFIG REQUIRED) diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index ed342c6b303..4ddd5f79352 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -599,7 +599,7 @@ public final class SwiftTargetBuildDescription { args += self.packageNameArgumentIfSupported(with: self.package, packageAccess: self.target.packageAccess) args += try self.macroArguments() - + // rdar://117578677 // Pass -fno-omit-frame-pointer to support backtraces // this can be removed once the backtracer uses DWARF instead of frame pointers diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift index 7c16c4d4ea1..a34348c9aa5 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift @@ -113,22 +113,15 @@ public class LLBuildManifestBuilder { try self.createSwiftCompileCommand(desc) case .clang(let desc): try self.createClangCompileCommand(desc) -<<<<<<< HEAD case .mixed(let desc): try self.createMixedCompileCommand(desc) -======= ->>>>>>> main } } } -<<<<<<< HEAD - try self.addTestDiscoveryGenerationCommand() -======= if self.buildParameters.testingParameters.library == .xctest { try self.addTestDiscoveryGenerationCommand() } ->>>>>>> main try self.addTestEntryPointGenerationCommand() // Create command for all products in the plan. @@ -277,14 +270,9 @@ extension LLBuildManifestBuilder { let outputs = testEntryPointTarget.target.sources.paths -<<<<<<< HEAD - guard let mainOutput = (outputs.first { $0.basename == TestEntryPointTool.mainFileName }) else { - throw InternalError("main output (\(TestEntryPointTool.mainFileName)) not found") -======= let mainFileName = TestEntryPointTool.mainFileName(for: buildParameters.testingParameters.library) guard let mainOutput = (outputs.first { $0.basename == mainFileName }) else { throw InternalError("main output (\(mainFileName)) not found") ->>>>>>> main } let cmdName = mainOutput.pathString self.manifest.addTestEntryPointCmd( diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 222fa3bbb03..b079878f3c2 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -884,7 +884,6 @@ public final class PackageBuilder { // Compute the path to public headers directory. let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent let publicHeadersPath = potentialModule.path.appending(try RelativePath(validating: publicHeaderComponent)) - guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) } From bc3219792b940f725b87083febca36ed88a8c27f Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Sun, 21 Jan 2024 20:52:19 -0500 Subject: [PATCH 170/178] Building post-merge. Tests failing. --- .../MixedTargetBuildDescription.swift | 19 ++++++++----- .../TargetBuildDescription.swift | 6 ++++ Sources/Build/BuildPlan/BuildPlan+Clang.swift | 2 +- Sources/Build/BuildPlan/BuildPlan+Swift.swift | 2 +- Sources/Build/BuildPlan/BuildPlan.swift | 2 +- .../Resolution/ResolvedTarget.swift | 2 +- Sources/PackagePlugin/PackageModel.swift | 20 +++++++++++-- .../PluginContextDeserializer.swift | 13 +++++---- Sources/PackagePlugin/PluginMessages.swift | 2 +- .../SPMTestSupport/MockBuildTestHelper.swift | 9 ++++++ .../SourceKitLSPAPI/BuildDescription.swift | 3 ++ Tests/BuildTests/BuildPlanTests.swift | 28 +++++++++---------- 12 files changed, 74 insertions(+), 34 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 097eac5f294..94de2e8be60 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -22,10 +22,10 @@ public final class MixedTargetBuildDescription { let target: ResolvedTarget /// The list of all resource files in the target. - var resources: [Resource] { self.target.underlyingTarget.resources } + var resources: [Resource] { self.target.underlying.resources } /// If this target is a test target. - var isTestTarget: Bool { self.target.underlyingTarget.type == .test } + var isTestTarget: Bool { self.target.underlying.type == .test } /// The objects in this target. This includes both the Swift and Clang object files. var objects: [AbsolutePath] { @@ -81,7 +81,7 @@ public final class MixedTargetBuildDescription { fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { - guard let mixedTarget = target.underlyingTarget as? MixedTarget else { + guard let mixedTarget = target.underlying as? MixedTarget else { throw InternalError("underlying target type mismatch \(target)") } @@ -89,10 +89,12 @@ public final class MixedTargetBuildDescription { self.buildToolPluginInvocationResults = buildToolPluginInvocationResults let clangResolvedTarget = ResolvedTarget( - target: mixedTarget.clangTarget, + packageIdentity: package.identity, + underlying: mixedTarget.clangTarget, dependencies: target.dependencies, defaultLocalization: target.defaultLocalization, - platforms: target.platforms + supportedPlatforms: target.supportedPlatforms, + platformVersionProvider: target.platformVersionProvider ) self.clangTargetBuildDescription = try ClangTargetBuildDescription( target: clangResolvedTarget, @@ -106,10 +108,12 @@ public final class MixedTargetBuildDescription { ) let swiftResolvedTarget = ResolvedTarget( - target: mixedTarget.swiftTarget, + packageIdentity: package.identity, + underlying: mixedTarget.swiftTarget, dependencies: target.dependencies, defaultLocalization: target.defaultLocalization, - platforms: target.platforms + supportedPlatforms: target.supportedPlatforms, + platformVersionProvider: target.platformVersionProvider ) self.swiftTargetBuildDescription = try SwiftTargetBuildDescription( package: package, @@ -119,6 +123,7 @@ public final class MixedTargetBuildDescription { buildParameters: buildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, + disableSandbox: false, fileSystem: fileSystem, observabilityScope: observabilityScope, isWithinMixedTarget: true diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index 128e3a27ac6..dfcc2667b69 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -121,6 +121,9 @@ public enum TargetBuildDescription { return swiftTargetBuildDescription.buildParameters case .clang(let clangTargetBuildDescription): return clangTargetBuildDescription.buildParameters + // TODO(ncooke3): How to handle the mixed case? + case .mixed(let mixedTargetBuildDescription): + return mixedTargetBuildDescription.clangTargetBuildDescription.buildParameters } } @@ -130,6 +133,9 @@ public enum TargetBuildDescription { return swiftTargetBuildDescription.toolsVersion case .clang(let clangTargetBuildDescription): return clangTargetBuildDescription.toolsVersion + // TODO(ncooke3): How to handle the mixed case? + case .mixed(let mixedTargetBuildDescription): + return mixedTargetBuildDescription.clangTargetBuildDescription.toolsVersion } } } diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift index 3446dba515d..0f00e78f39a 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Clang.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift @@ -42,7 +42,7 @@ extension BuildPlan { } case let target as MixedTarget where target.type == .library: // Add the modulemap of the dependency. - if case let .mixed(dependencyTargetDescription)? = targetMap[dependency] { + if case let .mixed(dependencyTargetDescription)? = targetMap[dependency.id] { // Add the dependency's modulemap. clangTarget.additionalFlags.append( "-fmodule-map-file=\(dependencyTargetDescription.moduleMap.pathString)" diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index ef3b1f0ae52..1ad9493dab5 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -50,7 +50,7 @@ extension BuildPlan { } } case let underlyingTarget as MixedTarget where underlyingTarget.type == .library: - guard case let .mixed(target)? = targetMap[dependency] else { + guard case let .mixed(target)? = targetMap[dependency.id] else { throw InternalError("unexpected mixed target \(underlyingTarget)") } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 3ff219d39d1..75dbc119c71 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -412,7 +412,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { guard let package = graph.package(for: target) else { throw InternalError("package not found for \(target)") } - targetMap[target] = try .mixed(MixedTargetBuildDescription( + targetMap[target.id] = try .mixed(MixedTargetBuildDescription( package: package, target: target, toolsVersion: toolsVersion, diff --git a/Sources/PackageGraph/Resolution/ResolvedTarget.swift b/Sources/PackageGraph/Resolution/ResolvedTarget.swift index 4e9fecaf804..da7abdbfa13 100644 --- a/Sources/PackageGraph/Resolution/ResolvedTarget.swift +++ b/Sources/PackageGraph/Resolution/ResolvedTarget.swift @@ -141,7 +141,7 @@ public struct ResolvedTarget { /// The list of platforms that are supported by this target. public let supportedPlatforms: [SupportedPlatform] - private let platformVersionProvider: PlatformVersionProvider + public let platformVersionProvider: PlatformVersionProvider /// Triple for which this resolved target should be compiled for. public let buildTriple: BuildTriple diff --git a/Sources/PackagePlugin/PackageModel.swift b/Sources/PackagePlugin/PackageModel.swift index d83f6a9cd60..760b0b17279 100644 --- a/Sources/PackagePlugin/PackageModel.swift +++ b/Sources/PackagePlugin/PackageModel.swift @@ -371,7 +371,7 @@ public struct ClangSourceModuleTarget: SourceModuleTarget { /// Represents a target consisting of a source code module containing both Swift and C-family language sources. public struct MixedSourceModuleTarget: SourceModuleTarget { - + /// Describes attributes specific to the target's Swift sources. public struct SwiftSourceAttributes { /// Any custom compilation conditions specified for the target's Swift sources. @@ -388,7 +388,7 @@ public struct MixedSourceModuleTarget: SourceModuleTarget { /// The directory containing public C headers, if applicable. This will /// only be set for targets that have a directory of a public headers. - public let publicHeadersDirectory: Path? + public let publicHeadersDirectory: URL? } /// Unique identifier for the target. @@ -401,10 +401,16 @@ public struct MixedSourceModuleTarget: SourceModuleTarget { /// The kind of module, describing whether it contains unit tests, contains /// the main entry point of an executable, or neither. public let kind: ModuleKind - + /// The absolute path of the target directory in the local file system. + @available(_PackageDescription, deprecated: 5.11) public let directory: Path + /// The absolute path of the target directory in the local file system. + @available(_PackageDescription, introduced: 5.11) + public let directoryURL: URL + + /// Any other targets on which this target depends, in the same order as /// they are specified in the package manifest. Conditional dependencies /// that do not apply have already been filtered out. @@ -431,6 +437,14 @@ public struct MixedSourceModuleTarget: SourceModuleTarget { /// Any custom linked frameworks required by the module, as specified in the /// package manifest. public let linkedFrameworks: [String] + + /// Paths of any sources generated by other plugins that have been applied to the given target before the plugin currently being executed. + @available(_PackageDescription, introduced: 5.11) + public let pluginGeneratedSources: [URL] + + /// Paths of any resources generated by other plugins that have been applied to the given target before the plugin currently being executed. + @available(_PackageDescription, introduced: 5.11) + public let pluginGeneratedResources: [URL] } /// Represents a target describing an artifact (e.g. a library or executable) diff --git a/Sources/PackagePlugin/PluginContextDeserializer.swift b/Sources/PackagePlugin/PluginContextDeserializer.swift index 5af5da09a6f..5defbcfa2ce 100644 --- a/Sources/PackagePlugin/PluginContextDeserializer.swift +++ b/Sources/PackagePlugin/PluginContextDeserializer.swift @@ -148,9 +148,9 @@ internal struct PluginContextDeserializer { case let .mixedSourceModuleInfo(moduleName, kind, sourceFiles, compilationConditions, preprocessorDefinitions, headerSearchPaths, publicHeadersDirId, linkedLibraries, linkedFrameworks): - let publicHeadersDir = try publicHeadersDirId.map { try self.path(for: $0) } + let publicHeadersDir = try publicHeadersDirId.map { try self.url(for: $0) } let sourceFiles = FileList(try sourceFiles.map { - let path = try self.path(for: $0.basePathId).appending($0.name) + let path = try self.url(for: $0.basePathId).appendingPathComponent($0.name) let type: FileType switch $0.type { case .source: @@ -162,13 +162,14 @@ internal struct PluginContextDeserializer { case .unknown: type = .unknown } - return File(path: path, type: type) + return File(path: Path(url: path), url: path, type: type) }) target = MixedSourceModuleTarget( id: String(id), name: wireTarget.name, kind: .init(kind), - directory: directory, + directory: Path(url: directory), + directoryURL: directory, dependencies: dependencies, moduleName: moduleName, sourceFiles: sourceFiles, @@ -178,7 +179,9 @@ internal struct PluginContextDeserializer { headerSearchPaths: headerSearchPaths, publicHeadersDirectory: publicHeadersDir), linkedLibraries: linkedLibraries, - linkedFrameworks: linkedFrameworks + linkedFrameworks: linkedFrameworks, + pluginGeneratedSources: pluginGeneratedSources, + pluginGeneratedResources: pluginGeneratedResources ) case let .binaryArtifactInfo(kind, origin, artifactId): diff --git a/Sources/PackagePlugin/PluginMessages.swift b/Sources/PackagePlugin/PluginMessages.swift index 0c1b09593c1..97daf3207cc 100644 --- a/Sources/PackagePlugin/PluginMessages.swift +++ b/Sources/PackagePlugin/PluginMessages.swift @@ -160,7 +160,7 @@ enum HostToPluginMessage: Codable { compilationConditions: [String], preprocessorDefinitions: [String], headerSearchPaths: [String], - publicHeadersDirId: Path.Id?, + publicHeadersDirId: URL.Id?, linkedLibraries: [String], linkedFrameworks: [String]) diff --git a/Sources/SPMTestSupport/MockBuildTestHelper.swift b/Sources/SPMTestSupport/MockBuildTestHelper.swift index d18050e953d..1bea958b978 100644 --- a/Sources/SPMTestSupport/MockBuildTestHelper.swift +++ b/Sources/SPMTestSupport/MockBuildTestHelper.swift @@ -202,4 +202,13 @@ extension TargetBuildDescription { throw BuildError.error("Unexpected \(self) type") } } + + public func mixedTarget() throws -> MixedTargetBuildDescription { + switch self { + case .mixed(let target): + return target + default: + throw BuildError.error("Unexpected \(self) type") + } + } } diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index 824df666a0d..096ad845396 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -70,6 +70,9 @@ public struct BuildDescription { return description case .swift(let description): return WrappedSwiftTargetBuildDescription(description: description) + default: + // TODO(ncooke3): What is supposed to happen here? + fatalError("`MixedTargetDescription` does not conform to `BuildTarget`.") } } else { if target.type == .plugin, let package = self.buildPlan.graph.package(for: target) { diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 84c5a4906ab..56753256ad8 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1732,7 +1732,7 @@ final class BuildPlanTests: XCTestCase { result.checkProductsCount(1) result.checkTargetsCount(2) - let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + let buildPath: AbsolutePath = result.plan.productsBuildPath let exe = try result.target(for: "exe").swiftTarget().compileArguments() #if os(macOS) @@ -1810,7 +1810,7 @@ final class BuildPlanTests: XCTestCase { #if os(macOS) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", @@ -1822,7 +1822,7 @@ final class BuildPlanTests: XCTestCase { ]) #elseif os(Linux) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, "-module-name", "exe", "-static-stdlib", "-emit-executable", "-Xlinker", "-rpath=$ORIGIN", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", @@ -1877,7 +1877,7 @@ final class BuildPlanTests: XCTestCase { result.checkProductsCount(1) result.checkTargetsCount(2) - let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + let buildPath: AbsolutePath = result.plan.productsBuildPath let exe = try result.target(for: "exe").swiftTarget().compileArguments() #if os(macOS) XCTAssertMatch(exe, [ @@ -1950,7 +1950,7 @@ final class BuildPlanTests: XCTestCase { #if os(macOS) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", @@ -1962,7 +1962,7 @@ final class BuildPlanTests: XCTestCase { ]) #elseif os(Linux) XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, "-module-name", "exe", "-static-stdlib", "-emit-executable", "-Xlinker", "-rpath=$ORIGIN", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", @@ -3074,14 +3074,14 @@ final class BuildPlanTests: XCTestCase { result.checkProductsCount(2) result.checkTargetsCount(2) - let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + let buildPath: AbsolutePath = result.plan.productsBuildPath let fooLinkArgs = try result.buildProduct(for: "Foo").linkArguments() let barLinkArgs = try result.buildProduct(for: "Bar-Baz").linkArguments() #if os(macOS) XCTAssertEqual(fooLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "Foo").pathString, "-module-name", "Foo", @@ -3096,7 +3096,7 @@ final class BuildPlanTests: XCTestCase { ]) XCTAssertEqual(barLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "libBar-Baz.dylib").pathString, "-module-name", "Bar_Baz", @@ -3109,7 +3109,7 @@ final class BuildPlanTests: XCTestCase { ]) #elseif os(Linux) XCTAssertEqual(fooLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "Foo").pathString, "-module-name", "Foo", @@ -3121,7 +3121,7 @@ final class BuildPlanTests: XCTestCase { ]) XCTAssertEqual(barLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "libBar-Baz.so").pathString, "-module-name", "Bar_Baz", @@ -3192,14 +3192,14 @@ final class BuildPlanTests: XCTestCase { result.checkProductsCount(2) result.checkTargetsCount(2) - let buildPath: AbsolutePath = result.plan.buildParameters.dataPath.appending(components: "debug") + let buildPath: AbsolutePath = result.plan.productsBuildPath let fooLinkArgs = try result.buildProduct(for: "Foo").linkArguments() let barLinkArgs = try result.buildProduct(for: "Bar-Baz").linkArguments() #if os(macOS) XCTAssertEqual(fooLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "Foo").pathString, "-module-name", "Foo", @@ -3213,7 +3213,7 @@ final class BuildPlanTests: XCTestCase { ]) #elseif os(Linux) XCTAssertEqual(fooLinkArgs, [ - result.plan.buildParameters.toolchain.swiftCompilerPath.pathString, + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "Foo").pathString, "-module-name", "Foo", From 43605b2cb4213432980f14e185e9e0d859402811 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 6 Aug 2024 19:55:09 -0400 Subject: [PATCH 171/178] Use printf over NSLog --- .../ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m | 2 +- .../ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m index 6426a5ca13c..2d33cd9ff80 100644 --- a/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m +++ b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnDynamicallyLinkedMixedTarget/main.m @@ -8,7 +8,7 @@ int main(int argc, const char *argv[]) { Engine *engine = [[Engine alloc] init]; OldCar *oldCar = [[OldCar alloc] init]; - NSLog(@"Hello, world!"); + printf("Hello, world!\n"); } return 0; } diff --git a/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m index de47370c06e..15c303c6d97 100644 --- a/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m +++ b/Fixtures/MixedTargets/DummyTargets/Sources/ClangExecutableDependsOnStaticallyLinkedMixedTarget/main.m @@ -8,7 +8,7 @@ int main(int argc, const char * argv[]) { Engine *engine = [[Engine alloc] init]; OldCar *oldCar = [[OldCar alloc] init]; - NSLog(@"Hello, world!"); + printf("Hello, world!\n"); } return 0; } From 642c9014dd2b23f2cc8852d2d9364925c2f29de8 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 6 Aug 2024 19:56:20 -0400 Subject: [PATCH 172/178] Fix tests that were failing post-merge --- .../SwiftTargetBuildDescription.swift | 2 +- Sources/Build/BuildPlan/BuildPlan+Swift.swift | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index dad134b54a4..a9dbb818d2d 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -142,7 +142,7 @@ public final class SwiftTargetBuildDescription { /// Whether or not the target belongs to a mixed language target. /// /// Mixed language targets consist of an underlying Swift and Clang target. - private let isWithinMixedTarget: Bool + let isWithinMixedTarget: Bool /// The swift version for this target. var swiftVersion: SwiftLanguageVersion { diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index 1ad9493dab5..d453607cac2 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -14,6 +14,7 @@ import struct Basics.InternalError import class PackageModel.BinaryTarget import class PackageModel.ClangTarget import class PackageModel.MixedTarget +import class PackageModel.SwiftTarget import class PackageModel.SystemLibraryTarget extension BuildPlan { @@ -61,6 +62,27 @@ extension BuildPlan { // Add the dependency's public headers. swiftTarget.appendClangFlags("-I", target.publicHeadersDir.pathString) + case let underlyingTarget as SwiftTarget where underlyingTarget.type == .library: + // The mixed target build description exposes it's public ObjC + // headers to it's Swift module in case the Swift source in the + // mixed module references types from the ObjC headers. + // + // In doing so, there is the possibility that one of the + // exposedpublic ObjC headers (directly or transitively) + // exports a Swift module import. Such a Swift module import + // (handled by this case) needs to be added to Swift import + // paths of the given mixed target's swift module. + guard swiftTarget.isWithinMixedTarget else { + return + } + + guard case let .swift(target)? = targetMap[dependency.id] else { + throw InternalError("unexpected swift target \(underlyingTarget)") + } + + // NOTE(ncooke3): Could also be `target.moduleMap!.parentDirectory`. + swiftTarget.appendClangFlags("-I", target.buildParameters.buildPath.pathString) + break default: break } From 5543ae05b06c74397dbe9fe34d15d32c0a5201d6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 6 Aug 2024 19:57:22 -0400 Subject: [PATCH 173/178] Fix mixed target related tests --- Tests/BuildTests/BuildPlanTests.swift | 4 ++-- Tests/FunctionalTests/MixedTargetTests.swift | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 56753256ad8..04f14010f5a 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1817,7 +1817,7 @@ final class BuildPlanTests: XCTestCase { "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, "-framework", "OtherFramework", "-Xlinker", "-add_ast_path", - "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, + "-Xlinker", buildPath.appending(components: "Modules", "exe.swiftmodule").pathString, "-g" ]) #elseif os(Linux) @@ -1957,7 +1957,7 @@ final class BuildPlanTests: XCTestCase { "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", "-target", defaultTargetTriple, "-Xlinker", "-add_ast_path", - "-Xlinker", buildPath.appending(component: "exe.swiftmodule").pathString, + "-Xlinker", buildPath.appending(components: "Modules", "exe.swiftmodule").pathString, "-g" ]) #elseif os(Linux) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index f4fb4a144ac..945f2381fa9 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -289,23 +289,23 @@ extension MixedTargetTests { // Test that statically linked mixed library is successfully // integrated into an Objective-C executable. try fixture(name: "MixedTargets") { fixturePath in - let output = try executeSwiftRun( + let (stdout, _) = try executeSwiftRun( fixturePath.appending(component: "DummyTargets"), "ClangExecutableDependsOnStaticallyLinkedMixedTarget" ) // The program should print "Hello, world!" - XCTAssert(output.stderr.contains("Hello, world!")) + XCTAssert(stdout.contains("Hello, world!")) } // Test that statically linked mixed library is successfully // integrated into a Swift executable. try fixture(name: "MixedTargets") { fixturePath in - let output = try executeSwiftRun( + let (stdout, _) = try executeSwiftRun( fixturePath.appending(component: "DummyTargets"), "SwiftExecutableDependsOnStaticallyLinkedMixedTarget" ) // The program should print "Hello, world!" - XCTAssert(output.stdout.contains("Hello, world!")) + XCTAssert(stdout.contains("Hello, world!")) } } @@ -320,23 +320,23 @@ extension MixedTargetTests { // Test that dynamically linked mixed library is successfully // integrated into an Objective-C executable. try fixture(name: "MixedTargets") { fixturePath in - let output = try executeSwiftRun( + let (stdout, _) = try executeSwiftRun( fixturePath.appending(component: "DummyTargets"), "ClangExecutableDependsOnDynamicallyLinkedMixedTarget" ) // The program should print "Hello, world!" - XCTAssert(output.stderr.contains("Hello, world!")) + XCTAssert(stdout.contains("Hello, world!")) } // Test that dynamically linked mixed library is successfully // integrated into a Swift executable. try fixture(name: "MixedTargets") { fixturePath in - let output = try executeSwiftRun( + let (stdout, _) = try executeSwiftRun( fixturePath.appending(component: "DummyTargets"), "SwiftExecutableDependsOnDynamicallyLinkedMixedTarget" ) // The program should print "Hello, world!" - XCTAssert(output.stdout.contains("Hello, world!")) + XCTAssert(stdout.contains("Hello, world!")) } } From 3503c940a23ee21c3a947f2e03250026665c6300 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Thu, 8 Aug 2024 15:46:13 -0400 Subject: [PATCH 174/178] Fix regression caused by early case loop exit --- Sources/Build/BuildPlan/BuildPlan+Swift.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index d453607cac2..84203602613 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -62,7 +62,7 @@ extension BuildPlan { // Add the dependency's public headers. swiftTarget.appendClangFlags("-I", target.publicHeadersDir.pathString) - case let underlyingTarget as SwiftTarget where underlyingTarget.type == .library: + case let underlyingTarget as SwiftTarget where underlyingTarget.type == .library && swiftTarget.isWithinMixedTarget: // The mixed target build description exposes it's public ObjC // headers to it's Swift module in case the Swift source in the // mixed module references types from the ObjC headers. @@ -72,9 +72,6 @@ extension BuildPlan { // exports a Swift module import. Such a Swift module import // (handled by this case) needs to be added to Swift import // paths of the given mixed target's swift module. - guard swiftTarget.isWithinMixedTarget else { - return - } guard case let .swift(target)? = targetMap[dependency.id] else { throw InternalError("unexpected swift target \(underlyingTarget)") From 7f72500b51588a105f5b61615285bf39d9af1636 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Fri, 9 Aug 2024 19:10:58 -0400 Subject: [PATCH 175/178] Post feb merge mixed target initializer fix --- Sources/PackageLoading/PackageBuilder.swift | 1 + Sources/PackageModel/Target/MixedTarget.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 0887323f94c..f980b43ea3e 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -998,6 +998,7 @@ public final class PackageBuilder { packageAccess: potentialModule.packageAccess, swiftVersion: try swiftVersion(), buildSettings: buildSettings, + buildSettingsDescription: manifestTarget.settings, usesUnsafeFlags: manifestTarget.usesUnsafeFlags ) diff --git a/Sources/PackageModel/Target/MixedTarget.swift b/Sources/PackageModel/Target/MixedTarget.swift index 0f6c07f6ea4..7f43b5d7504 100644 --- a/Sources/PackageModel/Target/MixedTarget.swift +++ b/Sources/PackageModel/Target/MixedTarget.swift @@ -39,6 +39,7 @@ public final class MixedTarget: Target { packageAccess: Bool, swiftVersion: SwiftLanguageVersion, buildSettings: BuildSettings.AssignmentTable = .init(), + buildSettingsDescription: [TargetBuildSettingDescription.Setting], pluginUsages: [PluginUsage] = [], usesUnsafeFlags: Bool ) throws { @@ -113,6 +114,7 @@ public final class MixedTarget: Target { dependencies: dependencies, packageAccess: packageAccess, buildSettings: buildSettings, + buildSettingsDescription: buildSettingsDescription, pluginUsages: pluginUsages, usesUnsafeFlags: usesUnsafeFlags ) From 387da75d5639761e154f28aac1e6b449bada34fb Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Wed, 14 Aug 2024 19:04:51 -0400 Subject: [PATCH 176/178] Post march merge buildplantests fix --- .../MixedTargetBuildDescription.swift | 1 + Tests/BuildTests/BuildPlanTests.swift | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 94de2e8be60..224d958b174 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -97,6 +97,7 @@ public final class MixedTargetBuildDescription { platformVersionProvider: target.platformVersionProvider ) self.clangTargetBuildDescription = try ClangTargetBuildDescription( + package: package, target: clangResolvedTarget, toolsVersion: toolsVersion, buildParameters: buildParameters, diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index c735eeb6316..e581c3f9316 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -1733,7 +1733,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1851,7 +1851,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, - "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", + "-module-name", "exe", "-Xlinker", "-no_warn_duplicate_libraries", "-emit-executable", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", @@ -1888,7 +1888,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1991,7 +1991,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertEqual(try result.buildProduct(for: "exe").linkArguments(), [ result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, - "-module-name", "exe", "-emit-executable", "-Xlinker", "-rpath", + "-module-name", "exe", "-Xlinker", "-no_warn_duplicate_libraries", "-emit-executable", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", @@ -3070,7 +3070,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let g = try loadPackageGraph( + let g = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -3131,6 +3131,7 @@ final class BuildPlanTests: XCTestCase { "-o", buildPath.appending(components: "Foo").pathString, "-module-name", "Foo", "-lBar-Baz", + "-Xlinker", "-no_warn_duplicate_libraries", "-emit-executable", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", @@ -3145,6 +3146,7 @@ final class BuildPlanTests: XCTestCase { "-L", buildPath.pathString, "-o", buildPath.appending(components: "libBar-Baz.dylib").pathString, "-module-name", "Bar_Baz", + "-Xlinker", "-no_warn_duplicate_libraries", "-emit-library", "-Xlinker", "-install_name", "-Xlinker", "@rpath/libBar-Baz.dylib", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", @@ -3188,7 +3190,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let g = try loadPackageGraph( + let g = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -3248,6 +3250,7 @@ final class BuildPlanTests: XCTestCase { "-L", buildPath.pathString, "-o", buildPath.appending(components: "Foo").pathString, "-module-name", "Foo", + "-Xlinker", "-no_warn_duplicate_libraries", "-emit-executable", "-Xlinker", "-rpath", "-Xlinker", "@loader_path", "@\(buildPath.appending(components: "Foo.product", "Objects.LinkFileList"))", From 97f6db6d5318a5556ab73a14ca1fd4590c7d187a Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 5 Nov 2024 10:50:56 -0500 Subject: [PATCH 177/178] main-merge-june-15 follow-up --- .../BuildDescription/MixedTargetBuildDescription.swift | 7 +++++-- .../Build/BuildDescription/TargetBuildDescription.swift | 1 + Sources/Build/BuildManifest/LLBuildManifestBuilder.swift | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift index 202ca1d7bc1..eab1cf60241 100644 --- a/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/MixedTargetBuildDescription.swift @@ -124,8 +124,6 @@ public final class MixedTargetBuildDescription { toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, buildParameters: buildParameters, -// destinationBuildParameters: buildParameters, -// toolsBuildParameters: toolsBuildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, shouldDisableSandbox: shouldDisableSandbox, @@ -336,4 +334,9 @@ public final class MixedTargetBuildDescription { string: productModuleMap ) } + + func symbolGraphExtractArguments() throws -> [String] { + try clangTargetBuildDescription.symbolGraphExtractArguments() + + swiftTargetBuildDescription.symbolGraphExtractArguments() + } } diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index ce31b7e2493..c5d6fa70001 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -145,6 +145,7 @@ public enum TargetBuildDescription { switch self { case .swift(let target): try target.symbolGraphExtractArguments() case .clang(let target): try target.symbolGraphExtractArguments() + case .mixed(let target): try target.symbolGraphExtractArguments() } } } diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift index 0576358a180..c7bbc6c82fe 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift @@ -150,6 +150,10 @@ public class LLBuildManifestBuilder { // Hook up the clang module target try self.createClangPrepareCommand(desc) } + case .mixed(let desc): + // TODO(ncooke3): Should the below implementation handle the + // underlying Clang build description like the above case does? + try self.createMixedCompileCommand(desc) } } From e5f35791672f4759e01351a7b2aa1b41229e6ec1 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 5 Nov 2024 11:45:07 -0500 Subject: [PATCH 178/178] Move platform conditional for easier testing --- Tests/FunctionalTests/MixedTargetTests.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/FunctionalTests/MixedTargetTests.swift b/Tests/FunctionalTests/MixedTargetTests.swift index 945f2381fa9..99581d50a8c 100644 --- a/Tests/FunctionalTests/MixedTargetTests.swift +++ b/Tests/FunctionalTests/MixedTargetTests.swift @@ -38,10 +38,8 @@ final class MixedTargetTests: XCTestCase { ) } } -} -#if os(macOS) -extension MixedTargetTests { + #if os(macOS) // MARK: - macOS Tests // The targets tested contain Objective-C, and thus require macOS to be tested. @@ -471,5 +469,5 @@ extension MixedTargetTests { ) } } + #endif // os(macOS) } -#endif // os(macOS)