Skip to content

Commit

Permalink
[PackageLoading] Filter versions containing older tools version
Browse files Browse the repository at this point in the history
We were only doing this for root packages, we also need to do this check
during dependency resolution.

<rdar://problem/44395499> Alamofire as a dependency shows manifest parse error instead of tools version message
  • Loading branch information
aciidgh committed Oct 18, 2018
1 parent a56c33d commit 3fedbbd
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 22 deletions.
4 changes: 2 additions & 2 deletions Sources/Commands/SwiftPackageTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
// FIXME: Probably lift this error defination to ToolsVersion.
throw ToolsVersionLoader.Error.malformed(specifier: value, file: pkg)
}
try writeToolsVersion(at: pkg, version: toolsVersion, fs: &localFileSystem)
try writeToolsVersion(at: pkg, version: toolsVersion, fs: localFileSystem)

case .setCurrent:
// Write the tools version with current version but with patch set to zero.
// We do this to avoid adding unnecessary constraints to patch versions, if
// the package really needs it, they can do it using --set option.
try writeToolsVersion(
at: pkg, version: ToolsVersion.currentToolsVersion.zeroedPatch, fs: &localFileSystem)
at: pkg, version: ToolsVersion.currentToolsVersion.zeroedPatch, fs: localFileSystem)
}

case .generateXcodeproj:
Expand Down
10 changes: 9 additions & 1 deletion Sources/PackageGraph/RepositoryPackageContainerProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,15 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve

// Otherwise, compute and cache the result.
let isValid = (try? self.toolsVersion(for: $0)).flatMap({
self.currentToolsVersion >= $0
guard $0 >= ToolsVersion.minimumRequired else {
return false
}

guard self.currentToolsVersion >= $0 else {
return false
}

return true
}) ?? false
self.validToolsVersionsCache[$0] = isValid

