8 Apr, 2025
Here's a little bash script that will set you up with two repos, local
for your app and localplatform
for your re-usable platform code.
It then allows you to build locally for dev, or to use Docker containers for multi-platform builds and runs.
What is nice is that the same Makefile
is used for local dev and your container, so they should be very close to identical, limiting the oppurtunity for bugs.
Also, make
is much better at managing dependencies that just using docker build
so changes are re-built much faster.
#!/bin/bash
set -e
#############################################
# Create localplatform repository
#############################################
echo "Creating localplatform repository..."
mkdir -p localplatform
cd localplatform
# Create platform.in (the build input)
cat > platform.in << 'EOF'
Platform input content
EOF
# Create localplatform/Makefile using the include approach and adding phony targets,
# with a DEV check and separate build directory based on ARCH.
cat > Makefile << 'EOF'
# localplatform/Makefile
# Determine the directory of this Makefile (so it works whether included or not)
LOCALPLATFORM_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
ifndef ARCH
$(error ARCH is not set. Use ARCH=local for a local build or amd64/arm64 for docker builds)
endif
BUILD_DIR := build/$(ARCH)
# Rule to build platform.txt from platform.in.
# (If platform.in is touched then platform.txt is re-built.)
$(BUILD_DIR)/platform.txt: $(LOCALPLATFORM_DIR)platform.in
@mkdir -p $(BUILD_DIR)
@echo "Building platform.txt for ARCH=$(ARCH)"
cp $(LOCALPLATFORM_DIR)platform.in $(BUILD_DIR)/platform.txt
.PHONY: platform-build
platform-build: $(BUILD_DIR)/platform.txt
.PHONY: platform-clean
platform-clean:
rm -rf $(BUILD_DIR)
# Docker targets – these build images that use a volume mount (in Dockerfile.build)
# so that a subsequent run will not rebuild the file if the source hasn’t changed.
.PHONY: docker-build
docker-build:
ifeq ($(DEV),1)
@echo "DEV mode enabled, skipping working tree check."
else
@if [ -n "$$(git -C $(LOCALPLATFORM_DIR) status --porcelain)" ]; then \
echo "localplatform working tree is dirty. Use DEV=1 to override." >&2; \
exit 1; \
fi
@if [ -n "$$(git status --porcelain)" ]; then \
echo "local repo working tree is dirty. Use DEV=1 to override." >&2; \
exit 1; \
fi
endif
@mkdir -p $(CURDIR)/build/$(ARCH)
# Build the builder image using the Dockerfile.build from the localplatform repo.
docker build --platform linux/$(ARCH) -f $(LOCALPLATFORM_DIR)Dockerfile.build -t localplatform-builder-$(ARCH) "$(CURDIR)"
# Run the builder container with a volume mount so that the build/$(ARCH) directory is created on host.
docker run --rm -e ARCH=$(ARCH) -v $(CURDIR):/src localplatform-builder-$(ARCH)
docker build --build-arg ARCH=$(ARCH) -f $(LOCALPLATFORM_DIR)Dockerfile.runtime -t localplatform-runtime-$(ARCH) "$(CURDIR)"
.PHONY: docker-run-without-build
docker-run-without-build:
# Ensure that the docker-build has been run so that the container exists
docker run --rm localplatform-runtime-$(ARCH)
EOF
# Create Dockerfile.build (for performing the build with volume mounting)
cat > Dockerfile.build << 'EOF'
FROM alpine:latest
RUN apk add --no-cache make git
WORKDIR /src
RUN git config --global --add safe.directory /src/localplatform
RUN git config --global --add safe.directory /src/
# Assume that the repository is mounted into /src
CMD ["make"]
EOF
# Create Dockerfile.runtime (for packaging the built assets)
cat > Dockerfile.runtime << 'EOF'
FROM alpine:latest
ARG ARCH
RUN test -n "$ARCH" || (echo "ARCH is not set!" && exit 1)
WORKDIR /build
# Copy the built assets from the appropriate directory
COPY build/${ARCH} /build
CMD ["cat", "app.txt"]
EOF
# Initialize git repo for localplatform and commit the files
git init .
git add .
git commit -m "Initial commit for localplatform"
cd ..
#############################################
# Create local repository
#############################################
echo "Creating local repository..."
mkdir -p local
cd local
# Create app.in (the build input for the app)
cat > app.in << 'EOF'
App input content
EOF
# Create local/Makefile which includes the submodule's Makefile.
# (Note: after adding the submodule the included file will be at localplatform/Makefile.)
cat > Makefile << 'EOF'
# local/Makefile
# Include localplatform's Makefile from the submodule
include localplatform/Makefile
ifndef ARCH
$(error ARCH is not set. Use ARCH=local for a local build or amd64/arm64 for docker builds)
endif
BUILD_DIR := build/$(ARCH)
.PHONY: all
.DEFAULT_GOAL := all
all: $(BUILD_DIR)/app.txt
$(BUILD_DIR):
@mkdir -p $(BUILD_DIR)
# Rule to build app.txt from the inputs:
# It depends on the platform.in from the submodule and app.in.
$(BUILD_DIR)/app.txt: localplatform/platform.in app.in | $(BUILD_DIR)
@echo "Building app.txt for ARCH=$(ARCH)"
cat localplatform/platform.in app.in > $@
echo $(ARCH) >> $@
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
EOF
# Initialize git repo for local and commit the files
git init .
cat << EOF > .gitignore
# Otherwise the prechecks for a clean working tree will fail
build
EOF
# Add the localplatform repo as a submodule in local.
echo "Adding localplatform as submodule in local..."
GIT_ALLOW_PROTOCOL=file git submodule add ../localplatform localplatform
git add .
git commit -m "Initial commit for local"
cd ..
echo "Bootstrap complete."
echo
echo "Run this to build:"
echo " cd local"
echo " ARCH=local make && cat build/local/app.txt && ARCH=amd64 make docker-build && ARCH=amd64 make docker-run-without-build"
echo
echo "If you run the local or docker make commands again you'll see the file is not rebuilt."
Be the first to comment.
Copyright James Gardner 1996-2020 All Rights Reserved. Admin.