Separation of Ports Build Process from Local Installation

GSoC 2019 project

Introduction

The FreeBSD ports framework currently makes it possible, in a single command, to build a third-party software package and all of its dependencies. However, that dependency installation mechanism has the limitation that dependencies must be fully installed onto the build system before building of ports that require them can proceed.

In general, there is no way for the mainstream ports framework to complete all work within a temporary location without frequently interrupting the process to modify the build host to install these dependencies.

Modifying the build host in the middle of a build is often undesirable and unsafe. To avoid this, existing port-building tools such as Poudriere and Synth use jails to accomplish isolation. However, due to their extensive scope of features, these tools are not designed for nor appropriate for integration into Mk/bsd.port.mk as a standard feature of the ports tree.

The Ports Separated Build project enables building of ports in isolation by staging all tools, include files, and shared objects into a temporary location used exclusively by the ports build process, eliminating the need to install intermediate dependencies onto the build host. It enables this through changes to the scripts and makefiles provided with the ports tree in /usr/ports/Mk/ rather than through a separate tool. It does not rely on Jails or other privileged features.

In this way, it is made to behave more like the FreeBSD src tree, in which "buildworld" is a self-contained and self-sufficient process requiring no special preparation of the build host.

Currently, this works for 97% of the ~4200 ports tested. Remaining obstacles to a general solution working for 100% of ports are discussed later in this document.

Code

The changes to the ports tree to enable this are published at:

The only major *new* piece of software involved is a userspace library for manipulating an unsuspecting program's filesystem namespace, redirecting file paths as configured by an environment variable. It is published in its own repository:

It is available as a port: devel/userns (found in the freebsd-ports repository above)

Working status

It is possible to perform the following (without root):

It will be more telling of actual success if this is done on a system or jail that doesn't already have any ports installed to /usr/local (or instead use a different LOCALBASE) since otherwise there is a chance that something accessed the real /usr/local when looking for a dependency (only a controlled location, defaulting to ${PORTBLDBASE}/depsroot, is meant to be accessible for dependencies in PORTS_SEPARATED_BUILD mode).

To the extent that building succeeds (the scheme is known to be compatible with some ports and not with others), the resulting packages are placed into $HOME/ports/packages/All. They can then be installed (as superuser) onto the system.

From the user's perspective, the most significant difference is that if this were done from the upstream ports framework, the process would be interrupted several times to install dependencies into the real /usr/local after requesting permission.

Background and Motivations

(this section will be of little interest to anyone already familiar with the history and established behavior of the Ports framework).

Now, much work has gone into compatibility with BSD environment (not strictly POSIX) and to make use of BSD/FreeBSD extensions where they are useful. Therefore the POSIX-compatibility goal can be reasonably relaxed to a goal of requiring only FreeBSD base. (A POSIX-compatible bootstrapper to set up the BSD tools would bridge this gap).

Most ports require more than just a FreeBSD-base environment: they need some additional tools to be installed and configured. Special privileges must be granted by the OS to accomplish this modification to the environment: Installation of packages (Ports tree default) or management of Jails (Poudriere and similar tools).

POSIX-compliant environment (or FreeBSD base) is only a starting point; build systems (including ports framework itself) can create a modified environment when needed. It is the kind of modification that needs to be made that determines whether special system privileges and features are required.

Compare this limitation of the existing ports framework to the base system source tree:

Implementation details

Ports building scheme overview

Section outline

PORTBLDROOT

Required dependency ports are installed to a temporary location rather than into the root of the build host. The temporary location is specified by the PORTBLDROOT make variable.

UserNS

To address the need of ported software to find dependencies in expected locations under ${LOCALBASE} without performing the real installation to this location, a library providing a virtual filesystem namespace has been developed as the first phase of this project.

"UserNS" intercepts an unsuspecting program's C library API calls to rewrite pathnames in file operations, giving the appearance to the application of a filesystem namespace other than that of the host system.

It is included in the modified ports tree as devel/userns.

The most comparable previous work is the Linux-only "fakechroot" library. The "fake chroot" functionality is only a special case of UserNS's capability, which allows creating a namespace as an aggregate of arbitrarily many locations in the real filesystem.

