Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📦 Adds support for Swift Package plugin command #1093

Merged
merged 18 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 1.8.3
- Update StencilSwiftKit to fix SPM resolving issue when building as a Command Plugin [#1023](https://github.com/krzysztofzablocki/Sourcery/issues/1023)
- Adds new `--cacheBasePath` option to `SourceryExecutable` to allow for plugins setting a default cache [#1093](https://github.com/krzysztofzablocki/Sourcery/pull/1093)

## 1.8.2
## New Features
Expand Down
27 changes: 20 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.5
// swift-tools-version:5.6

import PackageDescription
import Foundation
Expand All @@ -17,16 +17,16 @@ let package = Package(
.library(name: "SourceryJS", targets: ["SourceryJS"]),
.library(name: "SourcerySwift", targets: ["SourcerySwift"]),
.library(name: "SourceryFramework", targets: ["SourceryFramework"]),
.plugin(name: "SourceryCommandPlugin", targets: ["SourceryCommandPlugin"])
],
dependencies: [
.package(url: "https://github.com/jpsim/Yams.git", from: "4.0.6"),
.package(url: "https://github.com/kylef/Commander.git", .exact("0.9.1")),
.package(url: "https://github.com/kylef/Commander.git", exact: "0.9.1"),
// PathKit needs to be exact to avoid a SwiftPM bug where dependency resolution takes a very long time.
.package(url: "https://github.com/kylef/PathKit.git", .exact("1.0.1")),
.package(url: "https://github.com/StencilProject/Stencil.git", .upToNextMajor(from: "0.14.0")),
.package(url: "https://github.com/SwiftGen/StencilSwiftKit.git", .exact("2.10.1")),
.package(url: "https://github.com/tuist/XcodeProj.git", .exact("8.3.1")),
.package(url: "https://github.com/apple/swift-syntax.git", .exact("0.50600.1")),
.package(url: "https://github.com/kylef/PathKit.git", exact: "1.0.1"),
.package(url: "https://github.com/SwiftGen/StencilSwiftKit.git", exact: "2.10.1"),
.package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.3.1"),
.package(url: "https://github.com/apple/swift-syntax.git", exact: "0.50600.1"),
.package(url: "https://github.com/Quick/Quick.git", from: "3.0.0"),
.package(url: "https://github.com/Quick/Nimble.git", from: "9.0.0")
],
Expand Down Expand Up @@ -231,6 +231,19 @@ let package = Package(
name: "lib_InternalSwiftSyntaxParser",
url: "https://github.com/keith/StaticInternalSwiftSyntaxParser/releases/download/5.6/lib_InternalSwiftSyntaxParser.xcframework.zip",
checksum: "88d748f76ec45880a8250438bd68e5d6ba716c8042f520998a438db87083ae9d"
),
.plugin(
name: "SourceryCommandPlugin",
capability: .command(
intent: .custom(
verb: "sourcery-command",
description: "Sourcery command plugin for code generation"
),
permissions: [
.writeToPackageDirectory(reason: "Need permission to write generated files to package directory")
]
),
dependencies: ["SourceryExecutable"]
)
]
)
74 changes: 74 additions & 0 deletions Plugins/SourceryCommandPlugin/SourceryCommandPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import PackagePlugin
import Foundation

@main
struct SourceryCommandPlugin {
private func run(_ sourcery: String, withConfig configFilePath: String, cacheBasePath: String) throws {
let sourceryURL = URL(fileURLWithPath: sourcery)

let process = Process()
process.executableURL = sourceryURL
process.arguments = [
"--config",
configFilePath,
"--cacheBasePath",
cacheBasePath
]

try process.run()
process.waitUntilExit()

let gracefulExit = process.terminationReason == .exit && process.terminationStatus == 0
if !gracefulExit {
throw "🛑 The plugin execution failed with reason: \(process.terminationReason.rawValue) and status: \(process.terminationStatus) "
}
}
}

// MARK: - CommandPlugin

extension SourceryCommandPlugin: CommandPlugin {
func performCommand(context: PluginContext, arguments: [String]) async throws {
// Run one per target
for target in context.package.targets {
let configFilePath = target.directory.appending(subpath: ".sourcery.yml").string
let sourcery = try context.tool(named: "SourceryExecutable").path.string

guard FileManager.default.fileExists(atPath: configFilePath) else {
Diagnostics.warning("⚠️ Could not find `.sourcery.yml` for the given target")
return
}

try run(sourcery, withConfig: configFilePath, cacheBasePath: context.pluginWorkDirectory.string)
}
}
}

// MARK: - XcodeProjectPlugin

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension SourceryCommandPlugin: XcodeCommandPlugin {
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
for target in context.xcodeProject.targets {
guard let configFilePath = target
.inputFiles
.filter({ $0.path.lastComponent == ".sourcery.yml" })
.first?
.path
.string else {
Diagnostics.warning("⚠️ Could not find `.sourcery.yml` in Xcode's input file list")
return
}
let sourcery = try context.tool(named: "SourceryExecutable").path.string

try run(sourcery, withConfig: configFilePath, cacheBasePath: context.pluginWorkDirectory.string)
}
}
}
#endif

