NAME

Dist::Conformance::Check::SystemPackages - The system packages that a repository declares in metamerge.yml, validated, and resolved for a host

VERSION

version 0.003

SYNOPSIS

use 5.040;
use Dist::Conformance::Check::SystemPackages;

my $declared = Dist::Conformance::Check::SystemPackages->declared('/bogus/checkout');
my $host     = Dist::Conformance::Check::SystemPackages->resolve(
    $declared,
    packager => 'deb',
    distro   => 'ubuntu',
    release  => '24.04',
    codename => 'noble',
);
die "No package for @{ $host->{unresolved} }\n" if @{ $host->{unresolved} };
say for @{ $host->{packages} };

DESCRIPTION

CPAN has no field for a package of the operating system, such as ffmpeg or libmagic-dev, that a distribution needs. So a repository declares them in metamerge.yml, the file that [MetaMergeFile] of Dist::Zilla merges into META.json. x_system_packages names each dependency, and says which package gives it for each packager. x_system_sources names the third-party archives that some of the packages come from:

x_system_packages:
  FooLib:
    deb:
      name: libfoo-devel
      ubuntu/24.04: libfoo-dev
      debian/13: ~
    rpm: libfoo-devel
  ffmpeg:
    deb:
      name: ffmpeg
      source: jellyfin
    test: ffmpeg -version
x_system_sources:
  deb:
    - name: jellyfin
      uri: https://repo.test.test/ubuntu
      suites: ['{codename}']
      components: [main]
      key: https://repo.test.test/key.gpg
      fingerprint: 0123456789ABCDEF0123456789ABCDEF01234567
      distros: [ubuntu/24.04]

The declaration says what a host must install. This check does not ask what a host has installed. It reads a snapshot, which can be of a repository that is not on this machine, and does not run the tests. Other code installs the packages: it reads them with "declared" and "resolve", or with conformance packages (see conformance).

A dependency

A key of x_system_packages is the name of a dependency: a letter or a digit, then letters, digits, _, ., +, : and -, such as Foo::Lib or ffmpeg. The name is for people and for reports. No packager reads it.

Its value is a mapping. Each key other than test is the name of a packager: a lowercase letter, then lowercase letters and digits, as Provisioner::Packager->named takes it. A dependency has one packager at least. test is optional. It is one command line that must exit 0 once the package is installed, because a package that is installed is not yet a program that runs. It is one line that is not blank.

An entry

The value of a packager is the entry of the dependency for that packager. It is a package name, which applies to every host, or a mapping with these keys:

name

The package on a host that no other key matches.

DISTRO, DISTRO/RELEASE

The package on that distro, or on that release of it. DISTRO is the ID of /etc/os-release, such as ubuntu or debian, and RELEASE is its VERSION_ID, such as 24.04 or 13. Both are lowercase letters, digits, ., _ and -. A null value says that the host needs no package for the dependency.

source

Optional. The name of the archive in x_system_sources of the same packager that the package comes from.

The mapping has name, or a DISTRO or DISTRO/RELEASE key, or both. For a host, the most specific key wins: DISTRO/RELEASE, then DISTRO, then name.

What a package name is

A package name must be a plain name, and not an expression that the packager reads in some other way, such as foo=1.2, foo/sid or two names. Each packager has its own rule:

deb

The rule of the Debian policy: lowercase letters, digits, +, - and ., at least two characters, starting with a letter or a digit. The name must not end in -, because apt-get install foo- removes foo.

any other packager

Letters, digits, +, ., _ and -, starting with a letter or a digit, and not ending in -, until the packager has a rule of its own here.

A source

x_system_sources has a list of sources for each packager. Only deb takes sources. A source of deb is an apt archive, with the keys of a source of Provisioner::Packager::Deb, and two more:

name

The name that an entry gives as source, and the base name of the files of the source: lowercase letters, digits, . and -, from a letter or a digit. Two sources of a packager do not have the same name.

uri, key

The archive, and the URL of its signing key, as http or https.

suites

At least one suite. {codename} in a suite is replaced by the VERSION_CODENAME of the host. No other placeholder is allowed.

components

A list of components.

architectures

