func emitAPIBaseline()

in Sources/Commands/APIDigester.swift [57:166]


    func emitAPIBaseline(
        for modulesToDiff: Set<String>,
        at baselineDir: AbsolutePath?,
        force: Bool,
        outputStream: OutputByteStream,
        logLevel: Diagnostic.Severity
    ) throws -> AbsolutePath {
        var modulesToDiff = modulesToDiff
        let apiDiffDir = inputBuildParameters.apiDiff
        let baselineDir = (baselineDir ?? apiDiffDir).appending(component: baselineRevision.identifier)
        let baselinePath: (String)->AbsolutePath = { module in
            baselineDir.appending(component: module + ".json")
        }

        if !force {
            // Baselines which already exist don't need to be regenerated.
            modulesToDiff = modulesToDiff.filter {
                !localFileSystem.exists(baselinePath($0))
            }
        }

        guard !modulesToDiff.isEmpty else {
            // If none of the baselines need to be regenerated, return.
            return baselineDir
        }

        // Setup a temporary directory where we can checkout and build the baseline treeish.
        let baselinePackageRoot = apiDiffDir.appending(component: "\(baselineRevision.identifier)-checkout")
        if localFileSystem.exists(baselinePackageRoot) {
            try localFileSystem.removeFileTree(baselinePackageRoot)
        }

        // Clone the current package in a sandbox and checkout the baseline revision.
        let repositoryProvider = GitRepositoryProvider()
        let specifier = RepositorySpecifier(path: baselinePackageRoot)
        let workingCopy = try repositoryProvider.createWorkingCopy(
            repository: specifier,
            sourcePath: packageRoot,
            at: baselinePackageRoot,
            editable: false
        )

        try workingCopy.checkout(revision: baselineRevision)

        // Create the workspace for this package.
        let workspace = try Workspace(forRootPackage: baselinePackageRoot)

        let graph = try workspace.loadPackageGraph(
            rootPath: baselinePackageRoot,
            observabilityScope: self.observabilityScope
        )

        // Don't emit a baseline for a module that didn't exist yet in this revision.
        modulesToDiff.formIntersection(graph.apiDigesterModules)

        // Abort if we weren't able to load the package graph.
        if observabilityScope.errorsReported {
            throw Diagnostics.fatalError
        }

        // Update the data path input build parameters so it's built in the sandbox.
        var buildParameters = inputBuildParameters
        buildParameters.dataPath = workspace.location.workingDirectory

        // Build the baseline module.
        // FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the APIDigester. rdar://86112934
        let buildOp = BuildOperation(
            buildParameters: buildParameters,
            cacheBuildManifest: false,
            packageGraphLoader: { graph },
            buildToolPluginInvoker: { _ in [:] },
            outputStream: outputStream,
            logLevel: logLevel,
            fileSystem: localFileSystem,
            observabilityScope: observabilityScope
        )

        try buildOp.build()

        // Dump the SDK JSON.
        try localFileSystem.createDirectory(baselineDir, recursive: true)
        let group = DispatchGroup()
        let semaphore = DispatchSemaphore(value: Int(buildParameters.jobs))
        let errors = ThreadSafeArrayStore<Swift.Error>()
        for module in modulesToDiff {
            semaphore.wait()
            DispatchQueue.sharedConcurrent.async(group: group) {
                do {
                    try apiDigesterTool.emitAPIBaseline(
                        to: baselinePath(module),
                        for: module,
                        buildPlan: buildOp.buildPlan!
                    )
                } catch {
                    errors.append(error)
                }
                semaphore.signal()
            }
        }
        group.wait()

        for error in errors.get() {
            observabilityScope.emit(error)
        }
        if observabilityScope.errorsReported {
            throw Diagnostics.fatalError
        }

        return baselineDir
    }