From e9b2e222d76d0b2a854180b7cfdb534b1b01cd7e Mon Sep 17 00:00:00 2001 From: Felix Herrmann Date: Sun, 9 Nov 2025 19:57:43 +0100 Subject: [PATCH 1/6] Add Mirrors file --- .../SwiftPackageListCore/Files/Mirrors.swift | 71 +++++++++++++++++++ .../MirrorsTests.swift | 56 +++++++++++++++ .../Resources/Mirrors/mirrors_v1.json | 13 ++++ .../Resources/Mirrors/mirrors_v999.json | 3 + 4 files changed, 143 insertions(+) create mode 100644 Sources/SwiftPackageListCore/Files/Mirrors.swift create mode 100644 Tests/SwiftPackageListCoreTests/MirrorsTests.swift create mode 100644 Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v1.json create mode 100644 Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v999.json diff --git a/Sources/SwiftPackageListCore/Files/Mirrors.swift b/Sources/SwiftPackageListCore/Files/Mirrors.swift new file mode 100644 index 0000000..b3a066c --- /dev/null +++ b/Sources/SwiftPackageListCore/Files/Mirrors.swift @@ -0,0 +1,71 @@ +// +// Mirrors.swift +// SwiftPackageListCore +// +// Created by Felix Herrmann on 09.11.25. +// + +import Foundation + +struct Mirrors: VersionedFile { + let url: URL + let storage: Storage + + init(url: URL) throws { + self.url = url + self.storage = try Storage(url: url) + } +} + +// MARK: - Storage + +extension Mirrors { + enum Storage { + case v1(V1) + } +} + +extension Mirrors.Storage: VersionedFileStorage { + private struct Version: Decodable { + let version: Int + } + + init(url: URL) throws { + let data = try Data(contentsOf: url) + let decoder = JSONDecoder() + let version = try decoder.decode(Version.self, from: data) + + switch version.version { + case 1: + let v1 = try decoder.decode(V1.self, from: data) + self = .v1(v1) + default: + throw RuntimeError("Version \(version.version) of mirrors.json is not supported") + } + } +} + +// MARK: - V1 + +extension Mirrors.Storage { + struct V1: Decodable { + struct Object: Decodable { + let mirror: String + let original: String + } + + let object: [Object] + let version: Int + } +} + +// MARK: - Mirror + +extension Mirrors { + func mirror(for location: String) -> String? { + switch storage { + case .v1(let v1): + return v1.object.first(where: { $0.original == location })?.mirror + } + } +} diff --git a/Tests/SwiftPackageListCoreTests/MirrorsTests.swift b/Tests/SwiftPackageListCoreTests/MirrorsTests.swift new file mode 100644 index 0000000..e9962fd --- /dev/null +++ b/Tests/SwiftPackageListCoreTests/MirrorsTests.swift @@ -0,0 +1,56 @@ +// +// MirrorsTests.swift +// SwiftPackageListCoreTests +// +// Created by Felix Herrmann on 09.11.25. +// + +import XCTest +@testable import SwiftPackageListCore + +final class MirrorsTests: XCTestCase { + func testVersion1() throws { + let url = Bundle.module.url( + forResource: "mirrors_v1", + withExtension: "json", + subdirectory: "Resources/Mirrors" + ) + let unwrappedURL = try XCTUnwrap(url) + let mirrors = try Mirrors(url: unwrappedURL) + + guard case .v1(let storage) = mirrors.storage else { + XCTFail("\(unwrappedURL.path) was not recognized as v1") + return + } + + XCTAssertEqual(storage.version, 1) + XCTAssertEqual(storage.object.count, 2) + } + + func testUnsupportedVersion() throws { + let url = Bundle.module.url( + forResource: "mirrors_v999", + withExtension: "json", + subdirectory: "Resources/Mirrors" + ) + let unwrappedURL = try XCTUnwrap(url) + + XCTAssertThrowsError(try Mirrors(url: unwrappedURL)) { error in + XCTAssertTrue(error is RuntimeError) + XCTAssertEqual((error as? RuntimeError)?.description, "Version 999 of mirrors.json is not supported") + } + } + + func testMirror() throws { + let url = Bundle.module.url( + forResource: "mirrors_v1", + withExtension: "json", + subdirectory: "Resources/Mirrors" + ) + let unwrappedURL = try XCTUnwrap(url) + let mirrors = try Mirrors(url: unwrappedURL) + + XCTAssertEqual(mirrors.mirror(for: "https://github.com/FelixHerrmann/swift-package-list"), "https://github.com/example/swift-package-list") + XCTAssertEqual(mirrors.mirror(for: "https://github.com/FelixHerrmann/test"), "/Users/example/test") + } +} diff --git a/Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v1.json b/Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v1.json new file mode 100644 index 0000000..c2b4c0b --- /dev/null +++ b/Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v1.json @@ -0,0 +1,13 @@ +{ + "object" : [ + { + "mirror" : "https://github.com/example/swift-package-list", + "original" : "https://github.com/FelixHerrmann/swift-package-list" + }, + { + "mirror" : "/Users/example/test", + "original" : "https://github.com/FelixHerrmann/test" + } + ], + "version" : 1 +} diff --git a/Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v999.json b/Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v999.json new file mode 100644 index 0000000..d528d8e --- /dev/null +++ b/Tests/SwiftPackageListCoreTests/Resources/Mirrors/mirrors_v999.json @@ -0,0 +1,3 @@ +{ + "version": 999 +} From fd51025979b779699fe9bdb9e7f26a96f2069351 Mon Sep 17 00:00:00 2001 From: Felix Herrmann Date: Sun, 9 Nov 2025 20:10:10 +0100 Subject: [PATCH 2/6] Add Configuration directory to NativeProject --- .../Directories/Configuration.swift | 22 +++++++++++++++++++ .../Project/NativeProject.swift | 5 +++++ .../Project/SwiftPackage/SwiftPackage.swift | 7 ++++++ .../Project/XcodeProject/XcodeProject.swift | 9 ++++++++ .../XcodeWorkspace/XcodeWorkspace.swift | 8 +++++++ .../ProjectTests.swift | 19 ++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 Sources/SwiftPackageListCore/Directories/Configuration.swift diff --git a/Sources/SwiftPackageListCore/Directories/Configuration.swift b/Sources/SwiftPackageListCore/Directories/Configuration.swift new file mode 100644 index 0000000..8bb118f --- /dev/null +++ b/Sources/SwiftPackageListCore/Directories/Configuration.swift @@ -0,0 +1,22 @@ +// +// Configuration.swift +// SwiftPackageListCore +// +// Created by Felix Herrmann on 09.11.25. +// + +import Foundation + +struct Configuration: Directory { + let url: URL +} + +extension Configuration { + var mirrors: Mirrors? { + get throws { + let url = url.appendingPathComponent("mirrors.json") + guard FileManager.default.fileExists(atPath: url.path) else { return nil } + return try Mirrors(url: url) + } + } +} diff --git a/Sources/SwiftPackageListCore/Project/NativeProject.swift b/Sources/SwiftPackageListCore/Project/NativeProject.swift index af29c1a..9d9183b 100644 --- a/Sources/SwiftPackageListCore/Project/NativeProject.swift +++ b/Sources/SwiftPackageListCore/Project/NativeProject.swift @@ -11,9 +11,14 @@ import SwiftPackageList protocol NativeProject: Project { var workspaceURL: URL { get } var packageResolved: PackageResolved { get throws } + var configuration: Configuration? { get } } extension NativeProject { + var configuration: Configuration? { + return nil + } + func packages() throws -> [Package] { let packageResolved = try packageResolved diff --git a/Sources/SwiftPackageListCore/Project/SwiftPackage/SwiftPackage.swift b/Sources/SwiftPackageListCore/Project/SwiftPackage/SwiftPackage.swift index 5acdc3f..fc8d975 100644 --- a/Sources/SwiftPackageListCore/Project/SwiftPackage/SwiftPackage.swift +++ b/Sources/SwiftPackageListCore/Project/SwiftPackage/SwiftPackage.swift @@ -27,4 +27,11 @@ struct SwiftPackage: NativeProject { return try PackageResolved(url: url) } } + + var configuration: Configuration? { + let url = workspaceURL + .appendingPathComponent(".swiftpm") + .appendingPathComponent("configuration") + return Configuration(url: url) + } } diff --git a/Sources/SwiftPackageListCore/Project/XcodeProject/XcodeProject.swift b/Sources/SwiftPackageListCore/Project/XcodeProject/XcodeProject.swift index ec2b412..3982af9 100644 --- a/Sources/SwiftPackageListCore/Project/XcodeProject/XcodeProject.swift +++ b/Sources/SwiftPackageListCore/Project/XcodeProject/XcodeProject.swift @@ -37,4 +37,13 @@ struct XcodeProject: NativeProject { return try PackageResolved(url: url) } } + + var configuration: Configuration? { + let url = fileURL + .appendingPathComponent("project.xcworkspace") + .appendingPathComponent("xcshareddata") + .appendingPathComponent("swiftpm") + .appendingPathComponent("configuration") + return Configuration(url: url) + } } diff --git a/Sources/SwiftPackageListCore/Project/XcodeWorkspace/XcodeWorkspace.swift b/Sources/SwiftPackageListCore/Project/XcodeWorkspace/XcodeWorkspace.swift index 3809420..7e7c050 100644 --- a/Sources/SwiftPackageListCore/Project/XcodeWorkspace/XcodeWorkspace.swift +++ b/Sources/SwiftPackageListCore/Project/XcodeWorkspace/XcodeWorkspace.swift @@ -52,4 +52,12 @@ struct XcodeWorkspace: NativeProject { return try PackageResolved(url: url) } } + + var configuration: Configuration? { + let url = fileURL + .appendingPathComponent("xcshareddata") + .appendingPathComponent("swiftpm") + .appendingPathComponent("configuration") + return Configuration(url: url) + } } diff --git a/Tests/SwiftPackageListCoreTests/ProjectTests.swift b/Tests/SwiftPackageListCoreTests/ProjectTests.swift index a8bf252..fdcbfa9 100644 --- a/Tests/SwiftPackageListCoreTests/ProjectTests.swift +++ b/Tests/SwiftPackageListCoreTests/ProjectTests.swift @@ -27,6 +27,13 @@ final class ProjectTests: XCTestCase { .appendingPathComponent("Package.resolved") XCTAssertEqual(try xcodeProject.packageResolved.url, expectedPackageResolvedFileURL) + let expectedConfigurationDirectoryURL = unwrappedURL + .appendingPathComponent("project.xcworkspace") + .appendingPathComponent("xcshareddata") + .appendingPathComponent("swiftpm") + .appendingPathComponent("configuration") + XCTAssertEqual(xcodeProject.configuration?.url, expectedConfigurationDirectoryURL) + XCTAssertEqual(xcodeProject.name, "Project") XCTAssertEqual(xcodeProject.organizationName, "SwiftPackageList") } @@ -52,6 +59,12 @@ final class ProjectTests: XCTestCase { .appendingPathComponent("Package.resolved") XCTAssertEqual(try xcodeWorkspace.packageResolved.url, expectedPackageResolvedFileURL) + let expectedConfigurationDirectoryURL = unwrappedURL + .appendingPathComponent("xcshareddata") + .appendingPathComponent("swiftpm") + .appendingPathComponent("configuration") + XCTAssertEqual(xcodeWorkspace.configuration?.url, expectedConfigurationDirectoryURL) + XCTAssertEqual(xcodeWorkspace.name, "Workspace") XCTAssertEqual(xcodeWorkspace.organizationName, "SwiftPackageList") } @@ -71,6 +84,12 @@ final class ProjectTests: XCTestCase { .appendingPathComponent("Package.resolved") XCTAssertEqual(try swiftPackage.packageResolved.url, expectedPackageResolvedFileURL) + let expectedConfigurationDirectoryURL = unwrappedURL + .deletingLastPathComponent() + .appendingPathComponent(".swiftpm") + .appendingPathComponent("configuration") + XCTAssertEqual(swiftPackage.configuration?.url, expectedConfigurationDirectoryURL) + XCTAssertEqual(swiftPackage.name, "SwiftPackage") XCTAssertNil(swiftPackage.organizationName) } From 901a5cde0a9032d6b73e66ff80040e8dd471321e Mon Sep 17 00:00:00 2001 From: Felix Herrmann Date: Sun, 9 Nov 2025 20:29:27 +0100 Subject: [PATCH 3/6] Add mirror-based location mapping to v2 pins --- .../Files/PackageResolved.swift | 15 +++++++++------ .../Project/NativeProject.swift | 2 +- .../TuistDependencies/TuistDependencies.swift | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftPackageListCore/Files/PackageResolved.swift b/Sources/SwiftPackageListCore/Files/PackageResolved.swift index 4d7f951..c68a3e7 100644 --- a/Sources/SwiftPackageListCore/Files/PackageResolved.swift +++ b/Sources/SwiftPackageListCore/Files/PackageResolved.swift @@ -137,14 +137,14 @@ extension PackageResolved.Storage { // MARK: - Packages extension PackageResolved { - func packages(in sourcePackages: SourcePackages) throws -> [Package] { + func packages(in sourcePackages: SourcePackages, configuration: Configuration?) throws -> [Package] { switch self.storage { case .v1(let packageResolved): return try packages(pins: packageResolved.object.pins, sourcePackages: sourcePackages) case .v2(let packageResolved): - return try packages(pins: packageResolved.pins, sourcePackages: sourcePackages) + return try packages(pins: packageResolved.pins, sourcePackages: sourcePackages, configuration: configuration) case .v3(let packageResolved): - return try packages(pins: packageResolved.pins, sourcePackages: sourcePackages) + return try packages(pins: packageResolved.pins, sourcePackages: sourcePackages, configuration: configuration) } } @@ -173,22 +173,25 @@ extension PackageResolved { private func packages( pins: [PackageResolved.Storage.V2.Pin], - sourcePackages: SourcePackages + sourcePackages: SourcePackages, + configuration: Configuration? ) throws -> [Package] { let checkouts = sourcePackages.checkouts let registryDownloads = sourcePackages.registryDownloads let workspaceState = try sourcePackages.workspaceState + let mirrors = try configuration?.mirrors return try pins.map { pin -> Package in let name = workspaceState.packageName(for: pin.identity) ?? pin.identity + let location = mirrors?.mirror(for: pin.location) ?? pin.location let packageSource: PackageSource? switch pin.kind { case .localSourceControl: - let url = URL(fileURLWithPath: pin.location) + let url = URL(fileURLWithPath: location) packageSource = PackageSource(url: url) case .remoteSourceControl: - packageSource = checkouts.packageSource(location: pin.location) + packageSource = checkouts.packageSource(location: location) case .registry: packageSource = registryDownloads.packageSource(identity: pin.identity, version: pin.state.version) default: diff --git a/Sources/SwiftPackageListCore/Project/NativeProject.swift b/Sources/SwiftPackageListCore/Project/NativeProject.swift index 9d9183b..410b3e4 100644 --- a/Sources/SwiftPackageListCore/Project/NativeProject.swift +++ b/Sources/SwiftPackageListCore/Project/NativeProject.swift @@ -41,6 +41,6 @@ extension NativeProject { sourcePackages = SourcePackages(url: sourcePackagesDirectory) } - return try packageResolved.packages(in: sourcePackages) + return try packageResolved.packages(in: sourcePackages, configuration: configuration) } } diff --git a/Sources/SwiftPackageListCore/Project/TuistDependencies/TuistDependencies.swift b/Sources/SwiftPackageListCore/Project/TuistDependencies/TuistDependencies.swift index d501c5e..7be1e8b 100644 --- a/Sources/SwiftPackageListCore/Project/TuistDependencies/TuistDependencies.swift +++ b/Sources/SwiftPackageListCore/Project/TuistDependencies/TuistDependencies.swift @@ -59,6 +59,6 @@ struct TuistDependencies: Project { sourcePackages = SourcePackages(url: sourcePackagesDirectory) } - return try packageResolved.packages(in: sourcePackages) + return try packageResolved.packages(in: sourcePackages, configuration: nil) } } From 428ee5cba6f2d7ed9f826c03dc0376aa454f1966 Mon Sep 17 00:00:00 2001 From: Felix Herrmann Date: Sun, 9 Nov 2025 20:42:41 +0100 Subject: [PATCH 4/6] Fix file paths --- .../Resources/PackageResolved/Package_v2.resolved | 2 +- .../Resources/PackageResolved/Package_v3.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v2.resolved b/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v2.resolved index 3456e34..35fd4d5 100644 --- a/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v2.resolved +++ b/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v2.resolved @@ -3,7 +3,7 @@ { "identity" : "swift-package-list", "kind" : "localSourceControl", - "location" : "Users/example/swift-package-list", + "location" : "/Users/example/swift-package-list", "state" : { "revision" : "3a1b45c9e994aebaf47e8c4bd631bd79075f4abb", "version" : "1.0.1" diff --git a/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v3.resolved b/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v3.resolved index 70ec224..0fe6e40 100644 --- a/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v3.resolved +++ b/Tests/SwiftPackageListCoreTests/Resources/PackageResolved/Package_v3.resolved @@ -4,7 +4,7 @@ { "identity" : "swift-package-list", "kind" : "localSourceControl", - "location" : "Users/example/swift-package-list", + "location" : "/Users/example/swift-package-list", "state" : { "revision" : "3a1b45c9e994aebaf47e8c4bd631bd79075f4abb", "version" : "1.0.1" From ec975692faf13ed8169abcf0b25334045dc1eb1a Mon Sep 17 00:00:00 2001 From: Felix Herrmann Date: Sun, 9 Nov 2025 21:05:01 +0100 Subject: [PATCH 5/6] Resolve lint warnings --- Sources/SwiftPackageListCore/Files/Mirrors.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftPackageListCore/Files/Mirrors.swift b/Sources/SwiftPackageListCore/Files/Mirrors.swift index b3a066c..82ea691 100644 --- a/Sources/SwiftPackageListCore/Files/Mirrors.swift +++ b/Sources/SwiftPackageListCore/Files/Mirrors.swift @@ -7,6 +7,8 @@ import Foundation +// swiftlint:disable identifier_name type_name + struct Mirrors: VersionedFile { let url: URL let storage: Storage @@ -65,7 +67,10 @@ extension Mirrors { func mirror(for location: String) -> String? { switch storage { case .v1(let v1): - return v1.object.first(where: { $0.original == location })?.mirror + let object = v1.object.first { $0.original == location } + return object?.mirror } } } + +// swiftlint:enable identifier_name type_name From fb34ed97d47440eb76c8d72a3be9138b52178572 Mon Sep 17 00:00:00 2001 From: Felix Herrmann Date: Mon, 10 Nov 2025 00:18:22 +0100 Subject: [PATCH 6/6] Use macOS 26 on all macOS jobs --- .github/workflows/release-build.yml | 2 +- .github/workflows/xcodebuild.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 9203a71..46c01e6 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -7,7 +7,7 @@ on: jobs: release-build: name: Release Build - runs-on: macos-15 + runs-on: macos-26 outputs: sha: ${{ steps.sha256.outputs.sha256 }} env: diff --git a/.github/workflows/xcodebuild.yml b/.github/workflows/xcodebuild.yml index a6b94ca..e0ee243 100644 --- a/.github/workflows/xcodebuild.yml +++ b/.github/workflows/xcodebuild.yml @@ -32,7 +32,7 @@ jobs: - generic/platform=tvOS - generic/platform=watchOS - generic/platform=visionOS - runs-on: macos-15 + runs-on: macos-26 steps: - uses: maxim-lobanov/setup-xcode@v1 with: @@ -47,7 +47,7 @@ jobs: test: name: Test needs: build-libraries - runs-on: macos-15 + runs-on: macos-26 steps: - uses: maxim-lobanov/setup-xcode@v1 with: