feat: Begins update for more modern swift-dependencies implementation.
This commit is contained in:
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.swift]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
tab_width = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ DerivedData/
|
|||||||
.swiftpm/config/registries.json
|
.swiftpm/config/registries.json
|
||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
.netrc
|
.netrc
|
||||||
|
.nvim/*
|
||||||
|
|||||||
11
.swiftformat
Normal file
11
.swiftformat
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
--self init-only
|
||||||
|
--indent 2
|
||||||
|
--ifdef indent
|
||||||
|
--trimwhitespace always
|
||||||
|
--wraparguments before-first
|
||||||
|
--wrapparameters before-first
|
||||||
|
--wrapcollections preserve
|
||||||
|
--wrapconditions after-first
|
||||||
|
--typeblanklines preserve
|
||||||
|
--commas inline
|
||||||
|
--stripunusedargs closure-only
|
||||||
11
.swiftlint.yml
Normal file
11
.swiftlint.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
disabled_rules:
|
||||||
|
- closing_brace
|
||||||
|
- fuction_body_length
|
||||||
|
- opening_brace
|
||||||
|
- nesting
|
||||||
|
|
||||||
|
included:
|
||||||
|
- Sources
|
||||||
|
- Tests
|
||||||
|
|
||||||
|
ignore_multiline_statement_conditions: true
|
||||||
@@ -96,7 +96,8 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
codeCoverageEnabled = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/pointfreeco/combine-schedulers",
|
"location" : "https://github.com/pointfreeco/combine-schedulers",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb",
|
"revision" : "9fa31f4403da54855f1e2aeaeff478f4f0e40b13",
|
||||||
"version" : "1.0.0"
|
"version" : "1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/pointfreeco/swift-clocks",
|
"location" : "https://github.com/pointfreeco/swift-clocks",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb",
|
"revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53",
|
||||||
"version" : "1.0.0"
|
"version" : "1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/pointfreeco/swift-dependencies.git",
|
"location" : "https://github.com/pointfreeco/swift-dependencies.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5",
|
"revision" : "5526c8a27675dc7b18d6fa643abfb64bcb200b77",
|
||||||
"version" : "1.0.0"
|
"version" : "1.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -99,13 +99,22 @@
|
|||||||
"version" : "0.1.4"
|
"version" : "0.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-syntax",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/swiftlang/swift-syntax",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "0687f71944021d616d34d922343dcef086855920",
|
||||||
|
"version" : "600.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "xctest-dynamic-overlay",
|
"identity" : "xctest-dynamic-overlay",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
|
"revision" : "a3f634d1a409c7979cabc0a71b3f26ffa9fc8af1",
|
||||||
"version" : "1.0.2"
|
"version" : "1.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// swift-tools-version: 5.6
|
// swift-tools-version: 5.10
|
||||||
|
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ let package = Package(
|
|||||||
.plugin(name: "UpdateVersionPlugin", targets: ["UpdateVersionPlugin"])
|
.plugin(name: "UpdateVersionPlugin", targets: ["UpdateVersionPlugin"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "1.6.2"),
|
||||||
.package(url: "https://github.com/m-housh/swift-shell-client.git", from: "0.1.3"),
|
.package(url: "https://github.com/m-housh/swift-shell-client.git", from: "0.1.3"),
|
||||||
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"),
|
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"),
|
||||||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2")
|
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2")
|
||||||
@@ -26,15 +27,18 @@ let package = Package(
|
|||||||
.product(name: "ArgumentParser", package: "swift-argument-parser")
|
.product(name: "ArgumentParser", package: "swift-argument-parser")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.target(name: "TestSupport"),
|
||||||
.target(
|
.target(
|
||||||
name: "CliVersion",
|
name: "CliVersion",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
.product(name: "ShellClient", package: "swift-shell-client")
|
.product(name: "ShellClient", package: "swift-shell-client")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "CliVersionTests",
|
name: "CliVersionTests",
|
||||||
dependencies: ["CliVersion"]
|
dependencies: ["CliVersion", "TestSupport"]
|
||||||
),
|
),
|
||||||
.plugin(
|
.plugin(
|
||||||
name: "BuildWithVersionPlugin",
|
name: "BuildWithVersionPlugin",
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ struct GenerateVersionBuildPlugin: BuildToolPlugin {
|
|||||||
target: PackagePlugin.Target
|
target: PackagePlugin.Target
|
||||||
) async throws -> [PackagePlugin.Command] {
|
) async throws -> [PackagePlugin.Command] {
|
||||||
guard let target = target as? SourceModuleTarget else { return [] }
|
guard let target = target as? SourceModuleTarget else { return [] }
|
||||||
|
|
||||||
let gitDirectoryPath = target.directory
|
let gitDirectoryPath = target.directory
|
||||||
.removingLastComponent()
|
.removingLastComponent()
|
||||||
.removingLastComponent()
|
.removingLastComponent()
|
||||||
|
|
||||||
let tool = try context.tool(named: "cli-version")
|
let tool = try context.tool(named: "cli-version")
|
||||||
let outputPath = context.pluginWorkDirectory
|
let outputPath = context.pluginWorkDirectory
|
||||||
|
|
||||||
let outputFile = outputPath.appending("Version.swift")
|
let outputFile = outputPath.appending("Version.swift")
|
||||||
|
|
||||||
return [
|
return [
|
||||||
.buildCommand(
|
.buildCommand(
|
||||||
displayName: "Build With Version Plugin",
|
displayName: "Build With Version Plugin",
|
||||||
|
|||||||
222
Sources/CliVersion/CliClient.swift
Normal file
222
Sources/CliVersion/CliClient.swift
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import Foundation
|
||||||
|
#if canImport(FoundationNetworking)
|
||||||
|
import FoundationNetworking
|
||||||
|
#endif
|
||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import ShellClient
|
||||||
|
|
||||||
|
public extension DependencyValues {
|
||||||
|
|
||||||
|
var cliClient: CliClient {
|
||||||
|
get { self[CliClient.self] }
|
||||||
|
set { self[CliClient.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct CliClient {
|
||||||
|
public var build: @Sendable (BuildOptions) throws -> String
|
||||||
|
public var generate: @Sendable (GenerateOptions) throws -> String
|
||||||
|
public var update: @Sendable (UpdateOptions) throws -> String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CliClient: DependencyKey {
|
||||||
|
public static let testValue: CliClient = Self()
|
||||||
|
|
||||||
|
public static func live(environment: [String: String]) -> Self {
|
||||||
|
.init(
|
||||||
|
build: { try $0.run(environment) },
|
||||||
|
generate: { try $0.run() },
|
||||||
|
update: { try $0.run() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var liveValue: CliClient {
|
||||||
|
.live(environment: ProcessInfo.processInfo.environment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension CliClient {
|
||||||
|
|
||||||
|
// TODO: Use Int for `verbose`.
|
||||||
|
struct SharedOptions: Sendable {
|
||||||
|
let dryRun: Bool
|
||||||
|
let fileName: String
|
||||||
|
let target: String
|
||||||
|
let verbose: Bool
|
||||||
|
|
||||||
|
public init(
|
||||||
|
dryRun: Bool = false,
|
||||||
|
fileName: String,
|
||||||
|
target: String,
|
||||||
|
verbose: Bool = true
|
||||||
|
) {
|
||||||
|
self.dryRun = dryRun
|
||||||
|
self.fileName = fileName
|
||||||
|
self.target = target
|
||||||
|
self.verbose = verbose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BuildOptions: Sendable {
|
||||||
|
let gitDirectory: String?
|
||||||
|
let shared: SharedOptions
|
||||||
|
|
||||||
|
public init(
|
||||||
|
gitDirectory: String? = nil,
|
||||||
|
shared: SharedOptions
|
||||||
|
) {
|
||||||
|
self.gitDirectory = gitDirectory
|
||||||
|
self.shared = shared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GenerateOptions: Sendable {
|
||||||
|
let shared: SharedOptions
|
||||||
|
|
||||||
|
public init(shared: SharedOptions) {
|
||||||
|
self.shared = shared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpdateOptions: Sendable {
|
||||||
|
let gitDirectory: String?
|
||||||
|
let shared: SharedOptions
|
||||||
|
|
||||||
|
public init(
|
||||||
|
gitDirectory: String? = nil,
|
||||||
|
shared: SharedOptions
|
||||||
|
) {
|
||||||
|
self.gitDirectory = gitDirectory
|
||||||
|
self.shared = shared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public extension CliClient.SharedOptions {
|
||||||
|
var fileUrl: URL {
|
||||||
|
url(for: target).appendingPathComponent(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTarget() throws -> URL {
|
||||||
|
let targetUrl = fileUrl
|
||||||
|
.deletingLastPathComponent()
|
||||||
|
.deletingLastPathComponent()
|
||||||
|
|
||||||
|
guard targetUrl.lastPathComponent == "Sources" else {
|
||||||
|
return url(for: "Sources")
|
||||||
|
.appendingPathComponent(target)
|
||||||
|
.appendingPathComponent(fileName)
|
||||||
|
}
|
||||||
|
return fileUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func run<T>(
|
||||||
|
_ operation: () throws -> T
|
||||||
|
) rethrows -> T {
|
||||||
|
try withDependencies {
|
||||||
|
$0.logger.logLevel = .init(verbose: verbose)
|
||||||
|
} operation: {
|
||||||
|
try operation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension CliClient.BuildOptions {
|
||||||
|
|
||||||
|
func run(_ environment: [String: String]) throws -> String {
|
||||||
|
try shared.run {
|
||||||
|
@Dependency(\.gitVersionClient) var gitVersion
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let gitDirectory = gitDirectory ?? environment["PWD"]
|
||||||
|
|
||||||
|
guard let gitDirectory else {
|
||||||
|
throw CliClientError.gitDirectoryNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Building with git directory: \(gitDirectory)")
|
||||||
|
|
||||||
|
let fileUrl = shared.fileUrl
|
||||||
|
logger.debug("File url: \(fileUrl.cleanFilePath)")
|
||||||
|
|
||||||
|
let currentVersion = try gitVersion.currentVersion(in: gitDirectory)
|
||||||
|
|
||||||
|
let fileContents = buildTemplate
|
||||||
|
.replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"")
|
||||||
|
|
||||||
|
try fileClient.write(string: fileContents, to: fileUrl)
|
||||||
|
|
||||||
|
return fileUrl.cleanFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension CliClient.GenerateOptions {
|
||||||
|
|
||||||
|
func run() throws -> String {
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let targetUrl = try shared.parseTarget()
|
||||||
|
|
||||||
|
logger.debug("Generate target url: \(targetUrl.cleanFilePath)")
|
||||||
|
|
||||||
|
guard !fileClient.fileExists(targetUrl) else {
|
||||||
|
throw CliClientError.fileExists(path: targetUrl.cleanFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shared.dryRun {
|
||||||
|
try fileClient.write(string: optionalTemplate, to: targetUrl)
|
||||||
|
} else {
|
||||||
|
logger.debug("Skipping, due to dry-run being passed.")
|
||||||
|
}
|
||||||
|
return targetUrl.cleanFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension CliClient.UpdateOptions {
|
||||||
|
|
||||||
|
func run() throws -> String {
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
@Dependency(\.gitVersionClient) var gitVersionClient
|
||||||
|
@Dependency(\.logger) var logger
|
||||||
|
|
||||||
|
let targetUrl = try shared.parseTarget()
|
||||||
|
logger.debug("Target url: \(targetUrl.cleanFilePath)")
|
||||||
|
|
||||||
|
let currentVersion = try gitVersionClient.currentVersion(in: gitDirectory)
|
||||||
|
|
||||||
|
let fileContents = optionalTemplate
|
||||||
|
.replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"")
|
||||||
|
|
||||||
|
if !shared.dryRun {
|
||||||
|
try fileClient.write(string: fileContents, to: targetUrl)
|
||||||
|
} else {
|
||||||
|
logger.debug("Skipping due to dry run being passed.")
|
||||||
|
logger.debug("Parsed version: \(currentVersion)")
|
||||||
|
}
|
||||||
|
return targetUrl.cleanFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let optionalTemplate = """
|
||||||
|
// Do not set this variable, it is set during the build process.
|
||||||
|
let VERSION: String? = nil
|
||||||
|
"""
|
||||||
|
|
||||||
|
private let buildTemplate = """
|
||||||
|
// Do not set this variable, it is set during the build process.
|
||||||
|
let VERSION: String = nil
|
||||||
|
"""
|
||||||
|
|
||||||
|
enum CliClientError: Error {
|
||||||
|
case gitDirectoryNotFound
|
||||||
|
case fileExists(path: String)
|
||||||
|
}
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
import Foundation
|
import Foundation
|
||||||
#if canImport(FoundationNetworking)
|
#if canImport(FoundationNetworking)
|
||||||
import FoundationNetworking
|
import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
import XCTestDynamicOverlay
|
import XCTestDynamicOverlay
|
||||||
|
|
||||||
|
public extension DependencyValues {
|
||||||
|
|
||||||
|
/// Access a basic ``FileClient`` that can read / write data to the file system.
|
||||||
|
///
|
||||||
|
var fileClient: FileClient {
|
||||||
|
get { self[FileClient.self] }
|
||||||
|
set { self[FileClient.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents the interactions with the file system. It is able
|
/// Represents the interactions with the file system. It is able
|
||||||
/// to read from and write to files.
|
/// to read from and write to files.
|
||||||
///
|
///
|
||||||
@@ -13,25 +24,23 @@ import XCTestDynamicOverlay
|
|||||||
/// @Dependency(\.fileClient) var fileClient
|
/// @Dependency(\.fileClient) var fileClient
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
public struct FileClient {
|
@DependencyClient
|
||||||
|
public struct FileClient: Sendable {
|
||||||
|
|
||||||
|
public var fileExists: @Sendable (URL) -> Bool = { _ in true }
|
||||||
|
|
||||||
|
/// Read the contents of a file.
|
||||||
|
public var read: @Sendable (URL) throws -> String
|
||||||
|
|
||||||
/// Write `Data` to a file `URL`.
|
/// Write `Data` to a file `URL`.
|
||||||
public private(set) var write: (Data, URL) throws -> Void
|
public private(set) var write: @Sendable (Data, URL) throws -> Void
|
||||||
|
|
||||||
/// Create a new ``GitVersion/FileClient`` instance.
|
/// Read the contents of a file at the given path.
|
||||||
///
|
|
||||||
/// This is generally not interacted with directly, instead access as a dependency.
|
|
||||||
///
|
|
||||||
///```swift
|
|
||||||
/// @Dependency(\.fileClient) var fileClient
|
|
||||||
///```
|
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - write: Write the data to a file.
|
/// - path: The file path to read from.
|
||||||
public init(
|
public func read(_ path: String) throws -> String {
|
||||||
write: @escaping (Data, URL) throws -> Void
|
try read(url(for: path))
|
||||||
) {
|
|
||||||
self.write = write
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write's the the string to a file path.
|
/// Write's the the string to a file path.
|
||||||
@@ -40,8 +49,8 @@ public struct FileClient {
|
|||||||
/// - string: The string to write to the file.
|
/// - string: The string to write to the file.
|
||||||
/// - path: The file path.
|
/// - path: The file path.
|
||||||
public func write(string: String, to path: String) throws {
|
public func write(string: String, to path: String) throws {
|
||||||
let url = try url(for: path)
|
let url = url(for: path)
|
||||||
try self.write(string: string, to: url)
|
try write(string: string, to: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write's the the string to a file path.
|
/// Write's the the string to a file path.
|
||||||
@@ -50,49 +59,27 @@ public struct FileClient {
|
|||||||
/// - string: The string to write to the file.
|
/// - string: The string to write to the file.
|
||||||
/// - url: The file url.
|
/// - url: The file url.
|
||||||
public func write(string: String, to url: URL) throws {
|
public func write(string: String, to url: URL) throws {
|
||||||
try self.write(Data(string.utf8), url)
|
try write(Data(string.utf8), url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FileClient: DependencyKey {
|
extension FileClient: DependencyKey {
|
||||||
|
|
||||||
/// A ``FileClient`` that does not do anything.
|
/// A ``FileClient`` that does not do anything.
|
||||||
public static let noop = FileClient.init(
|
public static let noop = FileClient(
|
||||||
|
fileExists: { _ in true },
|
||||||
|
read: { _ in "" },
|
||||||
write: { _, _ in }
|
write: { _, _ in }
|
||||||
)
|
)
|
||||||
|
|
||||||
/// An `unimplemented` ``FileClient``.
|
/// An `unimplemented` ``FileClient``.
|
||||||
public static let testValue = FileClient(
|
public static let testValue = FileClient()
|
||||||
write: unimplemented("\(Self.self).write")
|
|
||||||
)
|
|
||||||
|
|
||||||
/// The live ``FileClient``
|
/// The live ``FileClient``
|
||||||
public static let liveValue = FileClient(
|
public static let liveValue = FileClient(
|
||||||
|
fileExists: { FileManager.default.fileExists(atPath: $0.cleanFilePath) },
|
||||||
|
read: { try String(contentsOf: $0, encoding: .utf8) },
|
||||||
write: { try $0.write(to: $1, options: .atomic) }
|
write: { try $0.write(to: $1, options: .atomic) }
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DependencyValues {
|
|
||||||
|
|
||||||
/// Access a basic ``FileClient`` that can read / write data to the file system.
|
|
||||||
///
|
|
||||||
public var fileClient: FileClient {
|
|
||||||
get { self[FileClient.self] }
|
|
||||||
set { self[FileClient.self] = newValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
fileprivate func url(for path: String) throws -> URL {
|
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public struct GitVersionClient {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - gitDirectory: The directory to run the command in.
|
/// - gitDirectory: The directory to run the command in.
|
||||||
public func currentVersion(in gitDirectory: String? = nil) throws -> String {
|
public func currentVersion(in gitDirectory: String? = nil) throws -> String {
|
||||||
try self.currentVersion(gitDirectory)
|
try currentVersion(gitDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override the `currentVersion` command and return the passed in version string.
|
/// Override the `currentVersion` command and return the passed in version string.
|
||||||
@@ -50,7 +50,7 @@ public struct GitVersionClient {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - version: The version string to return when `currentVersion` is called.
|
/// - version: The version string to return when `currentVersion` is called.
|
||||||
public mutating func override(with version: String) {
|
public mutating func override(with version: String) {
|
||||||
self.currentVersion = { _ in version }
|
currentVersion = { _ in version }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,32 +69,33 @@ extension GitVersionClient: TestDependencyKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DependencyValues {
|
public extension DependencyValues {
|
||||||
|
|
||||||
/// A ``GitVersionClient`` that can retrieve the current version from a
|
/// A ``GitVersionClient`` that can retrieve the current version from a
|
||||||
/// git directory.
|
/// git directory.
|
||||||
public var gitVersionClient: GitVersionClient {
|
var gitVersionClient: GitVersionClient {
|
||||||
get { self[GitVersionClient.self] }
|
get { self[GitVersionClient.self] }
|
||||||
set { self[GitVersionClient.self] = newValue }
|
set { self[GitVersionClient.self] = newValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ShellCommand {
|
public extension ShellCommand {
|
||||||
public static func gitCurrentSha(gitDirectory: String? = nil) -> Self {
|
static func gitCurrentSha(gitDirectory: String? = nil) -> Self {
|
||||||
GitVersion(workingDirectory: gitDirectory).command(for: .commit)
|
GitVersion(workingDirectory: gitDirectory).command(for: .commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func gitCurrentBranch(gitDirectory: String? = nil) -> Self {
|
static func gitCurrentBranch(gitDirectory: String? = nil) -> Self {
|
||||||
GitVersion(workingDirectory: gitDirectory).command(for: .branch)
|
GitVersion(workingDirectory: gitDirectory).command(for: .branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func gitCurrentTag(gitDirectory: String? = nil) -> Self {
|
static func gitCurrentTag(gitDirectory: String? = nil) -> Self {
|
||||||
GitVersion(workingDirectory: gitDirectory).command(for: .describe)
|
GitVersion(workingDirectory: gitDirectory).command(for: .describe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
fileprivate struct GitVersion {
|
|
||||||
|
private struct GitVersion {
|
||||||
@Dependency(\.logger) var logger: Logger
|
@Dependency(\.logger) var logger: Logger
|
||||||
@Dependency(\.shellClient) var shell: ShellClient
|
@Dependency(\.shellClient) var shell: ShellClient
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ fileprivate struct GitVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func command(for argument: VersionArgs) -> ShellCommand {
|
func command(for argument: VersionArgs) -> ShellCommand {
|
||||||
.init(
|
.init(
|
||||||
shell: .env,
|
shell: .env,
|
||||||
environment: nil,
|
environment: nil,
|
||||||
@@ -123,7 +124,7 @@ fileprivate struct GitVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate extension GitVersion {
|
private extension GitVersion {
|
||||||
func run(command: ShellCommand) throws -> String {
|
func run(command: ShellCommand) throws -> String {
|
||||||
try shell.background(command, trimmingCharactersIn: .whitespacesAndNewlines)
|
try shell.background(command, trimmingCharactersIn: .whitespacesAndNewlines)
|
||||||
}
|
}
|
||||||
|
|||||||
40
Sources/CliVersion/Helpers.swift
Normal file
40
Sources/CliVersion/Helpers.swift
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import Foundation
|
||||||
|
#if canImport(FoundationNetworking)
|
||||||
|
import FoundationNetworking
|
||||||
|
#endif
|
||||||
|
import Logging
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public extension Logger.Level {
|
||||||
|
|
||||||
|
init(verbose: Bool) {
|
||||||
|
if verbose {
|
||||||
|
self = .debug
|
||||||
|
} else {
|
||||||
|
self = .info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public extension URL {
|
||||||
|
var cleanFilePath: String {
|
||||||
|
absoluteString.replacingOccurrences(of: "file://", with: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public func url(for path: String) -> URL {
|
||||||
|
#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
|
||||||
|
}
|
||||||
23
Sources/TestSupport/TestSupport.swift
Normal file
23
Sources/TestSupport/TestSupport.swift
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
// swiftlint:disable force_try
|
||||||
|
|
||||||
|
/// Helper to create a temporary directory for running tests in.
|
||||||
|
///
|
||||||
|
/// The temporary directory will be removed after the operation has ran.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - operation: The operation to run with the temporary directory.
|
||||||
|
public func withTemporaryDirectory(
|
||||||
|
_ operation: (URL) throws -> Void
|
||||||
|
) rethrows {
|
||||||
|
let tempUrl = FileManager.default
|
||||||
|
.temporaryDirectory
|
||||||
|
.appendingPathComponent(UUID().uuidString)
|
||||||
|
|
||||||
|
try! FileManager.default.createDirectory(at: tempUrl, withIntermediateDirectories: false)
|
||||||
|
try operation(tempUrl)
|
||||||
|
try! FileManager.default.removeItem(at: tempUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:enable force_try
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import Foundation
|
|
||||||
import CliVersion
|
import CliVersion
|
||||||
|
import Foundation
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
|
||||||
extension CliVersionCommand {
|
extension CliVersionCommand {
|
||||||
@@ -9,15 +9,16 @@ extension CliVersionCommand {
|
|||||||
abstract: "Used for the build with version plugin.",
|
abstract: "Used for the build with version plugin.",
|
||||||
discussion: "This should generally not be interacted with directly, outside of the build plugin."
|
discussion: "This should generally not be interacted with directly, outside of the build plugin."
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptionGroup var shared: SharedOptions
|
@OptionGroup var shared: SharedOptions
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
name: .customLong("git-directory"),
|
name: .customLong("git-directory"),
|
||||||
help: "The git directory for the version."
|
help: "The git directory for the version."
|
||||||
)
|
)
|
||||||
var gitDirectory: String
|
var gitDirectory: String
|
||||||
|
|
||||||
|
// TODO: Use CliClient
|
||||||
func run() throws {
|
func run() throws {
|
||||||
try withDependencies {
|
try withDependencies {
|
||||||
$0.logger.logLevel = .debug
|
$0.logger.logLevel = .debug
|
||||||
@@ -29,10 +30,10 @@ extension CliVersionCommand {
|
|||||||
@Dependency(\.logger) var logger: Logger
|
@Dependency(\.logger) var logger: Logger
|
||||||
|
|
||||||
logger.info("Building with git-directory: \(gitDirectory)")
|
logger.info("Building with git-directory: \(gitDirectory)")
|
||||||
|
|
||||||
let fileUrl = URL(fileURLWithPath: shared.target)
|
let fileUrl = URL(fileURLWithPath: shared.target)
|
||||||
.appendingPathComponent(shared.fileName)
|
.appendingPathComponent(shared.fileName)
|
||||||
|
|
||||||
let fileString = fileUrl.fileString()
|
let fileString = fileUrl.fileString()
|
||||||
logger.info("File Url: \(fileString)")
|
logger.info("File Url: \(fileString)")
|
||||||
|
|
||||||
@@ -40,11 +41,10 @@ extension CliVersionCommand {
|
|||||||
|
|
||||||
let fileContents = buildTemplate
|
let fileContents = buildTemplate
|
||||||
.replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"")
|
.replacingOccurrences(of: "nil", with: "\"\(currentVersion)\"")
|
||||||
|
|
||||||
try fileClient.write(string: fileContents, to: fileUrl)
|
try fileClient.write(string: fileContents, to: fileUrl)
|
||||||
logger.info("Updated version file: \(fileString)")
|
logger.info("Updated version file: \(fileString)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,13 @@ import Foundation
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct CliVersionCommand: ParsableCommand {
|
struct CliVersionCommand: ParsableCommand {
|
||||||
|
static var configuration: CommandConfiguration = .init(
|
||||||
static var configuration: CommandConfiguration = .init(
|
commandName: "cli-version",
|
||||||
commandName: "cli-version",
|
version: VERSION ?? "0.0.0",
|
||||||
version: VERSION ?? "0.0.0",
|
subcommands: [
|
||||||
subcommands: [
|
Build.self,
|
||||||
Build.self,
|
Generate.self,
|
||||||
Generate.self,
|
Update.self,
|
||||||
Update.self
|
]
|
||||||
]
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
|
import CliVersion
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
import CliVersion
|
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
|
||||||
extension CliVersionCommand {
|
extension CliVersionCommand {
|
||||||
|
|
||||||
struct Generate: ParsableCommand {
|
struct Generate: ParsableCommand {
|
||||||
static var configuration: CommandConfiguration = .init(
|
static var configuration: CommandConfiguration = .init(
|
||||||
abstract: "Generates a version file in a command line tool that can be set via the git tag or git sha.",
|
abstract: "Generates a version file in a command line tool that can be set via the git tag or git sha.",
|
||||||
@@ -15,6 +14,7 @@ extension CliVersionCommand {
|
|||||||
|
|
||||||
@OptionGroup var shared: SharedOptions
|
@OptionGroup var shared: SharedOptions
|
||||||
|
|
||||||
|
// TODO: Use CliClient
|
||||||
func run() throws {
|
func run() throws {
|
||||||
@Dependency(\.logger) var logger: Logger
|
@Dependency(\.logger) var logger: Logger
|
||||||
@Dependency(\.fileClient) var fileClient
|
@Dependency(\.fileClient) var fileClient
|
||||||
@@ -35,12 +35,10 @@ extension CliVersionCommand {
|
|||||||
} else {
|
} else {
|
||||||
logger.info("Would generate file at: \(fileString)")
|
logger.info("Would generate file at: \(fileString)")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum GenerationError: Error {
|
private enum GenerationError: Error {
|
||||||
case fileExists(path: String)
|
case fileExists(path: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func parseTarget(_ target: String) -> URL {
|
|||||||
|
|
||||||
extension URL {
|
extension URL {
|
||||||
func fileString() -> String {
|
func fileString() -> String {
|
||||||
self.absoluteString
|
absoluteString
|
||||||
.replacingOccurrences(of: "file://", with: "")
|
.replacingOccurrences(of: "file://", with: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ let VERSION: String = nil
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
// TODO: Use Int for `verbose`.
|
||||||
struct SharedOptions: ParsableArguments {
|
struct SharedOptions: ParsableArguments {
|
||||||
|
|
||||||
@Argument(help: "The target for the version file.")
|
@Argument(help: "The target for the version file.")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import Foundation
|
|
||||||
import CliVersion
|
import CliVersion
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
|
||||||
extension CliVersionCommand {
|
extension CliVersionCommand {
|
||||||
@@ -17,8 +18,9 @@ extension CliVersionCommand {
|
|||||||
name: .customLong("git-directory"),
|
name: .customLong("git-directory"),
|
||||||
help: "The git directory for the version."
|
help: "The git directory for the version."
|
||||||
)
|
)
|
||||||
var gitDirectory: String? = nil
|
var gitDirectory: String?
|
||||||
|
|
||||||
|
// TODO: Use CliClient
|
||||||
func run() throws {
|
func run() throws {
|
||||||
try withDependencies {
|
try withDependencies {
|
||||||
$0.logger.logLevel = shared.verbose ? .debug : .info
|
$0.logger.logLevel = shared.verbose ? .debug : .info
|
||||||
@@ -54,6 +56,6 @@ extension CliVersionCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate enum UpdateError: Error {
|
private enum UpdateError: Error {
|
||||||
case versionFileDoesNotExist(path: String)
|
case versionFileDoesNotExist(path: String)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import XCTest
|
@_spi(Internal) import CliVersion
|
||||||
import CliVersion
|
import Dependencies
|
||||||
import ShellClient
|
import ShellClient
|
||||||
|
import TestSupport
|
||||||
|
import XCTest
|
||||||
|
|
||||||
final class GitVersionTests: XCTestCase {
|
final class GitVersionTests: XCTestCase {
|
||||||
|
|
||||||
@@ -21,8 +23,7 @@ final class GitVersionTests: XCTestCase {
|
|||||||
.deletingLastPathComponent()
|
.deletingLastPathComponent()
|
||||||
.deletingLastPathComponent()
|
.deletingLastPathComponent()
|
||||||
.deletingLastPathComponent()
|
.deletingLastPathComponent()
|
||||||
.absoluteString
|
.cleanFilePath
|
||||||
.replacingOccurrences(of: "file://", with: "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_overrides_work() throws {
|
func test_overrides_work() throws {
|
||||||
@@ -43,7 +44,6 @@ final class GitVersionTests: XCTestCase {
|
|||||||
print("VERSION: \(version)")
|
print("VERSION: \(version)")
|
||||||
// can't really have a predictable result for the live client.
|
// can't really have a predictable result for the live client.
|
||||||
XCTAssertNotEqual(version, "blob")
|
XCTAssertNotEqual(version, "blob")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_commands() throws {
|
func test_commands() throws {
|
||||||
@@ -65,38 +65,31 @@ final class GitVersionTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test_file_client() throws {
|
func test_file_client() throws {
|
||||||
@Dependency(\.fileClient) var fileClient
|
try withTemporaryDirectory { tmpDir in
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
let tmpDir = FileManager.default.temporaryDirectory
|
|
||||||
.appendingPathComponent("file-client-test")
|
|
||||||
|
|
||||||
try FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true)
|
|
||||||
defer { try! FileManager.default.removeItem(at: tmpDir) }
|
|
||||||
|
|
||||||
let filePath = tmpDir.appendingPathComponent("blob.txt")
|
|
||||||
try fileClient.write(string: "Blob", to: filePath)
|
|
||||||
|
|
||||||
let contents = try String(contentsOf: filePath)
|
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
XCTAssertEqual(contents, "Blob")
|
|
||||||
|
|
||||||
|
let filePath = tmpDir.appendingPathComponent("blob.txt")
|
||||||
|
try fileClient.write(string: "Blob", to: filePath)
|
||||||
|
|
||||||
|
let contents = try fileClient.read(filePath)
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
XCTAssertEqual(contents, "Blob")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_file_client_with_string_path() throws {
|
func test_file_client_with_string_path() throws {
|
||||||
@Dependency(\.fileClient) var fileClient
|
try withTemporaryDirectory { tmpDir in
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
|
||||||
let tmpDir = FileManager.default.temporaryDirectory
|
let filePath = tmpDir.appendingPathComponent("blob.txt")
|
||||||
.appendingPathComponent("file-client-string-test")
|
let fileString = filePath.cleanFilePath
|
||||||
|
|
||||||
try FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true)
|
try fileClient.write(string: "Blob", to: fileString)
|
||||||
defer { try! FileManager.default.removeItem(at: tmpDir) }
|
|
||||||
|
|
||||||
let filePath = tmpDir.appendingPathComponent("blob.txt")
|
let contents = try fileClient.read(fileString)
|
||||||
let fileString = filePath.absoluteString.replacingOccurrences(of: "file://", with: "")
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
try fileClient.write(string: "Blob", to: fileString)
|
|
||||||
|
|
||||||
let contents = try String(contentsOf: filePath)
|
XCTAssertEqual(contents, "Blob")
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
}
|
||||||
XCTAssertEqual(contents, "Blob")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user