chiark / gitweb /
Makefile: Include C++ headers in the cross-tools bundle.
[distorted-chroot] / Makefile
index dd96989845c5839cb1fa1ae81391d6c569b1131f..4b4a6161173e6855488628d358a8e914b46e7668 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,8 @@
 
 all::
 clean::
-.PHONY: all clean
+check::
+.PHONY: all clean check
 .SECONDEXPANSION: #sorry
 
 ###--------------------------------------------------------------------------
@@ -34,14 +35,18 @@ clean::
 
 CONFIG_VARS             =
 
+## Level of pickiness to aspire to.
+NOTIFY_FATAL            = 1
+
 ## Volume group from which to allocate chroot volumes and snapshots.
 CONFIG_VARS            += VG LVPREFIX
-VG                      = vg-$(shell hostname)
+VG                     := vg-$(shell hostname)
 LVPREFIX                =
 
 ## Logical volume size, as an LVM option.
-CONFIG_VARS            += LVSZ
+CONFIG_VARS            += LVSZ SNAPOPT
 LVSZ                    = -L8G
+SNAPOPT                         = -L8G
 
 ## Debian mirror.
 CONFIG_VARS            += DEBMIRROR
@@ -53,15 +58,14 @@ APTSRC                       = etc/aptsrc.conf $(wildcard etc/aptsrc.local.conf)
 
 ## APT configuration fragment names.  These will be linked into
 ## `/etc/apt/apt.conf.d' in each chroot.  To put a fragment f in a surprising
-## place, set $($f_APTCONFSRC).
-CONFIG_VARS            += APTCONF
-APTCONF_DIR             = etc/apt-conf.d
-APTCONF                         = $(notdir $(wildcard $(APTCONF_DIR)/[0-9]*[!~]))
+## place, set $(_$f_APTCONFSRC).
+CONFIG_VARS            += APTCONF $(foreach f,$(APTCONF),_$f_APTCONFSRC)
+APTCONF                         = $(notdir $(wildcard etc/apt-conf.d/[0-9]*[!~]))
 
 ## Proxy setting.
 CONFIG_VARS            += PROXY
