|
| 1 | +/* |
| 2 | + This source file is part of the Swift.org open source project |
| 3 | + |
| 4 | + Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors |
| 5 | + Licensed under Apache License v2.0 with Runtime Library Exception |
| 6 | + |
| 7 | + See http://swift.org/LICENSE.txt for license information |
| 8 | + See http://swift.org/CONTRIBUTORS.txt for Swift project authors |
| 9 | + */ |
| 10 | + |
| 11 | +import TSCBasic |
| 12 | +import PackageGraph |
| 13 | +import PackageModel |
| 14 | +import SourceControl |
| 15 | +import TSCUtility |
| 16 | + |
| 17 | +/// A downloaded artifact managed by the workspace. |
| 18 | +public final class ManagedArtifact { |
| 19 | + |
| 20 | + /// Represents the source of the artifact. |
| 21 | + public enum Source: Equatable { |
| 22 | + |
| 23 | + /// Represents a remote artifact, with the url it was downloaded from, its checksum, and its path relative to |
| 24 | + /// the workspace artifacts path. |
| 25 | + case remote(url: String, checksum: String, subpath: RelativePath) |
| 26 | + |
| 27 | + /// Represents a locally available artifact, with its path relative to its package. |
| 28 | + case local(path: String) |
| 29 | + } |
| 30 | + |
| 31 | + /// The package reference. |
| 32 | + public let packageRef: PackageReference |
| 33 | + |
| 34 | + /// The name of the binary target the artifact corresponds to. |
| 35 | + public let targetName: String |
| 36 | + |
| 37 | + /// The source of the artifact (local or remote). |
| 38 | + public let source: Source |
| 39 | + |
| 40 | + public init( |
| 41 | + packageRef: PackageReference, |
| 42 | + targetName: String, |
| 43 | + source: Source |
| 44 | + ) { |
| 45 | + self.packageRef = packageRef |
| 46 | + self.targetName = targetName |
| 47 | + self.source = source |
| 48 | + } |
| 49 | + |
| 50 | + /// Create an artifact downloaded from a remote url. |
| 51 | + public static func remote( |
| 52 | + packageRef: PackageReference, |
| 53 | + targetName: String, |
| 54 | + url: String, |
| 55 | + checksum: String, |
| 56 | + subpath: RelativePath |
| 57 | + ) -> ManagedArtifact { |
| 58 | + return ManagedArtifact( |
| 59 | + packageRef: packageRef, |
| 60 | + targetName: targetName, |
| 61 | + source: .remote(url: url, checksum: checksum, subpath: subpath) |
| 62 | + ) |
| 63 | + } |
| 64 | + |
| 65 | + /// Create an artifact present locally on the filesystem. |
| 66 | + public static func local( |
| 67 | + packageRef: PackageReference, |
| 68 | + targetName: String, |
| 69 | + path: String |
| 70 | + ) -> ManagedArtifact { |
| 71 | + return ManagedArtifact( |
| 72 | + packageRef: packageRef, |
| 73 | + targetName: targetName, |
| 74 | + source: .local(path: path) |
| 75 | + ) |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +// MARK: - JSON |
| 80 | + |
| 81 | +extension ManagedArtifact: JSONMappable, JSONSerializable, CustomStringConvertible { |
| 82 | + public convenience init(json: JSON) throws { |
| 83 | + try self.init( |
| 84 | + packageRef: json.get("packageRef"), |
| 85 | + targetName: json.get("targetName"), |
| 86 | + source: json.get("source") |
| 87 | + ) |
| 88 | + } |
| 89 | + |
| 90 | + public func toJSON() -> JSON { |
| 91 | + return .init([ |
| 92 | + "packageRef": packageRef, |
| 93 | + "targetName": targetName, |
| 94 | + "source": source, |
| 95 | + ]) |
| 96 | + } |
| 97 | + |
| 98 | + public var description: String { |
| 99 | + return "<ManagedArtifact: \(packageRef.name).\(targetName) \(source)>" |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +extension ManagedArtifact.Source: JSONMappable, JSONSerializable, CustomStringConvertible { |
| 104 | + public init(json: JSON) throws { |
| 105 | + let type: String = try json.get("type") |
| 106 | + switch type { |
| 107 | + case "local": |
| 108 | + self = try .local(path: json.get("path")) |
| 109 | + case "remote": |
| 110 | + let url: String = try json.get("url") |
| 111 | + let checksum: String = try json.get("checksum") |
| 112 | + let subpath = try RelativePath(json.get("subpath")) |
| 113 | + self = .remote(url: url, checksum: checksum, subpath: subpath) |
| 114 | + default: |
| 115 | + throw JSON.MapError.custom(key: nil, message: "Invalid type \(type)") |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + public func toJSON() -> JSON { |
| 120 | + switch self { |
| 121 | + case .local(let path): |
| 122 | + return .init([ |
| 123 | + "type": "local", |
| 124 | + "path": path, |
| 125 | + ]) |
| 126 | + case .remote(let url, let checksum, let subpath): |
| 127 | + return .init([ |
| 128 | + "type": "remote", |
| 129 | + "url": url, |
| 130 | + "checksum": checksum, |
| 131 | + "subpath": subpath.toJSON(), |
| 132 | + ]) |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + public var description: String { |
| 137 | + switch self { |
| 138 | + case .local(let path): |
| 139 | + return "local(path: \(path))" |
| 140 | + case .remote(let url, let checksum, let subpath): |
| 141 | + return "remote(url: \(url), checksum: \(checksum), subpath: \(subpath))" |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +// MARK: - |
| 147 | + |
| 148 | +/// A collection of managed artifacts which have been downloaded. |
| 149 | +public final class ManagedArtifacts { |
| 150 | + |
| 151 | + /// A mapping from package url, to target name, to ManagedArtifact. |
| 152 | + private var artifactMap: [String: [String: ManagedArtifact]] |
| 153 | + |
| 154 | + private var artifacts: AnyCollection<ManagedArtifact> { |
| 155 | + AnyCollection(artifactMap.values.lazy.flatMap({ $0.values })) |
| 156 | + } |
| 157 | + |
| 158 | + init(artifactMap: [String: [String: ManagedArtifact]] = [:]) { |
| 159 | + self.artifactMap = artifactMap |
| 160 | + } |
| 161 | + |
| 162 | + public subscript(packageURL packageURL: String, targetName targetName: String) -> ManagedArtifact? { |
| 163 | + artifactMap[packageURL]?[targetName] |
| 164 | + } |
| 165 | + |
| 166 | + public subscript(packageName packageName: String, targetName targetName: String) -> ManagedArtifact? { |
| 167 | + artifacts.first(where: { $0.packageRef.name == packageName && $0.targetName == targetName }) |
| 168 | + } |
| 169 | + |
| 170 | + public func add(_ artifact: ManagedArtifact) { |
| 171 | + artifactMap[artifact.packageRef.path, default: [:]][artifact.targetName] = artifact |
| 172 | + } |
| 173 | + |
| 174 | + public func remove(packageURL: String, targetName: String) { |
| 175 | + artifactMap[packageURL]?[targetName] = nil |
| 176 | + } |
| 177 | +} |
| 178 | + |
| 179 | +// MARK: - Collection |
| 180 | + |
| 181 | +extension ManagedArtifacts: Collection { |
| 182 | + public var startIndex: AnyIndex { |
| 183 | + artifacts.startIndex |
| 184 | + } |
| 185 | + |
| 186 | + public var endIndex: AnyIndex { |
| 187 | + artifacts.endIndex |
| 188 | + } |
| 189 | + |
| 190 | + public subscript(index: AnyIndex) -> ManagedArtifact { |
| 191 | + artifacts[index] |
| 192 | + } |
| 193 | + |
| 194 | + public func index(after index: AnyIndex) -> AnyIndex { |
| 195 | + artifacts.index(after: index) |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +// MARK: - JSON |
| 200 | + |
| 201 | +extension ManagedArtifacts: JSONMappable, JSONSerializable { |
| 202 | + public convenience init(json: JSON) throws { |
| 203 | + let artifacts = try Array<ManagedArtifact>(json: json) |
| 204 | + let artifactsByPackagePath = Dictionary(grouping: artifacts, by: { $0.packageRef.path }) |
| 205 | + let artifactMap = artifactsByPackagePath.mapValues({ artifacts in |
| 206 | + Dictionary(uniqueKeysWithValues: artifacts.lazy.map({ ($0.targetName, $0) })) |
| 207 | + }) |
| 208 | + self.init(artifactMap: artifactMap) |
| 209 | + } |
| 210 | + |
| 211 | + public func toJSON() -> JSON { |
| 212 | + artifacts.toJSON() |
| 213 | + } |
| 214 | +} |
| 215 | + |
| 216 | +// MARK: - CustomStringConvertible |
| 217 | + |
| 218 | +extension ManagedArtifacts: CustomStringConvertible { |
| 219 | + public var description: String { |
| 220 | + "<ManagedArtifacts: \(Array(artifacts))>" |
| 221 | + } |
| 222 | +} |
| 223 | + |
0 commit comments