in Sources/PackageLoading/ManifestLoader.swift [754:960]
func evaluateManifest(
at manifestPath: AbsolutePath,
packageIdentity: PackageIdentity,
toolsVersion: ToolsVersion,
delegateQueue: DispatchQueue,
completion: @escaping (Result<EvaluationResult, Error>) -> Void
) {
var evaluationResult = EvaluationResult()
delegateQueue.async {
self.delegate?.willParse(manifest: manifestPath)
}
// The compiler has special meaning for files with extensions like .ll, .bc etc.
// Assert that we only try to load files with extension .swift to avoid unexpected loading behavior.
assert(manifestPath.extension == "swift",
"Manifest files must contain .swift suffix in their name, given: \(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
// and validates it.
// Compute the path to runtime we need to load.
let runtimePath = self.runtimePath(for: toolsVersion)
// FIXME: Workaround for the module cache bug that's been haunting Swift CI
// <rdar://problem/48443680>
let moduleCachePath = (ProcessEnv.vars["SWIFTPM_MODULECACHE_OVERRIDE"] ?? ProcessEnv.vars["SWIFTPM_TESTS_MODULECACHE"]).flatMap{ AbsolutePath.init($0) }
var cmd: [String] = []
cmd += [self.toolchain.swiftCompilerPath.pathString]
cmd += verbosity.ccArgs
let macOSPackageDescriptionPath: AbsolutePath
// if runtimePath is set to "PackageFrameworks" that means we could be developing SwiftPM in Xcode
// which produces a framework for dynamic package products.
if runtimePath.extension == "framework" {
cmd += [
"-F", runtimePath.parentDirectory.pathString,
"-framework", "PackageDescription",
"-Xlinker", "-rpath", "-Xlinker", runtimePath.parentDirectory.pathString,
]
macOSPackageDescriptionPath = runtimePath.appending(component: "PackageDescription")
} else {
cmd += [
"-L", runtimePath.pathString,
"-lPackageDescription",
]
#if !os(Windows)
// -rpath argument is not supported on Windows,
// so we add runtimePath to PATH when executing the manifest instead
cmd += ["-Xlinker", "-rpath", "-Xlinker", runtimePath.pathString]
#endif
// note: this is not correct for all platforms, but we only actually use it on macOS.
macOSPackageDescriptionPath = runtimePath.appending(component: "libPackageDescription.dylib")
}
// Use the same minimum deployment target as the PackageDescription library (with a fallback of 10.15).
#if os(macOS)
let triple = Self._hostTriple.memoize {
Triple.getHostTriple(usingSwiftCompiler: self.toolchain.swiftCompilerPath)
}
do {
let version = try Self._packageDescriptionMinimumDeploymentTarget.memoize {
(try MinimumDeploymentTarget.computeMinimumDeploymentTarget(of: macOSPackageDescriptionPath, platform: .macOS))?.versionString ?? "10.15"
}
cmd += ["-target", "\(triple.tripleString(forPlatformVersion: version))"]
} catch {
return completion(.failure(error))
}
#endif
// Add any extra flags required as indicated by the ManifestLoader.
cmd += self.toolchain.swiftCompilerFlags
cmd += self.interpreterFlags(for: toolsVersion)
if let moduleCachePath = moduleCachePath {
cmd += ["-module-cache-path", moduleCachePath.pathString]
}
// Add the arguments for emitting serialized diagnostics, if requested.
if self.serializedDiagnostics, let databaseCacheDir = self.databaseCacheDir {
let diaDir = databaseCacheDir.appending(component: "ManifestLoading")
let diagnosticFile = diaDir.appending(component: "\(packageIdentity).dia")
do {
try localFileSystem.createDirectory(diaDir, recursive: true)
cmd += ["-Xfrontend", "-serialize-diagnostics-path", "-Xfrontend", diagnosticFile.pathString]
evaluationResult.diagnosticFile = diagnosticFile
} catch {
return completion(.failure(error))
}
}
cmd += [manifestPath.pathString]
cmd += self.extraManifestFlags
do {
try withTemporaryDirectory { tmpDir, cleanupTmpDir in
// Set path to compiled manifest executable.
#if os(Windows)
let executableSuffix = ".exe"
#else
let executableSuffix = ""
#endif
let compiledManifestFile = tmpDir.appending(component: "\(packageIdentity)-manifest\(executableSuffix)")
cmd += ["-o", compiledManifestFile.pathString]
// Compile the manifest.
Process.popen(arguments: cmd, environment: toolchain.swiftCompilerEnvironment, queue: delegateQueue) { result in
var cleanupIfError = DelayableAction(target: tmpDir, action: cleanupTmpDir)
defer { cleanupIfError.perform() }
let compilerResult : ProcessResult
do {
compilerResult = try result.get()
evaluationResult.compilerOutput = try (compilerResult.utf8Output() + compilerResult.utf8stderrOutput()).spm_chuzzle()
} catch {
return completion(.failure(error))
}
// Return now if there was an error.
if compilerResult.exitStatus != .terminated(code: 0) {
return completion(.success(evaluationResult))
}
// Pass an open file descriptor of a file to which the JSON representation of the manifest will be written.
let jsonOutputFile = tmpDir.appending(component: "\(packageIdentity)-output.json")
guard let jsonOutputFileDesc = fopen(jsonOutputFile.pathString, "w") else {
return completion(.failure(StringError("couldn't create the manifest's JSON output file")))
}
cmd = [compiledManifestFile.pathString]
#if os(Windows)
// NOTE: `_get_osfhandle` returns a non-owning, unsafe,
// unretained HANDLE. DO NOT invoke `CloseHandle` on `hFile`.
let hFile: Int = _get_osfhandle(_fileno(jsonOutputFileDesc))
cmd += ["-handle", "\(String(hFile, radix: 16))"]
#else
cmd += ["-fileno", "\(fileno(jsonOutputFileDesc))"]
#endif
do {
let packageDirectory = manifestPath.parentDirectory.pathString
let contextModel = ContextModel(packageDirectory: packageDirectory)
cmd += ["-context", try contextModel.encode()]
} catch {
return completion(.failure(error))
}
// If enabled, run command in a sandbox.
// This provides some safety against arbitrary code execution when parsing manifest files.
// We only allow the permissions which are absolutely necessary.
if self.isManifestSandboxEnabled {
let cacheDirectories = [self.databaseCacheDir, moduleCachePath].compactMap{ $0 }
let strictness: Sandbox.Strictness = toolsVersion < .v5_3 ? .manifest_pre_53 : .default
cmd = Sandbox.apply(command: cmd, strictness: strictness, writableDirectories: cacheDirectories)
}
// Run the compiled manifest.
var environment = ProcessEnv.vars
#if os(Windows)
let windowsPathComponent = runtimePath.pathString.replacingOccurrences(of: "/", with: "\\")
environment["Path"] = "\(windowsPathComponent);\(environment["Path"] ?? "")"
#endif
let cleanupAfterRunning = cleanupIfError.delay()
Process.popen(arguments: cmd, environment: environment, queue: delegateQueue) { result in
defer { cleanupAfterRunning.perform() }
fclose(jsonOutputFileDesc)
do {
let runResult = try result.get()
if let runOutput = try (runResult.utf8Output() + runResult.utf8stderrOutput()).spm_chuzzle() {
// Append the runtime output to any compiler output we've received.
evaluationResult.compilerOutput = (evaluationResult.compilerOutput ?? "") + runOutput
}
// Return now if there was an error.
if runResult.exitStatus != .terminated(code: 0) {
// TODO: should this simply be an error?
// return completion(.failure(ProcessResult.Error.nonZeroExit(runResult)))
evaluationResult.errorOutput = evaluationResult.compilerOutput
return completion(.success(evaluationResult))
}
// Read the JSON output that was emitted by libPackageDescription.
guard let jsonOutput = try localFileSystem.readFileContents(jsonOutputFile).validDescription else {
return completion(.failure(StringError("the manifest's JSON output has invalid encoding")))
}
evaluationResult.manifestJSON = jsonOutput
completion(.success(evaluationResult))
} catch {
completion(.failure(error))
}
}
}
}
} catch {
return completion(.failure(error))
}
}