-
Notifications
You must be signed in to change notification settings - Fork 164
design
Poudriere is a port builder tool, it has 2 main frontends:
- testport
- bulk
Both are designed to build packages in a clean room, and are able to handle both the legacy pkg_install and the new pkgng.
The code is written 100% in shell script and has very few dependencies out of base.
List of dependencies that should be installed on the box running poudriere:
Port | Optional | Description |
---|---|---|
devel/subversion | yes | Used to be able to get the sources and ports from the FreeBSD svn |
devel/git | yes | Used to be able to get the ports from the FreeBSD github mirror |
ports-mgmt/portlint | yes | Used for testport or bulk -t to run portlint prior to building the packages |
ports-mgmt/pkg | yes | Used for sanity checking a pkgng repository (incremental building) |
Poudriere is composed of jails and ports tree, it associates them to build packages.
The poudriere jail is designed to handle the jails themself.
Configuration of a jail is stored in /usr/local/etc/poudriere.d/jails/name/*
With the following files in the directory
File | Description |
---|---|
arch | Architecture of the jail |
fs | zfs dataset path if zfs is in use |
method | the method used to get the "sources" |
mnt | the path to the root of the jail on the host filesystem |
version | the version of the jail |
Creating a jail can be done using different methods: ftp, allbsd, gjb, svn, svn+http, svn+ssh, svn+https, svn+file, csup
Whatever method is used the principle is the same: getting the binaries and the sources for a given version/architecture of FreeBSD, and prepare a minimal setup so that it results in a usable jail.
The jails will never be touched again except when specifically requested for an update via "poudriere jail -u".
The jails created will then be considered as a reference of a clean vanilla jail
the minimal setup consists in doing the following:
Modify the login.conf to add the following environment variables:
- UNAME_r
- UNAME_v
- OSVERSION
which will set what is excepted on the jail in order to make sure the builds correctly discovers the release, version and OSVERSION of the jail they are building on.
Modify the make.conf to add the following variables:
- USE_PACKAGE_DEPENDS=yes
- BATCH=yes
- WRKDIRPREFIX=/wrkdirs
Only on i386 jail when the host is amd64:
- ARCH=i386
- MACHINE=i386
- MACHINE_ARCH=i386
If the jail is created on top of a zfs filesystem, then a @clean snapshot will be created
This is the default method, it fetches the prebuilt sets from the FreeBSD ftp's (default host can be tweaked via poudriere.conf)
This following set will be installed:
For version lower than 9+ jails:
- base
- dict
- src
- games
- lib32 if the jail is a amd64 one
For version upper than 9+ jails:
- base
- src
- games
- lib32 if the jail is a amd64 one
This method is the same has ftp except that it fetches the sets out of the gjb's snapshot hosting service or the allbsd one.
This method will build the jails from sources fetching them using svn (all svn possible methods are supported)
Only world is built, and the following features are supported: ccache and parallel (make -j) building.
The built jail can be tuned using src.conf: if a src.con files exists in /usr/local/etc/poudriere.d/ on the host, it will used as the src.conf for the jail. if a <jailname>-src.conf file exists in /usr/local/etc/poudriere.d/ on the host, it will be appended to the src.conf used to built the jail.
The rest is just "normal" building of world:
make buildworld
make installworld
make distrib-dirs
make distribution
This method is considered as deprecated and will be removed soon, it works the same way as the svn method except the sources are fetched using csup.
The update of the jail is done by calling freebsd-update from inside the jail. A parameter can be passed to specify a release the user wants to jump to, in that case freebsd-update will be called to perform an upgrade to the said version.
No update/upgrade is supported if the jail as been created using the method.
Only updates on the same branch are supported, sources will be updated, the world rebuilt and installed, once the new world is in the old files will be cleaned out:
make delete-old
make delete-old-libs
The ports tree management can be done using the following methods:
- portsnap
- svn+*
- git
the list of ports tree configured is located in /usr/local/poudriere.d/ports/<ptname>
With the following files in it
File | Description |
---|---|
fs | zfs dataset path if zfs is in use |
method | the method used to get the ports |
mnt | the path to the root of the ports tree on the host filesystem |
Ports trees are named to allow having different ports tree for example one per patch to test.
if no name is specified then the "default" one is used.
This is the default method as it requires no external dependencies. A directory which will contain the ports tree is created, in this directory a .snap hidden directory is created to receive all the portsnap temporary files.
Creation is done using the following command:
/usr/sbin/portsnap -d ${PTMNT}/.snap -p ${PTMNT} fetch extract
PTMNT being the path to the ports tree that has been created
As for the jails, the svn method can support the following submethods:
- svn
- svn+http
- svn+https
- svn+ssh
- svn+file
the following variable can be tweak in poudriere.conf: SVN_HOST which by default is svn.FreeBSD.org
Same as the two previous methods except that the ports tree will be fetched out of the git
The following variable can be tweak in poudriere.conf: GIT_URL which by default is: git://github.com/freebsd/freebsd-ports.git
The following command allows to update the ports tree
poudriere ports -u -p \<name\>
according to the method used to create the command it will run the following commands:
portsnap -d ${PTMNT}/.snap -p ${PTMNT} fetch update
or if run from crontab
portsnap -d ${PTMNT}/.snap -p ${PTMNT} cron update
svn -q update
git pull
Whatever build is run, the principle is the same.
First a reference jail is created by copying the original jail (chosen via the -j option) into the reference destination.
If the original jail has been created on top of zfs then the reference jail is created as a zfs clone of the @clean snapshot of the original jail.
Otherwise the jail is duplicated using pax(1)
if USE_TMPFS is set to "all" in poudriere.conf then the reference jail is mounted as tmpfs and the original jail duplicated using pax(1)
Once the reference jail is created:
The following directories are created inside the reference jail:
- /proc
- /dev
- /compat/linux/proc
- /usr/ports
- /wrkdirs
- /usr/local (or custom localbase is set)
- /distfiles
- /packages
- /new_packages
- /ccache
- /var/db/ports
Then the following filesystems are mounted in it:
- devfs is mounted in /dev with the following rules applied:
- everything hidden
- null unhide
- zero unhide
- random unhide
- urandom unhide
- stdin unhide
- stdout unhide
- stderr unhide
- fd unhide
- fd/* unhide
- the ports dir is nullfs mounted in /usr/ports in readonly
- the packages dir is nullfs mounted in /packages in readonly
- the distfiles dir is nullfs mounted in /distfiles in readonly
The option directories that can be found under /usr/local/etc/poudriere.d will be mounted if they exist respecting that order:
- /usr/local/etc/poudriere.d/<jailname>-<portstreename>-<setname>-options
- /usr/local/etc/poudriere.d/<jailname>-<portstreename>-options
- /usr/local/etc/poudriere.d/<jailname>-options
- /usr/local/etc/poudriere.d/options
It will be nullfs mounted in readonly as /var/db/ports inside of the jail directory
The reference jail is now populated:
- WITH_CCACHE_BUILD=yes is added to the jail's make.conf if CCACHE_DIR is defined
- MAKE_ENV+= CCACHE_DIR=/ccache is added to the jail's make.conf if CCACHE_DIR is defined
- PACKAGES=/packages is appended
- DISTDIR=/distfiles is appended
The content of the following is appended is they exists:
- /usr/local/etc/poudriere.d/make.conf
- /usr/local/etc/poudriere.d/<jailname>-<portstreename>-make.conf
- /usr/local/etc/poudriere.d/<jailname>-<setname>-make.conf
- /usr/local/etc/poudriere.d/<jailname>-<portstreename>-<setname>-make.conf
To finish the file corresponding to the variable RESOLV_CONF in poudriere.conf is copied inside the jail as /etc/resolv.conf
The jail is now ready to be used
The queue calculation is done on the reference jail directly. inside the /poudriere directory
if TMPFS_DATA is set or USE_TMPFS is set to all then /poudriere will be a tmpfs directory (highly recommanded)
The following directories are created inside the /poudriere directory:
- /poudriere/building # this is the pool of currently building ports
- /poudriere/pool # this is the pool of ready to build ports (no dependencies left to built)
- /poudriere/deps # this is the pool of left ports which at least depend on one port not already built
- /poudriere/rdeps # this is a reference of reverse dependencies. Each package dir contains a link to each package depending on it
- /poudriere/var/run # This will receive the pids of the running builders
- /poduriere/var/cache/origin-pkgname #cache to quickly figure out package name out of an origin
- /poudriere/var/cache/pkgname-origin #cache to quickly figure out origin out of a packagename
It initializes the log directory in
- ${POUDRIERE_DATA}/logs/bulk/<MASTERNAME>/<STARTTIME>
POUDRIERE_DATA being /usr/local/poduriere/data by default
MASTERNAME being if setname is set: <jailname>-<portstreename>-<setname> otherwise <jailname>-<portstreename> STARTTIME begin the time of the beginning of the build is the following format: date +%Y-%m-%d_%H:%M:%S
The different statistic files are initialized in the log directory:
file | default value |
---|---|
.poudriere.stats_queued | 0 |
.poudriere.stats_built | 0 |
.poudriere.stats_failed | 0 |
.poudriere.stats_ignored | 0 |
.poudriere.stats_skipped | 0 |
.poudriere.ports.built | "" |
.poudriere.ports.failed | "" |
.poudriere.ports.ignored | "" |
.poudriere.ports.skipped | "" |
The dependency calculation itself is done in parallel (using one process per core by default)
For each ports listed to be built en entry is created in /poudriere/deps corresponding to the package name
All the dependency are extracted from the port via: make -VPKG_DEPENDS -VBUILD_DEPENDS -VEXTRACT_DEPENDS -VLIB_DEPENDS -VPATCH_DEPENDS -VFETCH_DEPENDS -VRUN_DEPENDS
For each dependency an entry corresponding to the dependency pkgname is added to /poudriere/deps/<pkgname>/<depname> And also in an entry is added to /poudriere/rdeps/<depname>/<pkgname> (reverse dependency tracking)
The above process is also applied to the depname
The above will populate /poudriere/deps with all the packages that needs to be built each package will have entry for each dependency it depends on.
Now that the full dependency tree is populated, poudriere will sanity check the repository of packages if there are already packages in it In parallel (one process per core) each package is checked and deleted if:
- the origin of the package doesnt exists any more in the ports tree
- the version in ports is newer than the version of the package
- if CHECK_CHANGED_OPTIONS is set, the package is delete is the options activated in the ports tree for the given port are different from the one for the built package
After that poudriere will process all existing packages and will delete each one for which a dependency is missing and loop on this process until, no package is deleted.
To finish with the sanity check, all the dead symlinks are deleted.
The dependency calculation process ends by looping over all the entries in /poudriere/deps, and each time it figures out the package already exists in the package directory then the entry is removed from the queue.
All the ports with no dependency to be built (empty directories in /poudriere/deps) are now moved to /poudriere/pool/unbalanced as they are ready to be built straight away.
Before starting the bulk itself, the reference jail is marked a prepkg, On zfs (no tmpfs) jails a new zfs snapshot @prepkg is created On other filesystems a mtree file tracking all files of the jail is created.
The build is now started in parallel: one building jail per core will be created.
The reference jail is cloned using zfs clone or duplicated via pax(1) into new jails.
For each jail the following filesystems are mounted in it:
- devfs is mounted in /dev with the following rules applied:
- everything hidden
- null unhide
- zero unhide
- random unhide
- urandom unhide
- stdin unhide
- stdout unhide
- stderr unhide
- fd unhide
- fd/* unhide
- the ports dir is nullfs mounted in /usr/ports in readonly
- the packages dir is nullfs mounted in /packages in readonly
- the distfiles dir is nullfs mounted in /distfiles
- fdescfs is mounted in /dev/fd
- procfs is mounted in /proc
- linprocfs is mounted in /compat/linux/proc
- the specified ccache dir if any is nullfs mounted in /ccache
- /wrkdirs is mounted as tmpfs or mdfs according to configuration if needed
- the option directory is nullfs mounted readonly using the same rule as for the reference jail
For each port in /poudriere/pool a job is started on one of the above builders:
- the /poudriere/pool/<pkgname> dir is moved to /poudriere/building/<pkgname>
- umount all tmpfs within the builders to cleanup their content
- rollback the jail file system to the @prepkg state (using zfs rollback or using mtree -r + some pax hack)
- cleanup /wrkdirs from any previous build
- install all pkg, fetch, extract, patch, build and lib dependencies if any inside a jail(8) with no network
- run make check-config in a jail(8) with no network
- run make fetch in a jail(8) with network access
- run make checksum in a jail(8) with network access
- copy the distfiles required by the port and only them into /portdistfiles and modify /etc/make.conf so that the port will pickup its distfiles only in this directory
- run make extract in a jail(8) with no network
- run make patch in a jail(8) with no network
- run make configure in a jail(8) with no network
- run make build in a jail(8) with no network
- run make run-depends in a jail(8) with no network
- run make install-mtree in a jail(8) with no network
- if in test mode mark the filesystem as preinst (mtree file created)
- run make install in a jail(8) with no network
- modify /etc/make.conf to that is will create its packages in /new_packages and create that directory
- run make package in a jail(8) with no network
- if in test mode run make deinstall in a jail(8) with no network
- if in test mode check for leftovers by comparing the state of the filesystem with the one in the preinst mtree
- if all the above went ok copy the created packages from /new_packages to the final package directory
- remove all the /poudriere/deps/*/<pkgname> and move all the new empty directoris into /poudriere/pool/unbalanced so that they can be picked up in a jail for building.
- the /poudriere/building/<pkgname> is removed
The pool queue is designed to prioritize building packages that are depended on first. This allows the ready-to-build pool to more quickly get all packages in a ready-to-build state. This can help avoid bottlenecks where large dependencies must build before moving on, which may be a problem with large CPU sets.
- a /poudriere/pool/N pool is created for the number of POOL_BUCKETS (default is 10)
- packages are initially added into /poudriere/pool/unbalanced
- they are then moved to /poudriere/pool/N where N is the number of packages that directly depend on it. This is capped at POOL_BUCKETS-1
- the build queue reads from the highest priority pool down to 0
- as builds finish, they add new packages ready-to-build into /poudriere/pool/unbalanced and then call balance_pool to prioritize those
Once finished (all packages built) if the built packages are pkgng "pkg repo" in the repository (with a key to sign it is specified in poudriere.conf) if the built packages are pkg_install a INDEX file is created out of the packages in the repository