summaryrefslogtreecommitdiff
path: root/scripts/bootstrap.sh
blob: d5334b11b08f1cef21b7e011cc70cb1fe750eb25 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#!/bin/sh -e

#===============================================================
# README
#===============================================================
#
# overview
# --------
#
# Given a basic development environment ("Baseline System") that
# contains standard system utilities, this script bootstraps the
# Adélie Linux distribution for any suported target architecture
# (assuming that musl, gcc, etc. have been ported to it) without
# requiring 'root' privileges. The procedure is outlined below:
#
#     - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#                        (unstable, everything provided by user)
#
#     +-----------------+       User-provided tools. Dependency
#     | Baseline System |       of 'mcmtools', which will verify
#     +-----------------+       that these tools are available.
#              |
#        +----------+           Script to build pinned versions
#        | mcmtools |           of common system utilities, a
#        +----------+           host-arch host-libc toolchain, a
#              |                host-arch musl-libc toolchain...
#              |
#     - - - - -|- - - - - - - - - - - - - - - - - - - - - - - -
#              |                (stable versions, unstable libc)
#              |
#       +-------------+         ...and a 'chroot'-able rootfs. A
#       | seed rootfs |         sane, but not clean, environment
#       +-------------+         in which we begin the bootstrap.
#              |
#        +-----------+          Script to build 'PRoot' and its
#        | emulators |          dependencies, as well as static
#        +-----------+          QEMU user binaries. Add to seed.
#              |
#      +---------------+        Static musl libc cross toolchain
#      | musl cross tc |        targeting a given architecture.
#      +---------------+        Output binaries will run via the
#              |                'binfmt_misc' mechanism + QEMU.
#              |
#       +-------------+         Script to build Alpine Package
#       | build tools |         Keeper (APK) and dependencies.
#       +-------------+         All binaries are cross-compiled!
#              |
#              |
#       +-------------+         Script to build the minimum set
#       | seed rootfs |         of tools to start building the
#       +-------------+         "system/" package repository.
#
# mcmtools; a separate script
# that 
# mcmtools provides almost everything needed to build 'abuild',
# and it is a hard dependency for our bootstrap process now.
#
#     https://git.zv.io/toolchains/bootstrap
#
# The output of this step is a host-native toolchain with fixed
# versions of development utilities, and a host-architecture hosted toolchain that cross-compiles to both musl and/or
# a foreign CPU architecture. (Table above prevents this).
#

HERE="$(dirname $(readlink -f ${0}))";
TEMP="$(mktemp -d)";            # do not change if not expert!

#---------------------------------------------------------------
# initialization

usage ()
{
    printf "Usage: %s ARCH\n\n" "${0}";
    cat <<EOF
    ARCH is { aarch64, armv7, ppc64, ppc, x86_64, pmmx }

Optional environment variables:

    MCMTOOLS=/path/to/existing/mcmtools/ **MAY CAUSE DATA LOSS**
EOF
    exit 0;
}

##
# argv[1]: ARCH
#
# ARCH is the Adélie Linux target. This is the first step in the
# porting process, so e.g. mips64, sparc64, riscv64 will need to
# be added to this table when the time comes to port to them.
#
# ARCH is translated to canonical GCC and QEMU machine types.
#
case "${1}" in
#   adelie        gcc         qemu
#   ------        ---         ----
    aarch64)    m=aarch64;  q=aarch64;  ;;
    armv7)      m=armv7l ;  q=arm    ;  ;;
    ppc)        m=ppc    ;  q=ppc    ;  ;;
    ppc64)      m=ppc64  ;  q=ppc64  ;  ;;
    x86_64)     m=x86_64 ;  q=x86_64 ;  ;;
    pmmx)       m=i686   ;  q=i386   ;  ;;
    *) usage ;;
esac
shift;

test ! -n "${m}" && printf "Invoking '%s TARGET' where 'TARGET=%s' is not valid.\n" "${0}" "${m}" && exit 1;

##
# Internal variables. Do not modify this section directly.
#
CHAINS=https://git.zv.io/toolchains;
printf "CHAINS=%s\n" "${CHAINS}";