Expand Down
5 changes: 0 additions & 5 deletions Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ public enum ModuleError: Swift.Error {
/// We found multiple LinuxMain.swift files.
case multipleLinuxMainFound(package: String, linuxMainFiles: [AbsolutePath])

/// The package should support version 3 compiler but doesn't.
case mustSupportSwift3Compiler(package: String)

/// The tools version in use is not compatible with target's sources.
case incompatibleToolsVersions(package: String, required: [SwiftLanguageVersion], current: ToolsVersion)

Expand Down Expand Up @@ -82,8 +79,6 @@ extension ModuleError: CustomStringConvertible {
case .multipleLinuxMainFound(let package, let linuxMainFiles):
let files = linuxMainFiles.map({ $0.asString }).sorted().joined(separator: ", ")
return "package '\(package)' has multiple linux main files: \(files)"
case .mustSupportSwift3Compiler(let package):
return "package '\(package)' must support Swift 3 because its minimum tools version is 3"
case .incompatibleToolsVersions(let package, let required, let current):
if required.isEmpty {
return "package '\(package)' supported Swift language versions is empty"
Expand Down
3 changes: 3 additions & 0 deletions Sources/PackageModel/ToolsVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public struct ToolsVersion: CustomStringConvertible, Comparable {
"\(Versioning.currentVersion.minor)." +
"\(Versioning.currentVersion.patch)")!

/// The minimum tools version that is required by the package manager.
public static let minimumRequired: ToolsVersion = .v4

/// Regex pattern to parse tools version. The format is SemVer 2.0 with an
/// addition that specifying the patch version is optional.
static let toolsVersionRegex = try! NSRegularExpression(pattern: "^" +
Expand Down
13 changes: 11 additions & 2 deletions Sources/TestSupport/TestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public final class TestWorkspace {
let sourcesDir = packagePath.appending(component: "Sources")
let url = (isRoot ? packagePath : packagesDir.appending(RelativePath(package.path ?? package.name))).asString
let specifier = RepositorySpecifier(url: url)
let manifestPath = packagePath.appending(component: Manifest.filename)

// Create targets on disk.
let repo = repoProvider.specifierMap[specifier] ?? InMemoryGitRepository(path: packagePath, fs: fs as! InMemoryFileSystem)
Expand All @@ -86,14 +87,18 @@ public final class TestWorkspace {
try repo.createDirectory(targetDir, recursive: true)
try repo.writeFileContents(targetDir.appending(component: "file.swift"), bytes: "")
}
if let toolsVersion = package.toolsVersion {
try repo.writeFileContents(manifestPath, bytes: "")
try writeToolsVersion(at: packagePath, version: toolsVersion, fs: repo)
}
repo.commit()

let versions: [String?] = isRoot ? [nil] : package.versions
for version in versions {
let v = version.flatMap(Version.init(string:))
manifests[.init(url: url, version: v)] = Manifest(
name: package.name,
path: packagePath.appending(component: Manifest.filename),
path: manifestPath,
url: url,
version: v,
manifestVersion: .v4,
Expand Down Expand Up @@ -466,21 +471,25 @@ public struct TestPackage {
public let products: [TestProduct]
public let dependencies: [TestDependency]
public let versions: [String?]
// FIXME: This should be per-version.
public let toolsVersion: ToolsVersion?

public init(
name: String,
path: String? = nil,
targets: [TestTarget],
products: [TestProduct],
dependencies: [TestDependency] = [],
versions: [String?] = []
versions: [String?] = [],
toolsVersion: ToolsVersion? = nil
) {
self.name = name
self.path = path
self.targets = targets
self.products = products
self.dependencies = dependencies
self.versions = versions
self.toolsVersion = toolsVersion
}

public static func genericPackage1(named name: String) -> TestPackage {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Workspace/InitPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public final class InitPackage {

// Write the current tools version.
try writeToolsVersion(
at: manifest.parentDirectory, version: version, fs: &localFileSystem)
at: manifest.parentDirectory, version: version, fs: localFileSystem)
}

private func writeREADMEFile() throws {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Workspace/ToolsVersionWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Utility
/// - Parameters:
/// - path: The path of the package.
/// - version: The version to write.
public func writeToolsVersion(at path: AbsolutePath, version: ToolsVersion, fs: inout FileSystem) throws {
public func writeToolsVersion(at path: AbsolutePath, version: ToolsVersion, fs: FileSystem) throws {
let file = path.appending(component: Manifest.filename)
assert(fs.isFile(file), "Tools version file not present")

Expand Down
2 changes: 1 addition & 1 deletion Sources/Workspace/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@ extension Workspace {
at: packagePath, fileSystem: fileSystem)

// Make sure the package has the right minimum tools version.
guard toolsVersion >= .v4 else {
guard toolsVersion >= ToolsVersion.minimumRequired else {
throw WorkspaceDiagnostics.IncompatibleToolsVersion(
rootPackagePath: packagePath,
requiredToolsVersion: .v4,
Expand Down
4 changes: 4 additions & 0 deletions Tests/FunctionalTests/VersionSpecificTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class VersionSpecificTests: XCTestCase {

// Create the version to test against.
try fs.writeFileContents(depPath.appending(component: "Package.swift")) {
// FIXME: We end up filtering this manifest if it has an invalid
// tools version as they're assumed to be v3 manifests. Should we
// do something better?
$0 <<< "// swift-tools-version:4.2\n"
$0 <<< "NOT_A_VALID_PACKAGE"
}
try fs.writeFileContents(depPath.appending(component: "foo.swift")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,14 +280,18 @@ class RepositoryPackageContainerProviderTests: XCTestCase {
repo.commit()
try repo.tag(name: "1.0.0")

try repo.writeFileContents(filePath, bytes: "// swift-tools-version:3.1.0;hello\n")
try repo.writeFileContents(filePath, bytes: "// swift-tools-version:4.0")
repo.commit()
try repo.tag(name: "1.0.1")

try repo.writeFileContents(filePath, bytes: "// swift-tools-version:4.0.0\n")
try repo.writeFileContents(filePath, bytes: "// swift-tools-version:4.2.0;hello\n")
repo.commit()
try repo.tag(name: "1.0.2")

try repo.writeFileContents(filePath, bytes: "// swift-tools-version:4.2.0\n")
repo.commit()
try repo.tag(name: "1.0.3")

let inMemRepoProvider = InMemoryGitRepositoryProvider()
inMemRepoProvider.add(specifier: specifier, repository: repo)

Expand All @@ -307,21 +311,21 @@ class RepositoryPackageContainerProviderTests: XCTestCase {
}

do {
let provider = createProvider(ToolsVersion(version: "3.1.0"))
let provider = createProvider(ToolsVersion(version: "4.0.0"))
let ref = PackageReference(identity: "foo", path: specifier.url)
let container = try await { provider.getContainer(for: ref, completion: $0) }
let v = container.versions(filter: { _ in true }).map{$0}
XCTAssertEqual(v, ["1.0.1", "1.0.0"])
XCTAssertEqual(v, ["1.0.1"])
}

do {
let provider = createProvider(ToolsVersion(version: "4.0.0"))
let provider = createProvider(ToolsVersion(version: "4.2.0"))
let ref = PackageReference(identity: "foo", path: specifier.url)
let container = try await { provider.getContainer(for: ref, completion: $0) }
XCTAssertEqual((container as! RepositoryPackageContainer).validToolsVersionsCache, [:])
let v = container.versions(filter: { _ in true }).map{$0}
XCTAssertEqual((container as! RepositoryPackageContainer).validToolsVersionsCache, ["1.0.1": true, "1.0.0": true, "1.0.2": true])
XCTAssertEqual(v, ["1.0.2", "1.0.1", "1.0.0"])
XCTAssertEqual((container as! RepositoryPackageContainer).validToolsVersionsCache, ["1.0.1": true, "1.0.0": false, "1.0.3": true, "1.0.2": true])
XCTAssertEqual(v, ["1.0.3", "1.0.2", "1.0.1"])
}

do {
Expand Down
4 changes: 2 additions & 2 deletions Tests/WorkspaceTests/ToolsVersionWriterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ class ToolsVersionWriterTests: XCTestCase {
_ result: (ByteString) -> Void
) {
do {
var fs: FileSystem = InMemoryFileSystem()
let fs: FileSystem = InMemoryFileSystem()

let file = AbsolutePath("/pkg/Package.swift")

try fs.createDirectory(file.parentDirectory, recursive: true)
try fs.writeFileContents(file, bytes: stream.bytes)

try writeToolsVersion(
at: file.parentDirectory, version: version, fs: &fs)
at: file.parentDirectory, version: version, fs: fs)

result(try fs.readFileContents(file))
} catch {
Expand Down
42 changes: 42 additions & 0 deletions Tests/WorkspaceTests/WorkspaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,48 @@ final class WorkspaceTests: XCTestCase {
}
}

func testMinimumRequiredToolsVersionInDependencyResolution() throws {
let sandbox = AbsolutePath("/tmp/ws/")
let fs = InMemoryFileSystem()

let workspace = try TestWorkspace(
sandbox: sandbox,
fs: fs,
roots: [
TestPackage(
name: "Root",
targets: [
TestTarget(name: "Root", dependencies: ["Foo"]),
],
products: [],
dependencies: [
TestDependency(name: "Foo", requirement: .upToNextMajor(from: "1.0.0")),
]
),
],
packages: [
TestPackage(
name: "Foo",
targets: [
TestTarget(name: "Foo"),
],
products: [
TestProduct(name: "Foo", targets: ["Foo"]),
],
versions: ["1.0.0"],
toolsVersion: .v3
),
]
)

workspace.checkPackageGraph(roots: ["Root"]) { (graph, diagnostics) in
DiagnosticsEngineTester(diagnostics) { result in
result.check(diagnostic: .contains("/tmp/ws/pkgs/Foo @ 1.0.0..<2.0.0"), behavior: .error)
result.check(diagnostic: .contains("product dependency 'Foo' not found"), behavior: .error, location: "'Root' /tmp/ws/roots/Root")
}
}
}

func testToolsVersionRootPackages() throws {
let sandbox = AbsolutePath("/tmp/ws/")
let fs = InMemoryFileSystem()
Expand Down
1 change: 1 addition & 0 deletions Tests/WorkspaceTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ extension WorkspaceTests {
("testLocalDependencyWithPackageUpdate", testLocalDependencyWithPackageUpdate),
("testLocalLocalSwitch", testLocalLocalSwitch),
("testLocalVersionSwitch", testLocalVersionSwitch),
("testMinimumRequiredToolsVersionInDependencyResolution", testMinimumRequiredToolsVersionInDependencyResolution),
("testMissingEditCanRestoreOriginalCheckout", testMissingEditCanRestoreOriginalCheckout),
("testMultipleRootPackages", testMultipleRootPackages),
("testPackageMirror", testPackageMirror),
Expand Down

0 comments on commit 3fedbbd

Please sign in to comment.