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).
- with
- The script filename is
name.{Nest}.swift
- (or see below for other ways to specify the nest)
- When the script is run,
clutch
creates and/or builds the peer executable as needed:Build/Package.swift
is updated with an executable productname
when the script is first run.- Peer
Build/Sources/name/main.swift
(orname.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.
- The library module can depend on others, e.g.,
- 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.
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`
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.
- 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
.
- If there is no trailing segment, clutch uses the default nest
- Both the nest and peer name must be valid ASCII identifiers.
- The nest name is any trailing segment (ignoring .swift)
- 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}"]),
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: [ ... ]
// 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: [ ... ]
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
.
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
, andverbose
produce appropriate build flags. - Arbitrary
@
-delimited args are passed through to swift build command.
- The special values
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
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
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 name
Use 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
@main
and Package.swift
operations can fail when declarations don't follow expected format.
swift build
keeps 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.
@main
and Package.swift
operations can fail when declarations don't follow expected format.swift build
keeps the old executable when script edits produce no binary changes, resulting in harmless but unnecessary no-op builds.- The
@main
detection is simplistic for new scripts (and not done for updates). - The
Package.swift
editing for new scripts is also a simple scan.- It seeks
products:
andtargets:
(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
orCLUTCH_TARGET
- It seeks
- Users have to manually edit the package to rename/delete peers or fix errors.
- To delete, remove
Sources/peer
and two lines for peer inPackage.swift
- To delete, remove
- 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).
- Tested on macOS/Linux, but unproven in the wild...
- Command set, CLI interface, configuration, and error-handling could change.
- clatch is clutch done quick.
- It just builds and run scripts, without hooks for porting or testing.
- No nest-management commands, no environment configuration, no error or SystemCalls wrapper
- The
swift
command works fine if no libraries are needed.- Use
#!/usr/bin/env swift
at the start of a script file to run directly swift script.swift
does the same, without the#!
hash-bang lineswift -e 'statement{; statement}'
runs a snippet of code- Or
generateCode | swift -
to run code from the input stream
- Use
- swift-sh builds and runs using libraries from import comments
- Try Swift Argument Parser to simplify writing CLI's
- Try Shwift for async cross-platform scripting
- To install Swift package command-line tools, consider Mint
mint install swift-nest/clutch
- 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...