Skip to content

Commit 3d1764e

Browse files
Merge pull request #10 from SwiftPackageIndex/support-Package.resolved-v2
Support package.resolved v2
2 parents c4807e9 + 7f9b550 commit 3d1764e

File tree

12 files changed

+380
-75
lines changed

12 files changed

+380
-75
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
os: [macos-11, ubuntu-20.04]
27+
# https://github.com/actions/virtual-environments
28+
os: [macos-12, macos-11, ubuntu-20.04]
2829

2930
runs-on: ${{ matrix.os }}
3031
env:
@@ -39,7 +40,8 @@ jobs:
3940
strategy:
4041
fail-fast: false
4142
matrix:
42-
os: [macos-11, ubuntu-20.04]
43+
# https://github.com/actions/virtual-environments
44+
os: [macos-12, macos-11, ubuntu-20.04]
4345

4446
runs-on: ${{ matrix.os }}
4547
env:
@@ -54,7 +56,8 @@ jobs:
5456
strategy:
5557
fail-fast: false
5658
matrix:
57-
os: [macos-11, ubuntu-20.04]
59+
# https://github.com/actions/virtual-environments
60+
os: [macos-12, macos-11, ubuntu-20.04]
5861

5962
runs-on: ${{ matrix.os }}
6063
env:

