Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Sources/CoreCommands/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,7 @@ public struct ResolverOptions: ParsableArguments {
public var skipDependencyUpdate: Bool = false

@Flag(help: "Define automatic transformation of source control based dependencies to registry based ones.")
public var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation =
.disabled
public var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation?

/// Enables pruning unused dependencies to omit redundant calculations during resolution, and each phase thereafter.
/// Hidden from the generated help text as this feature is only currently being considered for traits.
Expand Down
2 changes: 1 addition & 1 deletion Sources/CoreCommands/SwiftCommandState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ public final class SwiftCommandState {
signingEntityCheckingMode: self.options.security.signingEntityCheckingMode,
skipSignatureValidation: !self.options.security.signatureValidation,
sourceControlToRegistryDependencyTransformation: self.options.resolver
.sourceControlToRegistryDependencyTransformation.workspaceConfiguration,
.sourceControlToRegistryDependencyTransformation?.workspaceConfiguration,
defaultRegistry: self.options.resolver.defaultRegistryURL.flatMap {
// TODO: should supportsAvailability be a flag as well?
.init(url: $0, supportsAvailability: true)
Expand Down
10 changes: 9 additions & 1 deletion Sources/PackageRegistry/RegistryConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@ public struct RegistryConfiguration: Hashable {
public enum Version: Int, Codable {
case v1 = 1
}

public static let version: Version = .v1

public var defaultRegistry: Registry?
public var scopedRegistries: [PackageIdentity.Scope: Registry]
public var registryAuthentication: [String: Authentication]
public var security: Security?
public var replaceScmWithRegistry: Bool?

public init() {
self.defaultRegistry = .none
self.scopedRegistries = [:]
self.registryAuthentication = [:]
self.security = .none
self.replaceScmWithRegistry = nil
}

public mutating func merge(_ other: RegistryConfiguration) {
Expand All @@ -57,6 +59,10 @@ public struct RegistryConfiguration: Hashable {
if let security = other.security {
self.security = security
}

if let replaceScmWithRegistry = other.replaceScmWithRegistry {
self.replaceScmWithRegistry = replaceScmWithRegistry
}
}

public func registry(for package: PackageIdentity) -> Registry? {
Expand Down Expand Up @@ -317,6 +323,7 @@ extension RegistryConfiguration: Codable {
case authentication
case security
case version
case replaceScmWithRegistry
}

fileprivate struct ScopeCodingKey: CodingKey, Hashable {
Expand Down Expand Up @@ -369,6 +376,7 @@ extension RegistryConfiguration: Codable {
forKey: .authentication
) ?? [:]
self.security = try container.decodeIfPresent(Security.self, forKey: .security) ?? nil
self.replaceScmWithRegistry = try container.decodeIfPresent(Bool.self, forKey: .replaceScmWithRegistry)
case nil:
throw DecodingError.dataCorruptedError(
forKey: .version,
Expand Down
6 changes: 3 additions & 3 deletions Sources/Workspace/Workspace+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ public struct WorkspaceConfiguration {
public var skipSignatureValidation: Bool

/// Attempt to transform source control based dependencies to registry ones
public var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation
public var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation?

/// URL of the implicitly configured, default registry
public var defaultRegistry: Registry?
Expand Down Expand Up @@ -857,7 +857,7 @@ public struct WorkspaceConfiguration {
fingerprintCheckingMode: CheckingMode,
signingEntityCheckingMode: CheckingMode,
skipSignatureValidation: Bool,
sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation,
sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation?,
defaultRegistry: Registry?,
manifestImportRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])?,
usePrebuilts: Bool,
Expand Down Expand Up @@ -897,7 +897,7 @@ public struct WorkspaceConfiguration {
fingerprintCheckingMode: .strict,
signingEntityCheckingMode: .warn,
skipSignatureValidation: false,
sourceControlToRegistryDependencyTransformation: .disabled,
sourceControlToRegistryDependencyTransformation: nil,
defaultRegistry: .none,
manifestImportRestrictions: .none,
usePrebuilts: false,
Expand Down
13 changes: 12 additions & 1 deletion Sources/Workspace/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,19 @@ public class Workspace {
// register the registry dependencies downloader with the cancellation handler
cancellator?.register(name: "registry downloads", handler: registryDownloadsManager)

// registries.json acts as a default; an explicit CLI flag overrides.
let effectiveTransformation: WorkspaceConfiguration.SourceControlToRegistryDependencyTransformation = {
if let cliValue = configuration.sourceControlToRegistryDependencyTransformation {
return cliValue
}
if registriesConfiguration.replaceScmWithRegistry == true {
return .swizzle
}
return .disabled
}()

if let transformationMode = RegistryAwareManifestLoader
.TransformationMode(configuration.sourceControlToRegistryDependencyTransformation)
.TransformationMode(effectiveTransformation)
{
manifestLoader = RegistryAwareManifestLoader(
underlying: manifestLoader,
Expand Down
52 changes: 26 additions & 26 deletions Sources/_InternalTestSupport/MockWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public final class MockWorkspace {
public let delegate = MockWorkspaceDelegate()
let skipDependenciesUpdates: Bool
public var sourceControlToRegistryDependencyTransformation: WorkspaceConfiguration
.SourceControlToRegistryDependencyTransformation
.SourceControlToRegistryDependencyTransformation?
var defaultRegistry: Registry?
public let traitConfiguration: TraitConfiguration
public var enabledTraitsMap: EnabledTraitsMap
Expand All @@ -122,7 +122,7 @@ public final class MockWorkspace {
customPackageContainerProvider: MockPackageContainerProvider? = .none,
skipDependenciesUpdates: Bool = false,
sourceControlToRegistryDependencyTransformation: WorkspaceConfiguration
.SourceControlToRegistryDependencyTransformation = .disabled,
.SourceControlToRegistryDependencyTransformation? = nil,
defaultRegistry: Registry? = .none,
customHostTriple: Triple = hostTriple,
traitConfiguration: TraitConfiguration = .default,
Expand Down Expand Up @@ -457,8 +457,8 @@ public final class MockWorkspace {
path: AbsolutePath? = nil,
revision: Revision? = nil,
checkoutBranch: String? = nil,
_ result: ([Basics.Diagnostic]) -> Void
) async {
_ result: ([Basics.Diagnostic]) throws -> Void
) async rethrows {
let observability = ObservabilitySystem.makeForTesting()
await observability.topScope.trap {
let ws = try self.getOrCreateWorkspace()
Expand All @@ -470,15 +470,15 @@ public final class MockWorkspace {
observabilityScope: observability.topScope
)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public func checkUnedit(
packageIdentity: String,
roots: [String],
forceRemove: Bool = false,
_ result: ([Basics.Diagnostic]) -> Void
) async {
_ result: ([Basics.Diagnostic]) throws -> Void
) async rethrows {
let observability = ObservabilitySystem.makeForTesting()
await observability.topScope.trap {
let rootInput = try PackageGraphRootInput(
Expand All @@ -493,15 +493,15 @@ public final class MockWorkspace {
observabilityScope: observability.topScope
)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public func checkResolve(
pkg: String,
roots: [String],
version: TSCUtility.Version,
_ result: ([Basics.Diagnostic]) -> Void
) async {
_ result: ([Basics.Diagnostic]) throws -> Void
) async rethrows {
let observability = ObservabilitySystem.makeForTesting()
await observability.topScope.trap {
let rootInput = try PackageGraphRootInput(
Expand All @@ -518,32 +518,32 @@ public final class MockWorkspace {
observabilityScope: observability.topScope
)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public func checkClean(_ result: ([Basics.Diagnostic]) -> Void) {
public func checkClean(_ result: ([Basics.Diagnostic]) throws -> Void) rethrows {
let observability = ObservabilitySystem.makeForTesting()
observability.topScope.trap {
let workspace = try self.getOrCreateWorkspace()
workspace.clean(observabilityScope: observability.topScope)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public func checkReset(_ result: ([Basics.Diagnostic]) -> Void) async {
public func checkReset(_ result: ([Basics.Diagnostic]) throws -> Void) async rethrows {
let observability = ObservabilitySystem.makeForTesting()
await observability.topScope.trap {
let workspace = try self.getOrCreateWorkspace()
await workspace.reset(observabilityScope: observability.topScope)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public func checkUpdate(
roots: [String] = [],
deps: [MockDependency] = [],
packages: [String] = [],
_ result: ([Basics.Diagnostic]) -> Void
_ result: ([Basics.Diagnostic]) throws -> Void
) async throws {
let dependencies = try deps.map { try $0.convert(
baseURL: self.packagesDir,
Expand All @@ -564,13 +564,13 @@ public final class MockWorkspace {
observabilityScope: observability.topScope
)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public func checkUpdateDryRun(
roots: [String] = [],
deps: [MockDependency] = [],
_ result: ([(PackageReference, Workspace.PackageStateChange)]?, [Basics.Diagnostic]) -> Void
_ result: ([(PackageReference, Workspace.PackageStateChange)]?, [Basics.Diagnostic]) throws -> Void
) async throws {
let dependencies = try deps.map { try $0.convert(
baseURL: self.packagesDir,
Expand All @@ -591,7 +591,7 @@ public final class MockWorkspace {
observabilityScope: observability.topScope
)
} ?? nil
result(changes, observability.diagnostics)
try result(changes, observability.diagnostics)
}

public func checkPackageGraph(
Expand Down Expand Up @@ -638,21 +638,21 @@ public final class MockWorkspace {
public func checkPackageGraphFailure(
roots: [String] = [],
deps: [MockDependency],
_ result: ([Basics.Diagnostic]) -> Void
_ result: ([Basics.Diagnostic]) throws -> Void
) async throws {
let dependencies = try deps.map { try $0.convert(
baseURL: self.packagesDir,
identityResolver: self.identityResolver
) }
await self.checkPackageGraphFailure(roots: roots, dependencies: dependencies, result)
try await self.checkPackageGraphFailure(roots: roots, dependencies: dependencies, result)
}

public func checkPackageGraphFailure(
roots: [String] = [],
dependencies: [PackageDependency] = [],
forceResolvedVersions: Bool = false,
_ result: ([Basics.Diagnostic]) -> Void
) async {
_ result: ([Basics.Diagnostic]) throws -> Void
) async rethrows {
let observability = ObservabilitySystem.makeForTesting()
await observability.topScope.trap {
let rootInput = try PackageGraphRootInput(
Expand All @@ -667,7 +667,7 @@ public final class MockWorkspace {
observabilityScope: observability.topScope
)
}
result(observability.diagnostics)
try result(observability.diagnostics)
}

public struct ResolutionPrecomputationResult {
Expand Down Expand Up @@ -942,7 +942,7 @@ public final class MockWorkspace {
public func loadDependencyManifests(
roots: [String] = [],
deps: [MockDependency] = [],
_ result: (Workspace.DependencyManifests, [Basics.Diagnostic]) -> Void
_ result: (Workspace.DependencyManifests, [Basics.Diagnostic]) throws -> Void
) async throws {
let observability = ObservabilitySystem.makeForTesting()
let dependencies = try deps.map { try $0.convert(
Expand All @@ -967,7 +967,7 @@ public final class MockWorkspace {
root: graphRoot,
observabilityScope: observability.topScope
)
result(manifests, observability.diagnostics)
try result(manifests, observability.diagnostics)
}

public func checkManagedDependencies(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import TSCTestSupport

extension String {
/// Returns `true` if the receiver matches the given `StringPattern`.
///
/// Intended for use inside Swift Testing's `#expect` / `#require`, e.g.
/// `#expect(output.matches(.contains("hello")))`.
public func matches(_ pattern: StringPattern) -> Bool {
pattern ~= self
}
}

extension Optional where Wrapped == String {
/// Returns `true` if the wrapped value exists and matches the given `StringPattern`.
///
/// A `nil` receiver never matches.
public func matches(_ pattern: StringPattern) -> Bool {
guard let self else { return false }
return pattern ~= self
}
}

extension Array where Element == String {
/// Returns `true` if the receiver matches the given sequence of `StringPattern`s.
public func matches(_ pattern: [StringPattern]) -> Bool {
pattern ~= self
}
}
Loading
Loading