Skip to content

Commit 2975732

Browse files
authored
implement archiving for registry publishing (#6118)
motivation: progress on registry publishing implementation changes: * implement compression using ZipArchiver * implement code flow to archive package for publishing per spec * add tests
1 parent 00ac2ad commit 2975732

File tree

13 files changed

+781
-180
lines changed

13 files changed

+781
-180
lines changed

Package.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -548,11 +548,14 @@ let package = Package(
548548
"swift-package",
549549
"swift-test",
550550
"swift-run",
551+
"Basics",
552+
"Build",
551553
"Commands",
552-
"Workspace",
554+
"PackageModel",
555+
"PackageRegistryTool",
556+
"SourceControl",
553557
"SPMTestSupport",
554-
"Build",
555-
"SourceControl"
558+
"Workspace",
556559
]
557560
),
558561
.testTarget(

Sources/Basics/Archiver+Zip.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ public struct ZipArchiver: Archiver, Cancellable {
2727
///
2828
/// - Parameters:
2929
/// - fileSystem: The file-system to used by the `ZipArchiver`.
30-
public init(fileSystem: FileSystem) {
30+
/// - cancellator: Cancellation handler
31+
public init(fileSystem: FileSystem, cancellator: Cancellator? = .none) {
3132
self.fileSystem = fileSystem
32-
self.cancellator = Cancellator(observabilityScope: .none)
33+
self.cancellator = cancellator ?? Cancellator(observabilityScope: .none)
3334
}
3435

3536
public func extract(
@@ -70,6 +71,48 @@ public struct ZipArchiver: Archiver, Cancellable {
7071
}
7172
}
7273

74+
public func compress(
75+
directory: AbsolutePath,
76+
to destinationPath: AbsolutePath,
77+
completion: @escaping (Result<Void, Error>) -> Void
78+
) {
79+
do {
80+
guard self.fileSystem.isDirectory(directory) else {
81+
throw FileSystemError(.notDirectory, directory)
82+
}
83+
84+
#if os(Windows)
85+
let process = TSCBasic.Process(
86+
// FIXME: are these the right arguments?
87+
arguments: ["tar.exe", "-a", "-c", "-f", destinationPath.pathString, directory.basename],
88+
workingDirectory: directory.parentDirectory
89+
)
90+
#else
91+
let process = TSCBasic.Process(
92+
arguments: ["zip", "-r", destinationPath.pathString, directory.basename],
93+
workingDirectory: directory.parentDirectory
94+
)
95+
#endif
96+
97+
guard let registrationKey = self.cancellator.register(process) else {
98+
throw StringError("Failed to register cancellation for Archiver")
99+
}
100+
101+
DispatchQueue.sharedConcurrent.async {
102+
defer { self.cancellator.deregister(registrationKey) }
103+
completion(.init(catching: {
104+
try process.launch()
105+
let processResult = try process.waitUntilExit()
106+
guard processResult.exitStatus == .terminated(code: 0) else {
107+
throw try StringError(processResult.utf8stderrOutput())
108+
}
109+
}))
110+
}
111+
} catch {
112+
return completion(.failure(error))
113+
}
114+
}
115+
73116
public func validate(path: AbsolutePath, completion: @escaping (Result<Bool, Error>) -> Void) {
74117
do {
75118
guard self.fileSystem.exists(path) else {

Sources/Basics/Archiver.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ public protocol Archiver {
2929
completion: @escaping (Result<Void, Error>) -> Void
3030
)
3131

32+
/// Asynchronously compress the contents of a directory to a destination archive.
33+
///
34+
/// - Parameters:
35+
/// - directory: The `AbsolutePath` to the archive to extract.
36+
/// - destinationPath: The `AbsolutePath` to the directory to extract to.
37+
/// - completion: The completion handler that will be called when the operation finishes to notify of its success.
38+
func compress(
39+
directory: AbsolutePath,
40+
to destinationPath: AbsolutePath,
41+
completion: @escaping (Result<Void, Error>) -> Void
42+
)
43+
3244
/// Asynchronously validates if a file is an archive.
3345
///
3446
/// - Parameters:

Sources/Commands/PackageTools/ArchiveSource.swift

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import ArgumentParser
14+
import Basics
1415
import CoreCommands
1516
import SourceControl
1617
import TSCBasic
@@ -32,25 +33,47 @@ extension SwiftPackageTool {
3233
var output: AbsolutePath?
3334

3435
func run(_ swiftTool: SwiftTool) throws {
35-
let packageRoot = try globalOptions.locations.packageDirectory ?? swiftTool.getPackageRoot()
36-
let repository = GitRepository(path: packageRoot)
36+
let packageDirectory = try globalOptions.locations.packageDirectory ?? swiftTool.getPackageRoot()
3737

38-
let destination: AbsolutePath
38+
let archivePath: AbsolutePath
3939
if let output = output {
40-
destination = output
40+
archivePath = output
4141
} else {
4242
let graph = try swiftTool.loadPackageGraph()
4343
let packageName = graph.rootPackages[0].manifest.displayName // TODO: use identity instead?
44-
destination = packageRoot.appending(component: "\(packageName).zip")
44+
archivePath = packageDirectory.appending(component: "\(packageName).zip")
4545
}
4646

47-
try repository.archive(to: destination)
47+
try SwiftPackageTool.archiveSource(
48+
at: packageDirectory,
49+
to: archivePath,
50+
fileSystem: localFileSystem,
51+
cancellator: swiftTool.cancellator
52+
)
4853

49-
if destination.isDescendantOfOrEqual(to: packageRoot) {
50-
let relativePath = destination.relative(to: packageRoot)
54+
if archivePath.isDescendantOfOrEqual(to: packageDirectory) {
55+
let relativePath = archivePath.relative(to: packageDirectory)
5156
print("Created \(relativePath.pathString)")
5257
} else {
53-
print("Created \(destination.pathString)")
58+
print("Created \(archivePath.pathString)")
59+
}
60+
}
61+
}
62+
63+
public static func archiveSource(
64+
at packageDirectory: AbsolutePath,
65+
to archivePath: AbsolutePath,
66+
fileSystem: FileSystem,
67+
cancellator: Cancellator?
68+
) throws {
69+
let gitRepositoryProvider = GitRepositoryProvider()
70+
if gitRepositoryProvider.repositoryExists(at: packageDirectory) {
71+
let repository = GitRepository(path: packageDirectory, cancellator: cancellator)
72+
try repository.archive(to: archivePath)
73+
} else {
74+
let zipArchiver = ZipArchiver(fileSystem: fileSystem, cancellator: cancellator)
75+
try tsc_await {
76+
zipArchiver.compress(directory: packageDirectory, to: archivePath, completion: $0)
5477
}
5578
}
5679
}

Sources/PackageRegistry/RegistryClient.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -869,29 +869,29 @@ extension RegistryClient {
869869
}
870870

871871
extension RegistryClient {
872-
enum SignatureFormat {
872+
public enum SignatureFormat {
873873
case CMS_1_0_0
874874
}
875875
}
876876

877877
extension RegistryClient {
878878
public struct PublishRequirements {
879-
let metadata: Metadata
880-
let signing: Signing
879+
public let metadata: Metadata
880+
public let signing: Signing
881881

882-
struct Metadata {
883-
let location: [MetadataLocation]
882+
public struct Metadata {
883+
public let location: [MetadataLocation]
884884
}
885885

886-
enum MetadataLocation {
886+
public enum MetadataLocation {
887887
case request
888888
case archive
889889
}
890890

891-
struct Signing {
892-
let required: Bool
893-
let acceptedSignatureFormats: [SignatureFormat]
894-
let trustedRootCertificates: [String]
891+
public struct Signing {
892+
public let required: Bool
893+
public let acceptedSignatureFormats: [SignatureFormat]
894+
public let trustedRootCertificates: [String]
895895
}
896896
}
897897
}

Sources/PackageRegistryTool/SwiftPackageRegistryTool+Auth.swift renamed to Sources/PackageRegistryTool/PackageRegistryTool+Auth.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,18 @@ extension SwiftPackageRegistryTool {
8888

8989
// compute and validate registry URL
9090
guard let registryURL = self.registryURL ?? configuration.configuration.defaultRegistry?.url else {
91-
throw ConfigurationError.unknownRegistry
91+
throw ValidationError.unknownRegistry
9292
}
9393

9494
try registryURL.validateRegistryURL()
9595

9696
guard let host = registryURL.host?.lowercased() else {
97-
throw ConfigurationError.invalidURL(registryURL)
97+
throw ValidationError.invalidURL(registryURL)
9898
}
9999

100100
// We need to be able to read/write credentials
101101
guard let authorizationProvider = try swiftTool.getRegistryAuthorizationProvider() else {
102-
throw StringError("No credential store available")
102+
throw ValidationError.unknownCredentialStore
103103
}
104104

105105
let authenticationType: RegistryConfiguration.AuthenticationType
@@ -169,7 +169,7 @@ extension SwiftPackageRegistryTool {
169169

170170
// Login URL must be HTTPS
171171
guard let loginURL = URL(string: "https://\(host)\(loginAPIPath ?? "/login")") else {
172-
throw ConfigurationError.invalidURL(registryURL)
172+
throw ValidationError.invalidURL(registryURL)
173173
}
174174

175175
// Build a RegistryConfiguration with the given authentication settings
@@ -271,18 +271,18 @@ extension SwiftPackageRegistryTool {
271271

272272
// compute and validate registry URL
273273
guard let registryURL = self.registryURL ?? configuration.configuration.defaultRegistry?.url else {
274-
throw ConfigurationError.unknownRegistry
274+
throw ValidationError.unknownRegistry
275275
}
276276

277277
try registryURL.validateRegistryURL()
278278

279279
guard let host = registryURL.host?.lowercased() else {
280-
throw ConfigurationError.invalidURL(registryURL)
280+
throw ValidationError.invalidURL(registryURL)
281281
}
282282

283283
// We need to be able to read/write credentials
284284
guard let authorizationProvider = try swiftTool.getRegistryAuthorizationProvider() else {
285-
throw StringError("No credential store available")
285+
throw ValidationError.unknownCredentialStore
286286
}
287287

288288
let authorizationWriter = authorizationProvider as? AuthorizationWriter

0 commit comments

Comments
 (0)