Package.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,11 @@ let package = Package(
2525
.executable(name: "swift-release-notes", targets: ["swift-release-notes"]),
2626
],
2727
dependencies: [
28-
.package(name: "SemanticVersion",
29-
url: "https://github.com/SwiftPackageIndex/SemanticVersion",
28+
.package(url: "https://github.com/SwiftPackageIndex/SemanticVersion",
3029
from: "0.3.1"),
31-
.package(name: "swift-argument-parser",
32-
url: "https://github.com/apple/swift-argument-parser",
30+
.package(url: "https://github.com/apple/swift-argument-parser",
3331
from: "1.0.0"),
34-
.package(name: "swift-parsing",
35-
url: "https://github.com/pointfreeco/swift-parsing",
32+
.package(url: "https://github.com/pointfreeco/swift-parsing",
3633
from: "0.4.1"),
3734
],
3835
targets: [
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2022 Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
18+
enum PackageResolved {
19+
case v1(V1)
20+
case v2(V2)
21+
22+
// object:
23+
// pins:
24+
// - package: String
25+
// repositoryURL: URL
26+
// state:
27+
// branch: String?
28+
// revision: CommitHash
29+
// version: SemVer?
30+
// - ...
31+
// version: 1
32+
struct V1: Decodable, Equatable {
33+
var object: Object
34+
35+
struct Object: Decodable, Equatable {
36+
var pins: [Pin]
37+
38+
struct Pin: Decodable, Equatable {
39+
var package: String
40+
var repositoryURL: URL
41+
}
42+
}
43+
}
44+
45+
// pins:
46+
// - identity: String
47+
// location: URL
48+
// state:
49+
// revision: CommitHash
50+
// version: SemVer?
51+
// - ...
52+
// version: 2
53+
struct V2: Decodable, Equatable {
54+
var pins: [Pin]
55+
56+
struct Pin: Decodable, Equatable {
57+
var identity: String
58+
var location: URL
59+
}
60+
}
61+
}
62+
63+
64+
extension PackageResolved {
65+
var v1: V1? {
66+
switch self {
67+
case let .v1(value):
68+
return value
69+
case .v2:
70+
return nil
71+
}
72+
}
73+
74+
var v2: V2? {
75+
switch self {
76+
case .v1:
77+
return nil
78+
case let .v2(value):
79+
return value
80+
}
81+
}
82+
}
83+
84+
85+
extension PackageResolved {
86+
func getPackageMap() -> [PackageId: URL] {
87+
switch self {
88+
case let .v1(value):
89+
return Dictionary(value.object.pins
90+
.map { ($0.package, $0.repositoryURL) },
91+
uniquingKeysWith: { first, _ in first })
92+
93+
case let .v2(value):
94+
return Dictionary(value.pins
95+
.map { ($0.identity, $0.location) },
96+
uniquingKeysWith: { first, _ in first })
97+
}
98+
}
99+
}
100+
101+
102+
extension PackageResolved: Decodable {
103+
init(from decoder: Decoder) throws {
104+
if let value = try? decoder.singleValueContainer().decode(V1.self) {
105+
self = .v1(value)
106+
return
107+
}
108+
if let value = try? decoder.singleValueContainer().decode(V2.self) {
109+
self = .v2(value)
110+
return
111+
}
112+
113+
throw DecodingError(message: "failed to decode PackageResolved")
114+
}
115+
}
116+
117+
118+
struct DecodingError: Error {
119+
var message: String
120+
}

Sources/ReleaseNotesCore/Parser.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ enum Parser {
6060
Prefix { $0 != newPackageToken && $0 != updatedRevisionToken }
6161
}
6262

63-
static let newPackage = Parse { Update(packageName: $0, oldRevision: nil) } with: {
63+
static let newPackage = Parse { Update(packageId: $0, oldRevision: nil) } with: {
6464
Skip {
6565
upToStart
6666
"\(newPackageToken) "
@@ -71,7 +71,7 @@ enum Parser {
7171
}
7272
}
7373

74-
static let updatedRevision = Parse(Update.init(packageName:oldRevision:)) {
74+
static let updatedRevision = Parse(Update.init(packageId:oldRevision:)) {
7575
Skip {
7676
upToStart
7777
"\(updatedRevisionToken) "

Sources/ReleaseNotesCore/ReleaseNotes.swift

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import ArgumentParser
1616
import Foundation
1717

1818

19-
typealias PackageName = String
19+
typealias PackageId = String
2020

2121

2222
struct ReleaseNotes: AsyncParsableCommand {
@@ -25,12 +25,14 @@ struct ReleaseNotes: AsyncParsableCommand {
2525
var workingDirecory: String = "."
2626

2727
func runAsync() async throws {
28-
guard let packageMap = getPackageMap(at: workingDirecory) else {
29-
print("Failed to parse Package.resolved in \(workingDirecory).")
28+
let path = URL(fileURLWithPath: workingDirecory)
29+
.appendingPathComponent("Package.resolved").path
30+
guard let packageMap = Self.getPackageMap(at: path) else {
31+
print("Failed to parse \(path).")
3032
return
3133
}
3234

33-
guard let output = try runPackageUpdate() else {
35+
guard let output = try Self.runPackageUpdate(in: workingDirecory) else {
3436
print("Package update did not return any changes.")
3537
return
3638
}
@@ -51,15 +53,15 @@ struct ReleaseNotes: AsyncParsableCommand {
5153

5254
print("\nRelease notes URLs (updating from):")
5355
for update in updates {
54-
let releasesURL = packageMap[update.packageName]
56+
let releasesURL = packageMap[caseIgnoring: update.packageId]
5557
.map { $0.absoluteString.droppingGitExtension + "/releases" }
56-
?? "\(update.packageName)"
58+
?? "\(update.packageId)"
5759
print(releasesURL, "(\(update.oldRevision?.description ?? "new package"))")
5860
}
5961
}
6062

61-
func runPackageUpdate() throws -> String? {
62-
let process = updateProcess()
63+
static func runPackageUpdate(in workingDirecory: String) throws -> String? {
64+
let process = updateProcess(workingDirecory: workingDirecory)
6365
let pipe = Pipe()
6466
process.standardOutput = pipe
6567

@@ -80,7 +82,7 @@ struct ReleaseNotes: AsyncParsableCommand {
8082
return stdout
8183
}
8284

83-
func updateProcess() -> Process {
85+
static func updateProcess(workingDirecory: String) -> Process {
8486
let process = Process()
8587
process.executableURL = URL(fileURLWithPath: "/bin/bash")
8688
process.arguments = [
@@ -90,43 +92,23 @@ struct ReleaseNotes: AsyncParsableCommand {
9092
return process
9193
}
9294

93-
func getPackageMap(at path: String) -> [PackageName: URL]? {
94-
// object:
95-
// pins:
96-
// - package: String
97-
// repositoryURL: URL
98-
// state:
99-
// branch: String?
100-
// revision: CommitHash
101-
// version: SemVer?
102-
// - ...
103-
// version: 1
104-
struct PackageResolved: Decodable {
105-
var object: Object
106-
107-
struct Object: Decodable {
108-
var pins: [Pin]
109-
110-
struct Pin: Decodable {
111-
var package: String
112-
var repositoryURL: URL
113-
}
114-
}
115-
}
116-
117-
let filePath = URL(fileURLWithPath: path)
118-
.appendingPathComponent("Package.resolved").path
119-
guard FileManager.default.fileExists(atPath: filePath),
120-
let json = FileManager.default.contents(atPath: filePath),
95+
static func getPackageMap(at path: String) -> [PackageId: URL]? {
96+
guard FileManager.default.fileExists(atPath: path),
97+
let json = FileManager.default.contents(atPath: path),
12198
let packageResolved = try? JSONDecoder()
12299
.decode(PackageResolved.self, from: json)
123100
else {
124101
return nil
125102
}
126103

127-
return Dictionary(packageResolved.object.pins
128-
.map { ($0.package, $0.repositoryURL) },
129-
uniquingKeysWith: { first, _ in first })
104+
return packageResolved.getPackageMap()
130105
}
131106

132107
}
108+
109+
110+
private extension Dictionary where Key == PackageId, Value == URL {
111+
subscript(caseIgnoring packageId: PackageId) -> URL? {
112+
first(where: { $0.key.lowercased() == packageId.lowercased() })?.value
113+
}
114+
}

Sources/ReleaseNotesCore/Update.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
// limitations under the License.
1414

1515
struct Update: CustomStringConvertible, Equatable {
16-
var packageName: PackageName
16+
var packageId: PackageId
1717
var oldRevision: Revision?
1818

1919
var description: String {
2020
if let oldRevision = oldRevision {
21-
return "\(packageName) @ \(oldRevision)"
21+
return "\(packageId) @ \(oldRevision)"
2222
} else {
23-
return "\(packageName) (new package)"
23+
return "\(packageId) (new package)"
2424
}
2525
}
2626
}

Tests/ReleaseNotesTests/Fixtures/Package.resolved-v1.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)