func evaluateManifest()

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))
        }
    }