Skip to content

Commit d31a027

Browse files
committed
Apply mirrors to root dependencies
We weren't applying mirrors to root dependencies so far. rdar://110869499
1 parent a2bdb72 commit d31a027

File tree

5 files changed

+215
-210
lines changed

5 files changed

+215
-210
lines changed

Sources/PackageGraph/PackageGraphRoot.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,30 @@ public struct PackageGraphRoot {
4747
}
4848

4949
/// The top level dependencies.
50-
public let dependencies: [PackageDependency]
50+
private let _dependencies: [PackageDependency]
51+
52+
public var dependencies: [PackageDependency] {
53+
guard let identityResolver else {
54+
return self._dependencies
55+
}
56+
57+
return self._dependencies.map { dependency in
58+
do {
59+
return try identityResolver.mappedDependency(for: dependency, fileSystem: localFileSystem)
60+
} catch {
61+
return dependency
62+
}
63+
}
64+
}
65+
66+
private let identityResolver: IdentityResolver?
5167

5268
/// Create a package graph root.
5369
/// Note this quietly skip inputs for which manifests are not found. this could be because the manifest failed to load or for some other reasons
5470
// FIXME: This API behavior wrt to non-found manifests is fragile, but required by IDEs
5571
// it may lead to incorrect assumption in downstream code which may expect an error if a manifest was not found
5672
// we should refactor this API to more clearly return errors for inputs that do not have a corresponding manifest
57-
public init(input: PackageGraphRootInput, manifests: [AbsolutePath: Manifest], explicitProduct: String? = nil) {
73+
public init(input: PackageGraphRootInput, manifests: [AbsolutePath: Manifest], explicitProduct: String? = nil, identityResolver: IdentityResolver? = nil) {
5874
self.packages = input.packages.reduce(into: .init(), { partial, inputPath in
5975
if let manifest = manifests[inputPath] {
6076
let packagePath = manifest.path.parentDirectory
@@ -77,7 +93,8 @@ public struct PackageGraphRoot {
7793
}
7894
}
7995

80-
self.dependencies = adjustedDependencies
96+
self._dependencies = adjustedDependencies
97+
self.identityResolver = identityResolver
8198
}
8299

83100
/// Returns the constraints imposed by root manifests + dependencies.

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 16 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import struct TSCBasic.StringError
2626
import struct TSCUtility.Version
2727

2828
enum ManifestJSONParser {
29-
private static let filePrefix = "file://"
30-
3129
struct Input: Codable {
3230
let package: Serialization.Package
3331
let errors: [String]
@@ -152,176 +150,25 @@ enum ManifestJSONParser {
152150
) throws -> PackageDependency {
153151
switch dependency.kind {
154152
case .registry(let identity, let requirement):
155-
return try Self.parseRegistryDependency(
156-
identity: .plain(identity),
157-
requirement: .init(requirement),
158-
identityResolver: identityResolver
159-
)
153+
return try identityResolver.mappedDependency(packageKind: .registry(.plain(identity)), at: identity, nameForTargetDependencyResolutionOnly: nil, requirement: .init(requirement), productFilter: .everything, fileSystem: fileSystem)
160154
case .sourceControl(let name, let location, let requirement):
161-
return try Self.parseSourceControlDependency(
162-
packageKind: packageKind,
163-
at: location,
164-
name: name,
165-
requirement: .init(requirement),
166-
identityResolver: identityResolver,
167-
fileSystem: fileSystem
168-
)
155+
return try identityResolver.mappedDependency(packageKind: packageKind, at: location, nameForTargetDependencyResolutionOnly: name, requirement: .init(requirement), productFilter: .everything, fileSystem: fileSystem)
169156
case .fileSystem(let name, let path):
170-
return try Self.parseFileSystemDependency(
171-
packageKind: packageKind,
172-
at: path,
173-
name: name,
174-
identityResolver: identityResolver,
175-
fileSystem: fileSystem
176-
)
177-
}
178-
}
179-
180-
private static func parseFileSystemDependency(
181-
packageKind: PackageReference.Kind,
182-
at location: String,
183-
name: String?,
184-
identityResolver: IdentityResolver,
185-
fileSystem: FileSystem
186-
) throws -> PackageDependency {
187-
let location = try sanitizeDependencyLocation(fileSystem: fileSystem, packageKind: packageKind, dependencyLocation: location)
188-
let path: AbsolutePath
189-
do {
190-
path = try AbsolutePath(validating: location)
191-
} catch PathValidationError.invalidAbsolutePath(let path) {
192-
throw ManifestParseError.invalidManifestFormat("'\(path)' is not a valid path for path-based dependencies; use relative or absolute path instead.", diagnosticFile: nil, compilerCommandLine: nil)
193-
}
194-
let identity = try identityResolver.resolveIdentity(for: path)
195-
return .fileSystem(identity: identity,
196-
nameForTargetDependencyResolutionOnly: name,
197-
path: path,
198-
productFilter: .everything)
199-
}
200-
201-
private static func parseSourceControlDependency(
202-
packageKind: PackageReference.Kind,
203-
at location: String,
204-
name: String?,
205-
requirement: PackageDependency.SourceControl.Requirement,
206-
identityResolver: IdentityResolver,
207-
fileSystem: FileSystem
208-
) throws -> PackageDependency {
209-
// cleans up variants of path based location
210-
var location = try sanitizeDependencyLocation(fileSystem: fileSystem, packageKind: packageKind, dependencyLocation: location)
211-
// location mapping (aka mirrors) if any
212-
location = identityResolver.mappedLocation(for: location)
213-
if PackageIdentity.plain(location).isRegistry {
214-
// re-mapped to registry
215-
let identity = PackageIdentity.plain(location)
216-
let registryRequirement: PackageDependency.Registry.Requirement
217-
switch requirement {
218-
case .branch, .revision:
219-
throw StringError("invalid mapping of source control to registry, requirement information mismatch: cannot map branch or revision based dependencies to registry.")
220-
case .exact(let value):
221-
registryRequirement = .exact(value)
222-
case .range(let value):
223-
registryRequirement = .range(value)
157+
// FIXME: This case should really also be handled by `mappedDependency()` but that is currently impossible because `sanitizeDependencyLocation()` relies on the fact that we're calling it with an incorrect (file-system) `packageKind` for SCM-based dependencies, so we have no ability to distinguish between actual file-system dependencies and SCM-based ones without introducing some secondary package kind or other flag to pick the different behaviors. That seemed much worse than having this extra code path be here and `DefaultIdentityResolver.sanitizeDependencyLocation()` being public, but it should eventually be cleaned up. It seems to me as if that will mostly be a case of fixing the test suite to not rely on these fairly arbitrary behaviors.
158+
159+
// cleans up variants of path based location
160+
let location = try DefaultIdentityResolver.sanitizeDependencyLocation(fileSystem: fileSystem, packageKind: packageKind, dependencyLocation: path)
161+
let path: AbsolutePath
162+
do {
163+
path = try AbsolutePath(validating: location)
164+
} catch PathValidationError.invalidAbsolutePath(let path) {
165+
throw StringError("'\(path)' is not a valid path for path-based dependencies; use relative or absolute path instead.")
224166
}
225-
return .registry(
226-
identity: identity,
227-
requirement: registryRequirement,
228-
productFilter: .everything
229-
)
230-
} else if let localPath = try? AbsolutePath(validating: location) {
231-
// a package in a git location, may be a remote URL or on disk
232-
// in the future this will check with the registries for the identity of the URL
233-
let identity = try identityResolver.resolveIdentity(for: localPath)
234-
return .localSourceControl(
235-
identity: identity,
236-
nameForTargetDependencyResolutionOnly: name,
237-
path: localPath,
238-
requirement: requirement,
239-
productFilter: .everything
240-
)
241-
} else {
242-
let url = SourceControlURL(location)
243-
// in the future this will check with the registries for the identity of the URL
244-
let identity = try identityResolver.resolveIdentity(for: url)
245-
return .remoteSourceControl(
246-
identity: identity,
247-
nameForTargetDependencyResolutionOnly: name,
248-
url: url,
249-
requirement: requirement,
250-
productFilter: .everything
251-
)
252-
}
253-
}
254-
255-
private static func parseRegistryDependency(
256-
identity: PackageIdentity,
257-
requirement: PackageDependency.Registry.Requirement,
258-
identityResolver: IdentityResolver
259-
) throws -> PackageDependency {
260-
// location mapping (aka mirrors) if any
261-
let location = identityResolver.mappedLocation(for: identity.description)
262-
if PackageIdentity.plain(location).isRegistry {
263-
// re-mapped to registry
264-
let identity = PackageIdentity.plain(location)
265-
return .registry(
266-
identity: identity,
267-
requirement: requirement,
268-
productFilter: .everything
269-
)
270-
} else if let url = URL(string: location){
271-
let SourceControlURL = SourceControlURL(url)
272-
// in the future this will check with the registries for the identity of the URL
273-
let identity = try identityResolver.resolveIdentity(for: SourceControlURL)
274-
let sourceControlRequirement: PackageDependency.SourceControl.Requirement
275-
switch requirement {
276-
case .exact(let value):
277-
sourceControlRequirement = .exact(value)
278-
case .range(let value):
279-
sourceControlRequirement = .range(value)
280-
}
281-
return .remoteSourceControl(
282-
identity: identity,
283-
nameForTargetDependencyResolutionOnly: identity.description,
284-
url: SourceControlURL,
285-
requirement: sourceControlRequirement,
286-
productFilter: .everything
287-
)
288-
} else {
289-
throw StringError("invalid location: \(location)")
290-
}
291-
}
292-
293-
private static func sanitizeDependencyLocation(fileSystem: FileSystem, packageKind: PackageReference.Kind, dependencyLocation: String) throws -> String {
294-
if dependencyLocation.hasPrefix("~/") {
295-
// If the dependency URL starts with '~/', try to expand it.
296-
return try AbsolutePath(validating: String(dependencyLocation.dropFirst(2)), relativeTo: fileSystem.homeDirectory).pathString
297-
} else if dependencyLocation.hasPrefix(filePrefix) {
298-
// FIXME: SwiftPM can't handle file locations with file:// scheme so we need to
299-
// strip that. We need to design a Location data structure for SwiftPM.
300-
let location = String(dependencyLocation.dropFirst(filePrefix.count))
301-
let hostnameComponent = location.prefix(while: { $0 != "/" })
302-
guard hostnameComponent.isEmpty else {
303-
if hostnameComponent == ".." {
304-
throw ManifestParseError.invalidManifestFormat(
305-
"file:// URLs cannot be relative, did you mean to use '.package(path:)'?", diagnosticFile: nil, compilerCommandLine: nil
306-
)
307-
}
308-
throw ManifestParseError.invalidManifestFormat(
309-
"file:// URLs with hostnames are not supported, are you missing a '/'?", diagnosticFile: nil, compilerCommandLine: nil
310-
)
311-
}
312-
return try AbsolutePath(validating: location).pathString
313-
} else if parseScheme(dependencyLocation) == nil {
314-
// If the URL has no scheme, we treat it as a path (either absolute or relative to the base URL).
315-
switch packageKind {
316-
case .root(let packagePath), .fileSystem(let packagePath), .localSourceControl(let packagePath):
317-
return try AbsolutePath(validating: dependencyLocation, relativeTo: packagePath).pathString
318-
case .remoteSourceControl, .registry:
319-
// nothing to "fix"
320-
return dependencyLocation
321-
}
322-
} else {
323-
// nothing to "fix"
324-
return dependencyLocation
167+
let identity = try identityResolver.resolveIdentity(for: path)
168+
return .fileSystem(identity: identity,
169+
nameForTargetDependencyResolutionOnly: name,
170+
path: path,
171+
productFilter: .everything)
325172
}
326173
}
327174

@@ -392,33 +239,6 @@ enum ManifestJSONParser {
392239
return settings
393240
}
394241

395-
/// Parses the URL type of a git repository
396-
/// e.g. https://github.com/apple/swift returns "https"
397-
/// e.g. [email protected]:apple/swift returns "git"
398-
///
399-
/// This is *not* a generic URI scheme parser!
400-
private static func parseScheme(_ location: String) -> String? {
401-
func prefixOfSplitBy(_ delimiter: String) -> String? {
402-
let (head, tail) = location.spm_split(around: delimiter)
403-
if tail == nil {
404-
//not found
405-
return nil
406-
} else {
407-
//found, return head
408-
//lowercase the "scheme", as specified by the URI RFC (just in case)
409-
return head.lowercased()
410-
}
411-
}
412-
413-
for delim in ["://", "@"] {
414-
if let found = prefixOfSplitBy(delim), !found.contains("/") {
415-
return found
416-
}
417-
}
418-
419-
return nil
420-
}
421-
422242
/// Looks for Xcode-style build setting macros "$()".
423243
fileprivate static let invalidValueRegex = try! RegEx(pattern: #"(\$\(.*?\))"#)
424244
}

0 commit comments

Comments
 (0)