extension String: LocalizedError {
public var errorDescription: String? { return self }
}
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,21 @@ You can also watch this quick getting started and intro to mocking video by Insi

Run `xcodebuild -scheme sourcery -destination generic/platform=macOS -archivePath sourcery.xcarchive archive` and export the binary from the archive.

- _SPM (for plugin use only)_
Add the package dependency to your `Package.swift` manifest from version `1.8.3`.

```
.package(url: "https://github.com/krzysztofzablocki/Sourcery.git", from: "1.8.3")
```

## Documentation

Full documentation for the latest release is available [here](http://merowing.info/Sourcery/).

## Usage

### Running the executable

Sourcery is a command line tool; you can either run it manually or in a custom build phase using the following command:

```
Expand All @@ -134,6 +143,32 @@ $ ./bin/sourcery --sources <sources path> --templates <templates path> --output

> Note: this command differs depending on how you installed Sourcery (see [Installing](#installing))

### Swift Package command

Sourcery can now be used as a Swift package command plugin. In order to do this, the package must be added as a dependency to your Swift package or Xcode project (see [Installing](#installing) above).

To provide a configuration for the plugin to use, place a `.sourcery.yml` file at the root of the target's directory (in the sources folder rather than the root of the package).

#### Running from the command line

To verify the plugin can be found by SwiftPM, use:

```
$ swift package plugin --list
```

To run the code generator, you need to allow changes to the project with the `--allow-writing-to-package-directory` flag:

```
$ swift package --allow-writing-to-package-directory sourcery-command
```

#### Running in Xcode

Inside a project/package that uses this command plugin, right-click the project and select "SourceryCommand" from the "SourceryPlugins" menu group.

> ⚠️ Note that this is only available from Xcode 14 onwards.

### Command line options

- `--sources` - Path to a source swift files or directories. You can provide multiple paths using multiple `--sources` option.
Expand All @@ -149,6 +184,7 @@ $ ./bin/sourcery --sources <sources path> --templates <templates path> --output
- `--prune` [default: false] - Prune empty generated files
- `--version` - Display the current version of Sourcery
- `--help` - Display help information
- `--cacheBasePath` - Base path to the cache directory. Can be overriden by the config file.

### Configuration file

Expand Down
11 changes: 7 additions & 4 deletions SourceryExecutable/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ func runCLI() {
or should be passed one by one (e.g. --args arg1=value --args arg2). Arguments are accessible in templates \
via `argument.<name>`. To pass in string you should use escaped quotes (\\").
"""),
Option<Path>("ejsPath", default: "", description: "Path to EJS file for JavaScript templates.")
) { watcherEnabled, disableCache, verboseLogging, logAST, logBenchmark, parseDocumentation, quiet, prune, serialParse, sources, excludeSources, templates, excludeTemplates, output, configPaths, forceParse, args, ejsPath in
Option<Path>("ejsPath", default: "", description: "Path to EJS file for JavaScript templates."),
Option<Path>("cacheBasePath", default: "", description: "Base path to Sourcery's cache directory")
) { watcherEnabled, disableCache, verboseLogging, logAST, logBenchmark, parseDocumentation, quiet, prune, serialParse, sources, excludeSources, templates, excludeTemplates, output, configPaths, forceParse, args, ejsPath, cacheBasePath in
do {
switch (quiet, verboseLogging) {
case (true, _):
Expand Down Expand Up @@ -140,7 +141,7 @@ func runCLI() {
sources: Paths(include: sources, exclude: excludeSources) ,
templates: Paths(include: templates, exclude: excludeTemplates),
output: output.string.isEmpty ? "." : output,
cacheBasePath: Path.defaultBaseCachePath,
cacheBasePath: cacheBasePath.string.isEmpty ? Path.defaultBaseCachePath : cacheBasePath,
forceParse: forceParse,
parseDocumentation: parseDocumentation,
args: arguments
Expand Down Expand Up @@ -187,11 +188,13 @@ func runCLI() {

let keepAlive = try configurations.flatMap { configuration -> [FolderWatcher.Local] in
configuration.validate()

let shouldUseCacheBasePathArg = configuration.cacheBasePath == Path.defaultBaseCachePath && !cacheBasePath.string.isEmpty

let sourcery = Sourcery(verbose: verboseLogging,
watcherEnabled: watcherEnabled,
cacheDisabled: disableCache,
cacheBasePath: configuration.cacheBasePath,
cacheBasePath: shouldUseCacheBasePathArg ? cacheBasePath : configuration.cacheBasePath,
prune: prune,
serialParse: serialParse,
arguments: configuration.args)
Expand Down