Skip to content

Commit 61ee7ad

Browse files
committed
Refactor registry configuration to new pattern
1 parent b60f47f commit 61ee7ad

File tree

4 files changed

+199
-51
lines changed

4 files changed

+199
-51
lines changed

Sources/Commands/SwiftPackageRegistryTool.swift

Lines changed: 38 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,21 @@ public struct SwiftPackageRegistryTool: ParsableCommand {
9292

9393
// TODO: Require login if password is specified
9494

95-
let path = try swiftTool.getRegistryConfigurationPath(fileSystem: localFileSystem, global: global)
96-
97-
var configuration = RegistryConfiguration()
98-
if localFileSystem.exists(path) {
99-
configuration = try RegistryConfiguration.readFromJSONFile(in: localFileSystem, at: path)
95+
let set: (inout RegistryConfiguration) throws -> Void = { configuration in
96+
if let scope = scope {
97+
configuration.scopedRegistries[scope] = .init(url: url)
98+
} else {
99+
configuration.defaultRegistry = .init(url: url)
100+
}
100101
}
101102

102-
if let scope = scope {
103-
configuration.scopedRegistries[scope] = .init(url: url)
103+
let configuration = try swiftTool.getRegistriesConfig()
104+
if global {
105+
try configuration.applyShared(handler: set)
104106
} else {
105-
configuration.defaultRegistry = .init(url: url)
107+
try configuration.applyLocal(handler: set)
106108
}
107109

108-
try configuration.writeToJSONFile(in: localFileSystem, at: path)
109-
110110
// TODO: Add login and password to .netrc
111111
}
112112
}
@@ -125,56 +125,45 @@ public struct SwiftPackageRegistryTool: ParsableCommand {
125125
var scope: String?
126126

127127
func run(_ swiftTool: SwiftTool) throws {
128-
let path = try swiftTool.getRegistryConfigurationPath(fileSystem: localFileSystem, global: global)
129-
var configuration = try RegistryConfiguration.readFromJSONFile(in: localFileSystem, at: path)
130-
131-
if let scope = scope {
132-
guard let _ = configuration.scopedRegistries[scope] else {
133-
throw RegistryConfigurationError.missingScope(scope)
128+
let unset: (inout RegistryConfiguration) throws -> Void = { configuration in
129+
if let scope = scope {
130+
guard let _ = configuration.scopedRegistries[scope] else {
131+
throw RegistryConfigurationError.missingScope(scope)
132+
}
133+
configuration.scopedRegistries.removeValue(forKey: scope)
134+
} else {
135+
guard let _ = configuration.defaultRegistry else {
136+
throw RegistryConfigurationError.missingScope()
137+
}
138+
configuration.defaultRegistry = nil
134139
}
135-
configuration.scopedRegistries.removeValue(forKey: scope)
136-
} else {
137-
guard let _ = configuration.defaultRegistry else {
138-
throw RegistryConfigurationError.missingScope()
139-
}
140-
configuration.defaultRegistry = nil
141140
}
142141

143-
try configuration.writeToJSONFile(in: localFileSystem, at: path)
142+
let configuration = try swiftTool.getRegistriesConfig()
143+
if global {
144+
try configuration.applyShared(handler: unset)
145+
} else {
146+
try configuration.applyLocal(handler: unset)
147+
}
144148
}
145149
}
146150
}
147151

148152
// MARK: -
149153

150-
private extension Decodable {
151-
static func readFromJSONFile(in fileSystem: FileSystem, at path: AbsolutePath) throws -> Self {
152-
let content = try fileSystem.readFileContents(path)
153-
let decoder = JSONDecoder.makeWithDefaults()
154-
return try decoder.decode(Self.self, from: Data(content.contents))
155-
}
156-
}
157-
158-
private extension Encodable {
159-
func writeToJSONFile(in fileSystem: FileSystem, at path: AbsolutePath) throws {
160-
let encoder = JSONEncoder.makeWithDefaults()
161-
let data = try encoder.encode(self)
162-
try fileSystem.writeFileContents(path, bytes: ByteString(data), atomically: true)
163-
}
164-
}
165154

166155
private extension SwiftTool {
167-
func getRegistryConfigurationPath(fileSystem: FileSystem, global: Bool) throws -> AbsolutePath {
168-
let filename = "registries.json"
169-
if global {
170-
return try fileSystem.getOrCreateSwiftPMConfigDirectory().appending(component: filename)
171-
} else {
172-
let directory = try configFilePath()
173-
if !fileSystem.exists(directory) {
174-
try fileSystem.createDirectory(directory, recursive: true)
175-
}
156+
func getRegistriesConfig(sharedConfigurationDirectory: AbsolutePath? = nil) throws -> Workspace.Configuration.Registries {
157+
let sharedConfigurationDirectory = try sharedConfigurationDirectory ?? self.getSharedConfigurationDirectory()
158+
let sharedRegistriesFile = sharedConfigurationDirectory.map { Workspace.DefaultLocations.registriesConfigurationFile(at: $0) }
159+
return try .init(
160+
localRegistriesFile: self.registriesConfigFile(),
161+
sharedRegistriesFile: sharedRegistriesFile,
162+
fileSystem: localFileSystem
163+
)
164+
}
176165

177-
return directory.appending(component: filename)
178-
}
166+
func registriesConfigFile() throws -> AbsolutePath {
167+
try self.getPackageRoot().appending(components: ".swiftpm", "config", "registries.json")
179168
}
180169
}

Sources/Commands/SwiftTool.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ public class SwiftTool {
536536
return netrcFilePath
537537
}
538538

539-
private func getSharedCacheDirectory() throws -> AbsolutePath? {
539+
func getSharedCacheDirectory() throws -> AbsolutePath? {
540540
if let explicitCachePath = options.cachePath {
541541
// Create the explicit cache path if necessary
542542
if !localFileSystem.exists(explicitCachePath) {
@@ -553,7 +553,7 @@ public class SwiftTool {
553553
}
554554
}
555555

556-
private func getSharedConfigurationDirectory() throws -> AbsolutePath? {
556+
func getSharedConfigurationDirectory() throws -> AbsolutePath? {
557557
if let explicitConfigPath = options.configPath {
558558
// Create the explicit config path if necessary
559559
if !localFileSystem.exists(explicitConfigPath) {

Sources/PackageRegistry/RegistryConfiguration.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ public struct RegistryConfiguration: Hashable {
3434
self.defaultRegistry = nil
3535
self.scopedRegistries = [:]
3636
}
37+
38+
public var isEmpty: Bool {
39+
return self.defaultRegistry == nil && self.scopedRegistries.isEmpty
40+
}
41+
42+
public mutating func merge(_ other: RegistryConfiguration) {
43+
if let defaultRegistry = other.defaultRegistry {
44+
self.defaultRegistry = defaultRegistry
45+
}
46+
47+
for (scope, registry) in other.scopedRegistries {
48+
self.scopedRegistries[scope] = registry
49+
}
50+
}
3751
}
3852

3953
// MARK: - Codable

Sources/Workspace/WorkspaceConfiguration.swift

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import Basics
1212
import Foundation
1313
import TSCBasic
14+
import PackageRegistry
1415

1516
// MARK: - Location
1617

@@ -62,6 +63,11 @@ extension Workspace {
6263
self.sharedConfigurationDirectory.map { DefaultLocations.mirrorsConfigurationFile(at: $0) }
6364
}
6465

66+
/// Path to the shared registries configuration.
67+
public var sharedRegistriesConfigurationFile: AbsolutePath? {
68+
self.sharedConfigurationDirectory.map { DefaultLocations.registriesConfigurationFile(at: $0) }
69+
}
70+
6571
/// Create a new workspace location.
6672
///
6773
/// - Parameters:
@@ -129,6 +135,10 @@ extension Workspace {
129135
path.appending(component: "mirrors.json")
130136
}
131137

138+
public static func registriesConfigurationFile(at path: AbsolutePath) -> AbsolutePath {
139+
path.appending(component: "registries.json")
140+
}
141+
132142
public static func manifestsDirectory(at path: AbsolutePath) -> AbsolutePath {
133143
path.appending(component: "manifests")
134144
}
@@ -360,6 +370,141 @@ extension Workspace.Configuration {
360370
}
361371
}
362372

373+
// MARK: - Registries
374+
375+
extension Workspace.Configuration {
376+
public class Registries {
377+
private let localRegistries: RegistriesStorage
378+
private let sharedRegistries: RegistriesStorage?
379+
private let fileSystem: FileSystem
380+
381+
private var _configuration = RegistryConfiguration()
382+
private let lock = Lock()
383+
384+
/// The registry configuration
385+
public var configuration: RegistryConfiguration {
386+
self.lock.withLock {
387+
return self._configuration
388+
}
389+
}
390+
391+
/// Initialize the workspace registries configuration
392+
///
393+
/// - Parameters:
394+
/// - localRegistriesFile: Path to the workspace registries configuration file
395+
/// - sharedRegistriesFile: Path to the shared registries configuration file, defaults to the standard location.
396+
/// - fileSystem: The file system to use.
397+
public init(
398+
localRegistriesFile: AbsolutePath,
399+
sharedRegistriesFile: AbsolutePath?,
400+
fileSystem: FileSystem
401+
) throws {
402+
self.localRegistries = .init(path: localRegistriesFile, fileSystem: fileSystem, deleteWhenEmpty: true)
403+
self.sharedRegistries = sharedRegistriesFile.map { .init(path: $0, fileSystem: fileSystem, deleteWhenEmpty: false) }
404+
self.fileSystem = fileSystem
405+
try self.computeRegistries()
406+
}
407+
408+
@discardableResult
409+
public func applyLocal(handler: (inout RegistryConfiguration) throws -> Void) throws -> RegistryConfiguration {
410+
try self.localRegistries.apply(handler: handler)
411+
try self.computeRegistries()
412+
return self.configuration
413+
}
414+
415+
@discardableResult
416+
public func applyShared(handler: (inout RegistryConfiguration) throws -> Void) throws -> RegistryConfiguration {
417+
guard let sharedRegistries = self.sharedRegistries else {
418+
throw InternalError("shared registries not configured")
419+
}
420+
try sharedRegistries.apply(handler: handler)
421+
try self.computeRegistries()
422+
return self.configuration
423+
}
424+
425+
// mutating the state we hold since we are passing it by reference to the workspace
426+
// access should be done using a lock
427+
private func computeRegistries() throws {
428+
try self.lock.withLock {
429+
var configuration = RegistryConfiguration()
430+
431+
if let sharedConfiguration = try sharedRegistries?.get() {
432+
configuration.merge(sharedConfiguration)
433+
}
434+
435+
let localConfiguration = try localRegistries.get()
436+
configuration.merge(localConfiguration)
437+
438+
self._configuration = configuration
439+
}
440+
}
441+
}
442+
}
443+
444+
extension Workspace.Configuration {
445+
private struct RegistriesStorage {
446+
private let path: AbsolutePath
447+
private let fileSystem: FileSystem
448+
private let deleteWhenEmpty: Bool
449+
450+
public init(path: AbsolutePath, fileSystem: FileSystem, deleteWhenEmpty: Bool) {
451+
self.path = path
452+
self.fileSystem = fileSystem
453+
self.deleteWhenEmpty = deleteWhenEmpty
454+
}
455+
456+
public func get() throws -> RegistryConfiguration {
457+
return try self.fileSystem.withLock(on: self.path.parentDirectory, type: .shared) {
458+
return try Self.load(self.path, fileSystem: self.fileSystem)
459+
}
460+
}
461+
462+
@discardableResult
463+
public func apply(handler: (inout RegistryConfiguration) throws -> Void) throws -> RegistryConfiguration {
464+
if !self.fileSystem.exists(self.path.parentDirectory) {
465+
try self.fileSystem.createDirectory(self.path.parentDirectory, recursive: true)
466+
}
467+
return try self.fileSystem.withLock(on: self.path.parentDirectory, type: .exclusive) {
468+
let configuration = try Self.load(self.path, fileSystem: self.fileSystem)
469+
var updatedConfiguration = configuration
470+
try handler(&updatedConfiguration)
471+
if updatedConfiguration != configuration {
472+
try Self.save(updatedConfiguration, to: self.path, fileSystem: self.fileSystem, deleteWhenEmpty: self.deleteWhenEmpty)
473+
}
474+
return updatedConfiguration
475+
}
476+
}
477+
478+
private static func load(_ path: AbsolutePath, fileSystem: FileSystem) throws -> RegistryConfiguration {
479+
guard fileSystem.exists(path) else {
480+
return RegistryConfiguration()
481+
}
482+
483+
let data: Data = try fileSystem.readFileContents(path)
484+
let decoder = JSONDecoder.makeWithDefaults()
485+
return try decoder.decode(RegistryConfiguration.self, from: data)
486+
}
487+
488+
private static func save(_ configuration: RegistryConfiguration, to path: AbsolutePath, fileSystem: FileSystem, deleteWhenEmpty: Bool) throws {
489+
if configuration.isEmpty {
490+
if deleteWhenEmpty && fileSystem.exists(path) {
491+
// deleteWhenEmpty is a backward compatibility mode
492+
return try fileSystem.removeFileTree(path)
493+
} else if !fileSystem.exists(path) {
494+
// nothing to do
495+
return
496+
}
497+
}
498+
499+
let encoder = JSONEncoder.makeWithDefaults()
500+
let data = try encoder.encode(configuration)
501+
if !fileSystem.exists(path.parentDirectory) {
502+
try fileSystem.createDirectory(path.parentDirectory, recursive: true)
503+
}
504+
try fileSystem.writeFileContents(path, data: data)
505+
}
506+
}
507+
}
363508

364509
// MARK: - Deprecated 8/20201
365510

0 commit comments

Comments
 (0)