Skip to content

Commit 15ea6e7

Browse files
committed
use idiomatic location for security directory and update location of configuration directory
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 locations
1 parent b057481 commit 15ea6e7

File tree

10 files changed

+178
-48
lines changed

10 files changed

+178
-48
lines changed

Sources/Basics/FileSystem+Extensions.swift

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

@@ -69,52 +68,126 @@ extension FileSystem {
6968
}
7069
}
7170

72-
// MARK: - config
71+
// MARK: - configuration
7372

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

88-
fileprivate var dotSwiftPMConfigDirectory: AbsolutePath {
89-
return self.dotSwiftPM.appending(component: "config")
83+
fileprivate var dotSwiftPMConfigurationDirectory: AbsolutePath {
84+
return self.dotSwiftPM.appending(component: "configuration")
85+
}
86+
}
87+
88+
extension FileSystem {
89+
public func getOrCreateSwiftPMConfigurationDirectory() throws -> AbsolutePath {
90+
let idiomaticConfigurationDirectory = self.swiftPMConfigurationDirectory
91+
92+
// temporary 5.6, remove on next version: transition from previous configuration location
93+
do {
94+
if !self.exists(idiomaticConfigurationDirectory) {
95+
try self.createDirectory(idiomaticConfigurationDirectory, recursive: true)
96+
}
97+
98+
if idiomaticConfigurationDirectory != self.dotSwiftPMConfigurationDirectory {
99+
// move content from old directory to new one but create symlink for the files for backwards compatibility (eg older xcode)
100+
let oldConfigDirectory = idiomaticConfigurationDirectory.parentDirectory
101+
if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) {
102+
let content = try self.getDirectoryContents(oldConfigDirectory)
103+
for item in content {
104+
if self.isFile(oldConfigDirectory.appending(component: item)) && !self.isSymlink(oldConfigDirectory.appending(component: item)) {
105+
try self.move(from: oldConfigDirectory.appending(component: item), to: idiomaticConfigurationDirectory.appending(component: item))
106+
try self.createSymbolicLink(oldConfigDirectory.appending(component: item), pointingAt: idiomaticConfigurationDirectory.appending(component: item), relative: false)
107+
}
108+
}
109+
}
110+
// re-point old symlink
111+
let oldConfigSymlink = self.dotSwiftPM.appending(component: "config")
112+
if self.exists(oldConfigSymlink, followSymlink: false) && self.isSymlink(oldConfigSymlink) {
113+
try self.removeFileTree(oldConfigSymlink)
114+
try self.createSymbolicLink(oldConfigSymlink, pointingAt: idiomaticConfigurationDirectory, relative: false)
115+
}
116+
} else {
117+
// move content from old directory to new one but create symlink for the files for backwards compatibility (eg older xcode)
118+
let oldConfigDirectory = self.dotSwiftPM.appending(component: "config")
119+
if self.exists(oldConfigDirectory, followSymlink: false) && self.isDirectory(oldConfigDirectory) {
120+
let content = try self.getDirectoryContents(oldConfigDirectory)
121+
for item in content {
122+
if self.isFile(oldConfigDirectory.appending(component: item)) && !self.isSymlink(oldConfigDirectory.appending(component: item)) {
123+
try self.move(from: oldConfigDirectory.appending(component: item), to: idiomaticConfigurationDirectory.appending(component: item))
124+
try self.createSymbolicLink(oldConfigDirectory.appending(component: item), pointingAt: idiomaticConfigurationDirectory.appending(component: item), relative: false)
125+
}
126+
}
127+
}
128+
}
129+
}
130+
// ~temporary 5.6 migration
131+
132+
// Create idiomatic if necessary
133+
if !self.exists(idiomaticConfigurationDirectory) {
134+
try self.createDirectory(idiomaticConfigurationDirectory, recursive: true)
135+
}
136+
// Create ~/.swiftpm if necessary
137+
if !self.exists(self.dotSwiftPM) {
138+
try self.createDirectory(self.dotSwiftPM, recursive: true)
139+
}
140+
// Create ~/.swiftpm/configuration symlink if necessary
141+
if !self.exists(self.dotSwiftPMConfigurationDirectory, followSymlink: false) {
142+
try self.createSymbolicLink(dotSwiftPMConfigurationDirectory, pointingAt: idiomaticConfigurationDirectory, relative: false)
143+
}
144+
145+
return idiomaticConfigurationDirectory
90146
}
91147
}
92148

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

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