Optional. A list of architectures: lowercase letters and digits.

pin

Optional. A mapping with an integer priority, and packages, a glob of package names.

fingerprint

Required. The full fingerprint of the signing key, as 40 hex digits. The archive gets the trust of root on the host, and a key URL can serve another key later, so the installer refuses a key with another fingerprint.

distros

Optional. A list of DISTRO and DISTRO/RELEASE, as in "An entry". The source applies only to a host that one of them matches. Default every host.

Each source is the source of an entry of the same packager. A source that no entry names is not valid, because nothing would install from it.

METHODS

declared

($from), a class method. The validated declaration of a repository, as it is written: a hash reference with packages, the mapping of x_system_packages, and sources, the mapping of x_system_sources, such as

{
    packages => { ffmpeg => { deb => { name => 'ffmpeg', source => 'jellyfin' }, test => 'ffmpeg -version' } },
    sources  => { deb => [ { name => 'jellyfin', uri => ..., fingerprint => ... } ] },
}

A repository that declares nothing gives { packages => {}, sources => {} }. "resolve" gives what one host installs.

$from is a Dist::Conformance::Snapshot, such as a Dist::Conformance::Snapshot::Tree of a remote repository, or the directory of a checkout. Only metamerge.yml is read.

Dies when metamerge.yml does not parse, and when the declaration is not valid, as "DESCRIPTION" says. Then the message starts with metamerge.yml is not valid:, and gives each error on a line of its own, as "validate" in Dist::Conformance::Schema does.

resolve

($declared, %host), a class method. What one host installs, from $declared as "declared" returns it. %host has:

packager

Required. The packager of the host, such as deb.

distro, release

The ID and the VERSION_ID of /etc/os-release. Without them, only name matches an entry, and only a source without distros applies.

codename

The VERSION_CODENAME of /etc/os-release, for {codename} in a suite.

Returns a hash reference:

{
    needs      => [ { name => 'FooLib', package => 'libfoo-dev', source => undef, test => undef }, ... ],
    packages   => [ 'libfoo-dev', 'ffmpeg' ],
    sources    => [ { name => 'jellyfin', suites => ['noble'], ... } ],
    programs   => [ 'ffmpeg -version' ],
    unresolved => [ 'BarLib' ],
}
needs

One item for each dependency that resolves on the host, by the sorted name of the dependency, because a YAML mapping has no order. package is the package for the host, or undef when the most specific key is null, so that the host needs nothing. source is the name of its source, and test the test of the dependency, or undef.

packages

The packages of needs, in their order, without duplicates.

sources

The sources that the packages of needs come from, in their order, once each, with {codename} filled in and without distros.

programs

The tests of needs, in their order.

unresolved

The sorted names of the dependencies that do not resolve on the host: those without an entry for the packager, those whose entry has no key that matches the host, and those whose source is not for the host. A host must not go on without them, so the caller decides what to do, and resolve does not die.

Dies without a packager, and on a source that a package needs and that needs a codename that %host does not give.

check

FUNCTIONS

os_release

($text). The variables of the text of an /etc/os-release, as a hash reference from each name to its value, such as { ID => 'ubuntu', VERSION_ID => '24.04', VERSION_CODENAME => 'noble' }. A value can be bare, in single quotes, or in double quotes with \ escapes, as the shell reads it. Blank lines and comments are skipped.

IN A FLAVOR

checks:
  SystemPackages: {}

STATUSES

The check passes when there is no metamerge.yml, when it declares nothing, and when the declaration is valid, because most repositories need no system packages. It fails when metamerge.yml does not parse, or when the declaration is not valid. The update changes nothing: what a repository needs is its own choice.

CONFIGURATION

None, other than "THE KEYS THAT EVERY CHECK TAKES" in Dist::Conformance::Check.

SEE ALSO

Please see those modules/websites for more information related to this module.

BUGS

Please report any bugs or feature requests on the bugtracker website https://github.com/Troglodyne-Internet-Widgets/Dist-Conformance/issues

When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.

AUTHORS

Current Maintainers:

  • George S. Baugh <george@troglodyne.net>

COPYRIGHT AND LICENSE

Copyright (c) 2026 Troglodyne LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.