From 37f3bfde6291bb44ceb9e91964655de168a080d0 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Mon, 13 Mar 2023 17:17:12 -0400 Subject: [PATCH] wip --- .../xcshareddata/swiftpm/Package.resolved | 27 ++++++++++ Makefile | 18 +++++-- Package.swift | 35 ++++++++++++- .../GitVersionBuildPlugin.swift | 51 +++++++++++++++++++ .../GitVersionPlugin/GitVersionPlugin.swift | 34 +++++++++++++ README.md | 40 ++++++++++++++- Sources/GitVersion/FileClient.swift | 14 +++-- Sources/GitVersion/SwiftBuild.swift | 51 ++++++++++++++++--- Sources/build-example/Build.swift | 19 +++++-- Sources/example/example.swift | 1 - .../GitVersionBuilder.swift | 34 +++++++++++++ Tests/GitVersionTests/GitVersionTests.swift | 16 +++--- 12 files changed, 311 insertions(+), 29 deletions(-) create mode 100644 Plugins/GitVersionBuildPlugin/GitVersionBuildPlugin.swift create mode 100644 Plugins/GitVersionPlugin/GitVersionPlugin.swift create mode 100644 Sources/git-version-builder/GitVersionBuilder.swift diff --git a/GitVersion.xcworkspace/xcshareddata/swiftpm/Package.resolved b/GitVersion.xcworkspace/xcshareddata/swiftpm/Package.resolved index 90864bc..2d20565 100644 --- a/GitVersion.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/GitVersion.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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", diff --git a/Makefile b/Makefile index 84d036a..5092163 100644 --- a/Makefile +++ b/Makefile @@ -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 + + diff --git a/Package.swift b/Package.swift index b269f97..63126df 100644 --- a/Package.swift +++ b/Package.swift @@ -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" + ] + ) ] ) diff --git a/Plugins/GitVersionBuildPlugin/GitVersionBuildPlugin.swift b/Plugins/GitVersionBuildPlugin/GitVersionBuildPlugin.swift new file mode 100644 index 0000000..42328d3 --- /dev/null +++ b/Plugins/GitVersionBuildPlugin/GitVersionBuildPlugin.swift @@ -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] + ) + ] + } +} diff --git a/Plugins/GitVersionPlugin/GitVersionPlugin.swift b/Plugins/GitVersionPlugin/GitVersionPlugin.swift new file mode 100644 index 0000000..daf01b9 --- /dev/null +++ b/Plugins/GitVersionPlugin/GitVersionPlugin.swift @@ -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") +// +// +// } +} diff --git a/README.md b/README.md index fe54f65..b904475 100644 --- a/README.md +++ b/README.md @@ -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 +``` + diff --git a/Sources/GitVersion/FileClient.swift b/Sources/GitVersion/FileClient.swift index 6f5e0ad..d736d89 100644 --- a/Sources/GitVersion/FileClient.swift +++ b/Sources/GitVersion/FileClient.swift @@ -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 } diff --git a/Sources/GitVersion/SwiftBuild.swift b/Sources/GitVersion/SwiftBuild.swift index 06c87a7..447d1de 100644 --- a/Sources/GitVersion/SwiftBuild.swift +++ b/Sources/GitVersion/SwiftBuild.swift @@ -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 diff --git a/Sources/build-example/Build.swift b/Sources/build-example/Build.swift index 2c3ecdf..b9084e5 100644 --- a/Sources/build-example/Build.swift +++ b/Sources/build-example/Build.swift @@ -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() + ) + } } } diff --git a/Sources/example/example.swift b/Sources/example/example.swift index 3876a07..f265341 100644 --- a/Sources/example/example.swift +++ b/Sources/example/example.swift @@ -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 { diff --git a/Sources/git-version-builder/GitVersionBuilder.swift b/Sources/git-version-builder/GitVersionBuilder.swift new file mode 100644 index 0000000..fa1fb25 --- /dev/null +++ b/Sources/git-version-builder/GitVersionBuilder.swift @@ -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) + } + + } +} + diff --git a/Tests/GitVersionTests/GitVersionTests.swift b/Tests/GitVersionTests/GitVersionTests.swift index 1632bf4..4b456aa 100644 --- a/Tests/GitVersionTests/GitVersionTests.swift +++ b/Tests/GitVersionTests/GitVersionTests.swift @@ -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) + }) } }