-PROXY                  := $(shell \
-       eval $$(apt-config $(foreach a,$(APTCONF), -c$(APTCONF_DIR)/$a) \
+PROXY                   = $(shell \
+       eval $$(apt-config $(foreach a,$(APTCONF),-cetc/apt-conf.d/$a) \
                shell proxy Acquire::http::proxy); \
        case $${proxy+t} in (t) echo "$$proxy" ;; (*) echo nil ;; esac)
 
@@ -72,8 +76,8 @@ DISTS                  = $(PRIMARY_DIST) buster bullseye sid
 
 ## Host's native architecture(s).
 CONFIG_VARS            += MYARCH NATIVE_ARCHS
-MYARCH                  = $(shell dpkg --print-architecture)
-OTHERARCHS              = $(shell dpkg --print-foreign-architectures)
+MYARCH                 := $(shell dpkg --print-architecture)
+OTHERARCHS             := $(shell dpkg --print-foreign-architectures)
 NATIVE_ARCHS            = $(MYARCH) $(OTHERARCHS)
 
 ## Foreign (emulated) architectures to support.
@@ -81,6 +85,7 @@ CONFIG_VARS           += FOREIGN_ARCHS
 FOREIGN_ARCHS           =
 
 ## Master lists of chroots to build and maintain.
+CONFIG_VARS            += NATIVE_CHROOTS FOREIGN_CHROOTS
 NATIVE_CHROOTS          = $(foreach a,$(NATIVE_ARCHS), \
                                $(foreach d,$(or $($a_DISTS) $(DISTS)), \
                                        $d-$a))
@@ -88,6 +93,78 @@ FOREIGN_CHROOTS               = $(foreach a,$(FOREIGN_ARCHS), \
                                $(foreach d,$(or $($a_DISTS) $(DISTS)), \
                                        $d-$a))
 
+## Extra packages to be installed in chroots.  `BASE_PACKAGES' are installed
+## through `debootstrap'; `EXTRA_PACKAGES' are installed later, using Apt,
+## which is faster in foreign chroots.
+CONFIG_VARS            += BASE_PACKAGES EXTRA_PACKAGES
+BASE_PACKAGES           = eatmydata
+EXTRA_PACKAGES          = build-essential
+EXTRA_PACKAGES         += ccache
+EXTRA_PACKAGES         += fakeroot
+EXTRA_PACKAGES         += libfile-fcntllock-perl
+EXTRA_PACKAGES         += locales
+
+## Extra packages from which to install the cross tools.
+CONFIG_VARS            += CROSS_PACKAGES
+CROSS_PACKAGES          = bash coreutils dash findutils
+CROSS_PACKAGES         += gzip m4 mawk sed tar xz-utils
+CROSS_PACKAGES         += apt ccache eatmydata fakeroot make
+CROSS_PACKAGES         += qemu-user-static
+CROSS_PACKAGES         += $(foreach a,$(FOREIGN_GNUARCHS),\
+                               gcc-$a g++-$a binutils-$a)
+
+## Native files to install in place of the foreign versions.  `MULTI' here
+## stands for the donor's GNU multiarch name.
+CONFIG_VARS            += CROSS_PATHS
+CROSS_PATHS            += \
+       $(addprefix /usr/bin/, \
+               apt apt-cache apt-config apt-get apt-key apt-mark) \
+       $(addprefix /usr/lib/apt/, \
+               methods/ solvers/) \
+       $(addprefix /bin/, \
+               cat chgrp chown cp date dd df dir echo false ln ls mkdir \
+               mknod mktemp mv pwd readlink rm rmdir sleep stty sync touch \
+               true uname vdir) \
+       $(addprefix /usr/bin/, \
+               [ arch b2sum base32 base64 basename chcon cksum comm \
+               csplit cut dircolors dirname du env expand expr factor fmt \
+               fold groups head hostid id install join link logname md5sum \
+               mkfifo nice nl nohup nproc numfmt od paste pathchk pinky pr \
+               printenv printf ptx realpath runcon seq sha1sum sha224sum \
+               sha256sum sha384sum sha512sum shred shuf sort split stat \
+               stdbuf sum tac tail tee test timeout tr truncate tsort tty \
+               unexpand uniq unlink users wc who whoami yes) \
+       /usr/lib/MULTI/coreutils/ \
+       $(addprefix /lib/MULTI/, \
+               libnsl.so.* libnss_*.so.*) \
+       /usr/bin/gpgv \
+       /usr/bin/qemu-*-static \
+       $(addprefix /bin/, \
+               bash dash gzip sed tar) \
+       $(addprefix /usr/bin/, \
+               ccache find m4 make mawk xargs xz) \
+       $(addprefix /usr/lib/MULTI/, \
+               libeatmydata.so* libfakeroot/) \
+       $(addprefix /etc/ld.so.conf.d/, \
+               MULTI.conf fakeroot*.conf) \
+       $(foreach a,$(FOREIGN_GNUARCHS), \
+               $(addprefix /usr/bin/$a-, \
+                       addr2line ar as c++filt dwp elfedit gprof ld ld.* \
+                       nm objcopy objdump ranlib readelf size strings \
+                       strip) \
+               $(addprefix /usr/bin/$a-, \
+                       cpp gcc g++ gcov gcov-dump gcov-tool gprof \
+                       gcc-ar gcc-nm gcc-ranlib) \
+               /usr/lib/gcc-cross/$a/ \
+               /usr/$a/include/c++)
+
+## Local packages to be compiled and installed in chroots.  Archives can be
+## found in `pkg/'.
+CONFIG_VARS            += LOCALPKGS $(foreach p,$(LOCALPKGS),$p_DEPS)
+LOCALPKGS               = mLib checkpath
+mLib_DEPS               =
+checkpath_DEPS          = mLib
+
 ## Which host architecture to use for foreign architectures.  It turns out
 ## that it's best to use a Qemu with the same host bitness as the target
 ## architecture; otherwise it has to do a difficult job of converting
@@ -95,16 +172,20 @@ FOREIGN_CHROOTS             = $(foreach a,$(FOREIGN_ARCHS), \
 32BIT_QEMUHOST          = $(or $(filter i386,$(NATIVE_ARCHS)),$(TOOLSARCH))
 64BIT_QEMUHOST          = $(or $(filter amd64,$(NATIVE_ARCHS)),$(TOOLSARCH))
 
-CONFIG_VARS            += $(foreach a,$(FOREIGN_ARCHS), $a_QEMUHOST)
+CONFIG_VARS            += $(foreach a,$(FOREIGN_ARCHS),$a_QEMUHOST)
 armel_QEMUHOST          = $(32BIT_QEMUHOST)
 armhf_QEMUHOST          = $(32BIT_QEMUHOST)
 arm64_QEMUHOST          = $(64BIT_QEMUHOST)
 i386_QEMUHOST           = $(32BIT_QEMUHOST)
 amd64_QEMUHOST          = $(64BIT_QEMUHOST)
 
+## Which distribution of Qemu to use.
+CONFIG_VARS            += $(foreach d,$(DISTS),$d_QEMUDIST)
+stretch_QEMUDIST        = buster
+
 ## Qemu architecture names.  These tell us which Qemu binary to use for a
 ## particular Debian architecture.
-CONFIG_VARS            += $(foreach a,$(FOREIGN_ARCHS), $a_QEMUARCH)
+CONFIG_VARS            += $(foreach a,$(FOREIGN_ARCHS),$a_QEMUARCH)
 armel_QEMUARCH          = arm
 armhf_QEMUARCH          = arm
 arm64_QEMUARCH          = aarch64
@@ -112,10 +193,10 @@ i386_QEMUARCH              = i386
 amd64_QEMUARCH          = x86_64
 
 ## Alias mapping for chroots.
-CONFIG_VARS            += $(foreach d,$(DISTS), $d_ALIASES)
+CONFIG_VARS            += $(foreach d,$(DISTS),$d_ALIASES)
 stretch_ALIASES                 = oldstable
 buster_ALIASES          = stable
-bullseye_ALIASE                 = testing
+bullseye_ALIASES        = testing
 sid_ALIASES             = unstable
 
 ## Which host architecture to use for commonly used tools in foreign chroots.
@@ -128,10 +209,12 @@ STATE                      = state
 
 ## A directory which will be spliced into chroots as `/usr/local.schroot/'.
 ## This will be our primary point of contact with the chroot.
-CONFIG_VARS            += LOCAL
+CONFIG_VARS            += LOCAL ABSLOCAL
 LOCAL                   = local.schroot
+ABSLOCAL                = $(abspath $(LOCAL))
 
 ## How to run a command as a privileged user.
+CONFIG_VARS            += ROOTLY
 ROOTLY                  = sudo
 
 ## Files to be copied into a chroot from the host.
@@ -142,12 +225,63 @@ SCHROOT_NSSDATABASES       = passwd shadow group gshadow
 -include local.mk
 
 ## All chroot names.
-CONFIG_VARS            += ALL_CHROOTS
+CONFIG_VARS            += ALL_ARCHS ALL_CHROOTS
+ALL_ARCHS               = $(NATIVE_ARCHS) $(FOREIGN_ARCHS)
 ALL_CHROOTS             = $(NATIVE_CHROOTS) $(FOREIGN_CHROOTS)
 
+## GNU names for foreign architectures.
+CONFIG_VARS            += FOREIGN_GNUARCHS
+FOREIGN_GNUARCHS       := $(foreach a,$(FOREIGN_ARCHS),\
+                               $(shell dpkg-architecture -A$a \
+                                       -qDEB_TARGET_GNU_TYPE))
+
 ###--------------------------------------------------------------------------
 ### Utilities.
 
+## Hack to force rebuilding.
+_force:
+.PHONY: _force
+
+## Hack to not delimit function arguments.  (Ugh!)
+comma                   = ,
+
+## Silent-rules machinery.
+V                       = 0
+V_AT                    = $(V_AT_$V)
+V_AT_0                  = @
+v_print                         = $(call v_print_$V,$1,$2)
+v_print_0               = printf "  %-8s %s\n" "$1" $(call squote,$2);
+v_tag                   = $(V_AT)$(call v_print,$1,$@)
+v_log                   = $(call v_log_$V,$1,$2)
+v_log_                  = $(call v_log_1,$1,$2)
+v_log_0                         = $2 >log/$1.log 2>&1
+v_log_1                         = $(call catchrc,$(call throwrc,$2) 2>&1 | tee log/$1.log)
+v_echo                  = $(call v_echo_$V,$1)
+v_echo_0                = :
+v_echo_1                = printf "%s\n" $1
+CLEANFILES             += log/*.log
+
+## Oh, shut up.
+SILENCE_LVM             = \
+       LVM_SUPPRESS_FD_WARNINGS=1; export LVM_SUPPRESS_FD_WARNINGS
+
+##     $(call definedp,VAR)
+##
+## Expand non-empty if and only if VAR is defined (but possibly empty).
+definedp                = $(filter-out undefined,$(origin $1))
+
+##     $(call catchrc,...$(call throwrc,CMD)...)
+##
+## Catch the exit status of some subpart of a complicated shell rune.
+catchrc                         = (exec 3>&1; exit $$({ $1; } 4>&1 >&3 3>&-))
+throwrc                         = { $1; echo $$? >&4; }
+
+##     $(call squote,TXT)
+##
+## Single-quote TXT.
+squote                  = '$(subst ','\'',$1)'
+#'
+
 ##     $(call chroot-dist,D-A) -> D
 ##     $(call chroot-arch,D-A) -> A
 ##
@@ -155,24 +289,42 @@ ALL_CHROOTS                = $(NATIVE_CHROOTS) $(FOREIGN_CHROOTS)
 chroot-dist             = $(patsubst %/,%,$(dir $(subst -,/,$1)))
 chroot-arch             = $(notdir $(subst -,/,$1))
 
+##     $(call package-dir-name,P-V) -> P
+##     $(call package-dir-version,P-V) -> V
+##
+## Parse (source) package directory names.
+package-dir-name        = $(sort $(foreach p,$(LOCALPKGS),$(if $(filter $p-$($p_VERSION),$1),$p)))
+package-dir-version     = $($(call package-dir-name,$1)_VERSION)
+
+##     $(call package-dir,P-V.A) -> P-V
+##     $(call package-name,P-V.A) -> P
+##     $(call package-version,P-V.A) -> V
+##     $(call package-arch,P-V.A) -> A
+##
+## Parse package stamp names.
+package-dir             = $(basename $1)
+package-name            = $(call package-dir-name,$(call package-dir,$1))
+package-version                 = $(call package-dir-version,$(call package-dir,$1))
+package-arch            = $(patsubst .%,%,$(suffix $1))
+
 ##     $(call native-chroot-p,D-A) -> D | <empty>
 ##
 ## Answer whether D-A is a native chroot.
-native-chroot-p                 = $(filter $(call chroot-arch,$1), $(NATIVE_ARCHS))
+native-chroot-p                 = $(filter $(call chroot-arch,$1),$(NATIVE_ARCHS))
 
 ##     $(call chroot-qemuhost,D-A) -> AA
 ##
 ## Answer the apporopriate Qemu host architecture for foreign chroot D-A.
 chroot-qemuhost                 = $($(call chroot-arch,$1)_QEMUHOST)
 
-##     $(call chroot-deps,PRE,D-A) -> PRE/DD-AA ... | <empty>
+##     $(call chroot-deps,PRE,D-A) -> PREDD-AA ... | <empty>
 ##
 ## Answer a list of additional dependencies for the chroot D-A: specifically,
 ## if D-A is foreign then include PRE/DD-AA entries for the tools
 ## architecture, and Qemu host architecture (if that's different).
 chroot-deps             = $(if $(call native-chroot-p,$2),, \
-                               $(addprefix $1/$(call chroot-dist,$2)-,\
-                                       $(sort $(call chroot-toolsarch,$2) \
+                               $(addprefix $1$(call chroot-dist,$2)-,\
+                                       $(sort $(TOOLSARCH) \
                                               $(call chroot-qemuhost,$2))))
 
 ## Substituting placeholders in files.
@@ -188,29 +340,115 @@ subst-file                = { \
        sed "$$substs"; \
 }
 
-## Silent-rules machinery.
-V                       = 0
-V_AT                    = $(V_AT_$V)
-V_AT_0                  = @
-v_print                         = $(call v_print_$V,$1,$2)
-v_print_0               = printf "  %-8s %s\n" "$1" "$2";
-v_tag                   = $(V_AT)$(call v_print,$1,$@)
-v_log                   = $(call v_log_$V,$1)
-v_log_                  = $(call v_log_1,$1)
-v_log_0                         = >$1.log 2>&1
-v_log_1                         = 2>&1 | tee $1.log
-CLEANFILES             += *.log
+###    $(call symlink-ok-p,LINK,DEST)
+###
+### Expand to `t' if LINK is a symbolic link to DEST, and empty otherwise.
+symlink-ok-p             = $(shell \
+       case $$(readlink 2>/dev/null $(patsubst %/,%,$1)) in ($2) echo t ;; esac)
+
+###    $(call general-notify,SEV,COLOUR,PREFIX,MSG)
+###
+### Report a message, highlighted in the right way, and maybe fail
+general-notify          = { \
+       echo "$$(tput bold; tput setaf $2)$3 "$4"$$(tput sgr0; tput op)"; \
+       if [ "$1" -le "$(NOTIFY_FATAL)" ]; then exit 2; fi; \
+}
+
+###    $(call report/SEV,MSG)
+###
+### Report a notification of a particular severity.
+notify/INFO             = $(call general-notify,3,6,---,$1)
+notify/WARN             = $(call general-notify,2,5,???,$1)
+notify/ERR              = $(call general-notify,1,1,!!!,$1)
+
+##     $(call check,SEV,MSG,UNLESS)
+##
+## If UNLESS completes successfully, all is OK; otherwise print MSG to stderr
+## and fail.
+check                   = @{ \
+       $(call v_echo,'check: '$(call squote,$3)''); \
+       if ! { $3; }; then $(call notify/$1,$2); fi; \
+}
+
+##     $(call check-executable,SEV,PATH)
+##
+## Verify that PATH is an executable program.
+check-executable        = $(call check,$1,"\`$2' is not an executable", \
+       [ -x "$2" ])
+
+##     $(call check-mountpoint,SEV,DIR)
+##
+## Verify that DIR is a mountpoint.
+check-mountpoint        = $(call check,$1,"\`$2' is not a mount point", \
+       mountpoint -q "$2")
+
+##     $(call check-symlink,SEV,LINK,DEST)
+##
+## Verify that LINK is a symbolic link pointing to DEST.
+check-symlink           = $(call check,$1,"\`$2' is not a link to \`$3'", \
+       [ -L "$2" ] && [ "$$(readlink "$2")" = "$3" ])
+
+###--------------------------------------------------------------------------
+### Python extensions.
+
+CC                      = gcc
+CFLAGS                  = -O2 -g -Wall
+
+LD                      = $(CC)
+LDFLAGS                         =
+
+c-source                = $(foreach c,$1,src/$c)
+c-object                = $(foreach c,$1,$(STATE)/obj/$(basename $c).o)
+
+PYEXT_PKGFLAGS         := $(shell pkg-config --cflags python2)
+PYEXT_CFLAGS            = -fPIC -fno-strict-aliasing $(PYEXT_PKGFLAGS)
+PYEXT_LDFLAGS           = -shared
+
+PYEXTS                 += jobclient
+jobclient_SOURCES       = jobclient.c
+
+PYEXT_ALLSRC            = $(foreach x,$(PYEXTS),\
+                               $(call c-source,$($x_SOURCES)))
+PYEXT_ALLOBJ            = $(foreach x,$(PYEXTS),\
+                               $(call c-object,$($x_SOURCES)))
+$(PYEXT_ALLOBJ): $(STATE)/obj/%.o: src/%.c
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,CC)$(CC) -c $(CFLAGS) $(PYEXT_CFLAGS) -o$@ $<
+
+PYMODULES               = $(foreach x,$(PYEXTS),$(STATE)/lib/python/$x.so)
+all:: $(PYMODULES)
+$(PYMODULES): $(STATE)/lib/python/%.so: $$(call c-object,$$($$*_SOURCES))
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,LD)$(LD) $(LDFLAGS) $(PYEXT_LDFLAGS) -o$@ $^
+
+###--------------------------------------------------------------------------
+### Scripts.
+
+SCRIPTS                        += chroot-maint
+SCRIPTS                        += mkchrootconf
+
+SUBST_SCRIPTS           = $(addprefix $(STATE)/bin/,$(SCRIPTS))
+all:: $(SUBST_SCRIPTS)
+$(SUBST_SCRIPTS): $(STATE)/bin/%: bin/% $(STATE)/config.sh
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,SUBST){ \
+               sed \
+               -e '2i### GENERATED by distorted-chroot: do not edit' \
+               -e '/@@@config@@@/ {' \
+                       -e 'r $(STATE)/config.sh' \
+                       -e 'd'\
+               -e '}' $<; \
+       } >$@.new && chmod +x $@.new && mv $@.new $@
 
 ###--------------------------------------------------------------------------
 ### APT configuration.
 
 ## In a chroot, `/etc/apt/sources.list' links to
 ## `/usr/local.schroot/etc/apt/sources.$d' for the appropriate distribution.
-APT_SOURCES             = $(foreach d,$(DISTS), $(LOCAL)/etc/apt/sources.$d)
-CLEANFILES             += $(APT_SOURCES)
+APT_SOURCES             = $(foreach d,$(DISTS),$(LOCAL)/etc/apt/sources.$d)
 all:: $(APT_SOURCES)
 
-$(foreach d,$(DISTS), $(STATE)/etc/apt/aptsrc.$d): $(STATE)/etc/apt/aptsrc.%:
+$(foreach d,$(DISTS),$(STATE)/etc/apt/aptsrc.$d): $(STATE)/etc/apt/aptsrc.%:
        $(V_AT)mkdir -p $(dir $@)
        $(call v_tag,GEN){ \
          echo "### -*-conf-*- GENERATED by distorted-chroot: do not edit"; \
@@ -224,15 +462,39 @@ $(APT_SOURCES): $(LOCAL)/etc/apt/sources.%: \
        $(call v_tag,GEN)bin/mkaptsrc \
                $(APTSRC) $($*_APTSRC) $(STATE)/etc/apt/aptsrc.$* \
                >$@.new && mv $@.new $@
+CLEANFILES             += $(APT_SOURCES)
 
 ## In a chroot, a link `/etc/apt/apt.conf.d/FOO' is created for each file in
 ## `/usr/local.schroot/etc/apt/apt.conf.d/FOO'.
 APT_CONFIGS           = $(addprefix $(LOCAL)/etc/apt/apt.conf.d/,$(APTCONF))
 all:: $(APT_CONFIGS)
 $(APT_CONFIGS): $(LOCAL)/etc/apt/apt.conf.d/%: \
-               $$(or $$($$*_APTCONFSRC) $$(APTCONF_DIR)/$$*)
+               $$(or $$(_$$*_APTCONFSRC) etc/apt-conf.d/$$*)
        $(V_AT)mkdir -p $(dir $@)
        $(call v_tag,COPY)cp $< $@.new && mv $@.new $@
+clean::; rm -f $(APT_CONFIGS)
+
+###--------------------------------------------------------------------------
+### Build hacks.
+
+check::; $(call check-executable,ERR,/usr/bin/eatmydata)
+
+EATMYDATA_HACKS                += apt-get aptitude dpkg
+SYMLINK_EATMYDATA_HACKS         = $(addprefix $(LOCAL)/hacks/,$(EATMYDATA_HACKS))
+all:: $(SYMLINK_EATMYDATA_HACKS)
+$(SYMLINK_EATMYDATA_HACKS): $(LOCAL)/hacks/%: \
+               $$(if $$(call symlink-ok-p,$$@,/usr/bin/eatmydata),,_force)
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,SYMLINK)ln -sf /usr/bin/eatmydata $@.new && mv $@.new $@
+clean::; rm -f $(SYMLINK_EATMYDATA_HACKS)
+
+SCRIPT_HACKS           += buildwrap
+COPY_SCRIPT_HACKS       = $(addprefix $(LOCAL)/hacks/,$(SCRIPT_HACKS))
+all:: $(COPY_SCRIPT_HACKS)
+$(COPY_SCRIPT_HACKS): $(LOCAL)/hacks/%: bin/%
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,COPY)cp $< $@.new && mv $@.new $@
+clean::; rm -f $(COPY_SCRIPT_HACKS)
 
 ###--------------------------------------------------------------------------
 ### `schroot' and `sbuild' configuration.
@@ -241,10 +503,15 @@ all:: schroot-config
 schroot-config::
 .PHONY: schroot-config
 
+check::; $(call check-mountpoint,WARN,/var/lib/sbuild/build)
+check::; $(call check-symlink,WARN,/build,/var/lib/sbuild/build)
+check::; $(call check-symlink,ERR,/schroot,/run/schroot/mount)
+
 %print-varlist          = { \
        echo "\#\#\# -*-sh-*- GENERATED by distorted-chroot: do not edit"; \
-       $(foreach v,$1, echo $v=\''$(subst ','\'\\\'\'',$($v))'\';) \
-} #'
+       $(foreach v,$1,$(if $(call definedp,$v),\
+               echo $v=\'$(call squote,$($v))\';)) \
+}
 schroot-config_HASH    := \
        $(shell $(call %print-varlist,$(CONFIG_VARS)) | \
                sha256sum | cut -c1-32)
@@ -252,66 +519,152 @@ schroot-config_FILE       = $(STATE)/config.sh-$(schroot-config_HASH)
 $(schroot-config_FILE):
        $(V_AT)mkdir -p $(STATE)
        $(V_AT)rm -f $(STATE)/config.sh-*
-       $(call v_tag,STAMP)$(call %print-varlist,$(CONFIG_VARS)) \
+       $(call v_tag,GEN)$(call %print-varlist,$(CONFIG_VARS)) \
                >$@.new && mv $@.new $@
 
 schroot-config:: $(STATE)/config.sh
 $(STATE)/config.sh: $(schroot-config_FILE)
        $(call v_tag,SYMLINK)ln -sf $(notdir $<) $@
 
-schroot-config:: $(STATE)/etc/schroot/sbuild.schroot
-$(STATE)/etc/schroot/sbuild.schroot: $(STATE)/config.sh bin/mkchrootconf
+schroot-config:: $(LOCAL)/etc/schroot/sbuild.schroot
+$(LOCAL)/etc/schroot/sbuild.schroot: $(STATE)/bin/mkchrootconf
        $(V_AT)mkdir -p $(dir $@)
-       $(call v_tag,GEN)bin/mkchrootconf >$@.new && mv $@.new $@
+       $(call v_tag,GEN)$(STATE)/bin/mkchrootconf >$@.new && \
+               $(ROOTLY) chown root:root $@.new && mv $@.new $@
+CLEANFILES             += $(LOCAL)/etc/schroot/sbuild.schroot
+check::; $(call check-symlink,WARN,/etc/schroot/chroot.d/sbuild,$(ABSLOCAL)/etc/schroot/sbuild.schroot)
 
-schroot-config:: $(STATE)/etc/schroot/sbuild.profile/copyfiles
-$(STATE)/etc/schroot/sbuild.profile/copyfiles: $(schroot-config_STAMP)
+schroot-config:: $(LOCAL)/etc/schroot/sbuild.profile/copyfiles
+$(LOCAL)/etc/schroot/sbuild.profile/copyfiles: $(schroot-config_STAMP)
        $(V_AT)mkdir -p $(dir $@)
        $(call v_tag,GEN){ \
          echo "### -*-conf-*- GENERATED by distorted-chroot: do not edit"; \
          for i in $(SCHROOT_COPYFILES); do echo "$$i"; done; \
        } >$@.new && mv $@.new $@
+CLEANFILES             += $(LOCAL)/etc/schroot/sbuild.profile/copyfiles
 
-schroot-config:: $(STATE)/etc/schroot/sbuild.profile/nssdatabases
-$(STATE)/etc/schroot/sbuild.profile/nssdatabases: $(schroot-config_STAMP)
+schroot-config:: $(LOCAL)/etc/schroot/sbuild.profile/nssdatabases
+$(LOCAL)/etc/schroot/sbuild.profile/nssdatabases: $(schroot-config_STAMP)
        $(V_AT)mkdir -p $(dir $@)
        $(call v_tag,GEN){ \
          echo "### -*-conf-*- GENERATED by distorted-chroot: do not edit"; \
          for i in $(SCHROOT_NSSDATABASES); do echo "$$i"; done; \
        } >$@.new && mv $@.new $@
+CLEANFILES             += $(LOCAL)/etc/schroot/sbuild.profile/nssdatabases
 
-schroot-config:: $(STATE)/etc/schroot/sbuild.profile/fstab
-$(STATE)/etc/schroot/sbuild.profile/fstab: \
+schroot-config:: $(LOCAL)/etc/schroot/sbuild.profile/fstab
+$(LOCAL)/etc/schroot/sbuild.profile/fstab: \
                etc/sbuild.fstab.in $(schroot-config_STAMP)
        $(V_AT)mkdir -p $(dir $@)
        $(call v_tag,SUBST)$(call subst-file,### -*-conf-*-) \
                <$< >$@.new && mv $@.new $@
+CLEANFILES             += $(LOCAL)/etc/schroot/sbuild.profile/fstab
 
-schroot-config:: $(STATE)/etc/sbuild.conf
-$(STATE)/etc/sbuild.conf: etc/sbuild.conf.in $(schroot-config_STAMP)
+check::; $(call check-symlink,WARN,/etc/schroot/sbuild,$(ABSLOCAL)/etc/schroot/sbuild.profile)
+
+schroot-config:: $(LOCAL)/etc/sbuild.conf
+$(LOCAL)/etc/sbuild.conf: etc/sbuild.conf.in $(schroot-config_STAMP)
        $(V_AT)mkdir -p $(dir $@)
        $(call v_tag,SUBST)$(call subst-file,### -*-perl-*-) \
                <$< >$@.new && mv $@.new $@
+CLEANFILES             += $(LOCAL)/etc/sbuild.conf
+check::; $(call check-symlink,WARN,/etc/sbuild/sbuild.conf,$(ABSLOCAL)/etc/sbuild.conf)
+check::; $(call check-executable,WARN,/usr/local.schroot/hacks/apt-get)
+
+SCHROOT_SCRIPTS                += 11private
+SCHROOT_SCRIPTS                += 15binfmt
+SCHROOT_SCRIPTS                += 51chrootenv
+COPY_SCHROOT_SCRIPTS    = $(addprefix $(LOCAL)/etc/schroot/setup.d/,$(SCHROOT_SCRIPTS))
+schroot-config:: $(COPY_SCHROOT_SCRIPTS)
+$(COPY_SCHROOT_SCRIPTS): \
+               $(LOCAL)/etc/schroot/setup.d/%: etc/schroot-scripts/%
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,COPY)cp $< $@.new && mv $@.new $@
+CLEANFILES             += $(COPY_SCHROOT_SCRIPTS)
+
+CHECK_SCHROOT_SCRIPTS   = $(addprefix check-script/,$(SCHROOT_SCRIPTS))
+check:: $(CHECK_SCHROOT_SCRIPTS)
+$(CHECK_SCHROOT_SCRIPTS): check-script/%:
+       $(call check-symlink,WARN,/etc/schroot/setup.d/$*,$(ABSLOCAL)/etc/schroot/setup.d/$*)
+.PHONY: $(addprefix check-script/,$(SCHROOT_SCRIPTS))
+
+###--------------------------------------------------------------------------
+### Ccache setup.
+
+CCACHE_CONFIGS          = $(foreach r,$(ALL_CHROOTS), \
+       /var/lib/sbuild/build/.ccache/$(LVPREFIX)$r/ccache.conf)
+all:: $(CCACHE_CONFIGS)
+$(CCACHE_CONFIGS): /var/lib/sbuild/build/.ccache/$(LVPREFIX)%/ccache.conf: \
+               etc/ccache.conf
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,COPY)cp $< $@.new && mv $@.new $@
 
 ###--------------------------------------------------------------------------
-### Constructing chroots.
+### `/usr/local/' structure.
+
+LOCAL_COMMON_DIRS       = share/ src/
+LOCAL_EXTRA_DIRS        = share/man/
+all:: $(foreach d,$(LOCAL_COMMON_DIRS) $(LOCAL_EXTRA_DIRS),$(LOCAL)/$d)
+$(foreach d,$(LOCAL_COMMON_DIRS) $(LOCAL_EXTRA_DIRS),$(LOCAL)/$d):
+       $(V_AT)mkdir -p $(dir $(patsubst %/,%,$@))
+       $(call v_tag,MKDIR)mkdir $@
+
+LOCAL_ARCH_DIRS                 = bin/ etc/ games/ include/ include.aside/
+LOCAL_ARCH_DIRS                += lib/ libexec/ sbin/
+LOCAL_ARCH_LINKS        = man
+man_LINKDEST            = share/man
+all:: $(foreach a,$(ALL_ARCHS),\
+       $(LOCAL)/$a/ \
+       $(foreach d,$(LOCAL_ARCH_DIRS),$(LOCAL)/$a/$d) \
+       $(foreach d,$(LOCAL_ARCH_LINKS),$(LOCAL)/$a/$d) \
+       $(foreach d,$(LOCAL_COMMON_DIRS),$(LOCAL)/$a/$d))
+
+$(foreach a,$(ALL_ARCHS),$(LOCAL)/$a/):
+       $(call v_tag,MKDIR)mkdir $@
+$(foreach a,$(ALL_ARCHS),\
+  $(foreach d,$(LOCAL_ARCH_DIRS),$(LOCAL)/$a/$d)):
+       $(V_AT)mkdir -p $(dir $(patsubst %/,%,$@))
+       $(call v_tag,MKDIR)mkdir $@
+$(foreach a,$(ALL_ARCHS),\
+  $(foreach d,$(LOCAL_ARCH_LINKS),$(LOCAL)/$a/$d)): \
+               $$(if $$(call symlink-ok-p,$$@,$$($$(notdir $$@)_LINKDEST)),,_force)
+       $(V_AT)mkdir -p $(notdir $@)
+       $(call v_tag,SYMLINK)ln -sf $($(notdir $@)_LINKDEST) $@
+$(foreach a,$(ALL_ARCHS),\
+  $(foreach d,$(LOCAL_COMMON_DIRS),$(LOCAL)/$a/$d)): \
+               $$(if $$(call symlink-ok-p,$$@,../$$(notdir $$(patsubst %/,%,$$@))),,_force)
+       $(V_AT)mkdir -p $(dir $(patsubst %/,%,$@))
+       $(call v_tag,SYMLINK)ln -sf ../$(notdir $(patsubst %/,%,$@)) $(patsubst %/,%,$@)
+
+###--------------------------------------------------------------------------
+### Main chroot maintenance.
+
+OPTS                    =
+FRESH                   = 1w
+JOBS                    = chroot cross-tools pkg-build
 
-BUILD_CHROOTS           = $(addprefix chroot/,$(ALL_CHROOTS))
-CHROOT_STAMPS           = $(addprefix $(STATE)/stamp/chroot.,$(ALL_CHROOTS))
-all:: setup-chroots
-setup-chroots: $(BUILD_CHROOTS)
-$(BUILD_CHROOTS): chroot/%: $(STATE)/stamp/chroot.%
-.PHONY: setup-chroots $(BUILD_CHROOTS)
+MAINTQ_                         = -q
+MAINTQ_0                = -q
+MAINT                   = +$(call v_tag,MAINT)\
+       PYTHONPATH=$(STATE)/lib/python $(STATE)/bin/chroot-maint \
+               $(MAINTQ_$V) $(OPTS)
 
-$(CHROOT_STAMPS): $(STATE)/stamp/chroot.%: $(STATE)/config.sh bin/mkbuildchroot
-       $(call v_tag,CHROOT)bin/mkbuildchroot \
-               $(call chroot-dist,$*) $(call chroot-arch,$*) \
-               $(call v_log,setup-chroot.$*)
+maint: all check
+       $(MAINT) -f$(FRESH) $(JOBS)
+.PHONY: maint
 
 ###--------------------------------------------------------------------------
-### Installing basic custom software.
+### Running random commands.
 
+CMD                     = echo %d %a
+subst-command           = $(subst %d,$(call chroot-dist,$1), \
+                               $(subst %a,$(call chroot-arch,$1), \
+                                       $(subst %r,$1, $(CMD))))
 
+run: $(foreach c,$(ALL_CHROOTS),run/$c)
+$(foreach c,$(ALL_CHROOTS),run/$c): run/%:
+       $(V_AT)$(call v_print,RUN,$(call subst-command,$*))\
+               $(call subst-command,$*)
 
 ###--------------------------------------------------------------------------
 ### Other maintenance targets.