Start to type the name of a package in the search bar and select from the menu to display a package and its dependencies. Click on a node in the graph to make that the top node. Click on the top node to toggle between displaying a package's dependencies and displaying the packages dependent on it.
51 |
The arrow in the background indicates whether you are displaying dependents or dependencies. The arrow always points in the direction of the parent or dependent package.
52 |
The swift dependency graph uses the Swift Package catalog from the Swift Package Index setup by Dave Verwer. The graph is written by Adam Fowler.
`)
132 | return html.join("")
133 | }
134 | return name
135 | }
136 |
137 | function tooltipForName(name) {
138 | let node = dependencyData[name]
139 | if (node.error === "FailedToLoad") {
140 | return `${name}\nDependencies unavailable. Cannot find a Package.swift on the 'master' branch`
141 | } else if(node.error === "InvalidManifest") {
142 | return `${name}\nDependencies unavailable. Failed to load Package.swift.\nEither it is corrupt or is an unsupported version.\nVersions supported range from 4.0 to 5.0.`
143 | } else if(node.error === "Unknown") {
144 | return `${name}\nDependencies unavailable. Failed to load Package.swift`
145 | }
146 | return name
147 | }
148 |
149 | function addChildrenRows(data, rootName, level, rootNameCount, packagesAdded, stack = []) {
150 | nodeId += 1
151 | if (level == 0) {
152 | return
153 | }
154 | let maxPackages = 12
155 | let nodeId2 = nodeId
156 | let root = dependencyData[rootName]
157 | let numChildren = root[direction].length
158 | var position = 0
159 |
160 | if (nodePositions[rootName] != undefined) {
161 | position = nodePositions[rootName]
162 | }
163 | var packagesToDisplay = root[direction]
164 | // if viewing dependents limit number to 12
165 | if (direction == "to") {
166 | packagesToDisplay = packagesToDisplay.slice(position*maxPackages,(position+1)*maxPackages)
167 | }
168 | var rows = packagesToDisplay.map(function(name){return [{v:`${name}#${nodeId2}`, f:renderName(name, `${name}#${nodeId2}`)}, rootNameCount, tooltipForName(name)]})
169 |
170 | stack.push(rootName)
171 | // if we have already display a complete tree for a package and it has more than 4 children show expand box
172 | if (packagesAdded.has(rootName) && numChildren > 4) {
173 | let stackString = stack.join("#")
174 | let name = `__expand__#${stackString}`
175 | if (name != expandedNode) {
176 | let row = data.addRow([{v:name, f:`
\u2193
`}, rootNameCount, "Show more ..."])
177 | //data.setRowProperty(row, "parent", )
178 | stack.pop()
179 | return
180 | }
181 | }
182 |
183 | // if node position is greater than zero than add previous node
184 | if (position > 0 && direction == "to") {
185 | data.addRow([{v:`__prev__#${rootName}`, f:"
\u2190
"}, rootNameCount, "Show more ..."])
186 | }
187 | data.addRows(rows)
188 | // if there are more than maxPackages to display then add a next button
189 | if (root[direction].length - position*maxPackages > maxPackages && direction == "to") {
190 | data.addRow([{v:`__next__#${rootName}`, f:"
\u2192
"}, rootNameCount, "Show more ..."])
191 | }
192 |
193 |
194 | packagesAdded.add(rootName)
195 |
196 | for(entry in packagesToDisplay) {
197 | let rootName = packagesToDisplay[entry]
198 | addChildrenRows(data, rootName, level-1, `${rootName}#${nodeId2}`, packagesAdded, stack)
199 | }
200 | stack.pop()
201 | }
202 |
203 | function drawChart() {
204 | var packagesAdded = new Set([])
205 |
206 | chart_data = new google.visualization.DataTable();
207 |
208 | chart_data.addColumn('string', 'Name');
209 | chart_data.addColumn('string', 'Parent');
210 | chart_data.addColumn('string', 'ToolTip');
211 |
212 | let root = dependencyData[rootName]
213 | nodeId = 0
214 | chart_data.addRows([[{v:rootName, f:renderName(rootName, rootName)}, "", tooltipForName(rootName)]]);
215 | addChildrenRows(chart_data, rootName, 8, rootName, packagesAdded)
216 | console.log(`Number of nodes ${nodeId}`)
217 | // set background
218 | var backgroundImages = {"to" : "images/solid-arrow-circle-down.svg", "on" : "images/solid-arrow-circle-up.svg"}
219 | document.body.style.backgroundImage = `url(${backgroundImages[direction]})`
220 |
221 | // Draw the chart, setting the allowHtml option to true for the tooltips.
222 | chart.draw(chart_data, {allowHtml:true, nodeClass:"chart-node", selectedNodeClass:"chart-node"});
223 |
224 | //var position = $('#httpsgithubcomvaporfluent73').offset();
225 | //console.log(position)
226 | }
227 |
--------------------------------------------------------------------------------
/Sources/swift-dependency-graph-lib/Packages.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Packages.swift
3 | // AsyncHTTPClient
4 | //
5 | // Created by Adam Fowler on 18/08/2019.
6 | //
7 |
8 | import Foundation
9 | import NIO
10 |
11 | typealias Future = EventLoopFuture
12 |
13 | public struct Package : Codable {
14 | /// has the package been setup fully with its dependencies setup
15 | public var readPackageSwift: Bool = false
16 | /// packages we are dependent on
17 | var dependencies: Set
18 | /// packages dependent on us
19 | var dependents: Set
20 | /// error
21 | var error : String? {
22 | // if setting an error the package must have been read
23 | didSet { readPackageSwift = true}
24 | }
25 |
26 | public init() {
27 | self.dependencies = []
28 | self.dependents = []
29 | }
30 |
31 | public init(dependencies: [String]) {
32 | self.readPackageSwift = true
33 | self.dependencies = Set(dependencies.map { Packages.cleanupName($0) })
34 | self.dependents = []
35 | }
36 |
37 | public func encode(to encoder: Encoder) throws {
38 | var container = encoder.container(keyedBy: CodingKeys.self)
39 | // enocde dependencies and dependents in alphabetical order
40 | try container.encode(dependencies.map{$0}.sorted(by:{ return $0.split(separator: "/").last! < $1.split(separator: "/").last! }), forKey: .dependencies)
41 | try container.encode(dependents.map{$0}.sorted(by:{ return $0.split(separator: "/").last! < $1.split(separator: "/").last! }), forKey: .dependents)
42 | try container.encode(error, forKey: .error)
43 | }
44 |
45 | enum CodingKeys : String, CodingKey {
46 | case dependencies = "on"
47 | case dependents = "to"
48 | case error = "error"
49 | }
50 | }
51 |
52 | enum PackagesError : Swift.Error {
53 | case corruptDependencies
54 | }
55 |
56 | public class Packages {
57 | public typealias Container = [String: Package]
58 | public private(set) var packages : Container
59 |
60 | public init() throws {
61 | self.packages = [:]
62 | self.loader = try PackageLoader(onAdd: self.add, onError: self.addLoadingError)
63 | }
64 |
65 | public init(packages: Container) throws {
66 | self.packages = packages
67 | self.loader = try PackageLoader(onAdd: self.add, onError: self.addLoadingError)
68 |
69 | // flag all packages as read
70 | for key in self.packages.keys {
71 | self.packages[key]?.readPackageSwift = true
72 | }
73 | }
74 |
75 | /// add a package
76 | func add(name: String, package: Package) {
77 | let name = Packages.cleanupName(name)
78 | lock.lock()
79 | defer {
80 | lock.unlock()
81 | }
82 |
83 | var package = package
84 | // if package already exists then add the dependent of the original package to the new one
85 | if let package2 = packages[name] {
86 | guard package2.readPackageSwift != true else {return}
87 | package.dependents = package2.dependents
88 | }
89 | packages[name] = package
90 |
91 | for dependency in package.dependencies {
92 | // guard against invalid urls. If invalid remove from dependency list
93 | guard PackageLoader.isValidUrl(url: dependency) else {
94 | print("Error: removed dependency as the URL(\(dependency) was invalid")
95 | packages[name]?.dependencies.remove(dependency)
96 | continue
97 | }
98 | let dependencyName = Packages.cleanupName(dependency)
99 | if packages[dependencyName] == nil {
100 | packages[dependencyName] = Package()
101 | }
102 | packages[dependencyName]!.dependents.insert(name)
103 | }
104 | }
105 |
106 | /// set loading package failed
107 | public func addLoadingError(name: String, error: Error) {
108 | print("Failed to load package from \(name) error: \(Packages.stringFromError(error))")
109 | let name = Packages.cleanupName(name)
110 | lock.lock()
111 | defer {
112 | lock.unlock()
113 | }
114 |
115 | // if package already exists
116 | let error = Packages.stringFromError(error)
117 | if packages[name] != nil {
118 | packages[name]?.error = error
119 | } else {
120 | var package = Package()
121 | package.error = error
122 | packages[name] = package
123 | }
124 | }
125 |
126 | /// import packages.json file
127 | /// - Parameters:
128 | /// - url: URL of packages json file
129 | /// - iterations: Number of iterations we will run emptying the package array after having added dependencies
130 | public func `import`(url: String, iterations : Int = 100) throws {
131 | // Load package names from url
132 | let packageNames = try loader.load(url: url, packages: self).map { Packages.cleanupName($0)}
133 |
134 | try loadPackages(packageNames, iterations: iterations)
135 |
136 | }
137 |
138 | /// Load list of packages
139 | /// - Parameters:
140 | /// - packageNames: List of package URLs
141 | /// - iterations: Number of iterations we will run emptying the package array after having added dependencies
142 | func loadPackages(_ packageNames: [String], iterations : Int = 100) throws {
143 | // remove duplicate packages, sort and remove packages we have already loaded
144 | var packageNames = Array(Set(packageNames)).sorted().compactMap { (name)->String? in
145 | let name = Packages.cleanupName(name)
146 | return packages[name] == nil ? name : nil
147 | }
148 |
149 | var iterations = iterations
150 | repeat {
151 | try loader.loadPackages(packageNames).wait()
152 |
153 | // verify we havent got stuck in a loop
154 | iterations -= 1
155 | guard iterations > 0 else { throw PackageLoaderError.looping }
156 |
157 | // create new list of packages containing packages that haven't been loaded
158 | packageNames = packages.compactMap {return !$0.value.readPackageSwift ? $0.key : nil}
159 | } while(packageNames.count > 0)
160 | }
161 |
162 | /// Remove package from dependency set, also needs to remove all of its dependecies
163 | /// - Parameter packageName: URL of package
164 | public func removePackage(_ packageName: String) throws {
165 | let name = Packages.cleanupName(packageName)
166 | guard let package = packages[name] else { return }
167 | // when you remove a package you have to remove it from its dependencies dependents lists
168 | for d in package.dependencies {
169 | guard var dependency = packages[d] else { throw PackagesError.corruptDependencies }
170 | dependency.dependents.remove(name)
171 | }
172 | // when you remove a package you have to remove it dependents
173 | for d in package.dependents {
174 | try removePackage(d)
175 | }
176 | packages[name] = nil
177 | }
178 |
179 | /// Remove packages filtered by including a string
180 | /// - Parameter filteredBy: String that packages need to contain to be removed
181 | public func removePackages(filteredBy: String) throws {
182 | let packages = self.packages.compactMap { (entry)->String? in
183 | if entry.key.contains(filteredBy) {
184 | return entry.key
185 | }
186 | return nil
187 | }
188 | try packages.forEach { try self.removePackage($0); print("Rebulding \($0)") }
189 | }
190 |
191 | /// save dependency file
192 | public func save(filename: String) throws {
193 | let encoder = JSONEncoder()
194 | encoder.outputFormatting = .sortedKeys
195 | let data = try encoder.encode(packages)
196 | try data.write(to: URL(fileURLWithPath: filename))
197 | }
198 |
199 | /// convert error to string
200 | static func stringFromError(_ error: Swift.Error) -> String {
201 | switch error {
202 | case PackageLoaderError.invalidToolsVersion:
203 | return "Requires later version of Swift"
204 | case PackageLoaderError.invalidManifest:
205 | return "InvalidManifest"
206 | case HTTPLoader.HTTPError.failedToLoad(_):
207 | return "FailedToLoad"
208 | default:
209 | return "\(error)"
210 | }
211 | }
212 |
213 | /// convert name from github/repository.git to github/repository
214 | public static func cleanupName(_ packageName: String) -> String {
215 | var packageName = packageName.lowercased()
216 | // if package is recorded as git@github.com changes to https://github.com/
217 | if packageName.hasPrefix("git@github.com") {
218 | var split = packageName.split(separator: "/", omittingEmptySubsequences: false)
219 | let split2 = split[0].split(separator: ":")
220 | //guard split2.count > 1 else {return nil}
221 | // set user name
222 | split[0] = split2[1]
223 | split.insert("github.com", at:0)
224 | split.insert("", at:0)
225 | split.insert("https:", at:0)
226 | packageName = split.joined(separator: "/")
227 | }
228 |
229 | if packageName.suffix(4) == ".git" {
230 | // remove .git suffix
231 | return String(packageName.prefix(packageName.count - 4))
232 | } else if packageName.last == "/" {
233 | // ensure name doesn't end with "/"
234 | return String(packageName.dropLast())
235 | }
236 | return packageName
237 | }
238 |
239 | let lock = NSLock()
240 | var loader: PackageLoader!
241 |
242 |
243 | }
244 |
245 |
--------------------------------------------------------------------------------
/Sources/swift-dependency-graph-lib/PackageLoader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PackageLoader.swift
3 | // AsyncHTTPClient
4 | //
5 | // Created by Adam Fowler on 18/08/2019.
6 | //
7 |
8 | import Foundation
9 | import NIO
10 | import Workspace
11 | import PackageLoading
12 | import PackageModel
13 | import TSCBasic
14 | import TSCUtility
15 |
16 | enum PackageLoaderError : Error {
17 | case invalidUrl
18 | case invalidToolsVersion
19 | case invalidManifest
20 | case looping
21 | case gitVersionLoadingFailed(errorOutput: String)
22 | case noVersions
23 | }
24 |
25 |
26 | class PackageLoader {
27 |
28 | let threadPool: NIOThreadPool
29 | let manifestLoader: PackageManifestLoader
30 | let eventLoopGroup: EventLoopGroup
31 | let httpLoader: HTTPLoader
32 | let onAdd: (String, Package)->()
33 | let onError: (String, Error)->()
34 | let taskQueue: TaskQueue<[String]>
35 |
36 | init(onAdd: @escaping (String, Package)->(), onError: @escaping (String, Error)->() = {_,_ in }) throws {
37 | self.threadPool = NIOThreadPool(numberOfThreads: System.coreCount)
38 | self.threadPool.start()
39 | self.manifestLoader = try PackageManifestLoader()
40 | self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
41 | self.httpLoader = HTTPLoader(eventLoopGroup: eventLoopGroup)
42 | self.onAdd = onAdd
43 | self.onError = onError
44 | self.taskQueue = .init(maxConcurrentTasks: 8, on: eventLoopGroup.next())
45 | }
46 |
47 | func syncShutdown() throws {
48 | try httpLoader.syncShutdown()
49 | try eventLoopGroup.syncShutdownGracefully()
50 | }
51 |
52 | /// load package names from json
53 | func load(url: String, packages: Packages) throws -> [String] {
54 | if url.hasPrefix("http") {
55 | // get package names
56 | return try httpLoader.getBody(url: url)
57 | .flatMapThrowing { (buffer) throws -> [String] in
58 | let data = Data(buffer)
59 | var names = try JSONSerialization.jsonObject(with: data, options: []) as? [String] ?? []
60 | // append self to the list
61 | names.append("https://github.com/adam-fowler/swift-dependency-graph.git")
62 | return names
63 | }.wait()
64 | } else {
65 | let data = try Data(contentsOf: URL(fileURLWithPath: url))
66 | return try JSONSerialization.jsonObject(with: data, options: []) as? [String] ?? []
67 | }
68 | }
69 |
70 | /// load packages from array of package names
71 | func loadPackages(_ packages: [String]) -> Future {
72 | let futures = packages.map { name in return addPackage(url: name)
73 | .map { return () }
74 | .flatMapError { (error)->Future in
75 | self.onError(name, error)
76 | return self.eventLoopGroup.next().makeSucceededFuture(Void())
77 | }
78 | }
79 | return EventLoopFuture.whenAllComplete(futures, on: eventLoopGroup.next()).map {_ in return () }
80 | }
81 |
82 | /// add a package, works out default branch and calls add package with branch name, then calls onAdd callback
83 | func addPackage(url: String) -> Future {
84 | // get package.swift from default branch
85 | return self.getDefaultBranch(url: url).flatMap { (branch)->Future<[String]> in
86 | return self.taskQueue.submitTask { self.addPackage(url: url, version: branch) }
87 | }
88 | .map { buffer in
89 | print("Adding \(url)")
90 | self.onAdd(url, Package(dependencies: buffer))
91 | }
92 | }
93 |
94 | func addPackage(url: String, version: String?) -> Future<[String]>{
95 |
96 | let repositoryUrl : String
97 | if let url = PackageLoader.getRawRepositoryUrl(url: url, version: version) {
98 | repositoryUrl = url
99 | } else {
100 | return self.eventLoopGroup.next().makeFailedFuture(PackageLoaderError.invalidUrl)
101 | }
102 | // Order of loading is
103 | // - Package@swift-5.swift
104 | // - Package.swift
105 | // - Package@swift-4.2.swift
106 | // - Package@swift-4.swift
107 | var errorPassedDown : Error? = nil
108 | var packageUrlToLoad = repositoryUrl + "/Package@swift-5.swift"
109 | return self.httpLoader.getBody(url: packageUrlToLoad)
110 |
111 | .flatMapError { (error)->Future<[UInt8]> in
112 | packageUrlToLoad = repositoryUrl + "/Package.swift"
113 | return self.httpLoader.getBody(url: packageUrlToLoad)
114 | }
115 | .flatMap { (buffer)->Future<[String]> in
116 | return self.manifestLoader.load(buffer, url: packageUrlToLoad, on: self.eventLoopGroup.next())
117 | }
118 | .flatMapError { (error)->Future<[String]> in
119 | errorPassedDown = error
120 | packageUrlToLoad = repositoryUrl + "/Package@swift-4.2.swift"
121 | return self.httpLoader.getBody(url: packageUrlToLoad)
122 | .flatMap { buffer in
123 | return self.manifestLoader.load(buffer, url: packageUrlToLoad, on: self.eventLoopGroup.next())
124 | }
125 | }
126 | .flatMapError { (error)->Future<[String]> in
127 | packageUrlToLoad = repositoryUrl + "/Package@swift-4.swift"
128 | return self.httpLoader.getBody(url: packageUrlToLoad)
129 | .flatMap { buffer in
130 | return self.manifestLoader.load(buffer, url: packageUrlToLoad, on: self.eventLoopGroup.next())
131 | }
132 | }
133 | .flatMapErrorThrowing { error in
134 | throw errorPassedDown ?? error
135 | }
136 |
137 | }
138 |
139 | func getDefaultBranch(url: String) -> Future {
140 | return threadPool.runIfActive(eventLoop: eventLoopGroup.next()) { ()->String in
141 | guard let lsRemoteOutput = try? TSCBasic.Process.checkNonZeroExit(
142 | args: Git.tool, "ls-remote", "--symref", url, "HEAD", environment: Git.environment).spm_chomp() else {return "master"}
143 | // split into tokens separated by space. The second token is the branch ref.
144 | let branchRefTokens = lsRemoteOutput.components(separatedBy: CharacterSet.whitespacesAndNewlines)
145 | var branch : Substring? = nil
146 | if branchRefTokens.count > 1, branchRefTokens[1].hasPrefix("refs/heads/") {
147 | // split branch ref by '/'. Last element is branch name
148 | branch = branchRefTokens[1].dropFirst(11)
149 | }
150 | if let branch = branch {
151 | return String(branch)
152 | }
153 | return "master"
154 | }
155 | }
156 |
157 | func getLatestVersion(url: String) -> Future {
158 | let regularExpressionXXX = try! NSRegularExpression(pattern: "[0-9]+\\.[0-9]+\\.[0-9]+$", options: [])
159 | let regularExpressionXX = try! NSRegularExpression(pattern: "[0-9]+\\.[0-9]+$", options: [])
160 | // Look into getting versions
161 | // git ls-remote --tags .
162 | return threadPool.runIfActive(eventLoop: eventLoopGroup.next()) { ()->String? in
163 | guard let lsRemoteOutput = try? Process.checkNonZeroExit(
164 | args: Git.tool, "ls-remote", "--tags", url, environment: Git.environment).spm_chomp() else {return nil}
165 | let tags = lsRemoteOutput.split(separator: "\n").compactMap { $0.split(separator:"/").last }
166 | let versions = tags
167 | .map {String($0)}
168 | .compactMap { (versionString)->(v:Version, s:String)? in
169 | /// if of form major.minor.patch
170 | let cleanVersionString : String
171 | let firstMatchRangeXXX = regularExpressionXXX.rangeOfFirstMatch(in: versionString, options: [], range: NSMakeRange(0, versionString.count))
172 | if let range = Range(firstMatchRangeXXX, in: versionString) {
173 | cleanVersionString = String(versionString[range])
174 | } else {
175 | /// if of form major.minor
176 | let firstMatchRangeXX = regularExpressionXX.rangeOfFirstMatch(in: versionString, options: [], range: NSMakeRange(0, versionString.count))
177 | if let range = Range(firstMatchRangeXX, in: versionString) {
178 | cleanVersionString = String(versionString[range])+".0"
179 | } else {
180 | return nil
181 | }
182 | }
183 | if let version = Version(cleanVersionString) {
184 | return (version, versionString)
185 | }
186 | return nil
187 | }
188 | .sorted {$0.v < $1.v}
189 | .map {$0.s}
190 |
191 | return versions.last
192 | }
193 | }
194 |
195 | /// get URL from github repository name
196 | static func getRawRepositoryUrl(url: String, version: String? = nil) -> String? {
197 | let url = Packages.cleanupName(url)
198 |
199 | // get Package.swift URL
200 | var split = url.split(separator: "/", omittingEmptySubsequences: false)
201 | if split.last == "" {
202 | split = split.dropLast()
203 | }
204 |
205 | if split.count > 2 && split[2] == "github.com" {
206 | split[2] = "raw.githubusercontent.com"
207 | } else if split.count > 2 && split[2] == "gitlab.com" {
208 | split.append("raw")
209 | }
210 |
211 | if let version = version {
212 | split.append("\(version)")
213 | } else {
214 | split.append("master")
215 | }
216 |
217 | return split.joined(separator: "/")
218 | }
219 |
220 | /// return if this is a valid repository name
221 | static func isValidUrl(url: String) -> Bool {
222 | let split = url.split(separator: "/", omittingEmptySubsequences: false)
223 | if split[0].hasPrefix("git@github.com") && split.count == 2
224 | || split.count > 4 && split[2] == "github.com"
225 | || split.count > 4 && split[2] == "www.github.com"
226 | || split.count > 4 && split[2] == "gitlab.com"
227 | || split.count > 4 && split[2] == "www.gitlab.com" {
228 | return true
229 | }
230 | return false
231 | }
232 | }
233 |
234 | public class PackageManifestLoader {
235 |
236 | let toolchain: UserToolchain
237 | let loader: ManifestLoader
238 |
239 | public init() throws {
240 | self.toolchain = try UserToolchain(destination: .hostDestination())
241 | self.loader = ManifestLoader(toolchain: toolchain)
242 | }
243 |
244 | // We will need to know where the Swift compiler is.
245 | var swiftCompiler: AbsolutePath = {
246 | let string: String
247 | #if os(macOS)
248 | string = try! Process.checkNonZeroExit(args: "xcrun", "--sdk", "macosx", "-f", "swiftc").spm_chomp()
249 | #else
250 | string = try! Process.checkNonZeroExit(args: "which", "swiftc").spm_chomp()
251 | #endif
252 | return try! AbsolutePath(validating: string)
253 | }()
254 |
255 | public func load(_ buffer: [UInt8], url: String, on eventLoop: EventLoop) -> EventLoopFuture<[String]> {
256 | let promise = eventLoop.makePromise(of: [String].self)
257 | let fs = InMemoryFileSystem()
258 |
259 | print("Loading manifest from \(url)")
260 |
261 | do {
262 | try fs.createDirectory(try AbsolutePath(validating: "/Package"))
263 | try fs.writeFileContents(try AbsolutePath(validating: "/Package/Package.swift"), bytes: ByteString(buffer))
264 |
265 | var toolsVersion = try ToolsVersionParser.parse(manifestPath: AbsolutePath(validating: "/Package/"), fileSystem: fs)
266 | if toolsVersion < ToolsVersion.minimumRequired {
267 | print("error: Package version is below minimum, trying minimum")
268 | toolsVersion = .minimumRequired
269 | }
270 | loader.load(
271 | package: AbsolutePath("/Package/"),
272 | baseURL: AbsolutePath("/Package/").pathString,
273 | toolsVersion: toolsVersion,
274 | packageKind: .local,
275 | fileSystem: fs,
276 | on: DispatchQueue.global()
277 | ) { result in
278 | switch result {
279 | case .failure(let error):
280 | switch error {
281 | case PackageLoaderError.invalidManifest:
282 | promise.fail(error)
283 | case is ManifestParseError:
284 | promise.fail(PackageLoaderError.invalidManifest)
285 | default:
286 | print("Error loading \(url) \(error)")
287 | promise.fail(error)
288 | }
289 | case .success(let manifest):
290 | let dependencies = manifest.dependencies.map {$0.url}
291 | promise.succeed(dependencies)
292 | }
293 | }
294 | } catch {
295 | print("Error loading \(url) \(error)")
296 | promise.fail(error)
297 | }
298 | return promise.futureResult
299 | }
300 | }
301 |
302 |
303 |
--------------------------------------------------------------------------------