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: 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/Files/Mirrors.swift b/Sources/SwiftPackageListCore/Files/Mirrors.swift new file mode 100644 index 0000000..82ea691 --- /dev/null +++ b/Sources/SwiftPackageListCore/Files/Mirrors.swift @@ -0,0 +1,76 @@ +// +// Mirrors.swift +// SwiftPackageListCore +// +// Created by Felix Herrmann on 09.11.25. +// + +import Foundation + +// swiftlint:disable identifier_name type_name + +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): + let object = v1.object.first { $0.original == location } + return object?.mirror + } + } +} + +// swiftlint:enable identifier_name type_name 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 af29c1a..410b3e4 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 @@ -36,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/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/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) } } 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/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/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) } 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 +} 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"