UserNS is controlled through an environment variable. Its basic usage within the ports framework is:

This is a colon-separated list of virtual%real entries:

Actual usage involves further remappings.

PORTENV

A new Make variable is introduced, PORTENV, to expand to the command-line prefix needed to run a command inside the virtual file namespace facilitated by UserNS.

${PORTENV} defaults to ${SETENV} when separated-build mode is not used.

bmake

FreeBSD's make, /usr/bin/make, is a statically linked executable and therefore cannot utilize UserNS. This is not a problem for the ports framework itself but when ports internally require a Make utility for building, UserNS should be used so that Makefile file-existence checks work correctly.

FreeBSD's built-in make is imported from NetBSD Make. The FreeBSD port devel/bmake is a dynamically-linked alternative, with the only functional difference being that it uses its own (NetBSD-style) makefile includes in /usr/local/share/mk instead of FreeBSD's /usr/share/mk. This is easily corrected by a wrapper script in Mk/Exec/make. UserNS redirects /usr/bin/make to this wrapper so that it is always used instead by any port attempting to use "make".

Many ports use devel/gmake (GNU Make) instead, which is also compatible with UserNS.

sub-make

Ports themselves do not know about ${PORTENV}. Make does not provide a reliable mechanism for running all target commands with an alternate shell. Usage of ${SETENV} by ports' targets is inconsistent, and MAKE_ENV / CONFIGURE_ENV etc. do not capture all cases.

Instead, any port-defined targets (typically pre-* post-* do-* ... *-configure *-build *-install etc.) are run in a Make subprocess which itself executes under the PORTENV environment.

ldconfig

One of the requirements for PORTENV is that it enables execution of programs installed into PORTBLDROOT. This requires a ld-elf.so.hints file that is correct for that environment.

FreeBSD uses /etc/rc.d/ldconfig to maintain the system-default /var/run/ld-elf.so.hints.

A new script, ${PORTSDIR}/Mk/Scripts/ldconfig.sh provides similar functionality, processing the library directories under ${PORTBLDROOT} to maintain ${PORTBLDROOT}/var/run/ld-elf.so.hints. PORTENV sets LD_ELF_HINTS_PATH to use this hints file.

pkg install scripts

Some packages contain post-install scripts which must be run for the package to be properly installed. PORTENV is used to run these scripts when packages are installed to ${PORTBLDROOT} such that the operations of the scripts apply to files within ${PORTBLDROOT}.

Use of /usr/sbin/service from post-install scripts is specially handled. It is remapped by UserNS to ${PORTSDIR}/Mk/Exec/service, which supports only the services which are deemed relevant to proper installation of port-building tools. Currently, the only service is "ldconfig", which executes ${PORTSDIR}/Mk/Scripts/ldconfig.sh described above.

bootstrap

The ports tree is expected to install any tools it needs which do not come from the base system. The official ports tree as-is installs ports-mgmt/pkg before building any port, since it is an essential part of the ports build system's ability to check dependencies and to install ports once they are build.

This ability to bootstrap the pkg tool is extended to also build devel/bmake and devel/userns and prepare them within ${PORTBLDROOT}, since these are essential components of separated-build mode functionality.

Causes of incompatibilities with the separated-build scheme

Needed work

The current status of the project is that it is usable for some but not all ports. Much work remains before it can be adopted officially into the ports tree:

What could have been done differently

Possible alternatives to UserNS approach:

Future direction: cross-compilation support

Although only tangential to the primary results of the project, one of the deliverables as a side-effect is the following collection of notes on adding a generally working cross-compilation capability to the ports framework.

When cross compiling, there is an important distinction between two different kinds of build-time dependency:

FreeBSD's ports tree did not initially record these distinctions in ports' build-time dependency information.

NetBSD's pkgsrc system, similar to and inspired by FreeBSD's ports, has slowly been solving this problem by adding a new type of dependency, TOOL_DEPENDS. This is similar to BUILD_DEPENDS except that it identifies a port providing commands which must be executed on the build host.

FreeBSD porting convention already provides a way to record this information, however in a way which might not be as obvious: the indication that a port depends on a program for building is an entry of the form:

