This document describes the Melange build process.
The melange yaml file consists of the following components that are key to the build process. Note that this is not
the official or a comprehensive melange.yaml
reference.
package.target-architectures
: describes which architectures to build for (if empty, build for all available archs).package.dependences.runtime
: list of apk packages that need to be available in the final apk package, henceruntime
.environment.contents
: list of apk packages and their source repositories that need to be available during the build, but not in the final apk package.pipeline
: list of steps to execute during the build.
For examples, see the examples directory.
Note that each step in the pipeline
can be a runs
, step, which executes commands, or it can be a uses
step, which
runs yet another pipeline. Each such pipeline optionally can declare that it needs certain apk packages available
at build time to run its steps. For example, the built-in pipeline fetch
needs the wget
package:
needs:
packages:
- wget
The melange build process involves three normally distinct directories.
- Source directory: Location of your sources for building the apk. It defaults to your current directory.
- Guest directory: Directory where the build process will occur, including laying down packages and placing your compiled source.
- Workspace directory: Directory where your sources will be copied over to enable working with, compiling and manipulating them without changing your actual sources.
The usage of the directories is:
- Create a guest directory, normally in
/tmp
space, and lay out all of the package contents. - Create the workspace directory, copy your sources over to it.
- Bind-mount the workspace directory into the guest as
{GUEST_DIR}/home/build
. - Run the build process in the guest.
For example, if our source directory is /home/user/src
with 3 files main.go
, go.mod
and go.sum
; our guest
directory is /tmp/guest
with busybox as a dependency, and our workspace is /tmp/ws
, then:
/home/user/src <-- source directory
/home/user/src/main.go <-- original file in source
/home/user/src/go.mod <-- original file in source
/home/user/src/go.sum <-- original file in source
/tmp/ws <-- workspace directory, bind-mounted to runner:/home/build
/tmp/ws/main.go <-- copied from source directory
/tmp/ws/go.mod <-- copied from source directory
/tmp/ws/go.sum <-- copied from source directory
/tmp/guest <-- temporary guest directory created by melange
/tmp/guest/bin <-- files and dirs created by apk package dependencies
/tmp/guest/bin/busybox <-- files and dirs created by apk package dependencies
/tmp/guest/home/build <-- bind-mounted from workspace at /tmp/ws
/tmp/guest/home/build/main.go <-- file bind-mounted from workspace at /tmp/ws
/tmp/guest/home/build/go.mod <-- file bind-mounted from workspace at /tmp/ws
/tmp/guest/home/build/go.sum <-- file bind-mounted from workspace at /tmp/ws
Details of how the above happens follows below.
Note that because the workspace is bind-mounted into the guest, changes due to the pipeline are persisted in the workspace directory, and not erased when the guest is removed during cleanup. Normally, the workspace is removed as well, but if you set the workspace directory as the same as the source directory, then the workspace is not removed, and all changes due to the build process persist.
The build process is as follows. The core routine is BuildPackage()
.
- Create the temporary working directory, known internally as the "guest directory" or
GuestDir
. - Evaluate each step in the pipeline to see if it has a
needs
section. If so, then add its listed packages to the build time package requirements defined inenvironment.contents
. - Use apko to create a tar stream of the packages listed in
environment.contents
and lay them out onto the workspace directory. - Overlay
/bin/sh
. This is an optimization step, and is not discussed here. Read Shell Overlay for more information. - Populate the build cache. This is an optimization step, and is not discussed here. Read Build Cache for more information.
- Create the workspace directory and bind-mount it into the guest at
/home/build
. - Populate the workspace. This copies over all of the files from the source directory to the workspace. Note that some files or directories can be excluded or ignored from copying to the workspace.
- Execute each step in the pipelines inside the workspace. This is done by:
- Checking if the step is a
uses
. If so, executeRun()
on it. - If it is a
runs
, then execute the commands in the step.
- Checking if the step is a
- Build any subpackages using the same process.
- Emit the final apk package as a
.apk
file. - Emit any subpackages as
.apk
files. - Clean up guest and workspace directories.
- If requested an index, generate and sign
APKINDEX
.
All of the build takes place within the guest directory. While apk packages can be simply laid out,
the pipeline runs steps may modify files outside of the workspace directory. They further may assume that
certain dependencies are in "standard" locations, e.g. /usr/bin/gcc
, while the actual installation
is relative to the workspace directory, e.g. ${GUEST_DIR}/usr/bin/gcc
.
To resolve this issue and contain the build, all run commands are executed inside a virtual container created by bubblewrap/. The root filesystem for this container is the guest directory, placing everything in its proper location.
bubblewrap, or the bwrap
command, itself is used when the actual runs
command in each pipeline is executed.
When melange builds for the architecture on which it is running - amd64 on amd64, arm64 on arm64, riscv64 on riscv64
etc. - all runs
commands inside pipelines run natively on the processor.
On the other hand, when building for alternative architecture, e.g. for arm64 while on amd64, the commands are run using binfmt_misc user-mode emulation.
melange does not need to do anything to make this work, provided binfmt_misc
is installed on the host system.