Name

theory - package API used by Mutiny

Synopsis

This document aims to explain the API available for writing packages for Mutiny. This should be considered the definitive document to consult when writing packages, libraries, package managers, or anything that consumes the data.

Repositories

Package managers must support multiple repositories. The internal prioritization of these repositories is dictated by the priority. User-facing sort (ex. when listing repositories installed on the system) is not defined, but praxis(7) sorts them alphabetically.

Repository names are alphanumeric plus _ and -.

The file and directory layout of a repository is as follows, italicized items representing optional items, anything else required:

  • <repository>/

    • dependencies

    • metadata/

    • libraries/

    • packages/

      • openssh#1.2.3/

        • action

        • dependencies

        • libraries - Libraries to be combined together to create the package.

        • metadata/ - All files under this directory are exported as environment variables after importing all libraries.

          • HOMEPAGE

          • LICENSES

          • SUMMARY

        • files/ - This directory’s path is exported as ${FILES}.

          • patches/

            • openssh-1.2.3-backport.patch

Metadata

Repositories must have a directory in their root named metadata.

Priority

Repositories must have a file within metadata named priority. The priority file must contain a non-negative integer.

There are meanings that go with each number, and they go like so:

  1. First-party primary repository, over which nothing has a greater priority. There should only be one repository with this level. (ex. Base packages for a system.)

  2. First-party secondary repository (such as a supplementary repository). (ex. Repositories containing software categories or projects; GNOME, KDE…​)

  3. First-party tertiary repository. (ex. A distribution developer’s repository.)

  4. Third-party repository. (ex. A distribution user’s repository.)

Summary

Repositories should have a summary. The contents should be a short, one-line blurb, describing the repository’s contents, objective, or theme.

For example, in listings, a package manager could show the summary next to the repository’s name.

Dependencies (optional)

Repositories can optionally have a file in metadata, named dependencies.

The file should be a list of the names of repositories on each line. Repositories listed in this file are dependent repositories, with which the package manager must have installed prior to the installation of the repository declaring these dependencies. The order in which they are listed in the file is unimportant.

Any repository listed will have its libraries directory searched when parsing package files; the order in which they are searched is determined by the priority value of the repository.

Do not add repositories to dependencies simply because a package in your repository depends on something in another repository. The package manager should deal with determining what repository needs to be installed to satisfy a dependency through usage of the universe meta-repository.

Packages

Repositories should contain a directory named packages; if they do not, package managers can ignore them entirely, as there’s not much use to a repository with no package.

See the packages section for details about what goes on within.

Packages

This term can be a bit overloaded. It’s just too useful of a term. When I say package, I mean "a collection containing instructions to build a piece of software".

In theory(5), a package means a directory containing an action, libraries, or both. If it’s just a directory with metadata, it is not a package, because there’s no actual build script. And, in fact, if you depend on libraries that also don’t provide any action file, your directory is not a package.

Why so lenient? Because a package can add actions on top of the actions declared in the libraries it uses, and same with metadata. It is a hierarchy of inheritance. It all gets combined to create a self-sufficient script, which is what’s actually used when building.

Unlike other, simpler package formats which provide simpler build scripts, using libraries allows for reducing code duplication in a tree of packages, and thus quicker mass changes.

Specification

Package specifications (informally referred to as "specs") are strings which specify a package.

Specifications take on multiple permutations, because they are made up of three different parts, of which only the name is required.

Given the fully-qualified spec package#1.0::repository…​

  • The package name is package

  • The package version is 1.0

  • The package repository is repository

For a package specification to be valid, it must match this regular expression:

(^[A-Za-z](?:[A-Za-z0-9_+-]*)?)(#[a-z0-9\._-]+)?(::[A-Za-z0-9_-]+)?

Breaking it down:

  • Group 1, the package name, is alphanumeric, plus _, +, and -. It can’t start with a number.

  • Group 2, the package version, is numeric plus ., _, -, and lowercase alpha characters. (for r1, etc.)

  • Group 3, the repository, is alphanumeric plus _, and -. It must start with an alphanumeric.

All parts of a package specification are case-sensitive.

Disambiguation

When used as user input, the only strictly required part of a spec is the inclusion of the package name. If any other part other than the name is omitted, it will be disambiguated in order to determine what packages can satisfy it.

If more than one package matches a specification, the package manager can either prompt the user in some fashion to be more specific, or just error.

Action

action files are really just shell scripts. These files should adhere to shell syntax as defined in POSIX 2016. The only thing that should go in them are phases.

src_configure() {
    call ./configure PREFIX=/ SBINDIR=/bin MANDIR=/share/man
}

src_make() {
    call make
}

src_check() {
    call shellspec
}

src_install() {
    call make install DESTDIR="${DESTDIR}"
}

Phases

The process of package building is split up into phases. Splitting up into phases allows for easier, more precise debugging, and also for sandboxing of specific parts of the build process.

Additionally, we split things more than some other package formats do. Many formats only define three (build, test, install) or four phases (prepare, build, check, package), or might not even have phases.

Everything in this section is required of any package manager implementation. If this isn’t adhered to, we’ll have issues with some packages building in one implementation but not another.

"By default" refers to a package which does not define any phases or import any libraries which define phases.

Note the difference between "not defined" and "does nothing". Packages must have each phase defined, regardless of if they have any function; if a phase listed here is not defined by either the package manager, or the package (or a library used by the package), the package manager must error out and fail, because that is an invalid package.

"Does nothing" would mean something like pkg_init() { true; }; a no-op.

"Not defined" would mean no definition of the function. (ex. Attempting to run function that is not defined would give an unknown command error)

pkg_init()

This phase is ran when a build environment is created for a package building session. Normally nothing is done, and this is a dummy function.

Examples of other definitions could be creating a custom PATH and script wrappers to be used for build systems that are too stubborn to cooperate with cross-compilation.

src_fetch()

Only ran during installation.

This phase’s purpose is to get any sources needed to make the package being built.

Usually you will not need to change this.

Examples of other definitions could include retrieval of git sources, hg, cvs, etc.

src_unpack()

Only ran during installation.

This phase’s purpose is to unpack any files retrived during src_fetch(). By default this means it will extract any archives downloaded into the [Build directory], and then change into the [Work directory].

Examples of other definitions could include checking out git sources into WORK, or similar.

src_prepare()

Only ran during installation.

This phase’s purpose is to prepare the package for the real build process; so, things which are normally done before building, like applying patches, generating Autotools scripts, etc. are to be done here.

By default it does nothing.

src_configure()

Only ran during installation.

This phase’s purpose is to run package configuration-related steps of the build process. Things like ./configure, cmake, or writing build configuration files would be done here.

By default it is not defined.

The rationale behind not providing a default definition is that it allows for more flexibility and less package manager dependent functionality. Rather than putting a default definition that, say, expects an Autotools-like package (./configure && make && make install), and putting that functionality in the package manager, it can be done with libraries.

src_make()

Only ran during installation.

This phase’s purpose is to run the compilation process for the package. Things like make, ninja, etc. would be done here.

By default it is not defined.

src_check()

Only ran during installation.

This phase’s purpose is to run tests for the package being built. Things like make check, ctest, ./setup.py test, etc. are done here.

By default it is not defined.

src_install()

Only ran during installation.

This phase’s purpose is to run the installation for the package; so, commands like make install DESTDIR="${DESTDIR}".

Under no circumstances should anything in this phase touch something outside the build environment. The package manager will merge files from the package to the system, and the build process may not even have access to the system anyway, instead being built in a chroot or a sandbox of some sort.

By default it is not defined.

pkg_premerge()

Only ran during installation.

This phase’s purpose is to run any commands that are required to be ran on the system itself before the package is merged into the filesystem.

By default it does nothing.

pkg_postmerge()

Only ran during installation.

This phase’s purpose is to run any commands that are required to be ran on the system itself after the package is merged into the filesystem.

(ex. Updating icon caches, displaying important information after a major package upgrade, …​)

By default it does nothing.

Building

This section is about the environment a package is built in.

When referring to a "build environment", this document is referring to the literal shell environment which the shell process is running in. This means it consists of things such as variables, functions, and the current working directory.

Idioms

There’s one special dependency that someone writing a package should always be able to expect to be installed, no matter what: idioms.

Why is idioms used? Because it provides good, general purpose flow control functionality in shell scripts. praxis(7) itself uses it.

The main functions that you would use from it when writing a package would be idioms-call(3), idioms-error(3), idioms-warn(3), idioms-die(3), and lastly, idioms-die(3).

If you’re implementing a package manager though and wish to not have the dependency, just implement those commands. They’re not very complex, and their existence (due to being basically included as part of this package API) makes their commands effectively keywords which can be.

Directories

The only requirements of the directory in which a package build is executed is that it is read-write accessable by the package manager, and that the work directory be entirely empty before any phases are ran.

The directories in which a build is executed are undefined. However, it is a good idea to use something located in a good location for temporary work. A directory in /var/tmp is a good idea; /tmp, not so much, as build directories should be allowed to persist for long periods of time.

Glossary

actions

The actual executable code used to build a package.

theory
  1. The package API. You are reading the documentation for it right now.

  2. A directory containing the data used to build a package. A directory contains a package if it has either an action, or a library (which creates actions).

Contributing

The canonical URL of this repository is https://git.mutiny.red/mutiny/praxis. Submit patches and bugs to <kylie@somas.is>.

There is also an IRC channel for praxis and other projects at irc://irc.freenode.net/#mutiny. Please don’t hesitate to message if you need help.

License

praxis is in the public domain.

To the extent possible under law, Kylie McClain <kylie@somas.is> has waived all copyright and related or neighboring rights to this work.