105178
// Create idiomatic if necessary
106-
if !self.exists(idiomaticConfigDirectory) {
107-
try self.createDirectory(idiomaticConfigDirectory, recursive: true)
179+
if !self.exists(idiomaticSecurityDirectory) {
180+
try self.createDirectory(idiomaticSecurityDirectory, recursive: true)
108181
}
109182
// Create ~/.swiftpm if necessary
110183
if !self.exists(self.dotSwiftPM) {
111184
try self.createDirectory(self.dotSwiftPM, recursive: true)
112185
}
113-
// Create ~/.swiftpm/config symlink if necessary
114-
if !self.exists(self.dotSwiftPMConfigDirectory, followSymlink: false) {
115-
try self.createSymbolicLink(dotSwiftPMConfigDirectory, pointingAt: idiomaticConfigDirectory, relative: false)
186+
// Create ~/.swiftpm/security symlink if necessary
187+
if !self.exists(self.dotSwiftPMSecurityDirectory, followSymlink: false) {
188+
try self.createSymbolicLink(dotSwiftPMSecurityDirectory, pointingAt: idiomaticSecurityDirectory, relative: false)
116189
}
117-
return idiomaticConfigDirectory
190+
return idiomaticSecurityDirectory
118191
}
119192
}
120193

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: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ protocol SwiftCommand: ParsableCommand {
246246
extension SwiftCommand {
247247
public func run() throws {
248248
let swiftTool = try SwiftTool(options: swiftOptions)
249+
// make sure common directories are created
250+
try swiftTool.createSharedDirectories()
249251
try self.run(swiftTool)
250252
if swiftTool.observabilityScope.errorsReported || swiftTool.executionStatus == .failure {
251253
throw ExitCode.failure
@@ -634,29 +636,39 @@ public class SwiftTool {
634636
}
635637

636638
do {
637-
return try localFileSystem.getOrCreateSwiftPMConfigDirectory()
639+
return try localFileSystem.getOrCreateSwiftPMConfigurationDirectory()
638640
} catch {
639641
self.observabilityScope.emit(warning: "Failed creating default configuration location, \(error)")
640642
return .none
641643
}
642644
}
643645

644646
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)
647+
if let explicitSecurityPath = options.securityPath {
648+
// Create the explicit security path if necessary
649+
if !localFileSystem.exists(explicitSecurityPath) {
650+
try localFileSystem.createDirectory(explicitSecurityPath, recursive: true)
650651
}
652+
return explicitSecurityPath
653+
}
654+
655+
do {
656+
let sharedSecurityDirectory = try localFileSystem.getOrCreateSwiftPMSecurityDirectory()
651657
// And make sure we can write files (locking the directory writes a lock file)
652-
try fileSystem.withLock(on: sharedSecurityDirectory, type: .exclusive) { }
658+
try localFileSystem.withLock(on: sharedSecurityDirectory, type: .exclusive) { }
653659
return sharedSecurityDirectory
654660
} catch {
655-
self.observabilityScope.emit(warning: "Failed creating shared security directory: \(error)")
661+
self.observabilityScope.emit(warning: "Failed creating default security location, \(error)")
656662
return .none
657663
}
658664
}
659665

666+
fileprivate func createSharedDirectories() throws {
667+
_ = try getSharedCacheDirectory()
668+
_ = try getSharedConfigurationDirectory()
669+
_ = try getSharedSecurityDirectory()
670+
}
671+
660672
/// Returns the currently active workspace.
661673
func getActiveWorkspace() throws -> Workspace {
662674
if let workspace = _workspace {

Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
5555
self.decoder = JSONDecoder.makeWithDefaults()
5656
self.validator = JSONModel.Validator(configuration: configuration.validator)
5757
self.signatureValidator = signatureValidator ?? PackageCollectionSigning(
58-
trustedRootCertsDir: configuration.trustedRootCertsDir ?? fileSystem.swiftPMConfigDirectory.appending(component: "trust-root-certs").asURL,
58+
trustedRootCertsDir: configuration.trustedRootCertsDir ?? fileSystem.swiftPMConfigurationDirectory.appending(component: "trust-root-certs").asURL,
5959
additionalTrustedRootCerts: sourceCertPolicy.allRootCerts.map { Array($0) },
6060
observabilityScope: observabilityScope,
6161
callbackQueue: .sharedConcurrent

Sources/PackageCollections/Storage/FilePackageCollectionsSourcesStorage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ struct FilePackageCollectionsSourcesStorage: PackageCollectionsSourcesStorage {
2626
init(fileSystem: FileSystem = localFileSystem, path: AbsolutePath? = nil) {
2727
self.fileSystem = fileSystem
2828

29-
self.path = path ?? fileSystem.swiftPMConfigDirectory.appending(component: "collections.json")
29+
self.path = path ?? fileSystem.swiftPMConfigurationDirectory.appending(component: "collections.json")
3030
self.encoder = JSONEncoder.makeWithDefaults()
3131
self.decoder = JSONDecoder.makeWithDefaults()
3232
}

Sources/SPMTestSupport/MockWorkspace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public final class MockWorkspace {
241241
resolvedVersionsFile: self.sandbox.appending(component: "Package.resolved"),
242242
sharedSecurityDirectory: self.fileSystem.swiftPMSecurityDirectory,
243243
sharedCacheDirectory: self.fileSystem.swiftPMCacheDirectory,
244-
sharedConfigurationDirectory: self.fileSystem.swiftPMConfigDirectory
244+
sharedConfigurationDirectory: self.fileSystem.swiftPMConfigurationDirectory
245245
),
246246
mirrors: self.mirrors,
247247
customToolsVersion: self.toolsVersion,

Sources/Workspace/WorkspaceConfiguration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ extension Workspace {
126126
resolvedVersionsFile: DefaultLocations.resolvedVersionsFile(forRootPackage: rootPath),
127127
sharedSecurityDirectory: fileSystem.swiftPMSecurityDirectory,
128128
sharedCacheDirectory: fileSystem.swiftPMCacheDirectory,
129-
sharedConfigurationDirectory: fileSystem.swiftPMConfigDirectory
129+
sharedConfigurationDirectory: fileSystem.swiftPMConfigurationDirectory
130130
)
131131
}
132132
}

Tests/FunctionalTests/MiscellaneousTests.swift

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ class MiscellaneousTestCase: XCTestCase {
415415
XCTAssert(output.contains("does not exist"), "Error from git was not propagated to process output: \(output)")
416416
}
417417
}
418-
418+
419419
func testLocalPackageUsedAsURL() throws {
420420
fixture(name: "Miscellaneous/LocalPackageAsURL", createGitRepo: false) { prefix in
421421
// This fixture has a setup that is trying to use a local package
@@ -431,7 +431,7 @@ class MiscellaneousTestCase: XCTestCase {
431431
XCTAssert(output.contains("Cannot clone from local directory"), "Didn't find expected output: \(output)")
432432
}
433433
}
434-
434+
435435
func testUnicode() {
436436
#if !os(Linux) && !os(Android) // TODO: - Linux has trouble with this and needs investigation.
437437
fixture(name: "Miscellaneous/Unicode") { prefix in
@@ -505,7 +505,7 @@ class MiscellaneousTestCase: XCTestCase {
505505
XCTAssertMatch(stderr, .contains("warning: '--generate-linuxmain' option is deprecated"))
506506
}
507507
}
508-
508+
509509
func testGenerateLinuxMain() {
510510
#if os(macOS)
511511
fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
@@ -552,13 +552,13 @@ class MiscellaneousTestCase: XCTestCase {
552552
}
553553
#endif
554554
}
555-
555+
556556
func testTestsCanLinkAgainstExecutable() throws {
557557
// Check if the host compiler supports the '-entry-point-function-name' flag.
558558
#if swift(<5.5)
559559
try XCTSkipIf(true, "skipping because host compiler doesn't support '-entry-point-function-name'")
560560
#endif
561-
561+
562562
fixture(name: "Miscellaneous/TestableExe") { prefix in
563563
do {
564564
let (stdout, _) = try executeSwiftTest(prefix)
@@ -648,6 +648,17 @@ class MiscellaneousTestCase: XCTestCase {
648648
try SwiftPMProduct.SwiftBuild.execute(["--cache-path", customCachePath.pathString], packagePath: path)
649649
XCTAssertDirectoryExists(customCachePath)
650650
}
651+
652+
fixture(name: "Miscellaneous/Simple") { path in
653+
try localFileSystem.chmod(.userUnWritable, path: path)
654+
let customCachePath = path.appending(components: "custom", "cache")
655+
XCTAssertNoSuchPath(customCachePath)
656+
let result = try SwiftPMProduct.SwiftBuild.executeProcess(["--cache-path", customCachePath.pathString], packagePath: path)
657+
XCTAssert(result.exitStatus != .terminated(code: 0))
658+
let output = try result.utf8stderrOutput()
659+
XCTAssert(output.contains("error: You don’t have permission"), "expected permissions error")
660+
XCTAssertNoSuchPath(customCachePath)
661+
}
651662
}
652663

653664
func testCustomConfigPath() {
@@ -657,5 +668,36 @@ class MiscellaneousTestCase: XCTestCase {
657668
try SwiftPMProduct.SwiftBuild.execute(["--config-path", customConfigPath.pathString], packagePath: path)
658669
XCTAssertDirectoryExists(customConfigPath)
659670
}
671+
672+
fixture(name: "Miscellaneous/Simple") { path in
673+
try localFileSystem.chmod(.userUnWritable, path: path)
674+
let customConfigPath = path.appending(components: "custom", "config")
675+
XCTAssertNoSuchPath(customConfigPath)
676+
let result = try SwiftPMProduct.SwiftBuild.executeProcess(["--config-path", customConfigPath.pathString], packagePath: path)
677+
XCTAssert(result.exitStatus != .terminated(code: 0))
678+
let output = try result.utf8stderrOutput()
679+
XCTAssert(output.contains("error: You don’t have permission"), "expected permissions error")
680+
XCTAssertNoSuchPath(customConfigPath)
681+
}
682+
}
683+
684+
func testCustomSecurityPath() {
685+
fixture(name: "Miscellaneous/Simple") { path in
686+
let customSecurityPath = path.appending(components: "custom", "security")
687+
XCTAssertNoSuchPath(customSecurityPath)
688+
try SwiftPMProduct.SwiftBuild.execute(["--security-path", customSecurityPath.pathString], packagePath: path)
689+
XCTAssertDirectoryExists(customSecurityPath)
690+
}
691+
692+
fixture(name: "Miscellaneous/Simple") { path in
693+
try localFileSystem.chmod(.userUnWritable, path: path)
694+
let customSecurityPath = path.appending(components: "custom", "security")
695+
XCTAssertNoSuchPath(customSecurityPath)
696+
let result = try SwiftPMProduct.SwiftBuild.executeProcess(["--security-path", customSecurityPath.pathString], packagePath: path)
697+
XCTAssert(result.exitStatus != .terminated(code: 0))
698+
let output = try result.utf8stderrOutput()
699+
XCTAssert(output.contains("error: You don’t have permission"), "expected permissions error")
700+
XCTAssertNoSuchPath(customSecurityPath)
701+
}
660702
}
661703
}

Utilities/Docker/docker-compose.2004.55.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ services:
1616
args:
1717
ubuntu_version: "focal"
1818
swift_version: "5.5"
19-
base_image: "swiftlang/swift:nightly-5.5-focal"
2019

2120
build:
2221
image: swift-package-manager:20.04-5.5

Utilities/Docker/docker-compose.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ services:
2727
- ~/.ssh:/root/.ssh
2828
- ~/.cache:/root/.cache
2929
- ~/.swiftpm/cache:/root/.swiftpm/cache
30-
- ~/.swiftpm/config:/root/.swiftpm/config
30+
- ~/.swiftpm/configuration:/root/.swiftpm/configuration
31+
- ~/.swiftpm/security:/root/.swiftpm/security
3132
# swift-package-manager code
3233
- ../..:/code/swift-package-manager:z
3334
# bootstrap script requires dependencies to be pre-fetched and in a specific place

0 commit comments

Comments
 (0)