diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:54:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:58:39 +0000 |
commit | 129a1fb4dbc375be0fa926964aa1be46a0cdbbef (patch) | |
tree | 04c0088df47415b24a5be1325d3656b8c3881c04 /MANIFEST-FORMAT.md | |
parent | Initial commit. (diff) | |
download | debputy-129a1fb4dbc375be0fa926964aa1be46a0cdbbef.tar.xz debputy-129a1fb4dbc375be0fa926964aa1be46a0cdbbef.zip |
Adding upstream version 0.1.21.upstream/0.1.21
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'MANIFEST-FORMAT.md')
-rw-r--r-- | MANIFEST-FORMAT.md | 1414 |
1 files changed, 1414 insertions, 0 deletions
diff --git a/MANIFEST-FORMAT.md b/MANIFEST-FORMAT.md new file mode 100644 index 0000000..13bedf7 --- /dev/null +++ b/MANIFEST-FORMAT.md @@ -0,0 +1,1414 @@ +# The debputy manifest format + +_This is [reference guide] and is primarily useful if you have an idea of what you are looking for._ +_If you are new to `debputy`, maybe you want to read [GETTING-STARTED-WITH-dh-debputy.md](GETTING-STARTED-WITH-dh-debputy.md) first._ + +<!-- To writers and reviewers: Check the documentation against https://documentation.divio.com/ --> + + +## Prerequisites + +This guide assumes familiarity with Debian packaging in general. Notably, you should understand +the different between a (Debian) source package and a (Debian) binary package (e.g., `.deb`) plus +how these concepts relates to `debian/control` (the source control file). + +Additionally, the reader is expected to have an understanding of globs and substitution variables. + +It is probably very useful to have an understanding on how a binary package is +assembled. While `debputy` handles the assembly for you, this document will not go in details +with this. Prior experience with `debhelper` (notably the `dh`-style `debian/rules`) may be +useful but should not be a strict requirement. + + +# The basic manifest + +The manifest is a YAML document with a dictionary at the top level layer. As usual with YAML +versions, you can choose to leave it implicit. All manifests must include a manifest version, which +will enable the format to change over time. For now, there is only one version (`0.1`) and you have +to include the line: + + manifest-version: "0.1" + + +On its own, the manifest containing only `manifest-version: "..."` will not do anything. So if you +end up only having the `manifest-version` key in the manifest, you can just remove the manifest and +rely entirely on the built-in rules. + + +# Path matching rules + +Most of the manifest is about declaring rules for a given path such as "foo must be a symlink" +or "bar must be owned by root:tty and have mode 02755". + +The manifest contains the following types of matches: + + 1) Exact path matches. These specify the path inside the debian package exactly without any + form of wildcards (e.g., `*` or `?`). However, they can use substitution variables. + Examples include: + * `usr/bin/sudo` + * `usr/lib/{{DEB_HOST_MULTIARCH}}/libfoo.so` + + Having a leading `/` is optional. I.e. `/usr/bin/sudo` and `usr/bin/sudo` are considered the + same path. + + 2) Glob based path matches. These specify a rule that match any path that matches a given + glob. These rules must contain a glob character (e.g., `*`) _and_ a `/`. Examples include: + + * `foo/*` + * `foo/*.txt` + * `usr/lib/{{DEB_HOST_MULTIARCH}}/lib*.so*` + + Note that if the glob does not contain a `/`, then it falls into the Basename glob rule + below. + + 3) Basename glob matches. These specify a rule that match any path where the basename matches + a given glob. They must contain a glob character (e.g., `*`) but either must not have a + `/` in them at all or start with `**/` and not have any `/` from there. + Examples include: + + * `"*.la"` + * `"**/*.md"` + * `"**/LICENSE"` + + The examples use explicit quoting because YAML often interprets `*` as an anchor rule in the + places where you are likely to use this kind of glob. The use of the `**/`-prefix is + optional when the basename is a glob. If you wanted to match all paths with the basename + of exactly `LICENSE`, then you have to use the prefix (that is, use `**/LICENSE`) as `LICENSE` + would be interpreted as an exact match for a top-level path. + +However, there are also cases where these matching rules can cause conflicts. This is covered +in the [Conflict resolution](#conflict-resolution) section below. + + +## Limitations on debputy path rules + +Path rules: + + 1) Must match relatively to the package root. + 2) Never resolves a symlink. + * If `bin/ls` is a symlink, then `bin/ls` matches the symlink (and never the target) + * If `bin` is a symlink, then `bin/ls` (or `bin/*`) would fail to match, because + matching would require resolving the symlink. + +These limitations are in place because of implementation details in `debputy`. + + +## Conflict resolution + +The manifest can contain seemly mutually exclusive rules. As an example, if you ask for +`foo/symlink` to be a symlink but also state that you want to remove `foo` entirely +from the debian package then the manifest now has two mutually exclusive requests. + +To resolve these problems, `debputy` relies on the following rules for conflict resolutions: + + 1. Requests are loosely-speaking ranked and applied from top to bottom: + 1. `installations` (ordered; "first match wins") + 2. `transformations` (ordered; "all matching rules applies") + + The "first match wins" rule is exactly what it says. + + The "all matching rules applies" rule means that each rule is applied in order. Often + this behaves like a simple case of either "first match wins" or "last match wins" (depending + on the context). + + Note for transformation rules, an early rule can change the file system layout, which will + affect whether a later rule matches. This is similar to how shell globs commands work: + + $ rm usr/lib/libfoo.la + $ chmod 0644 usr/lib/* + + Here the glob used with `chmod` will not match `usr/lib/libfoo.la` because it was removed. + As noted, a similar logic applies to transformation rules. + + 2. The ordered rules (such as every transformation inside `transformations`) are applied in the + listed order (top to bottom). Due to the explicit ordering, such rules generally do not trigger + other conflict resolution rules. + + Note keep in mind that all rules must in general apply to at least one path. + +Note that `debputy` will in some cases enforce that rules are not redundant. This feature is currently +only fully implemented for `installations`. + +## All definitions must be used + +Whenever you define a request or rule in the manifest, `debputy` will insist on it being used +at least once. The exception to this rule being conditional rules where the condition +evaluates to `false` (in which case the rule never used and does not trigger an error). + +This is useful for several reasons: + + 1. The definition may have been shadowed by another rule and would have been ignored otherwise + 2. The definition may no longer be useful, but its present might confuse a future reader of + the manifest. + +In all cases, `debputy` will tell you if a definition was unused and where you can find that +definition. + + +## debputy globs + +In general, the following rules applies to globs in `debputy`. + + * The `*` match 0 or more times any characters except `/`. + * The `?` match exactly one character except `/`. + * The glob `foo/*` matches _everything_ inside `foo/` including hidden files (i.e., paths starting + with `.`) unlike `bash`/`sh` globs. However, `foo/*` does _not_ match `foo/` itself (this latter + part matches `bash`/`sh` behaviour and should be unsurprising). + * For the special-cases where `**` is supported, then `**` matches zero or more levels of directories. + This means that `foo/**/*` match any path beneath `foo` (but still not `foo`). This is mostly relevant + for built-in path matches as it is currently not possible to define `foo/**/...` patterns in the manifest. + +Note that individual rules (such as `clean-after-removal`) may impose special cases to how globs +work. The rules will explicitly list if they divert from the above listed glob rules. + +# Rules for substituting manifest variables + +The `debputy` tool supports substitution in various places (usually paths) via the following +rules. That means: + + 1) All substitutions must start with `{{` and end with `}}`. The part inbetween is + the `MANIFEST_VARIABLE` and must match the regular expression `[A-Za-z0-9][-_:0-9A-Za-z]*`. + Note that you can use space around the variable name if you feel that increases readability. + (That is, `{{ FOO }}` can be used as an alternative to `{{FOO}}`). + 2) The `MANIFEST_VARIABLE` will be result from a set of built-in variables and the variables from + `dpkg-architecture`. + 3) You can use `{{token:DOUBLE_OPEN_CURLY_BRACE}}` and `{{token:DOUBLE_CLOSE_CURLY_BRACE}}` (built-in + variables) if you want a literal `{{` or `}}` would otherwise have triggered an undesired expansion. + 4) All `{{MANIFEST_VARIABLE}}` must refer to a defined variable. + - You can see the full list of `debputy` and plugin provided manifest variables via: + `debputy plugin list manifest-variables`. The manifest itself can declare its own variables + beyond that list. Please refer to the [Manifest Variables](#manifest-variables-variables) + section manifest variables declared inside the manifest. + 5) There are no expression syntax inside the `{{ ... }}` (unlike jinja2 and other template languages). + This rule may be changed in the future (with a new manifest version). + +Keep in mind that substitution _cannot_ be used everywhere. There are specific places where +it can be used. Also, substitution _cannot be used_ to introduce globs into paths. When a +substitution occurs inside a path all characters inserted are treated as literal characters. + +Note: While manifest variables can be substituted into various places in the `debputy` manifest, they +are distinct from `dpkg`'s "substvars" (`man 5 deb-substvars`) that are used in the `debian/control` +file. + +## Built-in or common substitution variables + + * `{{token:NEWLINE}}` or `{{token:NL}}` expands to a literal newline (LF) `\n`. + * `{{token:TAB}}` expands to a literal tab `\t`. + * `{{token:OPEN_CURLY_BRACE}}` / `{{token:CLOSE_CURLY_BRACE}}` expand to `{` / `}` + * `{{token:DOUBLE_OPEN_CURLY_BRACE}}` / `{{token:DOUBLE_CLOSE_CURLY_BRACE}}` expands to `{{` / `}}`. + * `{{PACKAGE}}` expands to the binary package name of the current binary package. This substitution + only works/applies when the substitution occurs in the context of a concrete binary package. + * Plus any of the variables produced by `dpkg-architecture`, such as `{{DEB_HOST_MULTIARCH}}`. + +The following variables from `/usr/share/dpkg/pkg-info.mk` (`dpkg`) are also available: + + * DEB_SOURCE (as `{{DEB_SOURCE}}`) + * DEB_VERSION (as `{{DEB_VERSION}}`) + * DEB_VERSION_EPOCH_UPSTREAM (as `{{DEB_VERSION_EPOCH_UPSTREAM}}`) + * DEB_VERSION_UPSTREAM_REVISION (as `{{DEB_VERSION_UPSTREAM_REVISION}}`) + * DEB_VERSION_UPSTREAM (as `{{DEB_VERSION_UPSTREAM}}`) + * SOURCE_DATE_EPOCH (as `{{SOURCE_DATE_EPOCH}}`) + +These have the same definition as those from the `dpkg` provided makefile. + + +# Restrictions on defining ownership of paths + +In some parts of the manifest, you can specify which user or group should have ownership of +a given path. As an example, you can define a directory to be owned by `root` with group `tty` +(think `chown root:tty <some/path>`). + +Ownership is generally defined via the keys `owner` and `group`. For each of them, you can use +one of the following formats: + + 1) A name (e.g., `owner: root`). + 2) An id (e.g., `owner: 0`). Please avoid using quotes around the ID in YAML as that can + cause `debputy` to read the number as a name. + 3) A name and an id with a colon inbetween (e.g., `owner: "root:0"`). The name must always + come first here. You may have to quote the value to prevent the YAML parser from being + confused. + +All three forms are valid and provide the same result. Unless you have a compelling reason to +pick a particular form, the name-only is recommended for simplicity. Notably, it does not +require your co-maintainer or future you to remember what the IDs mean. + +Regardless of which form you pick: + + 1) The provided owner must be defined by Debian `base-passwd` file, which are the only users guaranteed + to be present on every Debian system. + * Concretely, `debputy` verifies the input against `/usr/share/base-passwd/passwd.master` and + `/usr/share/base-passwd/group.master` (except for `root` / `0` as an optimization). + + 2) If the `name:id` form is used, then the name and the id values must match. I.e., `root:2` would + be invalid as the id for `root` is defined to be `0` in the `base-passwd` data files. + + 3) The `debputy` tool maintains a `deny`-list of owners that it refuses even though `base-passwd` + defines them. As a notable non-exhaustive example, `debputy` considers `nobody` or id `65534` + (the ID of `nobody` / `nogroup`) to be invalid owners. + + +# Conditional rules + +There are cases, where a given rule should only apply in certain cases - such as only when a given +build profile is active (`DEB_BUILD_PROFILES` / `dpkg-buildpackage -P`). For rules that +*support being conditional*, the condition is generally defined via the `when:` key and the condition +is then described beneath the `when:`. + +As an example: + + packages: + util-linux: + transformations: + - create-symlink + path: sbin/agetty + target: /sbin/getty + when: + # On Hurd, the package "hurd" ships "sbin/getty". + arch-matches: '!hurd-any' + + +When the condition under `when:` resolves to `true`, the rule will and must be used. When the +condition resolves to `false`, the rule will not be applied even if it could have been. However, +the rule may still be "partially" evaluated. As an example, for installation rules, the source +patterns will still be evaluated to reserve what it would have matched, so that following rules +behave deterministically regardless of how the condition evaluates. + +Note that conditions are *not* used as a conflict resolution and as such two conditional rules +can still cause conflicts even though their conditions are mutually exclusive. This may be +changed in a later version of `debputy` provided `debputy` can assert the two conditions +are mutually exclusive. + +The `when:` key has either a mapping, a list or a string as value depending on the condition. +Each supported condition is described in the following subsections. + +## Architecture match condition `arch-matches` (mapping) + +Sometimes, a rule needs to be conditional on the architecture. This can be done by using the +`arch-matches` rule. In 99.99% of the cases, `arch-matches` will be form you are looking for +and practically behaves like a comparison against `dpkg-architecture -qDEB_HOST_ARCH`. + +As an example: + + packages: + util-linux: + transformations: + - create-symlink + path: sbin/agetty + target: /sbin/getty + when: + # On Hurd, the package "hurd" ships "sbin/getty". + arch-matches: '!hurd-any' + +The `arch-matches` must be defined as a part of a mapping, where `arch-matches` is the key. The +value must be a string in the form of a space separated list architecture names or architecture +wildcards (same syntax as the architecture restriction in Build-Depends in debian/control except +there is no enclosing `[]` brackets). The names/wildcards can optionally be prefixed by `!` to +negate them. However, either *all* names / wildcards must have negation or *none* of them may +have it. + +For the cross-compiling specialists or curious people: The `arch-matches` rule behaves like a +`package-context-arch-matches` in the context of a binary package and like +`source-context-arch-matches` otherwise. The details of those are covered in their own section. + +## Explicit source or binary package context architecture match condition `source-context-arch-matches`, `package-context-arch-matches` (mapping) + +**These are special-case conditions**. Unless you know that you have a very special-case, +you should probably use `arch-matches` instead. These conditions are aimed at people with +corner-case special architecture needs. It also assumes the reader is familiar with the +`arch-matches` condition. + +To understand these rules, here is a quick primer on `debputy`'s concept of "source context" +vs "(binary) package context" architecture. For a native build, these two contexts are the +same except that in the package context an `Architecture: all` package always resolve to +`all` rather than `DEB_HOST_ARCH`. As a consequence, `debputy` forbids `arch-matches` and +`package-context-arch-matches` in the context of an `Architecture: all` package as a warning +to the packager that condition does not make sense. + +In the very rare case that you need an architecture condition for an `Architecture: all` package, +you can use `source-context-arch-matches`. However, this means your `Architecture: all` package +is not reproducible between different build hosts (which has known to be relevant for some +very special cases). + +Additionally, for the 0.0001% case you are building a cross-compiling compiler (that is, +`DEB_HOST_ARCH != DEB_TARGET_ARCH` and you are working with `gcc` or similar) `debputy` can be +instructed (opt-in) to use `DEB_TARGET_ARCH` rather than `DEB_HOST_ARCH` for certain packages when +evaluating an architecture condition in context of a binary package. This can be useful if the +compiler produces supporting libraries that need to be built for the `DEB_TARGET_ARCH` rather than +the `DEB_HOST_ARCH`. This is where `arch-matches` or `package-context-arch-matches` can differ +subtly from `source-context-arch-matches` in how they evaluate the condition. This opt-in currently +relies on setting `X-DH-Build-For-Type: target` for each of the relevant packages in +`debian/control`. However, unless you are a cross-compiling specialist, you will probably never +need to care about nor use any of this. + +Accordingly, the possible conditions are: + + * `arch-matches`: This is the form recommended to laymen and as the default use-case. This + conditional acts `package-context-arch-matches` if the condition is used in the context + of a binary package. Otherwise, it acts as `source-context-arch-matches`. + + * `source-context-arch-matches`: With this conditional, the provided architecture constraint is compared + against the build time provided host architecture (`dpkg-architecture -qDEB_HOST_ARCH`). This can + be useful when an `Architecture: all` package needs an architecture condition for some reason. + + * `package-context-arch-matches`: With this conditional, the provided architecture constraint is compared + against the package's resolved architecture. This condition can only be used in the context of a binary + package (usually, under `packages.<name>.`). If the package is an `Architecture: all` package, the + condition will fail with an error as the condition always have the same outcome. For all other + packages, the package's resolved architecture is the same as the build time provided host architecture + (`dpkg-architecture -qDEB_HOST_ARCH`). + + - However, as noted above there is a special case for when compiling a cross-compiling compiler, where + this behaves subtly different from `source-context-arch-matches`. + +All conditions are used the same way as `arch-matches`. Simply replace `arch-matches` with the other +condition. See the `arch-matches` description for an example. + +## Active build profile match condition `build-profiles-matches` (mapping) + +The `build-profiles-matches` condition is used to assert whether the active build profiles +(`DEB_BUILD_PROFILES` / `dpkg-buildpackage -P`) matches a given build profile restriction. + +As an example: + + # TODO: Not the best example (`create-symlink` is an unlikely use-case for this condition) + packages: + foo: + transformations: + - create-symlink + path: sbin/agetty + target: /sbin/getty + when: + build-profiles-matches: '<!pkg.foo.mycustomprofile>' + +The `build-profiles-matches` must be defined as a part of a mapping, where `build-profiles-matches` +is the key. The value is a string using the same syntax as the `Build-Profiles` field from `debian/control` +(i.e., a space separated list of `<[!]profile ...>` groups). + +## Can run produced binaries `can-execute-compiled-binaries` (string) + +The `can-execute-compiled-binaries` condition is used to assert the build can assume +that all compiled binaries can be run as-if they were native binaries. For native +builds, this condition always evaluates to `true`. For cross builds, the condition +is generally evaluates to `false`. However, there are special-cases where binaries +can be run during cross-building. Accordingly, this condition is subtly different +from the `cross-compiling` condition. + +Note this condition should *not* be used when you know the binary has been built +for the build architecture (`DEB_BUILD_ARCH`) or for determining whether build-time tests +should be run (for build-time tests, please use the `run-build-time-tests` condition instead). +Some upstream build systems are advanced enough to distinguish building a final product vs. +building a helper tool that needs to run during build. The latter will often be compiled by +a separate compiler (often using `$(CC_FOR_BUILD)`, `cc_for_build` or similar variable names +in upstream build systems for that compiler). + +As an example: + + # TODO: Not the best example (`create-symlink` is an unlikely use-case for this condition) + packages: + foo: + transformations: + - create-symlink + path: sbin/agetty + target: /sbin/getty + # Only for native builds or when we can transparently run a compiled + when: can-execute-compiled-binaries + +The `can-execute-compiled-binaries` condition is specified as a string. + +## Cross-Compiling condition `cross-compiling` (string) + +The `cross-compiling` condition is used to determine if the current build is performing a cross +build (i.e., `DEB_BUILD_GNU_TYPE` != `DEB_HOST_GNU_TYPE`). Often this has consequences for what +is possible to do. + +Note if you specifically want to know: + + * whether build-time tests should be run, then please use the `run-build-time-tests` condition. + * whether compiled binaries can be run as if it was a native binary, please use the + `can-execute-compiled-binaries` condition instead. That condition accounts for cross-building + in its evaluation. + +As an example: + + # TODO: Not the best example (`create-symlink` is an unlikely use-case for this condition) + packages: + foo: + transformations: + - create-symlink + path: sbin/agetty + target: /sbin/getty + when: cross-compiling + +The `cross-compiling` condition is specified as a string. + +## Whether build time tests should be run `run-build-time-tests` (string) + +The `run-build-time-tests` condition is used to determine whether (build time) tests should +be run for this build. This condition roughly translates into whether `nocheck` is present +in `DEB_BUILD_OPTIONS`. + +In general, the manifest *should not* prevent build time tests from being run during cross-builds. + +As an example: + + # TODO: Not the best example (`create-symlink` is an unlikely use-case for this condition) + packages: + foo: + transformations: + - create-symlink + path: sbin/agetty + target: /sbin/getty + when: run-build-time-tests + +The `run-build-time-tests` condition is specified as a string. + +## Negated condition `not` (mapping) + +It is possible to negate a condition via the `not` condition. + +As an example: + + packages: + util-linux: + transformations: + - create-symlink + path: sbin/getty + target: /sbin/agetty + when: + # On Hurd, the package "hurd" ships "sbin/getty". + # This example happens to also be an alternative to `arch-marches: '!hurd-any` + not: + arch-matches: 'hurd-any' + +The `not` condition is specified as a mapping, where the key is `not` and the +value is a nested condition. + +## All or any of a list of conditions `all-of`/`any-of` (list) + +It is possible to aggregate conditions using the `all-of` or `any-of` condition. This provide +`X and Y` and `X or Y` semantics (respectively). + +As an example: + + packages: + util-linux: + transformations: + - create-symlink + path: sbin/getty + target: /sbin/agetty + when: + # Only ship getty on linux except for s390(x) + all-of: + - arch-matches: 'linux-any' + - arch-matches: '!s390 !s390x' + +The `all-of` and `any-of` conditions are specified as lists, where each entry is a nested condition. +The lists need at least 2 entries as with fewer entries the `all-of` and `any-of` conditions are +redundant. + +# Packager provided definitions + +For more complex manifests or packages, it is possible define some common attributes for reuse. + +## Manifest Variables (`variables`) + +It is possible to provide custom manifest variables via the `variables` attribute. An example: + + manifest-version: '0.1' + definitions: + variables: + LIBPATH: "/usr/lib/{{DEB_HOST_MULTIARCH}}" + SONAME: "1" + installations: + - install: + source: build/libfoo.so.{{SONAME}}* + # The quotes here is for the YAML parser's sake. + dest-dir: "{{LIBPATH}}" + into: libfoo{{SONAME}} + +The value of the `variables` key must be a mapping, where each key is a new variable name and +the related value is the value of said key. The keys must be valid variable name and not shadow +existing variables (that is, variables such as `PACKAGE` and `DEB_HOST_MULTIARCH` *cannot* be +redefined). The value for each variable *can* refer to *existing* variables as seen in the +example above. + +As usual, `debputy` will insist that all declared variables must be used. + +Limitations: + * When declaring variables that depends on another variable declared in the manifest, the + order is important. The variables are resolved from top to bottom. + * When a manifest variable depends on another manifest variable, the existing variable is + currently always resolved in source context. As a consequence, some variables such as + `{{PACKAGE}}` cannot be used when defining a variable. This restriction may be + lifted in the future. + +# Installations + +For source packages building a single binary, the `dh_auto_install` from debhelper will default to +providing everything from upstream's install in the binary package. The `debputy` tool matches this +behaviour and accordingly, the `installations` feature is only relevant in this case when you need to +manually specify something upstream's install did not cover. + +For sources, that build multiple binaries, where `dh_auto_install` does not detect anything to install, +or when `dh_auto_install --destdir debian/tmp` is used, the `installations` section of the manifest is +used to declare what goes into which binary package. An example: + + installations: + - install: + sources: "usr/bin/foo" + into: foo + - install: + sources: "usr/*" + into: foo-extra + + +All installation rules are processed in order (top to bottom). Once a path has been matched, it can +no longer be matched by future rules. In the above example, then `usr/bin/foo` would be in the `foo` +package while everything in `usr` *except* `usr/bin/foo` would be in `foo-extra`. If these had been +ordered in reverse, the `usr/bin/foo` rule would not have matched anything and caused `debputy` +to reject the input as an error on that basis. This behaviour is similar to "DEP-5" copyright files, +except the order is reversed ("DEP-5" uses "last match wins", where here we are doing "first match wins") + +In the rare case that some path need to be installed into two packages at the same time, then this is +generally done by changing `into` into a list of packages. + +All installations are currently run in *source* package context. This implies that: + + 1) No package specific substitutions are available. Notably `{{PACKAGE}}` cannot be resolved. + 2) All conditions are evaluated in source context. For 99.9% of users, this makes no difference, + but there is a cross-build feature that changes the "per package" architecture which is affected. + +This is a limitation that should be fixed in `debputy`. + +**Attention debhelper users**: Note the difference between `dh_install` (etc.) vs. `debputy` on +overlapping matches for installation. + +## Install rule search directories + +Most install rules apply their patterns against search directories such as `debian/tmp` by default. + +The default search directory order (highest priority first) is: + + 1) The upstream install directory (usually, `debian/tmp`) + 2) The source package root directory (`.`) + +Each search directory is tried in order. When a pattern matches an entry in a search directory (even +if that entry is reserved by another package), further search directories will *not* be tried. As an example, +consider the pattern `usr/bin/foo*` and the files: + + `SOURCE_ROOT/debian/tmp/usr/bin/foo.sh` + `SOURCE_ROOT/usr/bin/foo.pl` + +Here the pattern will only match `SOURCE_ROOT/debian/tmp/usr/bin/foo.sh` and not `SOURCE_ROOT/usr/bin/foo.pl`. + +## Automatic discard rules + +The `debputy` framework provides some built-in discard rules that are applied by default during installation +time. These are always active and implicit, but can be overwritten by exact path matches for install rules. + +The `debputy` tool itself provides the following discard rules: + + * Discard of `.la` files. Their use is rare but not unheard of. You may need to overwrite this. + * Discard of python byte code (such as `__pycache__` directories). + * Discard of editor backup files (such as `*~`, `*.bak`, etc.). + * Discard of Version control files (such as `.gitignore`, etc.). + * Discard of GNU info's `dir` (`usr/share/info/dir`) as it causes file conflicts with other packages. + * Discard of `DEBIAN` directory. + +Note: Third-party plugins may provide additional automatic discard rules. Please use +`debputy plugin list automatic-discard-rules` to see all known automatic discard rules. + +If you find yourself needing a particular path installed that has been discarded by default, you can overrule +the default discard by spelling out the path. As an example, if you needed to install a `libfoo.la` file, +you could do: + + installations: + - install: + sources: + # By-pass automatic discard of `libfoo.la` - no globs *cannot* be used! + - "usr/lib/libfoo.la" + - "usr/lib/libfoo*.so*" + into: libfoo1 + +## Generic install (`install`) + +The generic `install` rule can be used to install arbitrary paths into packages and is *similar* to how +`dh_install` from debhelper works. It is a two "primary" uses. + + 1) The classic "install into directory" similar to the standard `dh_install` + 2) The "install as" similar to `dh-exec`'s `foo => bar` feature. + +Examples: + + installations: + - install: + source: "usr/bin/tool-a" + dest-dir: "usr/bin" + into: foo + - install: + source: "usr/bin/tool-b" + # Implicit dest-dir: "usr/bin + into: foo-extra + - install: + source: "usr/bin/tool-c.sh" + # Rename as a part of installing. + as: "usr/bin/tool-c" + into: foo-util + + +The value for `install` is a string, a list of strings or mapping. When it is a mapping, the +mapping has the following key/value pairs: + + * `source` or `sources` (required): A path match (`source`) or a list of path matches (`sources`) defining + the source path(s) to be installed. The path match(es) can use globs. Each match is tried against default + search directories. + - When a symlink is matched, then the symlink (not its target) is installed as-is. When a directory is + matched, then the directory is installed along with all the contents that have not already been installed + somewhere. + + * `into` (conditional): Either a package name or a list of package names for which these paths should be + installed. This key is conditional on whether there are multiple binary packages listed in + `debian/control`. When there is only one binary package, then that binary is the default for `into`. + Otherwise, the key is required. + + * `dest-dir` (optional): A path defining the destination *directory*. The value *cannot* use globs, but can + use substitution. If neither `as` nor `dest-dir` is given, then `dest-dir` defaults to the directory name + of the `source`. + + * `as` (optional): A path defining the path to install the source as. This is a full path. This option is + mutually exclusive with `dest-dir` and `sources` (but not `source`). When `as` is given, then `source` must + match exactly one "not yet matched" path. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules). + +When the input is a string or a list of string, then that value is used as shorthand for `source` +or `sources` (respectively). This form can only be used when `into` is not required. + + +## Install documentation (`install-docs`) + +This install rule resemble that of `dh_installdocs`. It is a shorthand over the generic +`install` rule with the following key features: + + 1) The default `dest-dir` is to use the package's documentation directory (usually something + like `/usr/share/doc/{{PACKAGE}}`, though it respects the "main documentation package" + recommendation from Debian Policy). The `dest-dir` or `as` can be set in case the + documentation in question goes into another directory or with a concrete path. In this + case, it is still "better" than `install` due to the remaining benefits. + 2) The rule comes with pre-defined conditional logic for skipping the rule under + `DEB_BUILD_OPTIONS=nodoc`, so you do not have to write that conditional yourself. + 3) The `into` parameter can be omitted as long as there is a exactly one non-`udeb` package + listed in `debian/control`. + +With these two things in mind, it behaves just like the `install` rule. + +Note: It is often worth considering to use a more specialized version of the `install-docs` +rule when one such is available. If you are looking to install an example or a manpage, +consider whether `install-examples` or `install-man` might be a better fit for your +use-case. + +Examples: + + installations: + - install-docs: + sources: + - "docs/README.some-topic.md" + - "docs/README.another-topic.md" + into: foo + + +The value for `install-docs` is a string, a list of strings or mapping. When it is +a mapping, the mapping has the following key/value pairs: + + * `source` or `sources` (required): A path match (`source`) or a list of path matches (`sources`) defining + the source path(s) to be installed. The path match(es) can use globs. Each match is tried against default + search directories. + - When a symlink is matched, then the symlink (not its target) is installed as-is. When a directory is + matched, then the directory is installed along with all the contents that have not already been installed + somewhere. + + - **CAVEAT**: Specifying `source: docs` where `docs` resolves to a directory for `install-docs` + will give you an `docs/docs` directory in the package, which is rarely what you want. Often, you + can solve this by using `docs/*` instead. + + * `dest-dir` (optional): A path defining the destination *directory*. The value *cannot* use globs, but can + use substitution. If neither `as` nor `dest-dir` is given, then `dest-dir` defaults to the relevant package + documentation directory (a la `/usr/share/doc/{{PACKAGE}}`). + + * `into` (conditional): Either a package name or a list of package names for which these paths should be + installed as docs. This key is conditional on whether there are multiple (non-`udeb`) binary + packages listed in `debian/control`. When there is only one (non-`udeb`) binary package, then that binary + is the default for `into`. Otherwise, the key is required. + + * `as` (optional): A path defining the path to install the source as. This is a full path. This option is + mutually exclusive with `dest-dir` and `sources` (but not `source`). When `as` is given, then `source` must + match exactly one "not yet matched" path. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules). This condition will + be combined with the built-in condition provided by these rules (rather than replacing it). + +When the input is a string or a list of string, then that value is used as shorthand for `source` +or `sources` (respectively). This form can only be used when `into` is not required. + +Note: While the canonical name for this rule use plural, the `install-doc` variant is accepted as +alternative name. + +## Install examples (`install-examples`) + +This install rule resemble that of `dh_installexamples`. It is a shorthand over the generic ` +install` rule with the following key features: + + 1) It pre-defines the `dest-dir` that respects the "main documentation package" recommendation from + Debian Policy. The `install-examples` will use the `examples` subdir for the package documentation + dir. + 2) The rule comes with pre-defined conditional logic for skipping the rule under + `DEB_BUILD_OPTIONS=nodoc`, so you do not have to write that conditional yourself. + 3) The `into` parameter can be omitted as long as there is a exactly one non-`udeb` package + listed in `debian/control`. + +With these two things in mind, it behaves just like the `install` rule. + +Examples: + + installations: + - install-examples: + source: "examples/*" + into: foo + + +The value for `install-examples` is a string, a list of strings or mapping. When it is +a mapping, the mapping has the following key/value pairs: + +* `source` or `sources` (required): A path match (`source`) or a list of path matches (`sources`) defining + the source path(s) to be installed. The path match(es) can use globs. Each match is tried against default + search directories. + - When a symlink is matched, then the symlink (not its target) is installed as-is. When a directory is + matched, then the directory is installed along with all the contents that have not already been installed + somewhere. + + - **CAVEAT**: Specifying `source: examples` where `examples` resolves to a directory for `install-examples` + will give you an `examples/examples` directory in the package, which is rarely what you want. Often, you + can solve this by using `examples/*` instead. + +* `into` (conditional): Either a package name or a list of package names for which these paths should be + installed as examples. This key is conditional on whether there are multiple (non-`udeb`) binary + packages listed in `debian/control`. When there is only one (non-`udeb`) binary package, then that binary + is the default for `into`. Otherwise, the key is required. + + +* `when` (optional): A condition as defined in [Conditional rules](#conditional-rules). This condition will + be combined with the built-in condition provided by these rules (rather than replacing it). + +When the input is a string or a list of string, then that value is used as shorthand for `source` +or `sources` (respectively). This form can only be used when `into` is not required. + +Note: While the canonical name for this rule use plural, the `install-example` variant is accepted as +alternative name. + +## Install manpages (`install-man`) + +Install rule for installing manpages similar to `dh_installman`. It is a shorthand over the generic +`install` rule with the following key features: + + 1) The rule can only match files (notably, symlinks cannot be matched by this rule). + 2) The `dest-dir` is computed per source file based on the manpage's section and language. + 3) The `into` parameter can be omitted as long as there is a exactly one non-`udeb` package + listed in `debian/control`. + 4) The rule comes with manpage specific attributes such as `language` and `section` for when the + auto-detection is insufficient. + 5) The rule comes with pre-defined conditional logic for skipping the rule under `DEB_BUILD_OPTIONS=nodoc`, + so you do not have to write that conditional yourself. + +With these things in mind, the rule behaves similar to the `install` rule. + +Examples: + + installations: + - install-man: + source: "man/foo.1" + into: foo + - install-man: + source: "man/foo.de.1" + language: derive-from-basename + into: foo + + +The value for `install-man` is a string, a list of strings or mapping. When it is a mapping, the mapping +has the following key/value pairs: + + * `source` or `sources` (required): A path match (`source`) or a list of path matches (`sources`) defining + the source path(s) to be installed. The path match(es) can use globs. Each match is tried against default + search directories. Only files can be matched. + + * `into` (conditional): Either a package name or a list of package names for which these paths should be + installed as manpages. This key is conditional on whether there are multiple (non-`udeb`) binary + packages listed in `debian/control`. When there is only one (non-`udeb`) binary package, then that binary + is the default for `into`. Otherwise, the key is required. + + * `section` (optional): If provided, it must be an integer between 1 and 9 (both inclusive), defining the + section the manpages belong overriding any auto-detection that `debputy` would have performed. + + * `language` (optional): If provided, it must be either a 2 letter language code (such as `de`), a 5 letter + language + dialect code (such as `pt_BR`), or one of the special keywords `C`, `derive-from-path`, or + `derive-from-basename`. The default is `derive-from-path`. + - When `language` is `C`, then the manpages are assumed to be "untranslated". + - When `language` is a language code (with or without dialect), then all manpages matched will be assumed + to be translated to that concrete language / dialect. + - When `language` is `derive-from-path`, then `debputy` attempts to derive the language from the path + (`man/<language>/man<section>`). This matches the default of `dh_installman`. When no language can + be found for a given source, `debputy` behaves like language was `C`. + - When `language` is `derive-from-basename`, then `debputy` attempts to derive the language from the + basename (`foo.<language>.1`) similar to `dh_installman` previous default. When no language can + be found for a given source, `debputy` behaves like language was `C`. Note this is prone to + false positives where `.pl`, `.so` or similar two-letter extensions gets mistaken for a language + code (`.pl` can both be "Polish" or "Perl Script", `.so` can both be "Somali" and "Shared Object" + documentation). In this configuration, such extensions are always assumed to be a language. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules). This condition will + be combined with the built-in condition provided by these rules (rather than replacing it). + + +When the input is a string or a list of string, then that value is used as shorthand for `source` +or `sources` (respectively). This form can only be used when `into` is not required. + +Comparison with debhelper: The `dh_installman` uses `derive-from-path` as default and then falls back +to `derive-from-basename`. The `debputy` tool does *not* feature the same fallback logic. If you want +the `derive-from-basename` with all of its false-positives, you have to explicitly request it. + + +## Discard (or exclude) upstream provided paths (`discard`) + +When installing paths from `debian/tmp` into packages, it might be useful to ignore some paths that you never +need installed. This can be done with the `discard` rule. + + +The value for `discard` is a string, a list of strings or mapping. When it is a mapping, the mapping +has the following key/value pairs: + + * `path` or `paths` (required): A path match (`path`) or a list of path matches (`paths`) defining the + source path(s) that should not be installed anywhere. The path match(es) can use globs. + - When a symlink is matched, then the symlink (not its target) is discarded as-is. When a directory is + matched, then the directory is discarded along with all the contents that have not already been installed + somewhere. + + * `search-dir` or `search-dirs` (optional): A path (`search-dir`) or a list to paths (`search-dirs`) that + defines which search directories apply to. This attribute is primarily useful for source packages that + uses [per package search dirs](#custom-installation-time-search-directories-installation-search-dirs), + and you want to restrict a discard rule to a subset of the relevant search dirs. Note all listed + search directories must be either an explicit search requested by the packager or a search directory + that `debputy` provided automatically (such as `debian/tmp`). Listing other paths will make `debputy` + report an error. + + - Note that the `path` or `paths` must match at least one entry in any of the search directories unless + *none* of the search directories exist (or the condition in `required-when` evaluates to false). When + none of the search directories exist, the discard rule is silently skipped. This special-case enables + you to have discard rules only applicable to certain builds that are only performed conditionally. + + * `required-when` (optional): A condition as defined in [Conditional rules](#conditional-rules). The discard + rule is always applied. When the conditional is present and evaluates to false, the discard rule can + silently match nothing. When the condition is absent, *or* it evaluates to true, then each pattern + provided must match at least one path. + +When the input is a string or a list of string, then that value is used as shorthand for `path` +or `paths` (respectively). + +Once a path is discarded, it cannot be matched by any other install rules. A path that is discarded, is +considered handled when `debputy` checks for paths you might have forgotten to install. The `discard` +feature is therefore *also* replaces the `debian/not-installed` file used by `debhelper` and `cdbs`. + +Note: A discard rule applies to *all* relevant search directories at the same time (including the source +root directory) unlike other install rules that only applies to the first search directory with a *match*. +This is to match the intuition that if you discard something, you want it gone no matter which search +directory it happened to be in. + +## Multi destination install (`multi-dest-install`) + +Special use install rule for installing the same source multiple times into the same package. + +It works similar to the `install` rule except: + + 1) `dest-dir` is renamed to `dest-dirs` and is conditionally mandatory (either `as` or `dest-dirs` + must be provided). + 2) Both `as` and `dest-dirs` are now list of paths must have at least two paths when provided. + +Please see `debputy plugin show plugable-manifest-rules multi-dest-install` for the full documentation. + +# Binary package rules + +Inside the manifest, the `packages` mapping can be used to define requests for the binary packages +you want `debputy` to produce. Each key inside `packages` must be the name of a binary package +defined in `debian/control`. The value is a dictionary defining which features that `debputy` +should apply to that binary package. An example could be: + + + packages: + foo: + transformations: + - create-symlink: + path: usr/share/foo/my-first-symlink + target: /usr/share/bar/symlink-target + - create-symlink: + path: usr/lib/{{DEB_HOST_MULTIARCH}}/my-second-symlink + target: /usr/lib/{{DEB_HOST_MULTIARCH}}/baz/symlink-target + bar: + transformations: + - create-directories: + - some/empty/directory.d + - another/empty/integration-point.d + - create-directories: + path: a/third-empty/directory.d + owner: www-data + group: www-data + +In this case, `debputy` will create some symlinks inside the `foo` package and some directories for +the `bar` package. The following subsections define the keys you can use under each binary package. + + +## Transformations (`packages.{{PACKAGE}}.transformations`) + +You can define a `transformations` under the package definition, which is a list a transformation +rules. An example: + + packages: + foo: + transformations: + - remove: 'usr/share/doc/{{PACKAGE}}/INSTALL.md' + - move: + source: bar/* + target: foo/ + + +Transformations are ordered and are applied in the listed order. A path can be matched by multiple +transformations; how that plays out depends on which transformations are applied and in which order. +A quick summary: + + - Transformations that modify the file system layout affect how path matches in later transformations. + As an example, `move` and `remove` transformations affects what globs and path matches expand to in + later transformation rules. + + - For other transformations generally the latter transformation overrules the earlier one, when they + overlap or conflict. + +### Remove transformation rule (`remove`) + +The remove transformation rule is mostly only useful for single binary source packages, where +everything from upstream's build system is installed automatically into the package. In those case, +you might find yourself with some files that are _not_ relevant for the Debian package (but would be +relevant for other distros or for non-distro local builds). Common examples include `INSTALL` files +or `LICENSE` files (when they are just a subset of `debian/copyright`). + +In the manifest, you can ask `debputy` to remove paths from the debian package by using the `remove` +transformation rule. An example being: + + packages: + foo: + transformations: + - remove: 'usr/share/doc/{{PACKAGE}}/INSTALL.md' + + +The value for `remove` is a string, a list of strings or mapping. When it is a mapping, the mapping +has the following key/value pairs: + + * `path` or `paths` (required): A path match (`path`) or a list of path matches (`paths`) defining the + path(s) inside the package that should be removed. The path match(es) can use globs. + - When a symlink is matched, then the symlink (not its target) is removed as-is. When a directory is + matched, then the directory is removed along with all the contents. + + * `keep-empty-parent-dirs` (optional): A boolean determining whether to prune parent directories that become + empty as a consequence of this rule. When provided and `true`, this rule will leave empty directories + behind. Otherwise, if this rule causes a directory to become empty that directory will be removed. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules). This condition will + be combined with the built-in condition provided by these rules (rather than replacing it). + +When the input is a string or a list of string, then that value is used as shorthand for `path` +or `paths` (respectively). + +Note that `remove` removes paths from future glob matches and transformation rules. + +This rule behaves roughly like the following shell snippet when applied: + +```shell +set -e +for p in ${paths}; do + rm -fr "${p}" + if [ "${keep_empty_parent_dirs}" != "true" ]; then + rmdir --ignore-fail-on-non-empty --parents "$(dirname "${p}")" + fi +done +``` + +### Move transformation rule (`move`) + +The move transformation rule is mostly only useful for single binary source packages, where +everything from upstream's build system is installed automatically into the package. In those case, +you might find yourself with some files that need to be renamed to match Debian specific requirements. + +This can be done with the `move` transformation rule, which is a rough emulation of the `mv` command +line tool. An example being: + + packages: + foo: + transformations: + - move: + source: bar/* + target: foo/ + # Note this example leaves bar/ as an empty directory. Real world usage would probably + # follow this with an remove rule for bar/ to avoid an empty directory + +The value for `move` is a mapping with the following key/value pairs: + + * `source` (required): A path match defining the source path(s) to be renamed. The value can use globs and + substitutions. + + * `target` (required): A path defining the target path. The value *cannot* use globs, but can use substitution. + If the target ends with a literal `/` (prior to substitution), the target will *always* be a directory. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules) + +There are two basic cases: + 1. If `source` match exactly one path and `target` is not a directory (including it does not exist), + then `target` is removed (if it existed) and `source` is renamed to `target`. + - If the `target` is a directory, and you intentionally want to replace it with a non-directory, please + add an explicit `remove` transformation for the directory prior to this transformation. + + 2. If `source` match more than one path *or* `target` is a directory (or is specified with a trailing slash), + then all matching sources a moved into `target` retaining their basename. If `target` already contains + overlapping basenames with `source`, then the transformation rule with abort with an error if the overlap + contains a directory. Otherwise, any overlapping paths in `target` will be implicitly removed first. + + - If the replacement of a directory is intentional, please add an explicit `remove` rule for it first. + - In the case that the `source` glob is exotic enough to match two distinct paths with the same basename, + then `debputy` will reject the transformation on account of it being ambiguous. + +In either case, parent directories of `target` are created as necessary as long as they do not trigger a conflict +(or require traversing symlinks or non-directories). Additionally, the paths matched by `source` will no longer +match anything (since they are now renamed/relocated), which may affect what path future path matches will apply +to. + +Note that like the `mv`-command, while the `source` paths are no longer present, their parent directories +will remain. + +### Create symlinks transformation rule (`create-symlink`) + +Often, the upstream build system will provide the symlinks for you. However, in some cases, it is useful for +the packager to define distribution specific symlinks. This can be done via the `create-symlink` transformation +rule. An example of how to do this is: + + packages: + foo: + transformations: + - create-symlink: + path: usr/share/foo/my-first-symlink + target: /usr/share/bar/symlink-target + - create-symlink: + path: usr/lib/{{DEB_HOST_MULTIARCH}}/my-second-symlink + target: /usr/lib/{{DEB_HOST_MULTIARCH}}/baz/symlink-target + +The value for the `create-symlink` key is a mapping, which contains the following keys: + + * `path` (required): The path that should be a symlink. The path may contain substitution variables + such as `{{DEB_HOST_MULTIARCH}}` but _cannot_ use globs. Parent directories are implicitly created + as necessary. + * Note that if `path` already exists, the behaviour of this transformation depends on the value of + `replacement-rule`. + + * `target` (required): Where the symlink should point to. The target may contain substitution variables + such as `{{DEB_HOST_MULTIARCH}}` but _cannot_ use globs. The link target is _not_ required to exist inside + the package. + * The `debputy` tool will normalize the target according to the rules of the Debian Policy. Use absolute + or relative target at your own preference. + + * `replacement-rule` (optional): This attribute defines how to handle if `path` already exists. It can be + set to one of the following values: + - `error-if-exists`: When `path` already exists, `debputy` will stop with an error. This is similar to + `ln -s` semantics. + - `error-if-directory`: When `path` already exists, **and** it is a directory, `debputy` will stop with an + error. Otherwise, remove the `path` first and then create the symlink. This is similar to `ln -sf` + semantics. + - `abort-on-non-empty-directory` (default): When `path` already exists, then it will be removed provided + it is a non-directory **or** an *empty* directory and the symlink will then be created. If the path is + a *non-empty* directory, `debputy` will stop with an error. + - `discard-existing`: When `path` already exists, it will be removed. If the `path` is a directory, all + its contents will be removed recursively along with the directory. Finally, the symlink is created. + This is similar to having an explicit `remove` rule just prior to the `create-symlink` that is conditional + on `path` existing (plus the condition defined in `when` if any). + + Keep in mind, that `replacement-rule` only applies if `path` exists. If the symlink cannot be created, + because a part of `path` exist and is *not* a directory, then `create-symlink` will fail regardless of the + value in `replacement-rule`. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules) + +This rule behaves roughly like the following shell snippet when applied: + +```shell +set -e +case "${replacement_rule}" in + error-if-directory) + F_FLAG="-f" + ;; + abort-on-non-empty-directory) + if [ -d "${path}" ]; then + rmdir "${path}" + fi + F_FLAG="-f" + ;; + discard-existing) + rm -fr "${path}" + ;; +esac +install -o "root" -g "root" -m "755" -d "$(dirname "${path}")" +ln -s ${F_FLAG} "${target}" "${path}" +``` + +### Create directories transformation rule (`create-directories`) + +NOTE: This transformation is only really needed if you need to create an empty directory somewhere +in your package as an integration point. All `debputy` transformations will create directories +as required. + +In most cases, upstream build systems and `debputy` will create all the relevant directories. However, in some +rare cases you may want to explicitly define a path to be a directory. Maybe to silence a linter that is +warning you about a directory being empty, or maybe you need an empty directory that nothing else is creating +for you. This can be done via the `create-directories` transformation rule. An example being: + + packages: + bar: + create-directories: + - some/empty/directory.d + - another/empty/integration-point.d + - path: a/third-empty/directory.d + owner: www-data + group: www-data + +The value for the `create-directories` key is either a string, a list of string or a mapping. When it is a +mapping, the mapping has the following key/value pairs: + + * `path` or `paths` (required): A path (`path`) or a list of path (`paths`) defining the + path(s) inside the package that should be created as directories. The path(es) _cannot_ use globs + but can use substitution variables. Parent directories are implicitly created (with owner `root:root` + and mode `0755` - only explicitly listed directories are affected by the owner/mode options) + + * `owner` (optional, default `root`): Denotes the owner of the directory (but _not_ what is inside the directory). + + * `group` (optional, default `root`): Denotes the group of the directory (but _not_ what is inside the directory). + + * `mode` (optional, default `"0755"`): Denotes the mode of the directory (but _not_ what is inside the directory). + Note that numeric mode must always be given as a string (i.e., with quotes). Symbolic mode can be used as well. + If symbolic mode uses a relative definition (e.g., `o-rx`), then it is relative to the directory's current mode + (if it already exists) or `0755` if the directory is created by this transformation. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules) + +When the input is a string or a list of string, then that value is used as shorthand for `path` +or `paths` (respectively). + +Unless you have a specific need for the mapping form, you are recommended to use the shorthand form of +just listing the directories you want created. + + +Note that implicitly created directories (by this or other transformations) always have owner `root:root` +and mode `"0755"`. If you need a directory tree with special ownership/mode, you will have to list all the +directories in that tree explicitly with the relevant attributes OR use `path-metadata` transformation rule +to change their metadata after creation. + + +This rule behaves roughly like the following shell snippet when applied: + +```shell +set -e +for p in ${paths}; do + install -o "root" -g "root" -m "755" -d "${p}" + chown "${owner}:${group}" "${p}" + chmod "${mode}" "${p}" +done +``` + +### Change path owner/group or mode (`path-metadata`) + +The `debputy` normalizes the path metadata (such as ownership and mode) similar to `dh_fixperms`. +For most packages, the default is what you want. However, in some cases, the package has a special +case or two that `debputy` does not cover. In that case, you can tell `debputy` to use the metadata +you want by using the `path-metadata` transformation. An example being: + + packages: + foo: + transformations: + - path-metadata: + path: usr/bin/sudo + mode: "0755" + - path-metadata: + path: usr/bin/write: + group: tty + - path-metadata: + path: /usr/sbin/spine + capabilities: cap_net_raw+ep + + +The value for the `path-metadata` key is a mapping. The mapping has the following key/value pairs: +Each key defines a path or a glob that may + + +* `path` or `paths` (required): A path match (`path`) or a list of path matches (`paths`) defining the + path(s) inside the package that should be affected. The path match(es) can use globs and substitution + variables. Special-rules for matches: + - Symlinks are never followed and will never be matched by this rule. + - Directory handling depends on the `recursive` attribute. + + * `owner` (conditional): Denotes the owner of the paths matched by `path` or `paths`. When omitted, no change of + owner is done. + + * `group` (conditional): Denotes the group of the paths matched by `path` or `paths`. When omitted, no change of + group is done. + + * `mode` (conditional): Denotes the mode of the paths matched by `path` or `paths`. When omitted, no change in + mode is done. Note that numeric mode must always be given as a string (i.e., with quotes). Symbolic mode can + be used as well. If symbolic mode uses a relative definition (e.g., `o-rx`), then it is relative to the + directory's current mode. + + * `capabilities` (conditional): Denotes a Linux capability that should be applied to the path. When provided, + `debputy` will cause the capability to be applied to all *files* denoted by the `path`/`paths` attribute + on install (via `postinst configure`) provided that `setcap` is installed on the system when the + `postinst configure` is run. + - If any non-file paths are matched, the `capabilities` will *not* be applied to those paths. + + * `capability-mode` (optional): Denotes the mode to apply to the path *if* the Linux capability denoted in + `capabilities` was successfully applied. If omitted, it defaults to `a-s` as generally capabilities are + used to avoid "setuid"/"setgid" binaries. The `capability-mode` is relative to the *final* path mode + (the mode of the path in the produced `.deb`). The `capability-mode` attribute cannot be used if + `capabilities` is omitted. + + * `recursive` (optional, default `false`): When a directory is matched, then the metadata changes are applied + to the directory itself. When `recursive` is `true`, then the transformation is *also* applied to all paths + beneath the directory. + + * `when` (optional): A condition as defined in [Conditional rules](#conditional-rules) + +At least one of `owner`, `group`, `mode`, or `capabilities` must be provided. + +This rule behaves roughly like the following shell snippet when applied: + +```shell +MAYBE_R_FLAG=$(${recursive} && printf "%s" "-R") + +for p in ${path}; do + if [ -n "${owner}" ] || [ -n "${group}" ]; then + chown $MAYBE_R_FLAG "${owner}:${group}" "${p}" + fi + if [ -n "${mode}" ]; then + chmod $MAYBE_R_FLAG "${mode}" "${p}" + fi +done +``` + +(except all symlinks will be ignored) + +## Custom binary version (`binary-version`) + +In the *rare* case that you need a binary package to have a custom version, you can use the `binary-version:` +key to describe the desired package version. An example being: + + packages: + foo: + # The foo package needs a different epoch because we took it over from a different + # source package with higher epoch version + binary-version: '1:{{DEB_VERSION_UPSTREAM_REVISION}}' + +Use this feature sparingly as it is generally not possible to undo as each version must be monotonously +higher than the previous one. This feature translates into `-v` option for `dpkg-gencontrol`. + +The value for the `binary-version` key is a string that defines the binary version. Generally, you will +want it to contain one of the versioned related substitution variables such as +`{{DEB_VERSION_UPSTREAM_REVISION}}`. Otherwise, you will have to remember to bump the version manually +with each upload as versions cannot be reused and the package would not support binNMUs either. + + +## Remove runtime created paths on purge or post removal (`clean-after-removal`) + +For some packages, it is necessary to clean up some run-time created paths. Typical use cases are +deleting log files, cache files, or persistent state. This can be done via the `clean-after-removal`. +An example being: + + packages: + foo: + clean-after-removal: + - /var/log/foo/*.log + - /var/log/foo/*.log.gz + - path: /var/log/foo/ + ignore-non-empty-dir: true + - /etc/non-conffile-configuration.conf + - path: /var/cache/foo + recursive: true + + +The `clean-after-removal` key accepts a list, where each element is either a mapping, a string or a list +of strings. When an element is a mapping, then the following key/value pairs are applicable: + + * `path` or `paths` (required): A path match (`path`) or a list of path matches (`paths`) defining the + path(s) that should be removed after clean. The path match(es) can use globs and manifest variables. + Every path matched will by default be removed via `rm -f` or `rmdir` depending on whether the path + provided ends with a *literal* `/`. Special-rules for matches: + - Glob is interpreted by the shell, so shell (`/bin/sh`) rules apply to globs rather than + `debputy`'s glob rules. As an example, `foo/*` will **not** match `foo/.hidden-file`. + - `debputy` cannot evaluate whether these paths/globs will match the desired paths (or anything at + all). Be sure to test the resulting package. + - When a symlink is matched, it is not followed. + - Directory handling depends on the `recursive` attribute and whether the pattern ends with a literal + "/". + - `debputy` has restrictions on the globs being used to prevent rules that could cause massive damage + to the system. + + * `recursive` (optional): When `true`, the removal rule will use `rm -fr` rather than `rm -f` or `rmdir` + meaning any directory matched will be deleted along with all of its contents. + + * `ignore-non-empty-dir` (optional): When `true`, each path must be or match a directory (and as a + consequence each path must with a literal `/`). The affected directories will be deleted only if they + are empty. Non-empty directories will be skipped. This option is mutually exclusive with `recursive`. + + * `delete-on` (optional, defaults to `purge`): This attribute defines when the removal happens. It can + be set to one of the following values: + - `purge`: The removal happens with the package is being purged. This is the default. At a technical + level, the removal occurs at `postrm purge`. + - `removal`: The removal happens immediately after the package has been removed. At a technical level, + the removal occurs at `postrm remove`. + + +This feature resembles the concept of `rpm`'s `%ghost` files. + +## Custom installation time search directories (`installation-search-dirs`) + +For source packages that does multiple build, it can be an advantage to provide a custom list of +installation-time search directories. This can be done via the `installation-search-dirs` key. A common +example is building the source twice with different optimization and feature settings where the second +build is for the `debian-installer` (in the form of a `udeb` package). A sample manifest snippet could +look something like: + + installations: + - install: + # Because of the search order (see below), `foo` installs `debian/tmp/usr/bin/tool`, + # while `foo-udeb` installs `debian/tmp-udeb/usr/bin/tool` (assuming both paths are + # available). Note the rule can be split into two with the same effect if that aids + # readability or understanding. + source: usr/bin/tool + into: + - foo + - foo-udeb + packages: + foo-udeb: + installation-search-dirs: + - debian/tmp-udeb + + +The `installation-search-dirs` key accepts a list, where each element is a path (str) relative from the +source root to the directory that should be used as a search directory (absolute paths are still interpreted +as relative to the source root). This list should contain all search directories that should be applicable +for this package (except the source root itself, which is always appended after the provided list). If the +key is omitted, then `debputy` will provide a default search order (In the `dh` integration, the default +is the directory `debian/tmp`). + +If a non-existing or non-directory path is listed, then it will be skipped (info-level note). If the path +exists and is a directory, it will also be checked for "not-installed" paths. + + +[reference documentation]: https://documentation.divio.com/reference/ |