Skip to content

Commit e7fe4cc

Browse files
committed
use idiomatic location for security directory and update location of configuration directory (swiftlang#3942)
motivation: use idiomatic location for security directory on macOS changes: * allow users to customize security directory location with new --security-path CLI option * on macOS use <user>/Library/org.swiftpm/security for security files and symlink from ~/.swiftpm/security * move configuration directory from <user>/Library/org.swiftpm to <user>/Library/org.swiftpm/configuration * add migration code from old configuraiton location to new one * add and adjust tests * update docker setup for new locationis
1 parent c8dbeeb commit e7fe4cc

File tree

10 files changed

+251
-105
lines changed

10 files changed

+251
-105
lines changed

Sources/Basics/FileSystem+Extensions.swift

Lines changed: 104 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ extension FileSystem {
2020
public var dotSwiftPM: AbsolutePath {
2121
self.homeDirectory.appending(component: ".swiftpm")
2222
}
23-
24-
/// SwiftPM security directory
25-
public var swiftPMSecurityDirectory: AbsolutePath {
26-
self.dotSwiftPM.appending(component: "security")
23+
24+
fileprivate var idiomaticSwiftPMDirectory: AbsolutePath? {
25+
return FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first.flatMap { AbsolutePath($0.path) }?.appending(component: "org.swift.swiftpm")
2726
}
2827
}
2928

@@ -68,52 +67,128 @@ extension FileSystem {
6867
}
6968
}
7069

71-
// MARK: - config
70+
// MARK: - configuration
7271

7372
extension FileSystem {
74-
private var idiomaticUserConfigDirectory: AbsolutePath? {
75-
return FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first.flatMap { AbsolutePath($0.path) }
76-
}
77-
7873
/// SwiftPM config directory under user's config directory (if exists)
79-
public var swiftPMConfigDirectory: AbsolutePath {
80-
if let path = self.idiomaticUserConfigDirectory {
81-
return path.appending(component: "org.swift.swiftpm")
74+
public var swiftPMConfigurationDirectory: AbsolutePath {
75+
if let path = self.idiomaticSwiftPMDirectory {
76+
return path.appending(component: "configuration")
8277
} else {
83-
return self.dotSwiftPMConfigDirectory
78+
return self.dotSwiftPMConfigurationDirectory
8479
}
8580
}
8681

87-
fileprivate var dotSwiftPMConfigDirectory: AbsolutePath {
88-
return self.dotSwiftPM.appending(component: "config")
82+
fileprivate var dotSwiftPMConfigurationDirectory: AbsolutePath {
83+
return self.dotSwiftPM.appending(component: "configuration")
84+
}
85+
}
86+
87+
extension FileSystem {
88+
public func getOrCreateSwiftPMConfigurationDirectory(observabilityScope: ObservabilityScope?) throws -> AbsolutePath {
89+
let idiomaticConfigurationDirectory = self.swiftPMConfigurationDirectory
90+
91+
// temporary 5.6, remove on next version: transition from previous configuration location
92+
if !self.exists(idiomaticConfigurationDirectory) {
93+
try self.createDirectory(idiomaticConfigurationDirectory, recursive: true)
94+
}
95+
96+
// in the case where ~/.swiftpm/configuration is not the idiomatic location (eg on macOS where its /Users/<user>/Library/org.swift.swiftpm/configuration)
97+
if idiomaticConfigurationDirectory != self.dotSwiftPMConfigurationDirectory {
98+
// copy the configuration files from old location (eg /Users/<user>/Library/org.swift.swiftpm) to new one (eg /Users/<user>/Library/org.swift.swiftpm/configuration)
99+
// but leave them there for backwards compatibility (eg older xcode)
100+
let oldConfigDirectory = idiomaticConfigurationDirectory.parentDirectory
101+
if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) {
102+
let configurationFiles = try self.getDirectoryContents(oldConfigDirectory)
103+
.map{ oldConfigDirectory.appending(component: $0) }
104+
.filter{ self.isFile($0) && !self.isSymlink($0) && $0.extension != "lock"}
105+
for file in configurationFiles {
106+
let destination = idiomaticConfigurationDirectory.appending(component: file.basename)
107+
observabilityScope?.emit(warning: "Usage of \(file) has been deprecated. Please delete it and use the new \(destination) instead.")
108+
if !self.exists(destination) {
109+
try self.copy(from: file, to: destination)
110+
}
111+
}
112+
}
113+
// in the case where ~/.swiftpm/configuration is the idiomatic location (eg on Linux)
114+
} else {
115+
// copy the configuration files from old location (~/.swiftpm/config) to new one (~/.swiftpm/configuration)
116+
// but leave them there for backwards compatibility (eg older toolchain)
117+
let oldConfigDirectory = self.dotSwiftPM.appending(component: "config")
118+
if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) {
119+
let configurationFiles = try self.getDirectoryContents(oldConfigDirectory)
120+
.map{ oldConfigDirectory.appending(component: $0) }
121+
.filter{ self.isFile($0) && !self.isSymlink($0) && $0.extension != "lock"}
122+
for file in configurationFiles {
123+
let destination = idiomaticConfigurationDirectory.appending(component: file.basename)
124+
observabilityScope?.emit(warning: "Usage of \(file) has been deprecated. Please delete it and use the new \(destination) instead.")
125+
if !self.exists(destination) {
126+
try self.copy(from: file, to: destination)
127+
}
128+
}
129+
}
130+
}
131+
// ~temporary 5.6 migration
132+
133+
// Create idiomatic if necessary
134+
if !self.exists(idiomaticConfigurationDirectory) {
135+
try self.createDirectory(idiomaticConfigurationDirectory, recursive: true)
136+
}
137+
// Create ~/.swiftpm if necessary
138+
if !self.exists(self.dotSwiftPM) {
139+
try self.createDirectory(self.dotSwiftPM, recursive: true)
140+
}
141+
// Create ~/.swiftpm/configuration symlink if necessary
142+
if !self.exists(self.dotSwiftPMConfigurationDirectory, followSymlink: false) {
143+
try self.createSymbolicLink(dotSwiftPMConfigurationDirectory, pointingAt: idiomaticConfigurationDirectory, relative: false)
144+
}
145+
146+
return idiomaticConfigurationDirectory
89147
}
90148
}
91149

150+
// MARK: - security
151+
92152
extension FileSystem {
93-
public func getOrCreateSwiftPMConfigDirectory() throws -> AbsolutePath {
94-
let idiomaticConfigDirectory = self.swiftPMConfigDirectory
153+
/// SwiftPM security directory under user's security directory (if exists)
154+
public var swiftPMSecurityDirectory: AbsolutePath {
155+
if let path = self.idiomaticSwiftPMDirectory {
156+
return path.appending(component: "security")
157+
} else {
158+
return self.dotSwiftPMSecurityDirectory
159+
}
160+
}
95161

96-
// temporary 5.5, remove on next version: transition from ~/.swiftpm/config to idiomatic location + symbolic link
97-
if idiomaticConfigDirectory != self.dotSwiftPMConfigDirectory &&
98-
self.exists(self.dotSwiftPMConfigDirectory) && self.isDirectory(self.dotSwiftPMConfigDirectory) &&
99-
!self.exists(idiomaticConfigDirectory) {
100-
print("transitioning \(self.dotSwiftPMConfigDirectory) to \(idiomaticConfigDirectory)")
101-
try self.move(from: self.dotSwiftPMConfigDirectory, to: idiomaticConfigDirectory)
162+
fileprivate var dotSwiftPMSecurityDirectory: AbsolutePath {
163+
return self.dotSwiftPM.appending(component: "security")
164+
}
165+
}
166+
167+
extension FileSystem {
168+
public func getOrCreateSwiftPMSecurityDirectory() throws -> AbsolutePath {
169+
let idiomaticSecurityDirectory = self.swiftPMSecurityDirectory
170+
171+
// temporary 5.6, remove on next version: transition from ~/.swiftpm/security to idiomatic location + symbolic link
172+
if idiomaticSecurityDirectory != self.dotSwiftPMSecurityDirectory &&
173+
self.exists(self.dotSwiftPMSecurityDirectory) &&
174+
self.isDirectory(self.dotSwiftPMSecurityDirectory) {
175+
try self.removeFileTree(self.dotSwiftPMSecurityDirectory)
102176
}
177+
// ~temporary 5.6 migration
103178

104179
// Create idiomatic if necessary
105-
if !self.exists(idiomaticConfigDirectory) {
106-
try self.createDirectory(idiomaticConfigDirectory, recursive: true)
180+
if !self.exists(idiomaticSecurityDirectory) {
181+
try self.createDirectory(idiomaticSecurityDirectory, recursive: true)
107182
}
108183
// Create ~/.swiftpm if necessary
109184
if !self.exists(self.dotSwiftPM) {
110185
try self.createDirectory(self.dotSwiftPM, recursive: true)
111186
}
112-
// Create ~/.swiftpm/config symlink if necessary
113-
if !self.exists(self.dotSwiftPMConfigDirectory, followSymlink: false) {
114-
try self.createSymbolicLink(dotSwiftPMConfigDirectory, pointingAt: idiomaticConfigDirectory, relative: false)
187+
// Create ~/.swiftpm/security symlink if necessary
188+
if !self.exists(self.dotSwiftPMSecurityDirectory, followSymlink: false) {
189+
try self.createSymbolicLink(dotSwiftPMSecurityDirectory, pointingAt: idiomaticSecurityDirectory, relative: false)
115190
}
116-
return idiomaticConfigDirectory
191+
return idiomaticSecurityDirectory
117192
}
118193
}
119194

Sources/Commands/Options.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ public struct SwiftToolOptions: ParsableArguments {
149149
@Option(help: "Specify the shared configuration directory")
150150
var configPath: AbsolutePath?
151151

152+
@Option(help: "Specify the shared security directory")
153+
var securityPath: AbsolutePath?
154+
152155
/// Disables repository caching.
153156
@Flag(name: .customLong("repository-cache"), inversion: .prefixedEnableDisable, help: "Use a shared cache when fetching repositories")
154157
var useRepositoriesCache: Bool = true

Sources/Commands/SwiftTool.swift

Lines changed: 81 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ public class SwiftTool {
294294
/// Path to the build directory.
295295
let buildPath: AbsolutePath
296296

297+
/// Path to the shared security directory
298+
let sharedSecurityDirectory: AbsolutePath?
299+
300+
/// Path to the shared cache directory
301+
let sharedCacheDirectory: AbsolutePath?
302+
303+
/// Path to the shared configuration directory
304+
let sharedConfigurationDirectory: AbsolutePath?
305+
297306
/// The process set to hold the launched processes. These will be terminated on any signal
298307
/// received by the swift tools.
299308
let processSet: ProcessSet
@@ -333,7 +342,7 @@ public class SwiftTool {
333342
self.observabilityScope.emit(error: "couldn't determine the current working directory")
334343
throw ExitCode.failure
335344
}
336-
originalWorkingDirectory = cwd
345+
self.originalWorkingDirectory = cwd
337346

338347
do {
339348
try Self.postprocessArgParserResult(options: options, observabilityScope: self.observabilityScope)
@@ -416,6 +425,11 @@ public class SwiftTool {
416425
customBuildPath ??
417426
(packageRoot ?? cwd).appending(component: ".build")
418427

428+
// make sure common directories are created
429+
self.sharedSecurityDirectory = try getSharedSecurityDirectory(options: self.options, observabilityScope: self.observabilityScope)
430+
self.sharedConfigurationDirectory = try getSharedConfigurationDirectory(options: self.options, observabilityScope: self.observabilityScope)
431+
self.sharedCacheDirectory = try getSharedCacheDirectory(options: self.options, observabilityScope: self.observabilityScope)
432+
419433
// set verbosity globals.
420434
// TODO: get rid of this global settings in TSC
421435
switch self.logLevel {
@@ -492,7 +506,7 @@ public class SwiftTool {
492506
}
493507

494508
func getMirrorsConfig(sharedConfigurationDirectory: AbsolutePath? = nil) throws -> Workspace.Configuration.Mirrors {
495-
let sharedConfigurationDirectory = try sharedConfigurationDirectory ?? self.getSharedConfigurationDirectory()
509+
let sharedConfigurationDirectory = sharedConfigurationDirectory ?? self.sharedConfigurationDirectory
496510
let sharedMirrorFile = sharedConfigurationDirectory.map { Workspace.DefaultLocations.mirrorsConfigurationFile(at: $0) }
497511
return try .init(
498512
localMirrorFile: self.mirrorsConfigFile(),
@@ -537,7 +551,7 @@ public class SwiftTool {
537551
func getRegistriesConfig(sharedConfigurationDirectory: AbsolutePath? = nil) throws -> Workspace.Configuration.Registries {
538552
let localRegistriesFile = try Workspace.DefaultLocations.registriesConfigurationFile(forRootPackage: self.getPackageRoot())
539553

540-
let sharedConfigurationDirectory = try sharedConfigurationDirectory ?? self.getSharedConfigurationDirectory()
554+
let sharedConfigurationDirectory = sharedConfigurationDirectory ?? self.sharedConfigurationDirectory
541555
let sharedRegistriesFile = sharedConfigurationDirectory.map {
542556
Workspace.DefaultLocations.registriesConfigurationFile(at: $0)
543557
}
@@ -607,56 +621,6 @@ public class SwiftTool {
607621
return providers
608622
}
609623

610-
private func getSharedCacheDirectory() throws -> AbsolutePath? {
611-
if let explicitCachePath = options.cachePath {
612-
// Create the explicit cache path if necessary
613-
if !localFileSystem.exists(explicitCachePath) {
614-
try localFileSystem.createDirectory(explicitCachePath, recursive: true)
615-
}
616-
return explicitCachePath
617-
}
618-
619-
do {
620-
return try localFileSystem.getOrCreateSwiftPMCacheDirectory()
621-
} catch {
622-
self.observabilityScope.emit(warning: "Failed creating default cache location, \(error)")
623-
return .none
624-
}
625-
}
626-
627-
private func getSharedConfigurationDirectory() throws -> AbsolutePath? {
628-
if let explicitConfigPath = options.configPath {
629-
// Create the explicit config path if necessary
630-
if !localFileSystem.exists(explicitConfigPath) {
631-
try localFileSystem.createDirectory(explicitConfigPath, recursive: true)
632-
}
633-
return explicitConfigPath
634-
}
635-
636-
do {
637-
return try localFileSystem.getOrCreateSwiftPMConfigDirectory()
638-
} catch {
639-
self.observabilityScope.emit(warning: "Failed creating default configuration location, \(error)")
640-
return .none
641-
}
642-
}
643-
644-
private func getSharedSecurityDirectory() throws -> AbsolutePath? {
645-
do {
646-
let fileSystem = localFileSystem
647-
let sharedSecurityDirectory = fileSystem.swiftPMSecurityDirectory
648-
if !fileSystem.exists(sharedSecurityDirectory) {
649-
try fileSystem.createDirectory(sharedSecurityDirectory, recursive: true)
650-
}
651-
// And make sure we can write files (locking the directory writes a lock file)
652-
try fileSystem.withLock(on: sharedSecurityDirectory, type: .exclusive) { }
653-
return sharedSecurityDirectory
654-
} catch {
655-
self.observabilityScope.emit(warning: "Failed creating shared security directory: \(error)")
656-
return .none
657-
}
658-
}
659-
660624
/// Returns the currently active workspace.
661625
func getActiveWorkspace() throws -> Workspace {
662626
if let workspace = _workspace {
@@ -665,28 +629,25 @@ public class SwiftTool {
665629

666630
let delegate = ToolWorkspaceDelegate(self.outputStream, logLevel: self.logLevel, observabilityScope: self.observabilityScope)
667631
let provider = GitRepositoryProvider(processSet: processSet)
668-
let sharedSecurityDirectory = try self.getSharedSecurityDirectory()
669-
let sharedCacheDirectory = try self.getSharedCacheDirectory()
670-
let sharedConfigurationDirectory = try self.getSharedConfigurationDirectory()
671632
let isXcodeBuildSystemEnabled = self.options.buildSystem == .xcode
672633
let workspace = try Workspace(
673634
fileSystem: localFileSystem,
674635
location: .init(
675-
workingDirectory: buildPath,
636+
workingDirectory: self.buildPath,
676637
editsDirectory: self.editsDirectory(),
677638
resolvedVersionsFile: self.resolvedVersionsFile(),
678-
sharedSecurityDirectory: sharedSecurityDirectory,
679-
sharedCacheDirectory: sharedCacheDirectory,
680-
sharedConfigurationDirectory: sharedConfigurationDirectory
639+
sharedSecurityDirectory: self.sharedSecurityDirectory,
640+
sharedCacheDirectory: self.sharedCacheDirectory,
641+
sharedConfigurationDirectory: self.sharedConfigurationDirectory
681642
),
682-
mirrors: self.getMirrorsConfig(sharedConfigurationDirectory: sharedConfigurationDirectory).mirrors,
683-
registries: try self.getRegistriesConfig(sharedConfigurationDirectory: sharedConfigurationDirectory).configuration,
643+
mirrors: self.getMirrorsConfig(sharedConfigurationDirectory: self.sharedConfigurationDirectory).mirrors,
644+
registries: try self.getRegistriesConfig(sharedConfigurationDirectory: self.sharedConfigurationDirectory).configuration,
684645
authorizationProvider: self.getAuthorizationProvider(),
685646
customManifestLoader: self.getManifestLoader(), // FIXME: doe we really need to customize it?
686647
customRepositoryProvider: provider, // FIXME: doe we really need to customize it?
687648
additionalFileRules: isXcodeBuildSystemEnabled ? FileRuleDescription.xcbuildFileTypes : FileRuleDescription.swiftpmFileTypes,
688-
resolverUpdateEnabled: !options.skipDependencyUpdate,
689-
resolverPrefetchingEnabled: options.shouldEnableResolverPrefetching,
649+
resolverUpdateEnabled: !self.options.skipDependencyUpdate,
650+
resolverPrefetchingEnabled: self.options.shouldEnableResolverPrefetching,
690651
resolverFingerprintCheckingMode: self.options.resolverFingerprintCheckingMode,
691652
sharedRepositoriesCacheEnabled: self.options.useRepositoriesCache,
692653
delegate: delegate
@@ -1011,7 +972,7 @@ public class SwiftTool {
1011972
case (false, .local):
1012973
cachePath = self.buildPath
1013974
case (false, .shared):
1014-
cachePath = try self.getSharedCacheDirectory().map{ Workspace.DefaultLocations.manifestsDirectory(at: $0) }
975+
cachePath = self.sharedCacheDirectory.map{ Workspace.DefaultLocations.manifestsDirectory(at: $0) }
1015976
}
1016977

1017978
var extraManifestFlags = self.options.manifestFlags
@@ -1062,6 +1023,61 @@ private func getEnvBuildPath(workingDir: AbsolutePath) -> AbsolutePath? {
10621023
return AbsolutePath(env, relativeTo: workingDir)
10631024
}
10641025

1026+
1027+
private func getSharedSecurityDirectory(options: SwiftToolOptions, observabilityScope: ObservabilityScope) throws -> AbsolutePath? {
1028+
if let explicitSecurityPath = options.securityPath {
1029+
// Create the explicit security path if necessary
1030+
if !localFileSystem.exists(explicitSecurityPath) {
1031+
try localFileSystem.createDirectory(explicitSecurityPath, recursive: true)
1032+
}
1033+
return explicitSecurityPath
1034+
}
1035+
1036+
do {
1037+
let sharedSecurityDirectory = try localFileSystem.getOrCreateSwiftPMSecurityDirectory()
1038+
// And make sure we can write files (locking the directory writes a lock file)
1039+
try localFileSystem.withLock(on: sharedSecurityDirectory, type: .exclusive) { }
1040+
return sharedSecurityDirectory
1041+
} catch {
1042+
observabilityScope.emit(warning: "Failed creating default security location, \(error)")
1043+
return .none
1044+
}
1045+
}
1046+
1047+
private func getSharedConfigurationDirectory(options: SwiftToolOptions, observabilityScope: ObservabilityScope) throws -> AbsolutePath? {
1048+
if let explicitConfigPath = options.configPath {
1049+
// Create the explicit config path if necessary
1050+
if !localFileSystem.exists(explicitConfigPath) {
1051+
try localFileSystem.createDirectory(explicitConfigPath, recursive: true)
1052+
}
1053+
return explicitConfigPath
1054+
}
1055+
1056+
do {
1057+
return try localFileSystem.getOrCreateSwiftPMConfigurationDirectory(observabilityScope: observabilityScope)
1058+
} catch {
1059+
observabilityScope.emit(warning: "Failed creating default configuration location, \(error)")
1060+
return .none
1061+
}
1062+
}
1063+
1064+
private func getSharedCacheDirectory(options: SwiftToolOptions, observabilityScope: ObservabilityScope) throws -> AbsolutePath? {
1065+
if let explicitCachePath = options.cachePath {
1066+
// Create the explicit cache path if necessary
1067+
if !localFileSystem.exists(explicitCachePath) {
1068+
try localFileSystem.createDirectory(explicitCachePath, recursive: true)
1069+
}
1070+
return explicitCachePath
1071+
}
1072+
1073+
do {
1074+
return try localFileSystem.getOrCreateSwiftPMCacheDirectory()
1075+
} catch {
1076+
observabilityScope.emit(warning: "Failed creating default cache location, \(error)")
1077+
return .none
1078+
}
1079+
}
1080+
10651081
/// A wrapper to hold the build system so we can use it inside
10661082
/// the int. handler without requiring to initialize it.
10671083
final class BuildSystemRef {

0 commit comments

Comments
 (0)