349 lines
11 KiB
Makefile
349 lines
11 KiB
Makefile
########################## Usage ###########################
|
|
|
|
define USAGE
|
|
========================================
|
|
USAGE: make TARGET=musl-toolchain [ARCH=cpu-arch] [program ...]
|
|
The TARGET value must be a musl-cross-make toolchain target.
|
|
The optional ARCH value must be a valid GCC -march CPU type.
|
|
|
|
Examples targets:
|
|
TARGET=arm-linux-musleabi
|
|
TARGET=arm-linux-musleabihf ARCH=armv7-a+fp
|
|
TARGET=mips-linux-musl
|
|
TARGET=mipsel-linux-muslsf
|
|
TARGET=x86_64-linux-musl
|
|
...
|
|
For additional targets, consult the musl manual:
|
|
https://musl.libc.org/doc/1.1.24/manual.html
|
|
|
|
Goals:
|
|
all
|
|
Builds all available programs:
|
|
$(sort $(ALL_PROGRAMS))
|
|
default
|
|
Builds default subset of programs:
|
|
$(sort $(DEFAULT_PROGRAMS))
|
|
musl
|
|
Builds the cross-compiler toolchain for TARGET.
|
|
archlist
|
|
Shows available CPU architectures for TARGET.
|
|
env
|
|
Shows shell commands to activate TARGET toolchain.
|
|
usage
|
|
Shows this message.
|
|
mostlyclean
|
|
Removes source code and temporary objects.
|
|
clean
|
|
Removes cross-compiler toolchain, sources, and objects.
|
|
========================================
|
|
endef
|
|
|
|
required_features := else-if order-only second-expansion target-specific
|
|
missing_features := $(filter-out $(.FEATURES),$(required_features))
|
|
ifneq (,$(missing_features))
|
|
$(error This version of make is missing required features: $(required_features))
|
|
endif
|
|
|
|
|
|
|
|
########################## Flags ###########################
|
|
|
|
# We need access to `command -v` to check if programs exist.
|
|
SHELL := /bin/bash
|
|
|
|
CFLAGS = -g0 -Os
|
|
CXXFLAGS = -g0 -Os
|
|
|
|
# Just in case the user forgets that we're doing static builds.
|
|
override LDFLAGS += -static
|
|
override CFLAGS += -static $(if $(ARCH),-march=$(ARCH))
|
|
override CXXFLAGS += -static $(if $(ARCH),-march=$(ARCH))
|
|
|
|
# Some builds need to be explicitly given these paths.
|
|
override LDFLAGS += -L$(SYSROOT)/lib
|
|
override CFLAGS += -I$(SYSROOT)/include
|
|
override CXXFLAGS += -I$(SYSROOT)/include
|
|
|
|
# Attempt to make builds reproducible.
|
|
# For most builds, this will get you a byte-for-byte identical output
|
|
# regardless of which machine you cross-compiled from. Failing that,
|
|
# two builds from the same build machine are identical.
|
|
ifneq (0,$(REPRODUCIBLE))
|
|
export SOURCE_DATE_EPOCH := 0
|
|
override CFLAGS += -ffile-prefix-map=$(MAKEFILE_DIR)=.
|
|
override CXXFLAGS += -ffile-prefix-map=$(MAKEFILE_DIR)=.
|
|
endif
|
|
|
|
# Intermediate files will be larger and build times will be slightly longer
|
|
# but the final binary can sometimes be much smaller.
|
|
ifneq (0,$(EXTRA_SMALL))
|
|
override LDFLAGS += -Wl,--gc-sections
|
|
override CFLAGS += -ffunction-sections -fdata-sections
|
|
override CXXFLAGS += -ffunction-sections -fdata-sections
|
|
endif
|
|
|
|
# The download command should take two extra arguments: OUTPUT_FILE URL
|
|
ifneq (,$(shell command -v curl))
|
|
DOWNLOAD := curl --silent --show-error -L -o
|
|
else ifneq (,$(shell command -v wget))
|
|
DOWNLOAD := wget --no-verbose -c -O
|
|
else
|
|
$(error No curl or wget detected, please manually specify the DOWNLOAD command.)
|
|
endif
|
|
|
|
# LibreSSL is a drop-in replacement for OpenSSL that's smaller and easier to build.
|
|
OPENSSL := libressl
|
|
# OPENSSL := openssl
|
|
|
|
BUILD_TRIPLE := $(shell $(filter-out --target%,$(CC)) -dumpmachine 2>/dev/null)
|
|
CONFIGURE_DEFAULTS = --build="$(BUILD_TRIPLE)" --host="$(TARGET)" --prefix="$(SYSROOT)"
|
|
|
|
|
|
########################## Paths ###########################
|
|
|
|
# NOTE: these paths need to be absolute.
|
|
# All other paths are built from these, including the toolchain binaries.
|
|
# A relative path would be useless once we `cd` into a source code directory.
|
|
MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
|
|
MAKEFILE_DIR := $(patsubst %/,%,$(dir $(MAKEFILE_PATH)))
|
|
|
|
SOURCE_ROOT := $(MAKEFILE_DIR)/sources
|
|
WORK_ROOT := $(MAKEFILE_DIR)/work
|
|
OUTPUT_ROOT := $(MAKEFILE_DIR)/output
|
|
TOOLCHAIN_ROOT := $(MAKEFILE_DIR)/sysroot
|
|
|
|
SYSROOT := $(TOOLCHAIN_ROOT)/$(TARGET)
|
|
OUTPUT := $(OUTPUT_ROOT)/$(TARGET)
|
|
PKG_CONFIG_PATH := $(TOOLCHAIN_ROOT)/lib/pkgconfig
|
|
|
|
CMAKE_DEFAULTS = -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX=$(SYSROOT) -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_FIND_ROOT_PATH=$(TOOLCHAIN_ROOT) -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY
|
|
# Having whitespace in our build paths _will_ result in failures.
|
|
# In addition to failures, a path containing whitespace may cause an
|
|
# improperly quoted $(RM) to delete things outside of the build directory.
|
|
ifneq (1,$(words $(MAKEFILE_DIR)))
|
|
$(error Whitespace detected in build path. This _will_ result in build failures.)
|
|
endif
|
|
|
|
|
|
######################## Functions #########################
|
|
|
|
# These "activate" functions are meant to be used with $(eval $(call ...))
|
|
define activate_paths
|
|
$(1): export SYSROOT=$(SYSROOT)
|
|
$(1): export PREFIX=$(SYSROOT)
|
|
$(1): export PKG_CONFIG_PATH=$(PKG_CONFIG_PATH)
|
|
endef
|
|
|
|
define activate_toolchain
|
|
$(call activate_paths,$(1))
|
|
$(1): export HOSTCC=zig cc
|
|
$(1): export HOSTCXX=zig c++
|
|
$(1): export AR=/opt/homebrew/bin/zig ar
|
|
$(1): export AS=llvm-as
|
|
$(1): export CC=/opt/homebrew/bin/zig cc --target=$(TARGET)
|
|
$(1): export CXX=/opt/homebrew/bin/zig c++ --target=$(TARGET)
|
|
# $(1): export LD=llvm-ld
|
|
$(1): export NM=llvm-nm
|
|
$(1): export OBJCOPY=/opt/homebrew/bin/zig objcopy
|
|
$(1): export OBJDUMP=llvm-objdump
|
|
$(1): export RANLIB=/opt/homebrew/bin/zig ranlib
|
|
$(1): export READELF=llvm-readelf
|
|
$(1): export STRIP=llvm-strip
|
|
endef
|
|
|
|
# Downloads and unpacks a tar file.
|
|
define tar_to_tar_zstd
|
|
mkdir -p "$(dir $(2))"
|
|
$(DOWNLOAD) "$(2).download" "$(1)"
|
|
mkdir -p "$(2).tmp"
|
|
tar --strip-components=1 -C "$(2).tmp" -xf "$(2).download"
|
|
tar --zstd -C "$(2).tmp" -cf "$(2)" "." || ($(RM) "$(2)" ; exit 1)
|
|
$(RM) -rf "$(2).download" "$(2).tmp"
|
|
endef
|
|
|
|
# Creates variables based on library names.
|
|
# This makes it easier for packages to depend on libraries
|
|
# that will be created by other packages.
|
|
# For example $(eval $(call export_library,/path/to/libsomething.a))
|
|
# will set libsomething := /path/to/libsomething.a
|
|
define export_library
|
|
$(basename $(notdir $(1))) := $(1)
|
|
endef
|
|
|
|
# Creates generic recipe chains for a package's binaries and libraries.
|
|
# This is where the magic happens!
|
|
# This would have been much cleaner to create with $(file <template.mak)
|
|
# but unfortunately the function wasn't added until GNU Make 4.0.
|
|
all_recipes :=
|
|
define create_recipes
|
|
name := $(strip $(1))
|
|
version := $(strip $(2))
|
|
url := $(strip $(3))
|
|
bin_names := $(notdir $(strip $(4)))
|
|
lib_names := $(notdir $(strip $(5)))
|
|
|
|
ifeq (,$$(name))
|
|
$$(error Package name cannot be empty)
|
|
else ifeq (,$$(version))
|
|
$$(error Package version for $$(name) cannot be empty)
|
|
else ifeq (,$$(url))
|
|
$$(error Package url for $$(name) cannot be empty)
|
|
else ifeq (,$$(bin_names)$$(lib_names))
|
|
$$(error The $$(name) package must provide at least one binary or library)
|
|
else ifneq (,$$(filter $$(name),$$(all_recipes)))
|
|
$$(error A recipe for $$(name) has already been created.)
|
|
else
|
|
all_recipes += $$(name)
|
|
endif
|
|
|
|
orig_src := $$(SOURCE_ROOT)/$$(name)-$$(version).tar.zstd
|
|
work_src := $$(WORK_ROOT)/$$(name)-$$(version)
|
|
src := $$(work_src)
|
|
bin_paths := $$(addprefix $$(OUTPUT)/bin/,$$(bin_names))
|
|
lib_paths := $$(addprefix $$(SYSROOT)/lib/,$$(lib_names))
|
|
|
|
# Export library names as variables for other recipes to depend on.
|
|
$$(foreach lib,$$(lib_paths),$$(eval $$(call export_library,$$(lib))))
|
|
|
|
# Building any one of the programs or libraries builds them all.
|
|
.PHONY: $$(bin_names) $$(lib_names)
|
|
$$(bin_names) $$(lib_names): | $$(bin_paths) $$(lib_paths)
|
|
|
|
# Bind variables to all of this package's recipes.
|
|
# These variable names will be reused later by other packages,
|
|
# so binding them here the only way to guarantee the correct value.
|
|
$$(bin_paths) $$(lib_paths): override URL := $$(url)
|
|
$$(bin_paths) $$(lib_paths): override SRC := $$(src)
|
|
$$(bin_paths) $$(lib_paths): override ORIG_SRC := $$(orig_src)
|
|
|
|
# We potentially have multiple output files generated from one recipe.
|
|
# If not handled correctly, building one program from the list can result in the
|
|
# recipe running once per program instead of just once overall.
|
|
# To work around this, we make each binary depend on an intermediate flag.
|
|
# See: https://stackoverflow.com/a/10609434/477563
|
|
BUILD_FLAG := $$(SYSROOT)/$$(name).built
|
|
.INTERMEDIATE: $$(BUILD_FLAG)
|
|
|
|
ifneq (,$$(bin_paths))
|
|
# Binaries need to be copied from SYSROOT/bin/ to OUTPUT/bin/.
|
|
$$(bin_paths): $$(BUILD_FLAG)
|
|
$$(eval $$(call activate_toolchain,$$@))
|
|
mkdir -p "$$(@D)"
|
|
install "$$(SYSROOT)/bin/$$(@F)" "$$@"
|
|
- $$(STRIP) --strip-unneeded "$$@"
|
|
ls -al "$$@"
|
|
endif
|
|
|
|
ifneq (,$$(lib_paths))
|
|
# Libraries are already in their final location.
|
|
$$(lib_paths): $$(BUILD_FLAG) ;
|
|
endif
|
|
|
|
# This is main build recipe that the package's makefile must provide.
|
|
# It should take the source code and output the built programs
|
|
# and libraries into the SYSROOT directory tree.
|
|
# Here we merely provide the recipe definition and base dependencies.
|
|
$$(BUILD_FLAG): $$(src)
|
|
|
|
$$(orig_src):
|
|
echo orig $$(ORIG_SRC)
|
|
$$(call tar_to_tar_zstd,$$(URL),$$@)
|
|
|
|
$$(work_src): $$(orig_src)
|
|
rm -rf $$(SRC)
|
|
mkdir -p $$(SRC)
|
|
tar -C $$(SRC) -xf $$(ORIG_SRC)
|
|
endef
|
|
|
|
# Never implicitly pass this makefile's command-line variables
|
|
# to other instances of make. This prevents TARGET, ARCH, etc,
|
|
# from tainting the other builds. Unfortunately, simply clearing
|
|
# the MAKEOVERRIDES variable isn't enough because make will
|
|
# auto-export any explicitly defined command-line variables.
|
|
define unexport_var
|
|
_var_assignment := $(1)
|
|
_var_parts := $$(subst =, ,$$(_var_assignment))
|
|
_var_name := $$(firstword $$(_var_parts))
|
|
_var_name := $$(subst :, ,$$(_var_name))
|
|
unexport $$(_var_name)
|
|
endef
|
|
$(foreach assignment,$(MAKEOVERRIDES),$(eval $(call unexport_var,$(assignment))))
|
|
MAKEOVERRIDES =
|
|
|
|
|
|
######################### Recipes ##########################
|
|
|
|
# Disable implicit rules.
|
|
.SUFFIXES:
|
|
|
|
# Secondary expansion is required because some programs will
|
|
# depend on library path variables that haven't been defined yet.
|
|
.SECONDEXPANSION:
|
|
|
|
# Don't allow different programs to be built simultaneously,
|
|
# but do allow those programs to compile in parallel.
|
|
# Building programs in parallel makes it much more difficult
|
|
# to notice and diagnose build failure reasons.
|
|
.NOTPARALLEL:
|
|
|
|
# Import all of the individual build components.
|
|
include $(MAKEFILE_DIR)/include/*.mak
|
|
|
|
# If no TARGET was specified, default to the usage guide.
|
|
.DEFAULT_GOAL := $(if $(TARGET),default,usage)
|
|
|
|
.PHONY: all
|
|
all: $(ALL_PROGRAMS)
|
|
ls -al "$(OUTPUT)/bin"
|
|
|
|
.PHONY: default
|
|
default: $(DEFAULT_PROGRAMS)
|
|
ls -al "$(OUTPUT)/bin"
|
|
|
|
.PHONY: help usage
|
|
help usage:
|
|
$(info $(USAGE))
|
|
|
|
# Apparently the help output varies between toolchains so we'll try both.
|
|
.PHONY: archlist
|
|
archlist:
|
|
$(eval $(call activate_toolchain,$@))
|
|
-@ "$(CC)" -march="x" 2>&1 | grep -F "valid arguments" || true
|
|
-@ "$(CC)" --target-help 2>&1 | sed -n '/Known.*-march/,/^$$/p' || true
|
|
|
|
# Cleans all sources except for musl.
|
|
.PHONY: mostlyclean
|
|
mostlyclean:
|
|
- $(RM) -r \
|
|
"$(SOURCE_ROOT)/"*.tgz \
|
|
"$(SOURCE_ROOT)/"*.tmp
|
|
|
|
# Cleans all compiled results.
|
|
.PHONY: clean
|
|
clean: mostlyclean
|
|
ifneq (,$(TARGET))
|
|
- $(RM) -r "$(OUTPUT)"
|
|
- $(RM) -r "$(SYSROOT)"
|
|
- $(RM) -r "$(MAKEFILE_DIR)/docker_context"
|
|
else
|
|
- $(RM) -r "$(OUTPUT_ROOT)"
|
|
- $(RM) -r "$(TOOLCHAIN_ROOT)"
|
|
endif
|
|
|
|
# Cleans musl toolchain artifacts.
|
|
.PHONY: distclean
|
|
distclean: clean
|
|
- $(RM) -r "$(SOURCE_ROOT)"
|
|
|
|
# Dumps the toolchain variables for use in shell environments.
|
|
# Meant to be used as: eval "$(make --silent TARGET=toolchain env)"
|
|
.PHONY: env
|
|
env:
|
|
ifeq (,$(TARGET))
|
|
$(error TARGET is required to dump environment variables)
|
|
endif
|
|
$(info $(subst : ,,$(call activate_toolchain)))
|
|
$(info LDFLAGS='$(LDFLAGS)')
|
|
$(info CFLAGS='$(CFLAGS)')
|
|
$(info CXXFLAGS='$(CXXFLAGS)')
|