Skip to content

Commit 16126dd

Browse files
authored
Merge pull request #2423 from hartbit/se0226-manifest-api
Manifest API changes necessary for SE-0226
2 parents 809e35a + 993d0c0 commit 16126dd

23 files changed

+556
-89
lines changed

Documentation/PackageDescription.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ Represents dependency on other targets in the package or products from other pac
578578
public static func target(name: String) -> Target.Dependency
579579

580580
/// A dependency on a product from a package dependency.
581-
public static func product(name: String, package: String? = nil) -> Target.Dependency
581+
public static func product(name: String, package: String) -> Target.Dependency
582582

583583
// A by-name dependency that resolves to either a target or a product,
584584
// as above, after the package graph has been loaded.

Sources/PackageDescription4/Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,18 @@ public final class Package {
147147
}
148148
}
149149

150+
/// The name of the package, or nil to deduce it from the URL.
151+
public let name: String?
152+
150153
/// The Git url of the package dependency.
151154
public let url: String
152155

153156
/// The dependency requirement of the package dependency.
154157
public let requirement: Requirement
155158

156159
/// Initializes and returns a newly allocated requirement with the specified url and requirements.
157-
init(url: String, requirement: Requirement) {
160+
init(name: String?, url: String, requirement: Requirement) {
161+
self.name = name
158162
self.url = url
159163
self.requirement = requirement
160164
}

Sources/PackageDescription4/PackageDependency.swift

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
extension Package.Dependency {
1212

1313
/// Create a package dependency that uses the version requirement, starting with the given minimum version,
14-
/// going up to the next major version.
14+
/// going up to the next major version.
1515
///
1616
/// This is the recommended way to specify a remote package dependency.
1717
/// It allows you to specify the minimum version you require, allows updates that include bug fixes
@@ -26,26 +26,74 @@ extension Package.Dependency {
2626
/// .package(url: "https://example.com/example-package.git", from: "1.2.3"),
2727
///
2828
/// - Parameters:
29+
/// - name: The name of the package, or nil to deduce it from the URL.
2930
/// - url: The valid Git URL of the package.
3031
/// - version: The minimum version requirement.
32+
@available(_PackageDescription, obsoleted: 5.2)
3133
public static func package(
3234
url: String,
3335
from version: Version
3436
) -> Package.Dependency {
35-
return .package(url: url, .upToNextMajor(from: version))
37+
return .init(name: nil, url: url, requirement: .upToNextMajor(from: version))
38+
}
39+
40+
/// Create a package dependency that uses the version requirement, starting with the given minimum version,
41+
/// going up to the next major version.
42+
///
43+
/// This is the recommended way to specify a remote package dependency.
44+
/// It allows you to specify the minimum version you require, allows updates that include bug fixes
45+
/// and backward-compatible feature updates, but requires you to explicitly update to a new major version of the dependency.
46+
/// This approach provides the maximum flexibility on which version to use,
47+
/// while making sure you don't update to a version with breaking changes,
48+
/// and helps to prevent conflicts in your dependency graph.
49+
///
50+
/// The following example allows the Swift package manager to select a version
51+
/// like a `1.2.3`, `1.2.4`, or `1.3.0`, but not `2.0.0`.
52+
///
53+
/// .package(url: "https://example.com/example-package.git", from: "1.2.3"),
54+
///
55+
/// - Parameters:
56+
/// - name: The name of the package, or nil to deduce it from the URL.
57+
/// - url: The valid Git URL of the package.
58+
/// - version: The minimum version requirement.
59+
@available(_PackageDescription, introduced: 5.2)
60+
public static func package(
61+
name: String? = nil,
62+
url: String,
63+
from version: Version
64+
) -> Package.Dependency {
65+
return .init(name: name, url: url, requirement: .upToNextMajor(from: version))
3666
}
3767

3868
/// Add a remote package dependency given a version requirement.
3969
///
4070
/// - Parameters:
71+
/// - name: The name of the package, or nil to deduce it from the URL.
4172
/// - url: The valid Git URL of the package.
4273
/// - requirement: A dependency requirement. See static methods on `Package.Dependency.Requirement` for available options.
74+
@available(_PackageDescription, obsoleted: 5.2)
4375
public static func package(
4476
url: String,
4577
_ requirement: Package.Dependency.Requirement
4678
) -> Package.Dependency {
4779
precondition(!requirement.isLocalPackage, "Use `.package(path:)` API to declare a local package dependency")
48-
return .init(url: url, requirement: requirement)
80+
return .init(name: nil, url: url, requirement: requirement)
81+
}
82+
83+
/// Add a remote package dependency given a version requirement.
84+
///
85+
/// - Parameters:
86+
/// - name: The name of the package, or nil to deduce it from the URL.
87+
/// - url: The valid Git URL of the package.
88+
/// - requirement: A dependency requirement. See static methods on `Package.Dependency.Requirement` for available options.
89+
@available(_PackageDescription, introduced: 5.2)
90+
public static func package(
91+
name: String? = nil,
92+
url: String,
93+
_ requirement: Package.Dependency.Requirement
94+
) -> Package.Dependency {
95+
precondition(!requirement.isLocalPackage, "Use `.package(path:)` API to declare a local package dependency")
96+
return .init(name: name, url: url, requirement: requirement)
4997
}
5098

5199
/// Add a package dependency starting with a specific minimum version, up to
@@ -57,16 +105,73 @@ extension Package.Dependency {
57105
/// .package(url: "https://example.com/example-package.git", "1.2.3"..<"1.2.6"),
58106
///
59107
/// - Parameters:
108+
/// - name: The name of the package, or nil to deduce it from the URL.
60109
/// - url: The valid Git URL of the package.
61110
/// - range: The custom version range requirement.
111+
@available(_PackageDescription, obsoleted: 5.2)
62112
public static func package(
63113
url: String,
64114
_ range: Range<Version>
65115
) -> Package.Dependency {
66116
#if PACKAGE_DESCRIPTION_4
67-
return .init(url: url, requirement: .rangeItem(range))
117+
return .init(name: nil, url: url, requirement: .rangeItem(range))
68118
#else
69-
return .init(url: url, requirement: ._rangeItem(range))
119+
return .init(name: nil, url: url, requirement: ._rangeItem(range))
120+
#endif
121+
}
122+
123+
/// Add a package dependency starting with a specific minimum version, up to
124+
/// but not including a specified maximum version.
125+
///
126+
/// The following example allows the Swift package manager to pick
127+
/// versions `1.2.3`, `1.2.4`, `1.2.5`, but not `1.2.6`.
128+
///
129+
/// .package(url: "https://example.com/example-package.git", "1.2.3"..<"1.2.6"),
130+
///
131+
/// - Parameters:
132+
/// - name: The name of the package, or nil to deduce it from the URL.
133+
/// - url: The valid Git URL of the package.
134+
/// - range: The custom version range requirement.
135+
@available(_PackageDescription, introduced: 5.2)
136+
public static func package(
137+
name: String? = nil,
138+
url: String,
139+
_ range: Range<Version>
140+
) -> Package.Dependency {
141+
#if PACKAGE_DESCRIPTION_4
142+
return .init(name: name, url: url, requirement: .rangeItem(range))
143+
#else
144+
return .init(name: name, url: url, requirement: ._rangeItem(range))
145+
#endif
146+
}
147+
148+
/// Add a package dependency starting with a specific minimum version, going
149+
/// up to and including a specific maximum version.
150+
///
151+
/// The following example allows the Swift package manager to pick
152+
/// versions 1.2.3, 1.2.4, 1.2.5, as well as 1.2.6.
153+
///
154+
/// .package(url: "https://example.com/example-package.git", "1.2.3"..."1.2.6"),
155+
///
156+
/// - Parameters:
157+
/// - name: The name of the package, or nil to deduce it from the URL.
158+
/// - url: The valid Git URL of the package.
159+
/// - range: The closed version range requirement.
160+
@available(_PackageDescription, obsoleted: 5.2)
161+
public static func package(
162+
url: String,
163+
_ range: ClosedRange<Version>
164+
) -> Package.Dependency {
165+
// Increase upperbound's patch version by one.
166+
let upper = range.upperBound
167+
let upperBound = Version(
168+
upper.major, upper.minor, upper.patch + 1,
169+
prereleaseIdentifiers: upper.prereleaseIdentifiers,
170+
buildMetadataIdentifiers: upper.buildMetadataIdentifiers)
171+
#if PACKAGE_DESCRIPTION_4
172+
return .init(name: nil, url: url, requirement: .rangeItem(range.lowerBound..<upperBound))
173+
#else
174+
return .init(name: nil, url: url, requirement: ._rangeItem(range.lowerBound..<upperBound))
70175
#endif
71176
}
72177

@@ -79,9 +184,12 @@ extension Package.Dependency {
79184
/// .package(url: "https://example.com/example-package.git", "1.2.3"..."1.2.6"),
80185
///
81186
/// - Parameters:
187+
/// - name: The name of the package, or nil to deduce it from the URL.
82188
/// - url: The valid Git URL of the package.
83189
/// - range: The closed version range requirement.
190+
@available(_PackageDescription, introduced: 5.2)
84191
public static func package(
192+
name: String? = nil,
85193
url: String,
86194
_ range: ClosedRange<Version>
87195
) -> Package.Dependency {
@@ -91,7 +199,11 @@ extension Package.Dependency {
91199
upper.major, upper.minor, upper.patch + 1,
92200
prereleaseIdentifiers: upper.prereleaseIdentifiers,
93201
buildMetadataIdentifiers: upper.buildMetadataIdentifiers)
94-
return .package(url: url, range.lowerBound..<upperBound)
202+
#if PACKAGE_DESCRIPTION_4
203+
return .init(name: name, url: url, requirement: .rangeItem(range.lowerBound..<upperBound))
204+
#else
205+
return .init(name: name, url: url, requirement: ._rangeItem(range.lowerBound..<upperBound))
206+
#endif
95207
}
96208

97209
#if !PACKAGE_DESCRIPTION_4
@@ -103,10 +215,27 @@ extension Package.Dependency {
103215
/// on multiple tightly coupled packages.
104216
///
105217
/// - Parameter path: The path of the package.
218+
@available(_PackageDescription, obsoleted: 5.2)
219+
public static func package(
220+
path: String
221+
) -> Package.Dependency {
222+
return .init(name: nil, url: path, requirement: ._localPackageItem)
223+
}
224+
225+
/// Add a dependency to a local package on the filesystem.
226+
///
227+
/// The Swift Package Manager uses the package dependency as-is
228+
/// and does not perform any source control access. Local package dependencies
229+
/// are especially useful during development of a new package or when working
230+
/// on multiple tightly coupled packages.
231+
///
232+
/// - Parameter path: The path of the package.
233+
@available(_PackageDescription, introduced: 5.2)
106234
public static func package(
235+
name: String? = nil,
107236
path: String
108237
) -> Package.Dependency {
109-
return .init(url: path, requirement: ._localPackageItem)
238+
return .init(name: name, url: path, requirement: ._localPackageItem)
110239
}
111240
#endif
112241
}

Sources/PackageDescription4/Target.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
*/
1010

1111
/// A target, the basic building block of a Swift package.
12-
///
12+
///
1313
/// Each target contains a set of source files that are compiled into a module or test suite.
1414
/// You can vend targets to other packages by defining products that include the targets.
15-
///
15+
///
1616
/// A target may depend on other targets within the same package and on products vended by the package's dependencies.
1717
public final class Target {
1818

@@ -93,7 +93,7 @@ public final class Target {
9393

9494
/// The `pkgconfig` name to use for a system library target.
9595
///
96-
/// If present, the Swift Package Manager tries to
96+
/// If present, the Swift Package Manager tries to
9797
/// search for the `<name>.pc` file to get the additional flags needed for the
9898
/// system target.
9999
public let pkgConfig: String?
@@ -400,7 +400,7 @@ public final class Target {
400400

401401
#if !PACKAGE_DESCRIPTION_4
402402
/// Create a system library target.
403-
///
403+
///
404404
/// Use system library targets to adapt a library installed on the system to work with Swift packages.
405405
/// Such libraries are generally installed by system package managers (such as Homebrew and apt-get)
406406
/// and exposed to Swift packages by providing a `modulemap` file along with other metadata such as the library's `pkgConfig` name.
@@ -500,6 +500,7 @@ extension Target.Dependency {
500500
/// - parameters:
501501
/// - name: The name of the product.
502502
/// - package: The name of the package.
503+
@available(_PackageDescription, obsoleted: 5.2, message: "the 'package' argument is mandatory as of tools version 5.2")
503504
public static func product(name: String, package: String? = nil) -> Target.Dependency {
504505
#if PACKAGE_DESCRIPTION_4
505506
return .productItem(name: name, package: package)
@@ -508,6 +509,20 @@ extension Target.Dependency {
508509
#endif
509510
}
510511

512+
/// Creates a dependency on a product from a package dependency.
513+
///
514+
/// - parameters:
515+
/// - name: The name of the product.
516+
/// - package: The name of the package.
517+
@available(_PackageDescription, introduced: 5.2)
518+
public static func product(name: String, package: String) -> Target.Dependency {
519+
#if PACKAGE_DESCRIPTION_4
520+
return .productItem(name: name, package: package)
521+
#else
522+
return ._productItem(name: name, package: package)
523+
#endif
524+
}
525+
511526
/// Creates a by-name dependency that resolves to either a target or a product but
512527
/// after the package graph has been loaded.
513528
///

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public enum ManifestParseError: Swift.Error {
2323
case runtimeManifestErrors([String])
2424

2525
case duplicateDependencyDecl([[PackageDependencyDescription]])
26+
27+
case targetDependencyUnknownPackage(targetName: String, packageName: String)
2628
}
2729

2830
/// Resources required for manifest loading.
@@ -230,7 +232,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
230232
baseURL: baseURL,
231233
fileSystem: fileSystem ?? localFileSystem
232234
)
233-
try manifestBuilder.build(v4: json)
235+
try manifestBuilder.build(v4: json, toolsVersion: toolsVersion)
234236

235237
// Throw if we encountered any runtime errors.
236238
guard manifestBuilder.errors.isEmpty else {
@@ -254,17 +256,46 @@ public final class ManifestLoader: ManifestLoaderProtocol {
254256
targets: manifestBuilder.targets
255257
)
256258

257-
try validate(manifest)
259+
try validate(manifest, toolsVersion: toolsVersion)
258260

259261
return manifest
260262
}
261263

262264
/// Validate the provided manifest.
263-
private func validate(_ manifest: Manifest) throws {
265+
private func validate(_ manifest: Manifest, toolsVersion: ToolsVersion) throws {
264266
let duplicateDecls = manifest.dependencies.map({ KeyedPair($0, key: PackageReference.computeIdentity(packageURL: $0.url)) }).spm_findDuplicateElements()
265267
if !duplicateDecls.isEmpty {
266268
throw ManifestParseError.duplicateDependencyDecl(duplicateDecls.map({ $0.map({ $0.item }) }))
267269
}
270+
271+
// If the tools version is 5.2 or greater, we want to make sure all target package dependencies are valid.
272+
if toolsVersion >= .v5_2 {
273+
let targetNames = Set(manifest.targets.map({ $0.name }))
274+
for target in manifest.targets {
275+
for targetDependency in target.dependencies {
276+
// If this is a target dependency (or byName that references a target), we don't need to check.
277+
if case .target = targetDependency { continue }
278+
if case .byName(let name) = targetDependency, targetNames.contains(name) { continue }
279+
280+
// If we can't find the package dependency it references, the manifest is invalid.
281+
if manifest.packageDependency(referencedBy: targetDependency) == nil {
282+
let packageName: String
283+
switch targetDependency {
284+
case .product(_, package: let name?),
285+
.byName(let name):
286+
packageName = name
287+
default:
288+
fatalError("Invalid case: this shouldn't be a target, or a product with no name")
289+
}
290+
291+
throw ManifestParseError.targetDependencyUnknownPackage(
292+
targetName: target.name,
293+
packageName: packageName
294+
)
295+
}
296+
}
297+
}
298+
}
268299
}
269300

270301
/// Load the JSON string for the given manifest.

0 commit comments

Comments
 (0)