Choosing Commits
A project requires other projects, which require others in turn, each at a commit. stift chooses one commit of each project for the build, as Go chooses versions, by minimal version selection, with git's history in place of version numbers.
The rule
- Start from the main project, the one being built. Read its stiftfile, and the stiftfile of each project it requires, at the commit required, from stift's cache. A project without a stiftfile requires nothing.
- Follow every requirement met, at the commit it names, until no new pair of project and commit appears. The requirements of every commit met count, not only those of the commits finally chosen, so the result does not depend on the order of reading.
- For each project, choose the commit required of it that contains all the others: the newest, in the project's own history.
- The build list is the main project, and every project its requirements reach through the requirements of the commits chosen.
A requirement is met by its commit or any commit that contains it. No commit newer than one required comes in, and two machines with the same stiftfiles choose the same commits.
An example: cl-harfbuzz requires cl-ffi at A, and cl-ft2 requires it at B, a later commit of the same branch. Pyssel requires both libraries, and gets cl-ffi at B, which contains A.
Test requirements
:test t marks a requirement that only the project's own tests need, such
as Parachute. It counts in the main project, and is ignored when the
project is someone else's dependency: nobody who uses cl-harfbuzz needs
Parachute for it.
When requirements diverge
When no commit required of a project contains all the others, they lie on either side of a fork, or of a branch a force-push rewrote. stift stops, and names each requirement and who made it:
The requirements on fakehacker.cc/dxtr/cl-ffi have diverged:
fakehacker.cc/dxtr/cl-harfbuzz at 1a2b3c4d5e6f requires 4f0c1a2b3c4d (branch "faster-calls")
fakehacker.cc/dxtr/cl-ft2 at 9d8e7f6a5b4c requires 7a6b5c4d3e2f
Neither commit contains the other. Require a commit that contains both, such
as a merge, or choose one in the :replace of the main project's stiftfile, or
of the workspace's stiftwork:
("https://fakehacker.cc/dxtr/cl-ffi.git" :commit "...")
The main project settles it, by requiring a commit that contains them all, such as a merge of both sides, or by choosing one with a replacement.
The stiftfile holds the build list
The main project's stiftfile lists its whole build list, so that it alone
says what is built, a moved pin shows in its diff, and setting up a build
needs no walk through other stiftfiles. stift tidy keeps it so:
- each project of the build list that the stiftfile does not require
itself gets an
:indirect tentry, at the commit chosen, with the ref that came with that commit; - an indirect entry that only test requirements bring in is marked
:test ttoo, so that projects using this one skip it with them; - an indirect entry that nothing needs any more goes;
- a requirement whose project needs a later commit moves to it. A requirement that follows a branch keeps the branch when the branch contains the new commit; otherwise the requirement takes the ref of the requirement that chose the commit.
The entries without :indirect are the user's: only get and remove
add and remove them. get, remove and update tidy the stiftfile
after their own change, and write nothing when tidying fails.
An indirect entry counts as a requirement of the main project, as in Go,
so update may move it past what the projects that need it require.
stift remove on an indirect entry sends it back to the commit the
projects that need it require.
The check before a build
Outside a workspace, every command that sets up a build, list,
download, setup, exec and test, first checks that the stiftfile
holds the build list its requirements make. It reads the stiftfile of each commit the stiftfile
lists, and stops when one of them requires a project the stiftfile does not
list, or a commit the stiftfile's commit for that project does not contain:
/home/me/app/stiftfile does not hold the build list its requirements make:
fakehacker.cc/dxtr/cl-ft2 at 9d8e7f6a5b4c requires fakehacker.cc/dxtr/cl-ffi at 7a6b5c4d3e2f, which the stiftfile does not list
Run stift tidy to bring it up to date.
It happens after an edit by hand, or a merge of two versions of the file.
stift never tidies unasked. In a workspace, stift chooses from every
requirement met instead, as tidy does, so the members' stiftfiles need
not be tidy (see Workspaces).
Replacements
An entry in the :replace of the main project's stiftfile chooses what
the build uses for a project, whatever its requirements say:
| Entry | What the build uses |
|---|---|
(URL :commit ID) |
That commit of the project's repository |
(URL :with URL2 :commit ID) |
That commit of another repository, such as a fork |
(URL :local DIR) |
The files in a directory, relative to the main project's |
A replaced project's stiftfile is read from its replacement, and its
requirements count as any project's. A replaced project gets no indirect
entry, and an entry it has stays as it is. :branch or :tag beside
:commit is the ref update follows, in the repository the files come
from.
Replacements count only in main projects: a library cannot rearrange the builds of the projects that use it.
Forks under the wrong name
A project's stiftfile names it with :project. When that names another
project than the one required, the repository is a fork or a mirror, and
stift stops. Require the original, and use the fork in its place with the
replacement (ORIGINAL :with FORK :commit ID); or take the fork as it is,
under the name it was required by, with (FORK :commit ID). A replaced
project's name is not checked.
Two projects, one system
A project provides the systems whose .asd files are in its tree, but for
those in hidden directories, whose names start with a dot, and, in a
project with a stiftfile, those in vendor/, where stift vendor puts
its copies of other projects (see Vendoring). When two
projects of the build list provide one system, ASDF would load one of them
without a word, so stift stops and names both, with who requires each. If
they are copies of one library, keep one with a replacement. If one
carries a copy of the other in its tree, leave the copy out with :exclude
on the requirement of the project that carries it:
(:project "example.org/me/app"
:require (("https://example.org/someone/foo.git" :branch "main"
:commit "..." :exclude ("vendor/"))))
:exclude names directories relative to the project's root, and counts
only in the main project. Two .asd files of one name within one project
are left to ASDF, which takes the shallower.
Why and graph
stift why prints the chains of requirements that bring a project into
the build list, the shortest through each of the stiftfile's own
requirements that leads to it. It takes the project's identity, URL or
name, or the name of a system it provides:
$ stift why cl-ffi
fakehacker.cc/dxtr/pyssel
requires fakehacker.cc/dxtr/cl-gfx at 5e03cf5ae3ff
which requires fakehacker.cc/dxtr/cl-ffi at b7248691986f
stift graph prints every requirement met, as tidy reads them, a line
each: the project and commit that requires, then the project and commit
required.
$ stift graph
fakehacker.cc/dxtr/pyssel fakehacker.cc/dxtr/cl-gfx@5e03cf5ae3ff
fakehacker.cc/dxtr/cl-gfx@5e03cf5ae3ff fakehacker.cc/dxtr/cl-ffi@b7248691986f