SYSTEM="-linux-musl";           # we only target musl on Linux
printf "SYSTEM=%s\n" "${SYSTEM}";

NATIVE=$(cc -dumpmachine);      # host arch, host libc
printf "NATIVE=%s\n" "${NATIVE}";

BUILDS="${NATIVE%%-*}${SYSTEM}"; # host arch, musl libc
printf "BUILDS=%s\n" "${BUILDS}";

TARGET="${m}${SYSTEM}";         # ultimate Adélie Linux target
printf "TARGET=%s\n" "${TARGET}";

MTOOLS=${MCMTOOLS:-"${TEMP}/mcmtools"}; # CAREFUL! MAY CAUSE DATA LOSS!
printf "MTOOLS=%s\n" "${MTOOLS}";


#---------------------------------------------------------------
# mcmtools

##
# Allow the user to supply an existing mcmtools installation. It
# is not ideal, but since mcmtools cannot be (easily) relocated,
# allow the user to save some CPU cycles at the cost of adding a
# bunch of tools to their existing installation. A temporary dir
# is used if this environment variable is omitted. Another case
# for providing a custom value is if '/tmp' is mounted weird.
#
if ! test -d "${MTOOLS}/emus/bin"; then # FIXME: no hard code
    cd "${TEMP}";

    test -d bootstrap                                          \
        || git clone ${CHAINS}/bootstrap.git;
    cd bootstrap;
    git checkout 773363265da441f472c207fb59c1acecc8ba2eb4;

    ## seed rootfs
    #
    # This will build absolutely everything that is needed to be
    # self-reliant, except for some build deps for QEMU.
    #
    test -d "${MTOOLS}" ||                                     \
    DEST="${MTOOLS}"                                           \
    ARCH=${BUILDS}                                             \
        ./bootstrap                                            \
        ;
    test -f "${MTOOLS}"/config.mak ||                          \
        cp "${MTOOLS}"/tmp/musl-cross-make/config.mak          \
        "${MTOOLS}"/config.mak                                 \
        ;

    ##
    # emulators
    #
    # Dependencies are built with the mcmtools host toolchain; a
    # reason to not force musl here is in the event that these
    # cannot be built statically and the host libc is different.
    # Our priority is to obtain a functioning 'PRoot' above all.
    #
    # QEMU itself is built inside an Alpine Linux rootfs; we do
    # this because we still need Python 3 to build it. You can
    # manually provide your own static QEMU user binaries and be
    # on your way without Alpine, but it is a good 'PRoot' test.
    #
    test -d "${MTOOLS}/emus/bin" ||                            \
    PATH="${MTOOLS}/host/bin:${MTOOLS}/sys/bin"                \
    DEST="${MTOOLS}"                                           \
        ./prootemu                                             \
        ;
fi


##
# Now we have a musl-targeting toolchain that runs on the host.
# We need to build the same toolchain, but static.
#
# This is required for it to run inside 'PRoot' if it differs in
# architecture.
#
if ! test -d "${MTOOLS}/sys/tc/cross"; then # FIXME: no hard code
    cd "${TEMP}";

    test -d musl-cross-make                                    \
        || git clone ${CHAINS}/musl-cross-make.git;
    cd musl-cross-make;
    git pull; # always use the latest

    cp "${MTOOLS}"/config.mak config.mak;

    ## musl cross tc
    #
    # Build this toolchain statically using the musl toolchain
    # from the seed rootfs so that it is known to work correctly
    # (the original musl toolchain itself may itself be linked
    # with glibc or be unsafe to use in some contexts).
    #
    PATH="${MTOOLS}/musl/bin:${MTOOLS}/sys/bin" \
        ./scripts/buildcross ${TARGET} \
        ;
    cd output;
    mkdir -p "${MTOOLS}"/sys/tc/cross;
    tar -C "${MTOOLS}"/sys/tc/cross \
        --strip-components=1 \
        -xzf ${TARGET}-cross.tgz \
        ;
fi


##
# Finish setting up the rootfs to support what we're doing.
#
# FIXME: what of this is actually needed?
#
(
    cd "${MTOOLS}"/sys;
    mkdir -p dev;
    mkdir -p proc;
    mkdir -p sys;
    rm -fr usr;
    ln -s . usr;
)

