aboutsummaryrefslogtreecommitdiff
path: root/theory.5.adoc
blob: e9af142eef0e70d9a2591df89912befd72dce196 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
= theory(5)

== 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,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_
    ** <<repo-metadata,metadata>>/
        *** <<repo-priority,priority>>
        *** _<<repo-summary,summary>>_
    ** _libraries_/
        *** <<library-actions,action>>
    ** packages/
        *** openssh#1.2.3/
            **** <<actions,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_

[#repo-metadata]
=== Metadata

Repositories *must* have a directory in their root named `metadata`.

[#repo-priority]
==== 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:

[start=0]
. 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.)
. First-party secondary repository (such as a supplementary repository).
  (ex. Repositories containing software categories or projects; GNOME, KDE...)
. First-party tertiary repository. (ex. A distribution developer's repository.)
. Third-party repository. (ex. A distribution user's repository.)

[#repo-summary]
==== 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.

[#repo-dependencies]
==== 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 <<repo-meta,`universe` meta-repository>>.

[#repo-packages]
=== 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 <<pkg,packages>> section for details about what goes on within.

[#pkg]
== 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.

[#pkg-specification]
=== 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,
<<pkg-disambiguation,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.

[#pkg-disambiguation]
==== 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.

[#pkg-action]
=== Action

:url-posix2016: http://pubs.opengroup.org/onlinepubs/9699919799/

`action` files are really just shell scripts. These files should adhere to shell syntax as defined
in {url-posix2016}[POSIX 2016]. The only thing that should go in them are <<pkg-phases,phases>>.

[source,sh]
----
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}"
}
----

[#pkg-phases]
=== 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.

[#build]
== 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.

[#build-idioms]
=== Idioms

:url-idioms: https://git.mutiny.red/mutiny/idioms

There's one special dependency that someone writing a package should always be able to expect to be
installed, no matter what: {url-idioms}[`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.adoc#,idioms-call(3)>>, <<idioms-error.3.adoc#,idioms-error(3)>>,
<<idioms-warn.3.adoc#,idioms-warn(3)>>, and lastly, <<idioms-die.3.adoc#,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]
== Glossary

[glossary]
actions::
    The actual executable code used to build a package.

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

    . 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).

include::footer.adoc[]