Many ports already make use of this. Unfortunately this convention is not always adhered to. By enforcing it as a rule, a port's attempts to use a port-provided program at build-time without specifying it appropriately in BUILD_DEPENDS can be considered as a bug in the port and fixed accordingly.

One way to enforce this is to remove ${LOCALBASE} from PATH and replace it with the path to a directory created by the ports framework containing only symlinks to the programs which have actually been registered correctly as dependencies. This needs to be done recursively so that run-depends of those programs are also satisfied.

PORTBLDROOT from the ports-separated-build project will need to be split into two different kinds of root, each with a specific purpose:

GSoC 2019

The original goals of the GSoC project, followed by weekly updates made during the project, are preserved below.

Project Objectives

This project aims to provide the capability of the FreeBSD ports infrastructure to safely and cleanly build ports and all their dependencies without superuser privileges, jails, or touching the installed system in any way, in the interest of improving the safety, reliability, and repeatability of ports building without the administrative and resource overhead of a separated build host or jail.

This will bring the building capability of ports tree in line with what is already possible for FreeBSD base system: separate build vs. install workflow.

Currently this level of separation can only be accomplished in practice through chroot or Jail. This project will eliminate the need for cooperation of the root user since /usr/local will not need to be touched. To enable this, file accesses will be redirected to a location controlled by the ports build process through use of a userspace library to intercept file accesses.

This implementation takes the possibility of cross-building of ports into consideration as a long-term goal (far beyond end of GSoC due to the significant challenges and history of ports not taking this into consideration).

Where possible, the need for the userspace file interception hack should be eliminated, by switching to use of build tools supporting sysroot (this is analogous to desirability of sysroot-capable compiler vs. user-mode emulation for cross-arch compilation). The interception tool serves to cover otherwise uncooperative software.

Goal #1 - working isolated build

User (no special privileges, no ahead-of-time preparation by system admin) may do:

make -C /usr/ports/<category>/<port> package

-> The port and its dependencies are built and installed within a pristine environment (File access attempts to /usr/local never occur).

-> The packaged port is identical (with exception of dates and hostnames) to the package from official FreeBSD repository, assuming the same SVN revision has been used.

Goal #2 - efficiency & performance

The building as done according to the first goal is done efficiently.

Goal #3 - concurrency

Independent ports are built concurrently.

Project Deliverables

Technical Challenges

Running the entire ports framework in a chroot (as is trivially possible) defeats the purpose of the project, since it only moves the problem of isolating each port's build environment from a physical host to a virtual one: Ports framework itself, not just a blanket chroot created by system administrator, should configure the build-environment of each port.

Weekly updates

06/03

06/10

06/18

06/24

07/01

07/09

07/15

07/22

Latest summary of port building success

Timeline

(Not updated)

Week

Task

Progress

05/27 - 05/31

Study dependency resolution mechanisms in Mk/bsd.port.mk, Mk/Scripts/*, what works, what doesn’t, in context of a temporary install location.

Familiarized with port dependency mechanisms in /usr/ports/Mk/Scripts. Studied /usr/src/lib/libc to understand chain of userspace calls between C API and system call. Implemented proof-of-concept redirection library intercepting open(...).

06/03 - 06/07

Implement file path redirection library and test against common tools

File redirection library is working for basic file operations performed by common shell utilities.

06/10 - 06/14

Modify bsd.port.mk (and related) to utilize install location and redirection lib

Working branch of freebsd-ports utilizes a virtual install location, and some ports are able to build using dependencies installed to this location. See above.

06/17 - 06/21

Demonstrate successful building of a port with dependencies

Ports with dependencies function under chroot, not under userspace file tool.

06/24 - 06/28

Document and present findings

07/01 - 07/12

Identify ports using statically linked build tools. Add option for building such tools to include file path redirection support.

07/15 - 07/26

Build a larger number of ports. See what breaks and why.

07/22 - 07/26

Document findings and present progress

07/29 - 08/04

Fix problems which prevent some ports from building.

08/11 - 08/23

Thoroughly test process. Document what can’t be fixed and why.

08/19 - 08/23

Thoroughly document all added components and present summary of compatibility.

SummerOfCode2019Projects/PortsSeparatedBuild (last edited 2019-08-26T16:57:30+0000 by TheronTarigo)