diff --git a/Brewfile b/Brewfile new file mode 100644 index 0000000..a5f9849 --- /dev/null +++ b/Brewfile @@ -0,0 +1 @@ +brew 'xz' diff --git a/README.md b/README.md index 9f0fd27..d456d4d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,16 @@ swift experimental-sdk list The output will either state that no Swift SDKs are available, or produce a list of those you previously had installed, in case you've used the `swift experimental-sdk install` command before. +### macOS Requirements + +The generator depends on the `xz` utility for more efficient downloading of package lists for Ubuntu. This is optional, but can be installed via the included `Brewfile`: + +```bash +brew bundle install +``` + +If `xz` is not found, the generator will fallback on `gzip`. + ## Supported platforms and minimum versions macOS as a host platform and Linux as both host and target platforms are supported by the generator. @@ -36,7 +46,7 @@ The generator also allows cross-compiling between any Linux distributions offici | -: | :- | :- | | macOS (arm64) | ✅ macOS 13.0+ | ❌ | | macOS (x86_64) | ✅ macOS 13.0+[^1] | ❌ | -| Ubuntu | ✅ 20.04+ | ✅ 20.04 / 22.04 | +| Ubuntu | ✅ 20.04+ | ✅ 20.04+ | | RHEL | ✅ Fedora 39[^2], UBI 9 | ✅ UBI 9 | | Amazon Linux 2 | ✅ Supported | ✅ Supported[^3] | | Debian 12 | ✅ Supported[^2] | ✅ Supported[^2][^3] | diff --git a/Sources/GeneratorCLI/GeneratorCLI.swift b/Sources/GeneratorCLI/GeneratorCLI.swift index 9f1af3d..a322f10 100644 --- a/Sources/GeneratorCLI/GeneratorCLI.swift +++ b/Sources/GeneratorCLI/GeneratorCLI.swift @@ -199,9 +199,9 @@ extension GeneratorCLI { @Option( help: """ - Version of the Linux distribution used as a target platform. Available options for Ubuntu: `20.04`, \ - `22.04` (default when `--linux-distribution-name` is `ubuntu`). Available options for RHEL: `ubi9` (default when \ - `--linux-distribution-name` is `rhel`). + Version of the Linux distribution used as a target platform. + Available options for Ubuntu: `20.04`, `22.04` (default when `--linux-distribution-name` is `ubuntu`), `24.04`. + Available options for RHEL: `ubi9` (default when `--linux-distribution-name` is `rhel`). """ ) var linuxDistributionVersion: String? diff --git a/Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator+Download.swift b/Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator+Download.swift index c2d7dbb..59db653 100644 --- a/Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator+Download.swift +++ b/Sources/SwiftSDKGenerator/Generator/SwiftSDKGenerator+Download.swift @@ -82,11 +82,21 @@ extension SwiftSDKGenerator { ) async throws { logger.debug("Parsing Ubuntu packages list...") + // Find xz path + let xzPath = try await which("xz") + if xzPath == nil { + logger.warning(""" + The `xz` utility was not found in `PATH`. \ + Consider installing it for more efficient downloading of package lists. + """) + } + async let mainPackages = try await client.parseUbuntuPackagesList( ubuntuRelease: versionsConfiguration.linuxDistribution.release, repository: "main", targetTriple: self.targetTriple, - isVerbose: self.isVerbose + isVerbose: self.isVerbose, + xzPath: xzPath ) async let updatesPackages = try await client.parseUbuntuPackagesList( @@ -94,7 +104,8 @@ extension SwiftSDKGenerator { releaseSuffix: "-updates", repository: "main", targetTriple: self.targetTriple, - isVerbose: self.isVerbose + isVerbose: self.isVerbose, + xzPath: xzPath ) async let universePackages = try await client.parseUbuntuPackagesList( @@ -102,7 +113,8 @@ extension SwiftSDKGenerator { releaseSuffix: "-updates", repository: "universe", targetTriple: self.targetTriple, - isVerbose: self.isVerbose + isVerbose: self.isVerbose, + xzPath: xzPath ) let allPackages = try await mainPackages @@ -175,21 +187,31 @@ extension SwiftSDKGenerator { extension HTTPClientProtocol { private func downloadUbuntuPackagesList( from url: String, + unzipWith zipPath: String, isVerbose: Bool ) async throws -> String? { - guard let packages = try await get(url: url).body?.unzip(isVerbose: isVerbose) else { + guard let packages = try await get(url: url).body?.unzip(zipPath: zipPath, isVerbose: isVerbose) else { throw FileOperationError.downloadFailed(url) } return String(buffer: packages) } + func packagesFileName(isXzAvailable: Bool) -> String { + if isXzAvailable { + return "Packages.xz" + } + // Use .gz if xz is not available + return "Packages.gz" + } + func parseUbuntuPackagesList( ubuntuRelease: String, releaseSuffix: String = "", repository: String, targetTriple: Triple, - isVerbose: Bool + isVerbose: Bool, + xzPath: String? ) async throws -> [String: URL] { let mirrorURL: String if targetTriple.arch == .x86_64 { @@ -200,13 +222,13 @@ extension HTTPClientProtocol { let packagesListURL = """ \(mirrorURL)/dists/\(ubuntuRelease)\(releaseSuffix)/\(repository)/binary-\( - targetTriple.arch! - .debianConventionName - )/Packages.gz + targetTriple.arch!.debianConventionName + )/\(packagesFileName(isXzAvailable: xzPath != nil)) """ guard let packages = try await downloadUbuntuPackagesList( from: packagesListURL, + unzipWith: xzPath ?? "/usr/bin/gzip", // fallback on gzip if xz not available isVerbose: isVerbose ) else { throw GeneratorError.ubuntuPackagesDecompressionFailure diff --git a/Sources/SwiftSDKGenerator/PlatformModels/LinuxDistribution.swift b/Sources/SwiftSDKGenerator/PlatformModels/LinuxDistribution.swift index 3ef9e1a..f10b818 100644 --- a/Sources/SwiftSDKGenerator/PlatformModels/LinuxDistribution.swift +++ b/Sources/SwiftSDKGenerator/PlatformModels/LinuxDistribution.swift @@ -10,10 +10,6 @@ // //===----------------------------------------------------------------------===// -private let ubuntuReleases = [ - "22.04": "jammy", -] - public enum LinuxDistribution: Hashable, Sendable { public enum Name: String { case rhel @@ -27,6 +23,7 @@ public enum LinuxDistribution: Hashable, Sendable { public enum Ubuntu: String, Sendable { case focal case jammy + case noble init(version: String) throws { switch version { @@ -34,6 +31,8 @@ public enum LinuxDistribution: Hashable, Sendable { self = .focal case "22.04": self = .jammy + case "24.04": + self = .noble default: throw GeneratorError.unknownLinuxDistribution(name: LinuxDistribution.Name.ubuntu.rawValue, version: version) } @@ -43,6 +42,7 @@ public enum LinuxDistribution: Hashable, Sendable { switch self { case .focal: return "20.04" case .jammy: return "22.04" + case .noble: return "24.04" } } @@ -60,7 +60,6 @@ public enum LinuxDistribution: Hashable, Sendable { "linux-libc-dev", "zlib1g", "zlib1g-dev", - "libc6", ] case .jammy: return [ "libc6", @@ -75,6 +74,19 @@ public enum LinuxDistribution: Hashable, Sendable { "zlib1g", "zlib1g-dev", ] + case .noble: return [ + "libc6", + "libc6-dev", + "libgcc-s1", + "libgcc-13-dev", + "libicu74", + "libicu-dev", + "libstdc++-13-dev", + "libstdc++6", + "linux-libc-dev", + "zlib1g", + "zlib1g-dev", + ] } } } diff --git a/Sources/SwiftSDKGenerator/SystemUtils/ByteBuffer+Utils.swift b/Sources/SwiftSDKGenerator/SystemUtils/ByteBuffer+Utils.swift index b56cd26..f16b82a 100644 --- a/Sources/SwiftSDKGenerator/SystemUtils/ByteBuffer+Utils.swift +++ b/Sources/SwiftSDKGenerator/SystemUtils/ByteBuffer+Utils.swift @@ -15,9 +15,9 @@ import Foundation import NIOCore public extension ByteBuffer { - func unzip(isVerbose: Bool) async throws -> ByteBuffer? { + func unzip(zipPath: String, isVerbose: Bool) async throws -> ByteBuffer? { let result = try await ProcessExecutor.runCollectingOutput( - executable: "/usr/bin/gzip", ["-cd"], + executable: zipPath, ["-cd"], standardInput: [self].async, collectStandardOutput: true, collectStandardError: false, diff --git a/Sources/SwiftSDKGenerator/SystemUtils/which.swift b/Sources/SwiftSDKGenerator/SystemUtils/which.swift new file mode 100644 index 0000000..472c32b --- /dev/null +++ b/Sources/SwiftSDKGenerator/SystemUtils/which.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import AsyncProcess +import Foundation + +/// Look for an executable using the `which` utility. +/// +/// - Parameter executableName: The name of the executable to search for. +/// - Throws: Any errors thrown by the ProcessExecutor. +/// - Returns: The path to the executable if found, otherwise nil. +func which(_ executableName: String) async throws -> String? { + let result = try await ProcessExecutor.runCollectingOutput( + executable: "/usr/bin/which", [executableName], collectStandardOutput: true, collectStandardError: false, + environment: ProcessInfo.processInfo.environment + ) + + guard result.exitReason == .exit(0) else { + return nil + } + + if let output = result.standardOutput { + let path = String(buffer: output).trimmingCharacters(in: .whitespacesAndNewlines) + return path.isEmpty ? nil : path + } + + return nil +}