4 Commits

Author SHA1 Message Date
T. R. Bernstein
6927464d47 Use Subprocess instead of Shwift
Some checks failed
Docs / docs (push) Has been cancelled
Docs / deploy (push) Has been cancelled
Drop Shwift: it is incompatible with musl (used by the Swift static
linking SDK), and its API is not meaningfully more concise than
Subprocess upon closer inspection.
2026-03-23 19:50:58 +01:00
T. R. Bernstein
31ed16c828 Cache build directory of linux containers
SwiftPM uses caches heavily to reduce compilation and download time.
Before this change, we through these caches away with each container.
2026-03-22 17:51:24 +01:00
T. R. Bernstein
ac1c86c431 Run linux container with same architechture as host
As the development team uses both Intel and Apple Silicon Macs,
we have to get the host CPU architecture at compilation time instead of
harcoding it.
If the container has a different architecture, the guest has to be
emulated.
2026-03-22 17:51:24 +01:00
T. R. Bernstein
4b28c293cb Use Shwift library instead of Subprocess
Shwift has a concise API, which makes writing shell code nice and easy.
This is an opinionated decision.
2026-03-22 17:51:05 +01:00
7 changed files with 77 additions and 44 deletions

View File

@@ -1,5 +1,5 @@
{
"originHash" : "0cb2e87817f52021ac25ffee6b27396f6d94e9fd604ca83db7f20a10e65fe6cf",
"originHash" : "17ce26ba5c862ca674cd3ceeb43a9fe8a5c5251c5561de65e632a06d79916342",
"pins" : [
{
"identity" : "noora",
@@ -28,22 +28,13 @@
"version" : "4.2.1"
}
},
{
"identity" : "shwift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/GeorgeLyon/Shwift",
"state" : {
"revision" : "d7be04898d094ddce6140cc6a2e9a83fc994b66d",
"version" : "3.1.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615",
"version" : "1.7.0"
"revision" : "626b5b7b2f45e1b0b1c6f4a309296d1d21d7311b",
"version" : "1.7.1"
}
},
{
@@ -82,6 +73,15 @@
"version" : "2.95.0"
}
},
{
"identity" : "swift-subprocess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-subprocess.git",
"state" : {
"revision" : "ba5888ad7758cbcbe7abebac37860b1652af2d9c",
"version" : "0.3.0"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",

View File

@@ -11,10 +11,11 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.7.1"),
.package(url: "https://github.com/apple/swift-log", from: "1.10.1"),
.package(url: "https://github.com/apple/swift-nio", from: "2.95.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.6.4"),
.package(url: "https://github.com/GeorgeLyon/Shwift", from: "3.1.1"),
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.3.0"),
.package(url: "https://github.com/tuist/Noora", from: "0.55.1")
],
targets: [
@@ -38,9 +39,10 @@ let package = Package(
.executableTarget(
name: "InotifyTaskCLI",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Logging", package: "swift-log"),
.product(name: "_NIOFileSystem", package: "swift-nio"),
.product(name: "Script", package: "Shwift"),
.product(name: "Subprocess", package: "swift-subprocess"),
.product(name: "Noora", package: "Noora")
],
path: "Sources/TaskCLI"

View File

@@ -1,4 +1,4 @@
import Script
import ArgumentParser
@main
struct Command: AsyncParsableCommand {

View File

@@ -0,0 +1,9 @@
struct Docker {
static func getLinuxPlatformStringWithHostArchitecture() -> String {
#if arch(x86_64)
return "linux/amd64"
#else
return "linux/arm64"
#endif
}
}

View File

@@ -1,9 +1,10 @@
import ArgumentParser
import Foundation
import Logging
import Script
import Noora
import Subprocess
struct GenerateDocumentationCommand: Script {
struct GenerateDocumentationCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "generate-documentation",
abstract: "Generate DocC documentation of all targets inside a Linux container.",
@@ -23,7 +24,6 @@ struct GenerateDocumentationCommand: Script {
let logger = global.makeLogger(labeled: "swift-inotify.cli.task.generate-documentation")
let fileManager = FileManager.default
let projectDirectory = URL(fileURLWithPath: fileManager.currentDirectoryPath)
let docker = try await executable(named: "docker")
let targets = try await Self.targets(for: projectDirectory)
@@ -42,16 +42,21 @@ struct GenerateDocumentationCommand: Script {
let script = Self.makeRunScript(for: targets)
logger.debug("Container script", metadata: ["script": "\(script)"])
do {
try await docker(
let dockerRunResult = try await Subprocess.run(
.name("docker"),
arguments: [
"run", "--rm",
"-v", "\(tempDirectory.path):/code",
"--platform", "linux/arm64",
"-v", "swift-inotify-build-cache:/code/.build",
"--platform", Docker.getLinuxPlatformStringWithHostArchitecture(),
"-w", "/code",
"swift:latest",
"/bin/bash", "-c", script,
)
} catch {
"/bin/bash", "-c", script
],
output: .standardOutput,
error: .standardError
)
if !dockerRunResult.terminationStatus.isSuccess {
noora.error("Documentation generation failed.")
return
}
@@ -103,10 +108,12 @@ struct GenerateDocumentationCommand: Script {
}
private static func packageTargets() async throws -> [(name: String, path: String)] {
let swift = try await executable(named: "swift")
let packageDescriptionOutput = try await outputOf {
try await swift("package", "describe", "--type", "json")
}
let packageDescriptionResult = try await Subprocess.run(
.name("swift"),
arguments: ["package", "describe", "--type", "json"],
output: .data(limit: 10_000),
error: .standardError
)
struct PackageDescription: Codable {
let targets: [Target]
@@ -116,8 +123,11 @@ struct GenerateDocumentationCommand: Script {
let path: String
}
let data = Data(packageDescriptionOutput.utf8)
let package = try JSONDecoder().decode(PackageDescription.self, from: data)
if !packageDescriptionResult.terminationStatus.isSuccess {
throw GenerateDocumentationError.unableToReadPackageDescription
}
let package = try JSONDecoder().decode(PackageDescription.self, from: packageDescriptionResult.standardOutput)
return package.targets.map { ($0.name, $0.path) }
}
@@ -162,13 +172,16 @@ struct GenerateDocumentationCommand: Script {
// MARK: - Dependency Injection
private func injectDoccPluginDependency(in directory: URL, logger: Logger) async throws {
let swift = try await executable(named: "swift")
do {
try await swift(
let swiftRunResult = try await Subprocess.run(
.name("swift"),
arguments: [
"package", "--package-path", directory.path(percentEncoded: false),
"add-dependency", "--from", Self.doccPluginMinVersion, Self.doccPluginURL
)
} catch {
],
output: .standardOutput,
error: .standardError
)
if !swiftRunResult.terminationStatus.isSuccess {
throw GenerateDocumentationError.dependencyInjectionFailed
}
@@ -178,11 +191,14 @@ struct GenerateDocumentationCommand: Script {
enum GenerateDocumentationError: Error, CustomStringConvertible {
case dependencyInjectionFailed
case unableToReadPackageDescription
var description: String {
switch self {
case .dependencyInjectionFailed:
"Failed to add swift-docc-plugin dependency to Package.swift."
case .unableToReadPackageDescription:
"Failed to read the package description."
}
}
}

View File

@@ -1,4 +1,4 @@
import Script
import ArgumentParser
import Logging
struct GlobalOptions: ParsableArguments {

View File

@@ -1,8 +1,9 @@
import ArgumentParser
import Foundation
import Script
import Noora
import Subprocess
struct TestCommand: Script {
struct TestCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "test",
abstract: "Run swift test in a linux container.",
@@ -17,21 +18,26 @@ struct TestCommand: Script {
let noora = Noora()
let logger = global.makeLogger(labeled: "swift-inotify.cli.task.test")
let currentDirectory = FileManager.default.currentDirectoryPath
let docker = Executable(path: "/opt/homebrew/bin/docker")
noora.info("Running tests on Linux.")
logger.debug("Current directory", metadata: ["current-directory": "\(currentDirectory)"])
do {
try await docker(
let dockerRunResult = try await Subprocess.run(
.name("docker"),
arguments: [
"run",
"-v", "\(currentDirectory):/code",
"-v", "swift-inotify-build-cache:/code/.build",
"--security-opt", "systempaths=unconfined",
"--platform", "linux/arm64",
"--platform", Docker.getLinuxPlatformStringWithHostArchitecture(),
"-w", "/code", "swift:latest",
"/bin/bash", "-c", "swift test --skip InotifyLimitTests; swift test --skip-build --filter InotifyLimitTests"
)
],
output: .standardOutput,
error: .standardError
)
if dockerRunResult.terminationStatus.isSuccess {
noora.success("All tests completed successfully.")
} catch {
} else {
noora.error("Not all tests completed successfully.")
}
}