##
# Build 'abuild', its dependencies, and other utilities.
# Once finished, add them to PATH.
#

PROOT_NO_SECCOMP=1 \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/tc/cross/bin \
CROSS_COMPILE=${TARGET}- \
SHELL=/bin/sh \
DEST=/usr/local \
CURL_CA_BUNDLE=/cacert.pem \
${MTOOLS}/sys/emus/bin/proot \
    -S "${MTOOLS}"/sys \
    -q "${MTOOLS}"/sys/emus/bin/qemu-${q} \
    -b "${HERE}" \
    "${HERE}"/setup-abuild \
    ;

echo ok
exit 0;

export PATH="${MTOOLS}/abuild/bin:${PATH}";
bash

##
# Additional configuration.
#
export PACKAGER="Zach van Rijn <me@zv.io>";
export CBUILD=${TARGET_ARCH};
export CBUILDROOT="${MCMTOOLS}/abuild/${TARGET_ARCH}";
export ABUILD_USERDIR="${CBUILDROOT}.conf";
export SRCDEST="${MCMTOOLS}/abuild/src";
export REPODEST="${MCMTOOLS}/abuild/apk";
export ABUILD_APK_INDEX_OPTS="--allow-untrusted"; # FIXME
#export BUILD_ROOT="${CBUILDROOT}";

SUDO_APK=abuild-apk

# get abuild configurables
[ -e "${MCMTOOLS}/abuild/share/abuild/functions.sh" ] || (echo "abuild not found" ; exit 1)
CBUILDROOT="$(CTARGET=$TARGET_ARCH . ${MCMTOOLS}/abuild/share/abuild/functions.sh ; echo $CBUILDROOT)"
. "${MCMTOOLS}/abuild/share/abuild/functions.sh"
[ -z "$CBUILD_ARCH" ] && die "abuild is too old (use 2.29.0 or later)"
[ -z "$CBUILDROOT" ] && die "CBUILDROOT not set for $TARGET_ARCH"

# deduce aports directory
[ -z "$APORTS" ] && APORTS=$(realpath $(dirname $0)/../)
[ -e "$APORTS/system/build-tools" ] || die "Unable to deduce packages directory"

apkbuildname() {
	local repo="${1%%/*}"
	local pkg="${1##*/}"
	[ "$repo" = "$1" ] && repo="system"
	echo $APORTS/$repo/$pkg/APKBUILD
}

msg() {
	[ -n "$quiet" ] && return 0
	local prompt="$GREEN>>>${NORMAL}"
	local name="${BLUE}bootstrap-${TARGET_ARCH}${NORMAL}"
        printf "${prompt} ${name}: %s\n" "$1" >&2
}

if [ -z "$TARGET_ARCH" ]; then
	program=$(basename $0)
	cat <<EOF
usage: $program TARGET_ARCH

This script creates a local cross-compiler, and uses it to
cross-compile an Adélie Linux base system for new architecture.

Steps for introducing new architecture include:
- adding the compiler tripler and arch type to abuild
- adding the arch type detection to apk-tools
- adjusting build rules for packages that are arch aware:
  gcc, musl, binutils, easy-kernel
- create new kernel config for easy-kernel

After these steps the initial cross-build can be completed
by running this with the target arch as parameter, e.g.:
	./$program aarch64

EOF
	return 1
fi


##
# Package signing keys. Public and Private keys are stored in a
# different location; variables for which are installed to arch-
# specific 'abuild.conf' file.
#
if [ ! -d "$CBUILDROOT/etc/apk/keys" ] || [ -n "$(find $CBUILDROOT -type f -name '*.rsa')" ]; then
	msg "Creating sysroot in $CBUILDROOT"
	mkdir -p "$CBUILDROOT/etc/apk/keys"
    abuild-keygen -an;
    p=$(find "${ABUILD_USERDIR}" -type f -name "*.rsa.pub");
    mv "${p}" "$CBUILDROOT/etc/apk/keys";
    grep 1>/dev/null PACKAGER_PUBKEY= "${ABUILD_USERDIR}/abuild.conf" || printf >> "${ABUILD_USERDIR}/abuild.conf" "PACKAGER_PUBKEY=\"%s\"\n" "$CBUILDROOT/etc/apk/keys/${p##*/}";
