swift-nest/clutch
- `clutch` script manager+runner tool is well-tested
Status: MVP
clutchscript manager+runner tool is well-testedclatchminimal runner tool works
Summary
Swift scripting is easier using clutch to build and launch scripts when
- scripts have dependencies, or
- you want common code in a library, or
- you want scattered scripts checked into a common local package, or
- scripts build & run faster because common code is pre-built in the nest package
For each script, clutch makes a matching "peer" executable in a local "nest" package:
- A "nest" host is a Swift package with a library of the same name (e.g., in
HOME/git/Build). - Clutch maintains a "peer" copy of the script in its own executable target module in the nest package.
- The script is an executable file anywhere
- with #!/usr/bin/env clutch on the first line (if clutch is on the PATH).
- The script filename is
name.{Nest}.swift
- (or see below for other ways to specify the nest)
- When the script is run,
clutchcreates and/or builds the peer executable as needed:
- Build/Package.swift is updated with an executable product name when the script is first run. - Peer Build/Sources/name/main.swift (or name.swift) is created or updated to match the script file. - The executable depends on the nest library module (Build) - The library module can depend on others, e.g., Swift Argument Parser or Shwift.
- When everything is up-to-date, clutch runs the executable with any arguments.
clutch can also run peers by name (from anywhere) and manage peers in the nest.
Quickstart
Tested on macOS 12+ and Linux (Ubuntu LTS/latest)
Assuming ~/git, PATH has ~/bin along with swift and git (and you're ok with Sources/clutch and nests/simple/Nest)...
# Build clutch and install to `~/bin` on `PATH` (for `/usr/bin/env`)
git clone https://github.com/swift-clutch/clutch.git
cd clutch && swift build
cp .build/debug/clutch ~/bin
# Create a sample nest package
cp -rf nests/simple/Nest ~/git/Nest
# or: mkdir ~/git/Nest && cd "$_" && swift package init --type library
# Create a hello script (anywhere)
cat > hello <<EOF
#!/usr/bin/env clutch
let name = CommandLine.arguments[1...].first ?? "World"
print("Hello \(name)")
EOF
chmod +x hello
./hello friend # builds, runs `~/git/Nest/Sources/hello/main.swift`
# Use clutch directly to run or manage peers or nests
clutch hello # run by name from anywhere (use `hello.{Nest}` if not default)
clutch cat-hello # output peer source (`clutch cat-hello > hi.swift`)
clutch path-hello # echo peer path (`vi "$(clutch path-hello)"`)
clutch peers-Nest # list peers known in nest `Nest`
clutch dir-Nest # emit location of nest `Nest`Usage
Install clutch, create a nest package, and write a Swift script file anywhere.
When you invoke the script, clutch runs it after ensuring its nest package peer (~/git/Nest/Sources/peer/peer.swift) is created, updated, and/or built. A nest package has a nest library (with the nest name) for common code and dependencies.
<details><summary>
Write name.{Nest.}swift: use #!/usr/bin/env clutch when clutch is on the PATH
</summary>
- The peer name is the initial filename segment (before
.).
- The nest name is any trailing segment (ignoring .swift) - If there is no trailing segment, clutch uses the default nest Nest. - Both the nest and peer name must be valid ASCII identifiers.
- The file is executable.
- The hash-bang on the first line depends on where you install clutch:
- #!/usr/bin/env clutch (best, if clutch is on your PATH) - #!/path/to/clutch (when clutch is installed but not on the PATH)
- The script file has valid top-level code, depending only on the nest library.
The nest peer in {nest}/Sources/{peer} will be created on first impression. The peer filename is main.swift, or {peer}.swift if it contains @main.
Package.swift will be updated with peer product and target declarations:
.executable(name: "{peer}", targets: [ "{peer}" ]),.executableTarget(name: "{peer}", dependencies: ["{nest}"]),
</details>
<details><summary>
The nest package, e.g., $HOME/git/{nest}/Package.swift
The package contains at least the nest library and an executable target for each associated script.
// in Package.swift (e.g., as created by `swift package --init`)
products: [
// peer product created for each script, using the script name {peer}
.executable(name: "{peer}", targets: [ "{peer}" ]),
.library(name: "{nest}", targets: ["{nest}"]),
],
targets: [
// peer executable created for each script
.executableTarget(name: "{peer}", dependencies: ["{nest}"]),
.target(
name: "{nest}",
dependencies: [ ... ] </summary>
The nest directory name must be the name of the library module.
For sample nest packages, see nests or use swift package init --type library.
Configuration
By default clutch builds using -c debug --quiet (to avoid delay and noise), the nest package is named Nest, and it lives at $HOME/git/Nest.
You can configure the nest location, output, or build flags.
Configuration is rarely needed (mostly just CLUTCH_NEST_RELPATH to change the git parent directly).
To configure, set environment variables:
CLUTCH_NEST_NAME: find nest in$HOME/{relative-path}/{nest-name}CLUTCH_NEST_RELPATH: relative path from HOME (defaults togit)CLUTCH_NEST_BASE: find nest in$CLUTCH_NEST_BASE/{nest-name}insteadCLUTCH_NEST_PATH: full path to nest directory (ignoring other variables)CLUTCH_LOG: any value to log clutch build steps to standard errorCLUTCH_BUILD:@{arg0}@{arg1}.., or{release} {loud | verbose}
- The special values release, loud, and verbose produce appropriate build flags. - Arbitrary @-delimited args are passed through to swift build command.
</details>
<details><summary>
Using clutch directly to run and manage peers
clutch name{.Nest} # Run peer by name
clutch [peers|path]-Nest # Emit Nest peers or location
clutch [cat|path]-name{.Nest} # Emit peer code or location</summary>
Use clutch directly to run scripts by filename or peer name
clutch name.swift # Create/build/run `name` from default nest
clutch name # Run by nameUse clutch to list peers in a nest and find or copy the peer source file:
clutch peers-Data # List peers in the `Data` nest
clutch path-name # Echo path to source file for peer `name`
clutch cat-init.Data # Output code from peer `init` in Data nest</details>
<details><summary>
Limitations (under development)
@mainandPackage.swiftoperations can fail when declarations don't follow expected format.swift buildkeeps the old executable when script edits produce no binary changes, resulting in harmless but unnecessary no-op builds.- Script exit codes are converted to 1.
- For known bugs and missing features, see README-clutch.
</summary>
- The
@maindetection is simplistic for new scripts (and not done for updates). - The
Package.swiftediting for new scripts is also a simple scan.
- It seeks products: and targets: (the latter with 2 leading spaces) - target: is common; please avoid 2 spaces before it. - And please avoid that text in comments or other declarations. - To avoid missed/invalid insertions, tag the line before the declaration: with CLUTCH_PRODUCT or CLUTCH_TARGET
- Users have to manually edit the package to rename/delete peers or fix errors.
- To delete, remove Sources/peer and two lines for peer in Package.swift
- Builds are based only on last-modified time.
- Swift does not re-link the binary after edits result in the same code (so a second clutch run would trigger another no-op build).
- Output streams and exit codes mix clutch, swift build, and executables.
- A failed exit code will always be 1 (even if the script exits with 2).
</details>
Package Status
- Tested on macOS/Linux, but unproven in the wild...
- -
- Command set, CLI interface, configuration, and error-handling could change.
Development
- Please create an issue with any feedback, to help get to 1.0 :)
- See README-clutch for known issues
- License: Apache 2.0
- Copyright Contributors on their contribution date. All rights reserved.
- Apologies for the cutesy nomenclature...
Package Metadata
Repository: swift-nest/clutch
Default branch: main
README: README.md