This commit is contained in:
2023-03-13 17:17:12 -04:00
parent b0559d9726
commit 37f3bfde62
12 changed files with 311 additions and 29 deletions

View File

@@ -18,6 +18,15 @@
"version" : "4.0.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
"version" : "1.2.2"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
@@ -36,6 +45,24 @@
"version" : "0.2.0"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin.git",
"state" : {
"revision" : "10bc670db657d11bdd561e07de30a9041311b2b1",
"version" : "1.1.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",

View File

@@ -2,10 +2,13 @@ DOCC_TARGET ?= GitVersion
DOCC_BASEPATH = $(shell basename "$(PWD)")
DOCC_DIR ?= ./docs
clean:
rm -rf .build
build-and-run:
swift run build-example
./.build/debug/example --help
./.build/debug/example
swift run -c release build-example
./.build/release/example --help
./.build/release/example
build-documentation:
swift package \
@@ -22,3 +25,12 @@ preview-documentation:
--disable-sandbox \
preview-documentation \
--target "$(DOCC_TARGET)"
test-linux:
docker run --rm \
--volume "$(PWD):$(PWD)" \
--workdir "$(PWD)" \
swift:5.7-focal \
swift test

View File

@@ -5,10 +5,11 @@ import PackageDescription
let package = Package(
name: "swift-git-version",
platforms: [
.macOS(.v10_15)
.macOS(.v12)
],
products: [
.library(name: "GitVersion", targets: ["GitVersion"])
.library(name: "GitVersion", targets: ["GitVersion"]),
.plugin(name: "GitVersionBuildPlugin", targets: ["GitVersionBuildPlugin"]),
],
dependencies: [
.package(url: "https://github.com/m-housh/swift-shell-client.git", from: "0.1.0"),
@@ -16,6 +17,13 @@ let package = Package(
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"),
],
targets: [
.executableTarget(
name: "git-version-builder",
dependencies: [
"GitVersion",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.target(
name: "GitVersion",
dependencies: [
@@ -34,10 +42,33 @@ let package = Package(
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "ShellClient", package: "swift-shell-client")
]
,
plugins: [
.plugin(name: "GitVersionBuildPlugin")
]
),
.testTarget(
name: "GitVersionTests",
dependencies: ["GitVersion"]
),
// .plugin(
// name: "GitVersionPlugin",
// capability: .command(
// intent: .custom(verb: "build-with-version", description: "Build a command line tool with git version."),
// permissions: [
// .writeToPackageDirectory(reason: "This command builds a command line tool with a git version.")
// ]
// ),
// dependencies: [
// "build-example"
// ]
// ),
.plugin(
name: "GitVersionBuildPlugin",
capability: .buildTool(),
dependencies: [
"git-version-builder"
]
)
]
)

View File

@@ -0,0 +1,51 @@
import Foundation
import PackagePlugin
@main
struct GitVersionBuildPlugin: BuildToolPlugin {
func createBuildCommands(
context: PackagePlugin.PluginContext,
target: PackagePlugin.Target
) async throws -> [PackagePlugin.Command] {
guard let target = target as? SourceModuleTarget else { return [] }
let buildTool = try context.tool(named: "git-version-builder")
let outputDir = context.pluginWorkDirectory
.appending(subpath: target.name)
try FileManager.default
.createDirectory(atPath: outputDir.string, withIntermediateDirectories: true)
let inputFiles = target.sourceFiles
.filter({ $0.type == .source && $0.path.stem == "Version" })
.map(\.path)
guard inputFiles.count == 1 else { return [] }
let outputFile = outputDir.appending(subpath: "Version.generated.swift")
print("Input swift files: \(inputFiles)")
// let originalContents = try String(contentsOfFile: inputFiles.first!.string)
// let updatedContents = originalContents.replacingOccurrences(of: "nil", with: "\"0.1.123\"")
//
// print("Updated contents")
// print(updatedContents)
// this fails.
return [
.buildCommand(
displayName: "Git Version Build Plugin",
executable: buildTool.path,
arguments: [
inputFiles.first!,
outputFile.string
]
// ,
// outputFiles: [outputFile]
)
]
}
}

View File

@@ -0,0 +1,34 @@
import PackagePlugin
import Foundation
@main
struct GitVersionPlugin: CommandPlugin {
func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws {
let tool = try context.tool(named: "build-example")
let url = URL(fileURLWithPath: tool.path.string)
for target in context.package.targets {
guard let target = target as? SourceModuleTarget else { continue }
let process = Process()
process.executableURL = url
try process.run()
process.waitUntilExit()
if process.terminationReason == .exit && process.terminationStatus == 0 {
print("Done building in: \(target.directory)")
} else {
let problem = "\(process.terminationReason): \(process.terminationStatus)"
Diagnostics.error("\(problem)")
}
}
}
// func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
// guard let target = target.sourceModule else { return [] }
// let tool = try context.tool(named: "build-example")
//
//
// }
}

View File

@@ -1,3 +1,41 @@
# swift-git-version
A description of this package.
A swift package that exposes some helpers to set the version of a command line tool to the
git tag or the git sha, if a tag is not set for the current commit.
## Usage
You can use this in your command line tool via the swift package manager.
```swift
let package = Package(
...
dependencies: [
.package(url: "https://github.com/m-housh/swift-git-version.git", from: "0.1.0")
],
targets: [
.executableTarget(
name: "my-executable",
dependencies: [
...
]
),
.executableTarget(
name: "my-executable-builder",
dependencies: [
.product(name: "GitVersion", package: "swift-git-version")
]
),
]
)
```
Inside of your executable (`my-executable`) in the above example, you will want to create
a file that contains your version variable.
```swift
// Do not set this variable, it is set by the build script.
let VERSION: String? = nil
```

View File

@@ -164,10 +164,14 @@ extension FileClient {
// MARK: - Private
fileprivate func url(for path: String) throws -> URL {
if #available(macOS 13.0, *) {
return URL(filePath: path)
} else {
// Fallback on earlier versions
#if os(Linux)
return URL(fileURLWithPath: path)
}
#else
if #available(macOS 13.0, *) {
return URL(filePath: path)
} else {
// Fallback on earlier versions
return URL(fileURLWithPath: path)
}
#endif
}

View File

@@ -101,6 +101,39 @@ public struct SwiftBuild {
}
extension ShellClient {
/// Reads contents at the given file path, then allows the caller to act on the
/// results after "nil" has been replaced with the current version string.
///
/// > Note: The file contents will be reset back to nil after the closure operation.
///
/// - Parameters:
/// - filePath: The file path to replace nil in.
/// - workingDirectory: Customize the working directory for the command.
/// - closure: The closure to run with the updated file content string.
public func replacingNilWithVersionString(
in filePath: String,
from workingDirectory: String? = nil,
_ closure: @escaping (String) throws -> Void
) throws {
@Dependency(\.fileClient) var fileClient: FileClient
@Dependency(\.gitVersionClient) var gitClient: GitVersionClient
@Dependency(\.logger) var logger: Logger
let currentVersion = try gitClient.currentVersion(in: workingDirectory)
let originalContents = try fileClient.readAsString(path: filePath)
logger.debug("Setting version: \(currentVersion)")
let updatedContents = originalContents
.replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"")
logger.debug("Set version")
try closure(updatedContents)
}
/// Replace nil in the given file path and then run the given closure.
///
/// > Note: The file contents will be reset back to nil after the closure operation.
@@ -115,17 +148,21 @@ extension ShellClient {
_ closure: @escaping () throws -> Void
) throws {
@Dependency(\.fileClient) var fileClient: FileClient
@Dependency(\.gitVersionClient) var gitClient: GitVersionClient
@Dependency(\.logger) var logger: Logger
let currentVersion = try gitClient.currentVersion(in: workingDirectory)
// grab the original contents, to set it back when done.
let originalContents = try fileClient.readAsString(path: filePath)
let updatedContents = originalContents
.replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"")
try fileClient.write(string: updatedContents, to: filePath)
defer { try! fileClient.write(string: originalContents, to: filePath) }
try self.replacingNilWithVersionString(
in: filePath,
from: workingDirectory
) { update in
try fileClient.write(string: update, to: filePath)
defer { try! fileClient.write(string: originalContents, to: filePath) }
try closure()
}
try closure()
}
/// Replace nil in the given file path and then build the project, using the

View File

@@ -10,9 +10,20 @@ public struct Build {
public static func main() throws {
@Dependency(\.shellClient) var shell: ShellClient
try shell.replacingNilWithVersionString(
in: "Sources/example/Version.swift",
build: SwiftBuild.release()
)
let gitDir = URL(fileURLWithPath: #file)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
try withDependencies {
$0.gitVersionClient = .liveValue
$0.logger.logLevel = .debug
} operation: {
try shell.replacingNilWithVersionString(
in: "Sources/example/Version.swift",
from: gitDir.absoluteString,
build: SwiftBuild.release()
)
}
}
}

View File

@@ -3,7 +3,6 @@ import ShellClient
/// An example of using the git version client with a command line tool
/// The ``VERSION`` variable get's set during the build process.
@main
public struct Example: ParsableCommand {

View File

@@ -0,0 +1,34 @@
import ArgumentParser
import Dependencies
import GitVersion
import ShellClient
@main
public struct GitVersionBuilder: AsyncParsableCommand {
public init() { }
@Argument
var input: String
@Argument
var output: String
public func run() async throws {
@Dependency(\.logger) var logger
@Dependency(\.fileClient) var fileClient
@Dependency(\.shellClient) var shell: ShellClient
logger.debug("Building with input file: \(input)")
logger.debug("Output file: \(output)")
try shell.replacingNilWithVersionString(
in: input
) { update in
logger.debug("Updating with:\n\(update)")
try fileClient.write(string: update, to: output)
}
}
}

View File

@@ -9,12 +9,12 @@ final class GitVersionTests: XCTestCase {
$0.gitVersionClient.override(with: "blob")
} operation: {
@Dependency(\.gitVersionClient) var versionClient
let version = try versionClient.currentVersion()
XCTAssertEqual(version, "blob")
}
}
func test_live() throws {
try withDependencies({
$0.logger.logLevel = .debug
@@ -22,18 +22,22 @@ final class GitVersionTests: XCTestCase {
$0.shellClient = .liveValue
$0.gitVersionClient = .liveValue
}, operation: {
@Dependency(\.gitVersionClient) var versionClient
let gitDir = URL(fileURLWithPath: #file)
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
let version = try versionClient.currentVersion(in: gitDir.absoluteString)
print("VERSION: \(version)")
// can't really have a predictable result for the live client.
XCTAssertNotEqual(version, "blob")
let other = try versionClient.currentVersion()
XCTAssertEqual(version, other)
})
}
}