fi


##
# APK database.
#
if [ ! -f "${CBUILDROOT}/._database-${TARGET_ARCH}" ]; then
    mkdir -p "${CBUILDROOT}/var/log"; # why not created by default?
    ${SUDO_APK} add --quiet --initdb --arch $TARGET_ARCH --root $CBUILDROOT
    touch "${CBUILDROOT}/._database-${TARGET_ARCH}";
fi

msg "Building cross-compiler"

# Build and install cross binutils (--with-sysroot)
CTARGET=$TARGET_ARCH BOOTSTRAP=nobase APKBUILD=$(apkbuildname binutils) abuild -r
exit
if ! CHOST=$TARGET_ARCH BOOTSTRAP=nolibc APKBUILD=$(apkbuildname musl) abuild up2date 2>/dev/null; then
	# C-library headers for target
	CHOST=$TARGET_ARCH BOOTSTRAP=nocc APKBUILD=$(apkbuildname musl) abuild -r

	# Minimal cross GCC
	EXTRADEPENDS_HOST="musl-dev" \
	CTARGET=$TARGET_ARCH BOOTSTRAP=nolibc APKBUILD=$(apkbuildname gcc) abuild -r

	# Cross build bootstrap C-library for the target
	EXTRADEPENDS_BUILD="gcc-pass2-$TARGET_ARCH" \
	CHOST=$TARGET_ARCH BOOTSTRAP=nolibc APKBUILD=$(apkbuildname musl) abuild -r
fi

# Full cross GCC
EXTRADEPENDS_TARGET="musl musl-dev" \
CTARGET=$TARGET_ARCH BOOTSTRAP=nobase APKBUILD=$(apkbuildname gcc) abuild -r

# Cross build-base
CTARGET=$TARGET_ARCH BOOTSTRAP=nobase APKBUILD=$(apkbuildname build-base) abuild -r

msg "Cross building base system"
msg "Change your abuild.conf NOW to avoid build errors!!"
read

# add implicit target prerequisite packages
apk info --quiet --installed --root "$CBUILDROOT" libgcc libstdc++ musl-dev || \
	${SUDO_APK} --root "$CBUILDROOT" add --repository "$REPODEST/system" libgcc libstdc++ musl-dev

# ordered cross-build
for PKG in musl pkgconf zlib \
	   gettext-tiny ncurses bash binutils make bison flex m4 \
	   openssl apk-tools linux-pam shadow \
	   gmp mpfr3 mpc1 isl gcc ca-certificates \
	   openrc libcap-ng coreutils sed gzip bzip2 diffutils \
	   attr libcap patch sudo acl fakeroot libarchive mawk \
	   pax-utils abuild grep findutils patch lzip unzip autoconf automake libtool \
	   ncurses util-linux lvm2 popt xz lddtree libssh2 curl build-tools pcre \
	   debianutils file shimmy procps zsh sharutils net-tools check kbd sysklogd vim db groff libpipeline man-db psmisc less \
	   adelie-base \
	    ; do

	CHOST=$TARGET_ARCH BOOTSTRAP=bootimage APKBUILD=$(apkbuildname $PKG) abuild -r

	case "$PKG" in
	fortify-headers | libc-dev | build-base)
		# headers packages which are implicit but mandatory dependency
		apk info --quiet --installed --root "$CBUILDROOT" $PKG || \
			${SUDO_APK} --update --root "$CBUILDROOT" --repository "$REPODEST/system" add $PKG
		;;
	musl | gcc)
		# target libraries rebuilt, force upgrade
		[ "$(apk upgrade --root "$CBUILDROOT" --repository "$REPODEST/main" --available --simulate | wc -l)" -gt 1 ] &&
			${SUDO_APK} upgrade --root "$CBUILDROOT" --repository "$REPODEST/main" --available
		;;
	esac
done