safing-portmaster/Earthfile
2024-03-29 11:07:55 +01:00

486 lines
16 KiB
Text

VERSION --arg-scope-and-set --global-cache 0.8
ARG --global go_version = 1.21
ARG --global distro = alpine3.18
ARG --global node_version = 18
ARG --global outputDir = "./dist"
# The list of rust targets we support. They will be automatically converted
# to GOOS, GOARCH and GOARM when building go binaries. See the +RUST_TO_GO_ARCH_STRING
# helper method at the bottom of the file.
ARG --global architectures = "x86_64-unknown-linux-gnu" \
"aarch64-unknown-linux-gnu" \
"x86_64-pc-windows-gnu"
# Compile errors here:
# "armv7-unknown-linux-gnueabihf" \
# "arm-unknown-linux-gnueabi" \
# Import the earthly rust lib since it already provides some useful
# build-targets and methods to initialize the rust toolchain.
IMPORT github.com/earthly/lib/rust:3.0.2 AS rust
go-deps:
FROM golang:${go_version}-${distro}
WORKDIR /go-workdir
# We need the git cli to extract version information for go-builds
RUN apk add git
# These cache dirs will be used in later test and build targets
# to persist cached go packages.
#
# NOTE: cache only gets persisted on successful builds. A test
# failure will prevent the go cache from being persisted.
ENV GOCACHE = "/.go-cache"
ENV GOMODCACHE = "/.go-mod-cache"
# Copying only go.mod and go.sum means that the cache for this
# target will only be busted when go.mod/go.sum change. This
# means that we can cache the results of 'go mod download'.
COPY go.mod .
COPY go.sum .
RUN go mod download
go-base:
FROM +go-deps
# Only copy go-code related files to improve caching.
# (i.e. do not rebuild go if only the angular app changed)
COPY cmds ./cmds
COPY runtime ./runtime
COPY service ./service
COPY spn ./spn
# The cmds/notifier embeds some icons but go:embed is not allowed
# to leave the package directory so there's a small go-package in
# assets. Once we drop the notify in favor of the tauri replacement
# we can remove the following line and also remove all go-code from
# ./assets
COPY assets ./assets
# Copy the git folder and extract version information
COPY .git ./.git
LET version = $(git tag --points-at)
IF [ "${version}" = "" ]
SET version = $(git describe --tags --abbrev=0 || echo "dev_build")
END
ENV VERSION="${version}"
LET source = $( ( git remote -v | cut -f2 | cut -d" " -f1 | head -n 1 ) || echo "unknown_source" )
ENV SOURCE="${source}"
# updates all go dependencies and runs go mod tidy, saving go.mod and go.sum locally.
update-go-deps:
FROM +go-base
RUN go get -u ./..
RUN go mod tidy
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT --if-exists go.sum AS LOCAL go.sum
# mod-tidy runs 'go mod tidy', saving go.mod and go.sum locally.
mod-tidy:
FROM +go-base
RUN go mod tidy
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT --if-exists go.sum AS LOCAL go.sum
# build-go runs 'go build ./cmds/...', saving artifacts locally.
# If --CMDS is not set, it defaults to building portmaster-start, portmaster-core and hub
build-go:
FROM +go-base
# Arguments for cross-compilation.
ARG GOOS=linux
ARG GOARCH=amd64
ARG GOARM
ARG CMDS=portmaster-start portmaster-core hub notifier
CACHE --sharing shared "$GOCACHE"
CACHE --sharing shared "$GOMODCACHE"
RUN mkdir /tmp/build
ENV CGO_ENABLED = "0"
IF [ "${CMDS}" = "" ]
LET CMDS=$(ls -1 "./cmds/")
END
# Build all go binaries from the specified in CMDS
FOR bin IN $CMDS
RUN go build -ldflags="-X github.com/safing/portbase/info.version=${VERSION} -X github.com/safing/portbase/info.buildSource=${SOURCE}" -o "/tmp/build/" ./cmds/${bin}
END
DO +GO_ARCH_STRING --goos="${GOOS}" --goarch="${GOARCH}" --goarm="${GOARM}"
FOR bin IN $(ls -1 "/tmp/build/")
SAVE ARTIFACT "/tmp/build/${bin}" AS LOCAL "${outputDir}/${GO_ARCH_STRING}/${bin}"
END
SAVE ARTIFACT "/tmp/build/" ./output
# Test one or more go packages.
# Run `earthly +test-go` to test all packages
# Run `earthly +test-go --PKG="service/firewall"` to only test a specific package.
# Run `earthly +test-go --TESTFLAGS="-short"` to add custom flags to go test (-short in this case)
test-go:
FROM +go-base
ARG GOOS=linux
ARG GOARCH=amd64
ARG GOARM
ARG TESTFLAGS
ARG PKG="..."
CACHE --sharing shared "$GOCACHE"
CACHE --sharing shared "$GOMODCACHE"
FOR pkg IN $(go list -e "./${PKG}")
RUN go test -cover ${TESTFLAGS} ${pkg}
END
test-go-all-platforms:
FROM alpine:3.18
FOR arch IN ${architectures}
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
BUILD +test-go --GOARCH="${GOARCH}" --GOOS="${GOOS}" --GOARM="${GOARM}"
END
# Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms
build-go-release:
FROM alpine:3.18
FOR arch IN ${architectures}
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${arch}"
IF [ -z GOARCH ]
RUN echo "Failed to extract GOARCH for ${arch}"; exit 1
END
IF [ -z GOOS ]
RUN echo "Failed to extract GOOS for ${arch}"; exit 1
END
BUILD +build-go --GOARCH="${GOARCH}" --GOOS="${GOOS}" --GOARM="${GOARM}"
END
# Builds all binaries from the cmds/ folder for linux/windows AMD64
# Most utility binaries are never needed on other platforms.
build-utils:
BUILD +build-go --CMDS="" --GOARCH=amd64 --GOOS=linux
BUILD +build-go --CMDS="" --GOARCH=amd64 --GOOS=windows
# Prepares the angular project by installing dependencies
angular-deps:
FROM node:${node_version}
WORKDIR /app/ui
RUN apt update && apt install zip
COPY desktop/angular/package.json .
COPY desktop/angular/package-lock.json .
COPY assets/data ./assets
RUN npm install
# Copies the UI folder into the working container
# and builds the shared libraries in the specified configuration (production or development)
angular-base:
FROM +angular-deps
ARG configuration="production"
COPY desktop/angular/ .
IF [ "${configuration}" = "production" ]
RUN npm run build-libs
ELSE
RUN npm run build-libs:dev
END
# Build an angualr project, zip it and save artifacts locally
angular-project:
ARG --required project
ARG --required dist
ARG configuration="production"
ARG baseHref="/"
FROM +angular-base --configuration="${configuration}"
IF [ "${configuration}" = "production" ]
ENV NODE_ENV="production"
END
RUN ./node_modules/.bin/ng build --configuration ${configuration} --base-href ${baseHref} "${project}"
RUN cwd=$(pwd) && cd "${dist}" && zip -r "${cwd}/${project}.zip" ./
SAVE ARTIFACT "./${project}.zip" AS LOCAL ${outputDir}/${project}.zip
SAVE ARTIFACT "./dist" AS LOCAL ${outputDir}/${project}
# Builds all angular projects
build-angular:
BUILD +angular-project --project=portmaster --dist=./dist --configuration=development --baseHref=/ui/modules/portmaster/
BUILD +angular-project --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=development --baseHref=/
# Build the angular projects (portmaster-UI and tauri-builtin) in production mode
angular-release:
BUILD +angular-project --project=portmaster --dist=./dist --configuration=production --baseHref=/ui/modules/portmaster/
# Build the angular projects (portmaster-UI and tauri-builtin) in dev mode
angular-dev:
BUILD +angular-project --project=portmaster --dist=./dist --configuration=development --baseHref=/ui/modules/portmaster/
# A base target for rust to prepare the build container
rust-base:
FROM rust:1.76-bookworm
RUN dpkg --add-architecture armhf
RUN dpkg --add-architecture arm64
RUN apt-get update -qq
# Tools and libraries required for cross-compilation
RUN apt-get install --no-install-recommends -qq \
autoconf \
autotools-dev \
libtool-bin \
clang \
cmake \
bsdmainutils \
gcc-multilib \
linux-libc-dev \
linux-libc-dev-amd64-cross \
linux-libc-dev-arm64-cross \
linux-libc-dev-armel-cross \
linux-libc-dev-armhf-cross \
build-essential \
curl \
wget \
file \
libsoup-3.0-dev \
libwebkit2gtk-4.1-dev
# Install library dependencies for all supported architectures
# required for succesfully linking.
FOR arch IN amd64 arm64 armhf
RUN apt-get install --no-install-recommends -qq \
libsoup-3.0-0:${arch} \
libwebkit2gtk-4.1-0:${arch} \
libssl3:${arch} \
libayatana-appindicator3-1:${arch} \
librsvg2-bin:${arch} \
libgtk-3-0:${arch} \
libjavascriptcoregtk-4.1-0:${arch} \
libssl-dev:${arch} \
libayatana-appindicator3-dev:${arch} \
librsvg2-dev:${arch} \
libgtk-3-dev:${arch} \
libjavascriptcoregtk-4.1-dev:${arch}
END
# Note(ppacher): I've no idea why we need to explicitly create those symlinks:
# Some how all the other libs work but libsoup and libwebkit2gtk do not create the link file
RUN cd /usr/lib/aarch64-linux-gnu && \
ln -s libwebkit2gtk-4.1.so.0 libwebkit2gtk-4.1.so && \
ln -s libsoup-3.0.so.0 libsoup-3.0.so
RUN cd /usr/lib/arm-linux-gnueabihf && \
ln -s libwebkit2gtk-4.1.so.0 libwebkit2gtk-4.1.so && \
ln -s libsoup-3.0.so.0 libsoup-3.0.so
# For what ever reason trying to install the gcc compilers together with the above
# command makes apt fail due to conflicts with gcc-multilib. Installing in a separate
# step seems to work ...
RUN apt-get install --no-install-recommends -qq \
g++-mingw-w64-x86-64 \
gcc-aarch64-linux-gnu \
gcc-arm-none-eabi \
gcc-arm-linux-gnueabi \
gcc-arm-linux-gnueabihf \
libc6-dev-arm64-cross \
libc6-dev-armel-cross \
libc6-dev-armhf-cross \
libc6-dev-amd64-cross
# Add some required rustup components
RUN rustup component add clippy
RUN rustup component add rustfmt
# Install architecture targets
FOR arch IN ${architectures}
RUN rustup target add ${arch}
END
DO rust+INIT --keep_fingerprints=true
# For now we need tauri-cli 1.5 for bulding
DO rust+CARGO --args="install tauri-cli --version ^1.5.11"
# Required for cross compilation to work.
ENV PKG_CONFIG_ALLOW_CROSS=1
tauri-src:
FROM +rust-base
WORKDIR /app/tauri
# --keep-ts is necessary to ensure that the timestamps of the source files
# are preserved such that Rust's incremental compilation works correctly.
COPY --keep-ts ./desktop/tauri/ .
COPY assets/data ./assets
COPY packaging/linux ./../../packaging/linux
COPY (+angular-project/dist/tauri-builtin --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/") ./../angular/dist/tauri-builtin
WORKDIR /app/tauri/src-tauri
build-tauri:
FROM +tauri-src
ARG --required target
ARG output = ".*/release/(([^\./]+|([^\./]+\.(dll|exe)))|bundle/.*\.(deb|msi|AppImage))"
ARG bundle="none"
# if we want tauri to create the installer bundles we also need to provide all external binaries
# we need to do some magic here because tauri expects the binaries to include the rust target tripple.
# We already knwo that triple because it's a required argument. From that triple, we use +RUST_TO_GO_ARCH_STRING
# function from below to parse the triple and guess wich GOOS and GOARCH we need.
RUN mkdir /tmp/gobuild
RUN mkdir ./binaries
DO +RUST_TO_GO_ARCH_STRING --rustTarget="${target}"
RUN echo "GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} GO_ARCH_STRING=${GO_ARCH_STRING}"
# Our tauri app has externalBins configured so tauri will try to embed them when it finished compiling
# the app. Make sure we copy portmaster-start and portmaster-core in all architectures supported.
# See documentation for externalBins for more information on how tauri searches for the binaries.
COPY (+build-go/output --GOOS="${GOOS}" --CMDS="portmaster-start portmaster-core" --GOARCH="${GOARCH}" --GOARM="${GOARM}") /tmp/gobuild
# Place them in the correct folder with the rust target tripple attached.
FOR bin IN $(ls /tmp/gobuild)
# ${bin$.*} does not work in SET commands unfortunately so we use a shell
# snippet here:
RUN set -e ; \
dest="./binaries/${bin}-${target}" ; \
if [ -z "${bin##*.exe}" ]; then \
dest="./binaries/${bin%.*}-${target}.exe" ; \
fi ; \
cp "/tmp/gobuild/${bin}" "${dest}" ;
END
# Just for debugging ...
RUN ls -R ./binaries
# The following is exected to work but doesn't. for whatever reason cargo-sweep errors out on the windows-toolchain.
#
# DO rust+CARGO --args="tauri build --bundles none --ci --target=${target}" --output="release/[^/\.]+"
#
# For, now, we just directly mount the rust target cache and call cargo ourself.
DO rust+SET_CACHE_MOUNTS_ENV
RUN --mount=$EARTHLY_RUST_TARGET_CACHE cargo tauri build --bundles "${bundle}" --ci --target="${target}"
DO rust+COPY_OUTPUT --output="${output}"
# BUG(cross-compilation):
#
# The above command seems to correctly compile for all architectures we want to support but fails during
# linking since the target libaries are not available for the requested platforms. Maybe we need to download
# the, manually ...
#
# The earthly rust lib also has support for using cross-rs for cross-compilation but that fails due to the
# fact that cross-rs base docker images used for building are heavily outdated (latest = ubunut:16.0, main = ubuntu:20.04)
# which does not ship recent enough glib versions (our glib dependency needs glib>2.70 but ubunut:20.04 only ships 2.64)
#
# The following would use the CROSS function from the earthly lib, this
# DO rust+CROSS --target="${target}"
SAVE ARTIFACT "target/${target}/release/" AS LOCAL "${outputDir}/tauri/${target}"
tauri-release:
FROM alpine:3.18
ARG bundle="none"
FOR arch IN ${architectures}
BUILD +build-tauri --target="${arch}" --bundle="${bundle}"
END
build-all:
BUILD +build-go-release
BUILD +tauri-release
release:
LOCALLY
IF ! git diff --quiet
RUN echo -e "\033[1;31m Refusing to release a dirty git repository. Please commit your local changes first! \033[0m" ; exit 1
END
BUILD +build-all
# Takes GOOS, GOARCH and optionally GOARM and creates a string representation for file-names.
# in the form of ${GOOS}_{GOARCH} if GOARM is empty, otherwise ${GOOS}_${GOARCH}v${GOARM}.
# Thats the same format as expected and served by our update server.
#
# The result is available as GO_ARCH_STRING environment variable in the build context.
GO_ARCH_STRING:
FUNCTION
ARG --required goos
ARG --required goarch
ARG goarm
LET result = "${goos}_${goarch}"
IF [ "${goarm}" != "" ]
SET result = "${goos}_${goarch}v${goarm}"
END
ENV GO_ARCH_STRING="${result}"
# Takes a rust target (--rustTarget) and extracts architecture and OS and arm version
# and finally calls GO_ARCH_STRING.
#
# The result is available as GO_ARCH_STRING environment variable in the build context.
# It also exports GOOS, GOARCH and GOARM environment variables.
RUST_TO_GO_ARCH_STRING:
FUNCTION
ARG --required rustTarget
LET goos=""
IF [ -z "${rustTarget##*linux*}" ]
SET goos="linux"
ELSE
SET goos="windows"
END
LET goarch=""
LET goarm=""
IF [ -z "${rustTarget##*x86_64*}" ]
SET goarch="amd64"
ELSE IF [ -z "${rustTarget##*arm*}" ]
SET goarch="arm"
SET goarm="6"
IF [ -z "${rustTarget##*v7*}" ]
SET goarm="7"
END
ELSE IF [ -z "${rustTarget##*aarch64*}" ]
SET goarch="arm64"
ELSE
RUN echo "GOARCH not detected"; \
exit 1;
END
ENV GOOS="${goos}"
ENV GOARCH="${goarch}"
ENV GOARM="${goarm}"
DO +GO_ARCH_STRING --goos="${goos}" --goarch="${goarch}" --goarm="${goarm}"