Skip to content

Commit 9fa45d6

Browse files
authored
fix VersionSetSpecifier calculation for versions with pre-release identifiers (#6492) (#6495)
motivation: some version calculations were incorrect when dealing with pre-release identifiers, leading to dependency resolution errors changes: * fix Version::nextPatch() to take into pre-release identifiers into account * add tests
1 parent d30259e commit 9fa45d6

File tree

5 files changed

+163
-11
lines changed

5 files changed

+163
-11
lines changed

Sources/PackageGraph/PubGrub/PartialSolution.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public struct PartialSolution {
5555
/// Create a new derivation assignment and add it to the partial solution's
5656
/// list of known assignments.
5757
public mutating func derive(_ term: Term, cause: Incompatibility) {
58-
let derivation = Assignment.derivation(term, cause: cause, decisionLevel: decisionLevel)
58+
let derivation = Assignment.derivation(term, cause: cause, decisionLevel: self.decisionLevel)
5959
self.assignments.append(derivation)
6060
register(derivation)
6161
}

Sources/PackageGraph/PubGrub/PubGrubDependencyResolver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,9 @@ public struct PubGrubDependencyResolver {
514514
return .conflict
515515
}
516516

517-
self.delegate?.derived(term: unsatisfiedTerm.inverse)
518-
state.derive(unsatisfiedTerm.inverse, cause: incompatibility)
519517

518+
state.derive(unsatisfiedTerm.inverse, cause: incompatibility)
519+
self.delegate?.derived(term: unsatisfiedTerm.inverse)
520520
return .almostSatisfied(node: unsatisfiedTerm.node)
521521
}
522522

Sources/PackageGraph/Version+Extensions.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import struct TSCUtility.Version
1414

1515
extension Version {
1616
func nextPatch() -> Version {
17-
return Version(major, minor, patch + 1)
17+
if self.prereleaseIdentifiers.isEmpty {
18+
return Version(self.major, self.minor, self.patch + 1)
19+
} else {
20+
return Version(self.major, self.minor, self.patch, prereleaseIdentifiers: self.prereleaseIdentifiers + ["0"])
21+
}
1822
}
1923
}

Tests/PackageGraphTests/PubgrubTests.swift

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ final class PubgrubTests: XCTestCase {
227227
XCTAssertEqual(a2?.requirement, .range(v1..<v1_5))
228228
}
229229

230-
func testSolutionUndecided() {
230+
func testSolutionUndecided() throws {
231231
var solution = PartialSolution()
232232
solution.derive("a^1.0.0", cause: rootCause)
233233
solution.decide(.empty(package: "b"), at: v2)
@@ -240,7 +240,7 @@ final class PubgrubTests: XCTestCase {
240240
XCTAssertEqual(undecided, [Term("a^1.5.0"), Term("d^1.9.9")])
241241
}
242242

243-
func testSolutionAddAssignments() {
243+
func testSolutionAddAssignments() throws {
244244
let root = Term(rootNode, .exact("1.0.0"))
245245
let a = Term("[email protected]")
246246
let b = Term("[email protected]")
@@ -404,7 +404,7 @@ final class PubgrubTests: XCTestCase {
404404
XCTAssertEqual(state2.solution.assignments.count, 2)
405405
}
406406

407-
func testSolutionFindSatisfiers() {
407+
func testSolutionFindSatisfiers() throws {
408408
var solution = PartialSolution()
409409
solution.decide(rootNode, at: v1) // ← previous, but actually nil because this is the root decision
410410
solution.derive(Term(.product("a", package: aRef), .any), cause: _cause) // ← satisfier
@@ -1168,6 +1168,37 @@ final class PubgrubTests: XCTestCase {
11681168
])
11691169
}
11701170

1171+
// top level package -> beta version
1172+
// beta version -> version
1173+
func testHappyPath10() throws {
1174+
let package = PackageReference.root(identity: .plain("package"), path: .root)
1175+
try builder.serve(package, at: .unversioned, with: [
1176+
"module": [
1177+
"foo": (.versionSet(.range("1.0.0-alpha" ..< "2.0.0")), .specific(["foo"]))
1178+
]])
1179+
try builder.serve("foo", at: "1.0.0-alpha.1", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]])
1180+
try builder.serve("foo", at: "1.0.0-alpha.2", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]])
1181+
try builder.serve("foo", at: "1.0.0-beta.1", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]])
1182+
try builder.serve("foo", at: "1.0.0-beta.2", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]])
1183+
try builder.serve("foo", at: "1.0.0-beta.3", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]])
1184+
try builder.serve("bar", at: v1)
1185+
try builder.serve("bar", at: v1_1)
1186+
try builder.serve("bar", at: v1_5)
1187+
1188+
let resolver = builder.create()
1189+
let dependencies = builder.create(dependencies: [
1190+
package: (.unversioned, .everything)
1191+
])
1192+
1193+
let result = resolver.solve(constraints: dependencies)
1194+
1195+
AssertResult(result, [
1196+
("package", .unversioned),
1197+
("foo", .version("1.0.0-beta.3")),
1198+
("bar", .version(v1_5))
1199+
])
1200+
}
1201+
11711202
func testResolutionWithSimpleBranchBasedDependency() throws {
11721203
try builder.serve("foo", at: .revision("master"), with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]])
11731204
try builder.serve("bar", at: v1)
@@ -1826,16 +1857,16 @@ final class PubGrubTestsBasicGraphs: XCTestCase {
18261857
final class PubGrubDiagnosticsTests: XCTestCase {
18271858

18281859
func testMissingVersion() throws {
1829-
try builder.serve("foopkg", at: v1_1)
1860+
try builder.serve("package", at: v1_1)
18301861

18311862
let resolver = builder.create()
18321863
let dependencies = try builder.create(dependencies: [
1833-
"foopkg": (.versionSet(v2Range), .specific(["foopkg"])),
1864+
"package": (.versionSet(v2Range), .specific(["package"])),
18341865
])
18351866
let result = resolver.solve(constraints: dependencies)
18361867

18371868
XCTAssertEqual(result.errorMsg, """
1838-
Dependencies could not be resolved because no versions of 'foopkg' match the requirement 2.0.0..<3.0.0 and root depends on 'foopkg' 2.0.0..<3.0.0.
1869+
Dependencies could not be resolved because no versions of 'package' match the requirement 2.0.0..<3.0.0 and root depends on 'package' 2.0.0..<3.0.0.
18391870
""")
18401871
}
18411872

@@ -1853,6 +1884,74 @@ final class PubGrubDiagnosticsTests: XCTestCase {
18531884
""")
18541885
}
18551886

1887+
func testResolutionNonExistentBetaVersion() throws {
1888+
try builder.serve("package", at: "0.0.1")
1889+
1890+
let resolver = builder.create()
1891+
let dependencies = try builder.create(dependencies: [
1892+
"package": (.versionSet(.range("1.0.0-beta" ..< "2.0.0")), .specific(["package"])),
1893+
])
1894+
let result = resolver.solve(constraints: dependencies)
1895+
1896+
XCTAssertEqual(result.errorMsg, """
1897+
Dependencies could not be resolved because no versions of 'package' match the requirement 1.0.0-beta..<2.0.0 and root depends on 'package' 1.0.0-beta..<2.0.0.
1898+
""")
1899+
}
1900+
1901+
func testResolutionNonExistentTransitiveVersion() throws {
1902+
try builder.serve("package", at: v1_5, with: [
1903+
"package": ["foo": (.versionSet(v1Range), .specific(["foo"]))]
1904+
])
1905+
try builder.serve("foo", at: "0.0.1")
1906+
1907+
let resolver = builder.create()
1908+
let dependencies = try builder.create(dependencies: [
1909+
"package": (.versionSet(v1Range), .specific(["package"]))
1910+
])
1911+
let result = resolver.solve(constraints: dependencies)
1912+
1913+
XCTAssertEqual(result.errorMsg, """
1914+
Dependencies could not be resolved because no versions of 'foo' match the requirement 1.0.0..<2.0.0 and root depends on 'package' 1.0.0..<2.0.0.
1915+
'package' practically depends on 'foo' 1.0.0..<2.0.0 because no versions of 'package' match the requirement {1.0.0..<1.5.0, 1.5.1..<2.0.0} and 'package' 1.5.0 depends on 'foo' 1.0.0..<2.0.0.
1916+
""")
1917+
}
1918+
1919+
func testResolutionNonExistentTransitiveBetaVersion() throws {
1920+
try builder.serve("package", at: v1_5, with: [
1921+
"package": ["foo": (.versionSet(.range("1.0.0-beta" ..< "2.0.0")), .specific(["foo"]))]
1922+
])
1923+
try builder.serve("foo", at: "0.0.1")
1924+
1925+
let resolver = builder.create()
1926+
let dependencies = try builder.create(dependencies: [
1927+
"package": (.versionSet(v1Range), .specific(["package"]))
1928+
])
1929+
let result = resolver.solve(constraints: dependencies)
1930+
1931+
XCTAssertEqual(result.errorMsg, """
1932+
Dependencies could not be resolved because no versions of 'foo' match the requirement 1.0.0-beta..<2.0.0 and root depends on 'package' 1.0.0..<2.0.0.
1933+
'package' practically depends on 'foo' 1.0.0-beta..<2.0.0 because no versions of 'package' match the requirement {1.0.0..<1.5.0, 1.5.1..<2.0.0} and 'package' 1.5.0 depends on 'foo' 1.0.0-beta..<2.0.0.
1934+
""")
1935+
}
1936+
1937+
func testResolutionBetaVersionNonExistentTransitiveVersion() throws {
1938+
try builder.serve("package", at: "1.0.0-beta.1", with: [
1939+
"package": ["foo": (.versionSet(v1Range), .specific(["foo"]))]
1940+
])
1941+
try builder.serve("foo", at: "0.0.1")
1942+
1943+
let resolver = builder.create()
1944+
let dependencies = try builder.create(dependencies: [
1945+
"package": (.versionSet(.range("1.0.0-beta" ..< "2.0.0")), .specific(["package"])),
1946+
])
1947+
let result = resolver.solve(constraints: dependencies)
1948+
1949+
XCTAssertEqual(result.errorMsg, """
1950+
Dependencies could not be resolved because no versions of 'foo' match the requirement 1.0.0..<2.0.0 and root depends on 'package' 1.0.0-beta..<2.0.0.
1951+
'package' practically depends on 'foo' 1.0.0..<2.0.0 because no versions of 'package' match the requirement {1.0.0-beta..<1.0.0-beta.1, 1.0.0-beta.1.0..<2.0.0} and 'package' 1.0.0-beta.1 depends on 'foo' 1.0.0..<2.0.0.
1952+
""")
1953+
}
1954+
18561955
func testResolutionLinearErrorReporting() throws {
18571956
try builder.serve("foo", at: v1, with: ["foo": ["bar": (.versionSet(v2Range), .specific(["bar"]))]])
18581957
try builder.serve("bar", at: v2, with: ["bar": ["baz": (.versionSet(.range("3.0.0"..<"4.0.0")), .specific(["baz"]))]])
@@ -2355,6 +2454,8 @@ final class PubGrubDiagnosticsTests: XCTestCase {
23552454
try builder.serve("foo", at: v1_1, with: [
23562455
"foo": ["bar": (.revision("master"), .specific(["bar"]))]
23572456
])
2457+
try builder.serve("bar", at: .revision("master"))
2458+
23582459
let resolver = builder.create()
23592460
let dependencies = try builder.create(dependencies: [
23602461
"foo": (.versionSet(v1Range), .specific(["foo"])),
@@ -2367,10 +2468,12 @@ final class PubGrubDiagnosticsTests: XCTestCase {
23672468
""")
23682469
}
23692470

2370-
func testNonVersionDependencyInVersionDependency3() throws {
2471+
func testNonVersionDependencyInVersionDependency2() throws {
23712472
try builder.serve("foo", at: v1, with: [
23722473
"foo": ["bar": (.unversioned, .specific(["bar"]))]
23732474
])
2475+
try builder.serve("bar", at: .unversioned)
2476+
23742477
let resolver = builder.create()
23752478
let dependencies = try builder.create(dependencies: [
23762479
"foo": (.versionSet(.exact(v1)), .specific(["foo"])),
@@ -2382,6 +2485,31 @@ final class PubGrubDiagnosticsTests: XCTestCase {
23822485
""")
23832486
}
23842487

2488+
func testNonVersionDependencyInVersionDependency3() throws {
2489+
try builder.serve("foo", at: "1.0.0-beta.1", with: [
2490+
"foo": ["bar": (.revision("master"), .specific(["bar"]))]
2491+
])
2492+
try builder.serve("foo", at: "1.0.0-beta.2", with: [
2493+
"foo": ["bar": (.revision("master"), .specific(["bar"]))]
2494+
])
2495+
try builder.serve("foo", at: "1.0.0-beta.3", with: [
2496+
"foo": ["bar": (.revision("master"), .specific(["bar"]))]
2497+
])
2498+
try builder.serve("bar", at: .revision("master"))
2499+
2500+
let resolver = builder.create()
2501+
let dependencies = try builder.create(dependencies: [
2502+
"foo": (.versionSet(.range("1.0.0-beta" ..< "2.0.0")), .specific(["foo"])),
2503+
])
2504+
let result = resolver.solve(constraints: dependencies)
2505+
2506+
XCTAssertEqual(result.errorMsg, """
2507+
Dependencies could not be resolved because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar' and root depends on 'foo' 1.0.0-beta..<2.0.0.
2508+
'foo' {1.0.0-beta..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} cannot be used because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'.
2509+
'foo' {1.0.0-beta..<1.0.0-beta.2, 1.0.0-beta.2.0..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} cannot be used because no versions of 'foo' match the requirement {1.0.0-beta..<1.0.0-beta.1, 1.0.0-beta.1.0..<1.0.0-beta.2, 1.0.0-beta.2.0..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} and package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'.
2510+
""")
2511+
}
2512+
23852513
func testIncompatibleToolsVersion1() throws {
23862514
try builder.serve("a", at: v1, toolsVersion: .v5)
23872515

Tests/PackageGraphTests/VersionSetSpecifierTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ final class VersionSetSpecifierTests: XCTestCase {
9999
XCTAssertEqual(VersionSetSpecifier.ranges(["1.0.0"..<"2.0.0", "2.0.1"..<"5.0.0"]).difference(.range("1.0.0"..<"2.0.0")), .range("2.0.1"..<"5.0.0"))
100100
XCTAssertEqual(VersionSetSpecifier.ranges(["3.2.0"..<"3.2.3", "3.2.4"..<"4.0.0"]).difference(.exact("3.2.2")), .ranges(["3.2.0"..<"3.2.2", "3.2.4"..<"4.0.0"]))
101101
XCTAssertEqual(VersionSetSpecifier.ranges(["3.2.0"..<"3.2.1", "3.2.3"..<"4.0.0"]).difference(.exact("3.2.0")), .range("3.2.3"..<"4.0.0"))
102+
103+
104+
XCTAssertEqual(VersionSetSpecifier.exact("1.0.0-beta").difference(.exact("1.0.0-beta")), .empty)
105+
XCTAssertEqual(VersionSetSpecifier.exact("2.0.0-beta").difference(.exact("1.0.0")), .exact("2.0.0-beta"))
106+
XCTAssertEqual(VersionSetSpecifier.exact("2.0.0-beta").difference(.exact("1.0.0-beta")), .exact("2.0.0-beta"))
107+
108+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"1.0.0-beta").difference(.range("1.0.0-beta"..<"1.0.0-beta")), .empty)
109+
XCTAssertEqual(VersionSetSpecifier.range("2.0.0-beta"..<"2.0.0-beta").difference(.range("1.0.0"..<"2.0.0")), .range("2.0.0-beta"..<"2.0.0-beta"))
110+
XCTAssertEqual(VersionSetSpecifier.range("2.0.0-beta"..<"2.0.0-beta").difference(.range("1.0.0-beta"..<"2.0.0")), .range("2.0.0-beta"..<"2.0.0-beta"))
111+
112+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"2.0.0").difference(.exact("2.0.0")), .range("1.0.0-beta"..<"2.0.0"))
113+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"2.0.0").difference(.exact("1.0.0-beta")), .range("1.0.0-beta.0"..<"2.0.0"))
114+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"2.0.0").difference(.exact("1.0.0-beta.5")), .ranges(["1.0.0-beta"..<"1.0.0-beta.5", "1.0.0-beta.5.0"..<"2.0.0"]))
115+
116+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"2.0.0").difference(.range("1.0.0-beta.3" ..< "2.0.0")), .range("1.0.0-beta"..<"1.0.0-beta.3"))
117+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta.5"..<"1.0.0-beta.30").difference(.range("1.0.0-beta.10" ..< "2.0.0")), .range("1.0.0-beta.5"..<"1.0.0-beta.10"))
118+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"1.0.0-beta.30").difference(.range("1.0.0-beta.3" ..< "1.0.0-beta.10")), .ranges(["1.0.0-beta"..<"1.0.0-beta.3", "1.0.0-beta.10"..<"1.0.0-beta.30"]))
119+
120+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-alpha"..<"2.0.0").difference(.range("1.0.0-beta" ..< "2.0.0")), .range("1.0.0-alpha"..<"1.0.0-beta"))
121+
XCTAssertEqual(VersionSetSpecifier.range("1.0.0-beta"..<"2.0.0").difference(.range("1.0.0-alpha" ..< "2.0.0")), .empty)
102122
}
103123

104124
func testEquality() {

0 commit comments

Comments
 (0)