This wiki page is intended to describe the issues of, and mechanisms for, bootstrapping a Debian rootfs image from sources. Otherwise known as profile builds (and previously staged builds). It has developed somewhat over a couple of years as practical experience has grown, and is now fairly mature.
There is a real need to bootstrap Debian from sources when doing new ports or flavours. Every new architecture or optimisation flavour needs to do this at least once, and making it easier than the current 'really very hard' would be great. It is also very useful for cross-compiling to new or non-self-hosted architectures, and for a genuinely new arch at least part of the system (toolchains+build-essential) has to be cross-built until there is enough to become self-hosting.
Recent new bootstraps have been done for sh4, armhf, uclibc and avr32. More are coming down the line, including the imminent arm64. Subarch flavoured rebuilds (e.g. to optimise for a particular CPU) are particularly useful on ARM and MIPS architectures.
This is also helpful when bringing a lagged architecture up to date, especially considering documentation tools (that may be too old), optional dependencies (that may be too old or not exist) like php5, which depends on everything and the kitchen sink, etc. I wish this had been implemented when starting to work on m68k…
Currently people tend to use non-debian tools (such as Yocto/gentoo/OpenEmbedded) to get a basic rootfs image of the target arch/ABI then do native building within that. This works but needs a great deal of manual loop-breaking and we really out to be able to bootstrap our own OS.
Putting the necessary bootstrapping metadata and build rules into the packages themselves in an orderly fashion enables the info to be maintained easily. QA tests to report on breakage will help enormously here. It also makes for a repeatable and deterministic process.
This work does need build-system and policy changes, which are detailed on this page.
An important principle is that the packaging changes necessary for this to work are reasonably clear and transparent. A Debian packager should not have to understand this stuff (staged builds and cross-building) in loving detail to avoid breaking things whilst making maintenance changes. Considering this principle helps when deciding between different technically-satisfactory ways of achieving things.
All the metadata needed should be part of the packages, so it can be maintained over time. Any solution with external patches/metadata is doomed to bitrot.
The concept is simple: add support for minimal/reduced/staged builds to packages involved in build-dependency loops, so that build loops are broken. Also ensure packages cross-build properly so that an initial native-building system can be produced.
Working out which packages to modify, and how, is a manual process, done by examining build-dep loops and choosing which packages are most easily and cleanly modified. Once that is done, building bootstrap-able packages is an automatable process.
This spec caters for multiple stages of staged/bootstrap build, so that if necessary a package can have stage1, stage2, etc before the final, normal, build. Almost all packages only need one stage other than the standard build. Only toolchain packages are known to have more than one stage at this time.
The reduced dependencies are specified in the control file, using a slightly modified Build-Depends: syntax.
Build-Depends[stage1]: foo, libbar
(The initial patch used new fields named Build-Depends-Stage1, Build-Depends-Stage2 etc. rather than the above syntax.)
An environment variable (DEB_BUILD_PROFILE) is used to control when packages are built in reduced staged/bootstrap mode, and at what stage.. debian/rules can check this variable and miss out some optional features to reduce the dependency tree (e.g. building kerberos without LDAP support). dpkg-buildpackage/dpkg-checkbuilddeps also checks the reduced/changed build-dependencies instead of the normal ones.
DEB_BUILD_PROFILE is used because it is aready preserved and passed on by all build tools, and this is a build option like other things it is used to control.
So setting DEB_BUILD_PROFILE=stage1 will cause dpkg-buildpackage to call dpkg-checkbuilddeps with -Pstage1 so that Stage1 dependencies are checked, rather than the normal set. Similarly other tools like apt and xdeb will use the Build-Depends-StageN dependencies for the top-level package.
Bootstrapped/Staged packages are marked as such (in the control file) with X-Staged-Build=N and not uploaded to normal repositories. It is important to avoid accidentally mistaking a bootstrap/staged package for a 'real' (normally-built) package. As soon as possible a bootstrap package should be rebuilt as a full package, to avoid having to rebuild many packages aginst the full version once it is available. The details for this are not fleshed-out, but an extra control header seems the obvious thing to do. A version suffix may be useful too, mostly to help humans.
This process is usually called 'staged' builds as well as 'bootstrap' or 'profile' builds. Exact field names and variable names is a subject for bikeshedding. Whatever is most likely to be clear to developers and not clash with other purposes is best.
Proof-of-concept patches for packages that need to understand the new fields have been made. They are here: http://wookware.org/software/cyclicdeps/patches/
dpkg bug: 661538
apt bug: 661537 (needs updating for profiles)
- python-debian bug: coming soon
xdeb bug: 669250
The dpkg bug gives a useful idea of how this spec has developed.
Bootstrapping is closely related to support for cross-building Debian packages because at least part of the process must be done cross. Enough packages to make a bootable image need to be cross-buildable, because you cannot magic a system out of thin air. To move from cross to native building you need build-essential to be cross-buildable.
The number of build-loops that must be broken for cross-building is much smaller than the number that need to be broken for native building. This spec proposes that we start by fixing the loops that mean you can't even cross-build a base Debian image before going on to fix all the packages which have native build-dep loops.
Debian/Ubuntu cross-building is documented here: https://wiki.linaro.org/CrossBuilding
Patched sources with build-profile patches applied and extra multiarch and profile build support in package metadata are in http://people.debian.org/~wookey/bootstrap.html
For cross-building to be reliable cross-dependency metadata needs to be in packages, so that it is clear whether a build dependency should be satisfied by the build architecture or the host architecture. Multiarch information can be used to provide this information along with build-dependency decoration for the farily rare exceptions. Details are specified here: https://wiki.ubuntu.com/MultiarchCross
The current state of buildability using this technology is recorded here: http://people.linaro.org/~wookey/buildd/ and http://people.canonical.com/~cjwatson/cross/armhf/raring/
Prior to useful amounts of multiarch metadata is in packages, heurisitics must be used, as implemented in xdeb (and the now-deprecated apt-cross), or all dependencies must be installed for both host and native, as implemented in xapt. These are all ugly and horrid, but better than nothing. xdeb or xapt are better than multiarch-cross in whezzy and precise, but multiarch-cross is where all the current work is, and as of end 2012 it works for much of the base system in Debian unstable and Ubuntu raring.
The full automated bootstrapping process needs to keep track of staged builds and rebuilding things as needed so that they don't hang around any longer than necessary. However any such tool could get out of sync with the current status, unless it is always determinable from the current package-set state. This spec attempts to define things such that it is always intrinsically stateful.
Packges do need to be uploaded to a bootstrap repository in order to satisfy build-dependecies (that's the whole point). However because packages of the same version get built, and thus uploaded, more than once we need a way to deal with this. One proposal is to append ~stageN+M to the package version automatically, where N is the stage number and M a continuously incremented (by the buildd) number. Or do binNMUs, which are already recognised by the tools and package management system very well, and almost all packages are (supposedly) binNMU safe. This part has not yet been implmented.
See 'Changed binary packages' below for a discussion of how profiles causing a binary package normally built to be omitted should be dealt with. How/if we choose to mark the profile info in control files affects whether a static analysis tool can determine in advance if the package set has a fully linearisable build-order or not.
The toolchain has a complex 3-stage bootstrapping process involving binutils, gcc, glibc and kernel-headers. It uses the DEB_STAGE variable name internally to control the build stages. The multiple stage sequencing is currently managed by external packages in Debian (buildcross) and Ubuntu (<arch>-cross-toolchain-base and gcc-cross-defaults), using binary -source packages.
Using multiarch to supply cross-libc actually removes some of the complexity of cross-toolchain building, and was the subject of a 2012 GSOC project. That was successful and cross-toolchains can now be built that way. Work is ongoing to get this in a state suitable for the archive, so allowing cross-toolchains to be built by buildds. This work is tracked on the MultiarchCrossToolchains page
Circular dependencies/staged builds
The main issue is circular build-dependencies. These fall into three main areas:
- Most languages depend on themselves to build (gcc, openjdk, mono, haskell, perl, python, ada(gnat)).
Libraries sometimes circularly depend:
kerberos -> ldap -> kerberos
qt -> poppler -> cups -> qt
- Documentation packages. Many packages need documentation tools (sgmltools, jade, tex, doxygen) which cannot be built until many other packages are built. This is largely only a problem for native-builds, as the doc-tools are generally available when cross-building.
The generic way to deal with all of these is 'profile builds', where a version of the package is built with lesser functionality and thus a smaller dependency tree. This allows the depending package to then be built, then for the 'stageN' package to be built normally.
Encoding the build-profile dependencies in the control file makes it simple for automatic build-ordering and analysis tool.
The original spec was proposed here: https://wiki.ubuntu.com/Specs/M/ARMAutomatedBootstrap
This wiki page now comprises the spec developed out that spec and proposes some further ideas and changes.
CircularBuildDependencies is a list of loops found in the initial (2011) analysis.
'Staged' builds are invoked by setting DEB_BUILD_OPTIONS to specify a staged build to dpkg-buildpackage. The original patch uses 'stage=N', but the dpkg team has suggested that using 'DEB_BUILD_PROFILE=<string>' is more versatile as arbitrary profiles could be useful for other purposes ('embedded' profile for example, and maybe <cross>). When no profile option is set then a normal build occurs. Some packages may need more than one staged build. We do not know what the maximum number of stages needed is: it is proably two, but to assume so would be foolish. We count up from Stage1, Stage2 to 'normal'. Hopefully this is reasonably clear to the average packager.
Any 'staged' package must be identified as such in the metadata so it is not accidentally uploaded as a 'real' package. The mechanism is an extra header inserted by dpkg-genchanges, to indicate the staged build. The existing patch sets 'Staged-Build=N' when DEB_BUILD_OPTIONS=stageN. The proposed profile mechanism would set Built-with-Profile=profile, i.e in the case of staged builds, Built-with-Profile=stage1 etc.
It must be possible for the build-tools to identify what build-stages are available. By reading the profiles specified in the Build-Depends. The existence of that defines such a stage as being available.
Let's consider kerberos as a typical example of a library package involved in a circular dependency. krb5 needs libldap2-dev to build (from openldap). openldap need libkrb5-dev (from krb5) to build. To fix this we add a staged build to krb5 to miss out the generation of the krb5-ldap package. This is easy to do with a debhelper-based package by simply setting DH_OPTIONS="--no-package=krb5-ldap", and running configure with --without-ldap (when DEB_BUILD_OPTIONS=stage=1).
Dealing with changed build dependencies
The DEB_BUILD_PROFILE syntax is: Build_Depends: foo , bar <!stage1>, libfoo2 <!stage1 !stage2>, wibble <stage2>
Which is an implausibly complicated example which normally build-depnds on foo, bar, libfoo2. For a stage1 build it doesn't need bar or libfoo2. For stage2 it needs foo, bar and also needs wibble, but does not need libfoo2
This syntax allows the usual case of missing out a build-dep but also supports substitutions. It needs a dpkg patch to understand the <> syntax. Its major disadvantage is that packages using this syntax cannot be uploaded until the dpkg on the buildds understands this syntax. And the Wheezy dpkg does not understand this syntax.
The earlier proposal was to use extra Build-depends lines:
Build-Depends-StageN simply lists all the build-dependencies again except changing or missing out some as required. This does need to be maintained along with the normal build-depends. This makes it very easy to implement as existing tools will not barf as no new syntax is used. However it is an ugly implementation inside dpkg with many extra fields defined and a limit (of 2) on how many stages could be supported. No other profiles types are possible without hard-defining more feilds inside dpkg.
So for krb5 we'd add:
Build-Depends-Stage1:debhelper (>= 7), byacc | bison, comerr-dev, docbook-to-man, libkeyutils-dev [!kfreebsd-i386 !kfreebsd-amd64 !hurd-i386] libncurses5-dev, libssl-dev, ss-dev, texinfo
For packages which depend on themselves (usually languages), the Build-dependencies should be changed to depend on lang | lang-bootstrap. In a normal repository the (native version) lang-bootstrap will not be available so a lang will be used. In a bootstraping environment lang may well not be available in which case lang-bootstrap needs to be built. The bootstraping tool knows to do a staged build in this case.
Setting DEB_BUILD_OPTIONS=profile=stage1 and building this package causes it to produce lang-bootstrap (which is normally not emitted). This is implemented by adding a new control stanza for lang-bootstrap and specifying --no-package=lang-bootstrap in debian/rules for normal builds, but not for the stage1 build (which will probably exclude a load of other stuff).
For documentation issues being able to specify DEB_BUILD_OPTIONS=nodocs would be simplest. Building with docs affects the dependencies, so it is not like other DEB_BUILD_OPTIONS, so perhaps this is not a good mechanism to use? Something generic is attractive if we can make it work.
Documentation loops are primarily an issue for native building, although they do cause issues for cross-building too (gobject introspection, perl module docs).
Changed binary packages
Sometimes a profile/staged build will omit a binary package that is normally produced. e.g. if there is a foo-gui binary that brings in a lot of gtk build-deps, but foo is also a low-level package needed by other things.
If these changed binary outputs are not recording in package metadata then a tool analysing the build-order cannot determine in advance whether a build-order will complete, because although it knows that a package has a build profile which needs reduced build-deps, it does not know if any of the package binaries will not be produced for this particular profile, and thus what could be built afterwards. For an iterative process where stuff that can be built next is built, then the new state examined for more things to build, this does not really matter, but it would be good if analysis tools like dose3 could determine from package files alone whether or not a bootstrap order exists.
A suggested mechanism to allow this is to add a field to affected binary packages saying Build-Profile: !stage1 (for packages not built in stage1) Build_Profile: stage2 (for packages built only in stage2)
Nearly all packages would not have any such notation and would thus always be produced by all profiles, as now.
This feature may be deemed unnecessary complexity that is only actually needed by external analysis tools. Maintaining this data means that a bootstrap analysis can be known to be correct and if it says there is a build order then that order will actually work. Without this info the tool may produce a build order but it may not actually work in practice because some binary-package will not be built, but will actually be needed.
It's a minor change to dpkg-gencontrol
These are some related activities and documents which have generated input for this one.
- GSOC 2011 project
Thanks to Jonathan Austin, Steve ?McIntyre, Steve Lanagsek and Loic Minier for helping clarify the thoughts described above.