Search code examples
swiftswift-package-manager

How can I load a Swift package configuration programmatically?


I'm trying to use SwiftPM to analyse a local Package.swift file. I could not find any documentation on how to do so. Ideally I'd call something like

import PackageDescription

let package = try Package(at: "pathToFile/Package.swift")
print(package.dependencies) ...

I tried using

 let package = try PackageBuilder.loadPackage(
            packagePath: .init(absolutePath),
            swiftCompiler: .init("/usr/bin/swift"),
            swiftCompilerFlags: [],
            diagnostics: .init())

but this fails with error: no such module \'PackageDescription\'\nimport PackageDescription\n ^", diagnosticFile: nil) as my Package.swift file starts with import PackageDescription as expected.

Maybe there's something going on here.


Solution

  • You should provide a full path to the compiler with swiftCompiler param to avoid your issue e.g.:

    import PackageModel
    import PackageLoading
    import PackageGraph
    import Workspace
    
    let swiftCompiler: AbsolutePath = {
        let string: String
    #if os(macOS)
        string = try! Process.checkNonZeroExit(args: "xcrun", "--sdk", "macosx", "-f", "swiftc").spm_chomp()
    #else
        string = try! Process.checkNonZeroExit(args: "which", "swiftc").spm_chomp()
    #endif
        return AbsolutePath(string)
    }()
    
    let package = AbsolutePath(".../swift-package-manager")
    let diagnostics = DiagnosticsEngine()
    
    do {
        let manifest = try ManifestLoader.loadManifest(packagePath: package, swiftCompiler: swiftCompiler, packageKind: .local)
        let loadedPackage = try PackageBuilder.loadPackage(packagePath: package, swiftCompiler: swiftCompiler, diagnostics: diagnostics)
        let graph = try Workspace.loadGraph(packagePath: package, swiftCompiler: swiftCompiler, diagnostics: diagnostics)
        
        // Manifest
        let products = manifest.products.map({ $0.name }).joined(separator: ", ")
        print("Products:", products)
        let targets = manifest.targets.map({ $0.name }).joined(separator: ", ")
        print("Targets:", targets)
        
        // Package
        let executables = loadedPackage.targets.filter({ $0.type == .executable }).map({ $0.name })
        print("Executable targets:", executables)
        
        // PackageGraph
        let numberOfFiles = graph.reachableTargets.reduce(0, { $0 + $1.sources.paths.count })
        print("Total number of source files (including dependencies):", numberOfFiles)
    }
    catch {
        print(error)
    }
    
    

    Outputs:

    Products: SwiftPM, SwiftPM-auto, PackageDescription
    Targets: PackageDescription, LLBuildManifest, SourceControl, SPMLLBuild, PackageModel, PackageLoading, PackageGraph, Build, Xcodeproj, Workspace, Commands, swift-package, swift-build, swift-test, swift-run, swiftpm-xctest-helper, SPMTestSupport, BuildTests, CommandsTests, WorkspaceTests, FunctionalTests, FunctionalPerformanceTests, PackageDescription4Tests, PackageLoadingTests, PackageLoadingPerformanceTests, PackageModelTests, PackageGraphTests, PackageGraphPerformanceTests, SourceControlTests, XcodeprojTests
    Executable targets: ["swift-run", "swift-build", "swift-package", "swiftpm-xctest-helper", "swift-test"]
    Total number of source files (including dependencies): 344