Where starci is not running: compiling to a workflow¶
A basic script can be written out as a GitHub Actions workflow, which Forgejo and Gitea Actions read too, or as a CircleCI configuration:
starci compile > .github/workflows/ci.yml
starci compile --target circleci > .circleci/config.yml
Each step becomes a job that does what a worker does: checks out only the
step's inputs, downloads its tools and checks their checksums, builds an image
with its packages, and runs its command in that container. A job that has
already passed with the same inputs skips the work, using the Actions cache as
the record. needs and worker carry over, as job dependencies and runner
labels, and so does network = False.
A workflow file has to say in advance what happens for every event, so for
GitHub the script is asked once for each kind: a push to the default branch,
a push to another branch, a tag, a pull request, a pull request from a fork,
each declared schedule, and a manual run. A step that turns up for some and
not others becomes a job with a condition, and the events that lead to any
step at all become the workflow's on:. So
if ctx.event == "tag":
steps.append(publish)
compiles to a publish job that runs only for tags, and a script that will
not do something for a pull request from a fork still will not once compiled.
Before writing anything, compile downloads the script's tools for --arch
and checks them as starci validate does, so that a pin that no longer holds
is found here and not in every job. --offline skips that.
A compiled file is a copy of the script, and goes stale when the script is
edited without it. --check writes nothing and fails unless the file is what
would be written, which makes it a step worth running on every push:
starci compile --check .github/workflows/ci.yml
It says where the two first differ and how to write the file again. It needs no network and downloads nothing.
Some things cannot be frozen into a file, and the header of the generated workflow says which apply:
- Anything decided by a particular value rather than by the kind of event: a rule about which files changed, about a branch other than "is it the default one", about what a tag is called. Those are frozen at what a stand-in gave.
- A step that is a different command for different events cannot be one job; compiling says so and asks for two names.
- The CircleCI output does not do this yet: it is the plan for a push.
preferandavoidare dropped. For CircleCI so isworker: it chooses machines by resource class, not by label.- A step's services are started by the job itself, the way a worker starts
them, and stopped at its end. They are not the CI tool's own
services:, which could not be reached by name from the step's container. A pass is keyed on a service's image by name: a tag that moves does not run the job again, as it would on a worker. - Advanced scripts cannot be compiled to a workflow at all, or frozen into an executable: their steps are only known as they run.
Every script in examples/basic is compiled both ways under
examples/basic/compiled.
The CircleCI output keys a pass on the exact list of files the inputs select, asked of git the way a worker asks, where the GitHub output has to make do with globs. It uses the machine executor, since each job starts the step's container itself.
The GitHub output has been linted and one of its jobs run by hand; the CircleCI output is accepted by CircleCI's own validator and one of its jobs has been run by hand. Neither has been run on the service it is for.
Or an executable¶
starci compile --target binary -o ci
./ci # in a checkout; ./ci path/to/checkout for another
writes a copy of starci with the script appended, which does what
starci run --local does and nothing else. Anything after the checkout is a
run flag (-v, --event, --sha, --slots).
By default the script travels and is evaluated when the executable runs, so
advanced scripts and scripts that read ctx work as they do under
run --local. With --freeze the plan is evaluated now and travels instead:
no script is evaluated at run time, advanced scripts are refused, and anything
decided by ctx is fixed at what a push about which nothing is known gives,
as in the CircleCI output.
What is inside is starci and the script. Tools are still downloaded on first
use, by checksum, images are pulled by Docker, and the repository is the
checkout it is run in, so it needs Docker and nothing else. --base names the
starci executable to copy, which is how to build for another OS or
architecture. On macOS the appended copy no longer passes codesign -v, though
it runs; a file that was downloaded may need its quarantine flag cleared.
Or run starci inside the CI tool¶
The other way is a workflow whose one job calls starci run --local. The CI
tool is then only the trigger. starci reads why the run started from the
tool's own environment (GitHub, Forgejo and Gitea Actions, and CircleCI are
recognised) and the script is told exactly what it would be told had starci
received the event: the tag, the pull request and whether it is from a fork,
the schedule that fired, the files a push changed. Nothing is approximated,
and advanced scripts work.