346 lines
7.8 KiB
Go

package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"text/template"
)
const dockerFileContents = `
FROM debian:bookworm
RUN apt-get update && apt-get install -y \
{{ if (eq .Cross "arm64") -}}
crossbuild-essential-arm64 \
{{ end -}}
build-essential bc libssl-dev bison flex libelf-dev ncurses-dev ca-certificates zstd kmod python3
COPY gokr-rebuild-kernel /usr/bin/gokr-rebuild-kernel
COPY config.addendum.txt /usr/src/config.addendum.txt
{{- range $idx, $path := .Patches }}
COPY {{ $path }} /usr/src/{{ $path }}
{{- end }}
RUN echo 'builduser:x:{{ .Uid }}:{{ .Gid }}:nobody:/:/bin/sh' >> /etc/passwd && \
chown -R {{ .Uid }}:{{ .Gid }} /usr/src
USER builduser
WORKDIR /usr/src
ENV GOKRAZY_IN_DOCKER=1
ENTRYPOINT ["/usr/bin/gokr-rebuild-kernel"]
`
var dockerFileTmpl = template.Must(template.New("dockerfile").
Funcs(map[string]interface{}{
"basename": func(path string) string {
return filepath.Base(path)
},
}).
Parse(dockerFileContents))
func copyFile(dest, src string) error {
log.Printf("copyFile(dest=%s, src=%s)", dest, src)
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
n, err := io.Copy(out, in)
if err != nil {
return err
}
log.Printf(" -> %d bytes copied", n)
st, err := in.Stat()
if err != nil {
return err
}
if err := out.Chmod(st.Mode()); err != nil {
return err
}
return out.Close()
}
func find(filename string) (string, error) {
if _, err := os.Stat(filename); err == nil {
return filename, nil
}
return "", fmt.Errorf("could not find file %q", filename)
}
func getContainerExecutable() (string, error) {
// Probe podman first, because the docker binary might actually
// be a thin podman wrapper with podman behavior.
choices := []string{"podman", "docker"}
for _, exe := range choices {
p, err := exec.LookPath(exe)
if err != nil {
continue
}
resolved, err := filepath.EvalSymlinks(p)
if err != nil {
return "", err
}
return resolved, nil
}
return "", fmt.Errorf("none of %v found in $PATH", choices)
}
func rebuildKernel() error {
overwriteContainerExecutable := flag.String("overwrite_container_executable",
"",
"E.g. docker or podman to overwrite the automatically detected container executable")
keepBuildContainer := flag.Bool("keep_build_container",
false,
"do not delete build container after building the kernel")
cross := flag.String("cross",
"",
"if non-empty, cross-compile for the specified arch (one of 'arm64')")
flavor := flag.String("flavor",
"vanilla",
"which kernel flavor to build. one of vanilla (kernel.org) or raspberrypi (https://github.com/raspberrypi/linux/tags)")
dtbs := flag.String("dtbs",
"raspberrypi",
"which device tree files (.dtb files) to copy. 'raspberrypi' or empty")
flag.Parse()
if *cross != "" && *cross != "arm64" {
return fmt.Errorf("invalid -cross value %q: expected one of 'arm64'")
}
abs, err := os.Getwd()
if err != nil {
return err
}
if !strings.HasSuffix(strings.TrimSuffix(abs, "/"), "/_build") {
return fmt.Errorf("gokr-rebuild-kernel is not run from a _build directory")
}
series, err := os.ReadFile("series")
if err != nil {
return err
}
patches := strings.Split(strings.TrimSpace(string(series)), "\n")
executable, err := getContainerExecutable()
if err != nil {
return err
}
if *overwriteContainerExecutable != "" {
executable = *overwriteContainerExecutable
}
execName := filepath.Base(executable)
var patchPaths []string
for _, filename := range patches {
path, err := find(filename)
if err != nil {
return err
}
patchPaths = append(patchPaths, path)
}
kernelPath, err := find("../vmlinuz")
if err != nil {
return err
}
libPath, err := find("../lib")
if err != nil {
return err
}
// TODO: just ensure the file exists, i.e. we are in _build
if _, err := find("config.addendum.txt"); err != nil {
return err
}
u, err := user.Current()
if err != nil {
return err
}
upstreamURL, err := os.ReadFile("upstream-url.txt")
if err != nil {
return err
}
dockerFile, err := os.Create("Dockerfile")
if err != nil {
return err
}
if err := dockerFileTmpl.Execute(dockerFile, struct {
Uid string
Gid string
Patches []string
Cross string
}{
Uid: u.Uid,
Gid: u.Gid,
Patches: patches,
Cross: *cross,
}); err != nil {
return err
}
if err := dockerFile.Close(); err != nil {
return err
}
log.Printf("building %s container for kernel compilation", execName)
dockerBuild := exec.Command(execName,
"build",
// "--platform=linux/amd64",
"--rm=true",
"--tag=gokr-rebuild-kernel",
".")
dockerBuild.Stdout = os.Stdout
dockerBuild.Stderr = os.Stderr
log.Printf("%v", dockerBuild.Args)
if err := dockerBuild.Run(); err != nil {
return fmt.Errorf("%s build: %v (cmd: %v)", execName, err, dockerBuild.Args)
}
log.Printf("compiling kernel")
var dockerRun *exec.Cmd
dockerArgs := []string{
"run",
// "--platform=linux/amd64",
"--volume", abs + ":/tmp/buildresult:Z",
}
if !*keepBuildContainer {
dockerArgs = append(dockerArgs, "--rm")
}
if execName == "podman" {
dockerArgs = append(dockerArgs, "--userns=keep-id")
}
dockerArgs = append(dockerArgs,
"gokr-rebuild-kernel",
"-cross="+*cross,
"-flavor="+*flavor,
strings.TrimSpace(string(upstreamURL)))
dockerRun = exec.Command(executable, dockerArgs...)
dockerRun.Stdout = os.Stdout
dockerRun.Stderr = os.Stderr
log.Printf("%v", dockerRun.Args)
if err := dockerRun.Run(); err != nil {
return fmt.Errorf("%s run: %v (cmd: %v)", execName, err, dockerRun.Args)
}
if err := copyFile(kernelPath, "vmlinuz"); err != nil {
return err
}
// remove symlinks that only work when source/build directory are present
for _, subdir := range []string{"build", "source"} {
matches, err := filepath.Glob(filepath.Join("lib/modules", "*", subdir))
if err != nil {
return err
}
for _, match := range matches {
log.Printf("removing build/source symlink %s", match)
if err := os.Remove(match); err != nil {
return err
}
}
}
// replace kernel modules directory
rm := exec.Command("rm", "-rf", filepath.Join(libPath, "modules"))
rm.Stdout = os.Stdout
rm.Stderr = os.Stderr
log.Printf("%v", rm.Args)
if err := rm.Run(); err != nil {
return fmt.Errorf("%v: %v", rm.Args, err)
}
cp := exec.Command("cp", "-r", filepath.Join("lib/modules"), libPath)
cp.Stdout = os.Stdout
cp.Stderr = os.Stderr
log.Printf("%v", cp.Args)
if err := cp.Run(); err != nil {
return fmt.Errorf("%v: %v", cp.Args, err)
}
if *cross == "arm64" {
if *dtbs != "" {
// replace device tree files
rm = exec.Command("sh", "-c", "rm ../*.dtb")
rm.Stdout = os.Stdout
rm.Stderr = os.Stderr
log.Printf("%v", rm.Args)
if err := rm.Run(); err != nil {
return fmt.Errorf("%v: %v", rm.Args, err)
}
cp = exec.Command("sh", "-c", "cp *.dtb ..")
cp.Stdout = os.Stdout
cp.Stderr = os.Stderr
log.Printf("%v", cp.Args)
if err := cp.Run(); err != nil {
return fmt.Errorf("%v: %v", cp.Args, err)
}
}
if *flavor == "raspberrypi" {
// replace overlays directory
overlaysPath, err := find("../overlays")
if err != nil {
return err
}
rm = exec.Command("rm", "-rf", overlaysPath)
rm.Stdout = os.Stdout
rm.Stderr = os.Stderr
log.Printf("%v", rm.Args)
if err := rm.Run(); err != nil {
return fmt.Errorf("%v: %v", rm.Args, err)
}
cp = exec.Command("cp", "-r", "overlays", overlaysPath)
cp.Stdout = os.Stdout
cp.Stderr = os.Stderr
log.Printf("%v", cp.Args)
if err := cp.Run(); err != nil {
return fmt.Errorf("%v: %v", cp.Args, err)
}
}
}
return nil
}
func main() {
if os.Getenv("GOKRAZY_IN_DOCKER") == "1" {
indockerMain()
} else {
if err := rebuildKernel(); err != nil {
log.Fatal(err)
}
}
}