joeylib2/make/common.mk
2026-10-13 14:01:46 -05:00

171 lines
10 KiB
Makefile

# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# Shared variables used by per-target Makefile fragments.
#
# Source this only via the per-target make file (make/iigs.mk etc), which
# sets PLATFORM first; make/tools.mk and the top Makefile include it without.
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
# Tool paths (AMIGA_CC, LLVM816_ROOT, LIBXMP_DIR, ...) come from the file
# install.sh generates; it is the only place they are defined.
-include $(REPO_DIR)/toolchains/config.mk
# $(call requireTool,FILE,INSTALL-TARGET): stop with a clear message when a
# port's compiler is missing, instead of letting recipes run an empty
# command. Goals that only clean need no toolchain. The missing path is not
# printed: with nothing installed it is empty or a bare suffix, and
# `./install.sh doctor` names exactly what is wrong.
requireTool = $(if $(filter-out clean%,$(or $(MAKECMDGOALS),all)),$(if $(wildcard $(1)),,$(error $(2) is not installed; run: ./install.sh install $(2))))
# Letter case, for the port tags (IIGS) and the program names each port
# derives from a program's id (Hello on the Amiga, hello on DVX).
lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))
uc = $(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,$(subst f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,$(subst k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,$(subst p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,$(subst u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,$(subst z,Z,$(1)))))))))))))))))))))))))))
capFirst = $(patsubst a%,A%,$(patsubst b%,B%,$(patsubst c%,C%,$(patsubst d%,D%,$(patsubst e%,E%,$(patsubst f%,F%,$(patsubst g%,G%,$(patsubst h%,H%,$(patsubst i%,I%,$(patsubst j%,J%,$(patsubst k%,K%,$(patsubst l%,L%,$(patsubst m%,M%,$(patsubst n%,N%,$(patsubst o%,O%,$(patsubst p%,P%,$(patsubst q%,Q%,$(patsubst r%,R%,$(patsubst s%,S%,$(patsubst t%,T%,$(patsubst u%,U%,$(patsubst v%,V%,$(patsubst w%,W%,$(patsubst x%,X%,$(patsubst y%,Y%,$(patsubst z%,Z%,$(1)))))))))))))))))))))))))))
# The ports, and which of them can build here: a port is available when its
# real compiler binary is installed (DVX also needs the DVX SDK). The top
# Makefile's `make` and a project's `make` build exactly these.
JOEY_PORTS := iigs amiga atarist ste dos x68000 dvx sdl3 web win32 win16 psp haiku haiku32 beos
HAVE_IIGS := $(if $(wildcard $(LLVM816_ROOT)/tools/llvm-mos-build/bin/clang),1)
HAVE_AMIGA := $(if $(wildcard $(AMIGA_CC)),1)
HAVE_ATARIST := $(if $(wildcard $(ST_CC)),1)
HAVE_STE := $(HAVE_ATARIST)
HAVE_DOS := $(if $(wildcard $(DOS_CC)),1)
HAVE_X68000 := $(if $(wildcard $(X68K_ROOT)/bin/m68k-xelf-gcc),1)
HAVE_DVX := $(if $(and $(wildcard $(DOS_CC)),$(wildcard $(DVX_SDK)/sdk.mk)),1)
HAVE_SDL3 := $(if $(wildcard $(SDL3_DIR)/lib/libSDL3.a),1)
HAVE_WEB := $(if $(and $(wildcard $(EMCC)),$(wildcard $(SDL3_WEB_DIR)/lib/libSDL3.a)),1)
HAVE_WIN32 := $(if $(wildcard $(WIN32_CC)),1)
HAVE_WIN16 := $(if $(wildcard $(WIN16_WATCOM)/binl64/owcc),1)
HAVE_PSP := $(if $(and $(wildcard $(PSP_CC)),$(wildcard $(SDL3_PSP_DIR)/lib/libSDL3.a)),1)
HAVE_HAIKU := $(if $(wildcard $(HAIKU_CROSS)g++),1)
HAVE_HAIKU32 := $(if $(wildcard $(HAIKU32_ROOT)/cross/bin/i586-pc-haiku-g++),1)
HAVE_BEOS := $(if $(and $(HAVE_HAIKU32),$(wildcard $(BEOS_R5_SYSROOT)/system-lib/libbe.so)),1)
JOEY_PORTS_INSTALLED := $(foreach p,$(JOEY_PORTS),$(if $(HAVE_$(call uc,$(p))),$(p)))
# Where things are built. JOEY_BUILD holds what is JoeyLib's -- each port's
# library and the host tools -- and PROJECT_BUILD the programs: for JoeyLib's
# own examples both are build/. A project (make/project.mk) sets JOEY_PROJECT
# to its directory, so its programs land in its own build/ while JoeyLib's
# library is built once, in JoeyLib's. Point JOEY_BUILD elsewhere to keep a
# project's build out of the JoeyLib checkout altogether.
JOEY_BUILD ?= $(REPO_DIR)/build
PROJECT_DIR := $(or $(JOEY_PROJECT),$(REPO_DIR))
PROJECT_BUILD ?= $(PROJECT_DIR)/build
# make/tools.mk runs as a separate make; it builds into the same place.
export JOEY_BUILD
INCLUDE_DIR := $(REPO_DIR)/include
# Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos,
# x68000, dvx); code shared across machines of one CPU lives in a processor
# folder (src/m68k, src/x86); the hosted core and its backends in src/hosted
# (sdl3, web and psp build the same SDL3 backend, natively, for a browser
# and for the PSP; win32 and win16 their own).
SRC_DIR := $(REPO_DIR)/src
SRC_CORE := $(REPO_DIR)/src/core
SRC_CG := $(REPO_DIR)/src/codegen
SRC_68K := $(REPO_DIR)/src/m68k
SRC_X86 := $(REPO_DIR)/src/x86
EXAMPLES := $(REPO_DIR)/examples
# Host tools (make/tools.mk, and a program's own bake tools) build with the
# host's cc.
HOST_CC ?= cc
HOST_CFLAGS := -std=c99 -Wall -Wextra -Werror -O2
# Portable C sources for libjoey -- present on every target.
CORE_C_SRCS := $(wildcard $(SRC_CORE)/*.c)
# Generic portable-C op defaults (the src/generic backend) -- present on every
# target. spriteEmitStub.c is excluded: it is the no-codegen template and
# redefines jlSpriteCompile / spriteCompiledDraw, which the real
# src/codegen/spriteCompile.c already provides.
GENERIC_C_SRCS := $(filter-out %/spriteEmitStub.c, $(wildcard $(SRC_DIR)/generic/*.c))
# Common include flags. Per-port code can include port.h / surfaceInternal.h
# directly because SRC_CORE is in the include path. EXTRA_CFLAGS is the one
# command-line hook for extra flags on every port (make EXTRA_CFLAGS=-DX);
# make/iigs.mk, which does not use COMMON_CFLAGS, honours it too.
COMMON_CFLAGS := -I$(INCLUDE_DIR) -I$(SRC_CORE) -Wall -Wextra -Werror -O2 $(EXTRA_CFLAGS)
ifdef PLATFORM
# One port's directories: its library (BUILD, LIBDIR) is JoeyLib's, its
# programs (PROGRAM_BUILD, BINDIR) the project's.
BUILD := $(JOEY_BUILD)/$(PLATFORM)
LIBDIR := $(BUILD)/lib
PROGRAM_BUILD := $(PROJECT_BUILD)/$(PLATFORM)
BINDIR := $(PROGRAM_BUILD)/bin
PORT_TAG := $(call uc,$(PLATFORM))
# Baked assets (sprite and tile banks) carry the target they were baked for.
# DVX shows the DOS port's stage and runs its x86 code, so it takes the DOS
# bakes; the hosted ports' stage (SDL3, the web, Win32, Win16, the PSP,
# Haiku, BeOS) is chunky like the DOS one, so they take them too (the cels of a
# pre-compiled bank, without their code); the STe build is the ST port, so it
# takes the ST's (same planes, same compiled sprites); every other port its
# own.
ASSET_PLATFORM := $(if $(filter dvx sdl3 web win32 win16 psp haiku haiku32 beos,$(PLATFORM)),dos,$(if $(filter ste,$(PLATFORM)),atarist,$(PLATFORM)))
# The host sprite pre-compiler for this port's bakes (.spr -> .spc); its rule
# is make/programs.mk's.
SPRITEBAKE := $(JOEY_BUILD)/tools/spritebake-$(ASSET_PLATFORM)
endif
# ---- programs -----------------------------------------------------------------
# What a program's variables mean is listed in make/programs.mk. These are the
# lookups that file and the program descriptions use; each port sets
# PROGRAM_EXT, PROGRAM_FOLDERS (programs may ship in a folder of their own),
# PROGRAM_DATA_APART (DATA is staged apart from the program, for the
# packager) and PROGRAM_OPTIONAL_APART (optional DATA apart again), and may
# redefine programNameRule, programDir, programBin and programTarget.
# $(call programVar,ID,VAR): the program's VAR on this port -- ID_<PORT>_VAR
# when that is defined (even empty), else ID_VAR.
programVar = $(if $(filter undefined,$(origin $(1)_$(PORT_TAG)_$(2))),$($(1)_$(2)),$($(1)_$(PORT_TAG)_$(2)))
# $(call programOnPort,ID): ID when it is built for this port (ID_PORTS,
# every port by default), else nothing.
programOnPort = $(if $(filter $(PLATFORM),$(or $($(1)_PORTS),$(JOEY_PORTS))),$(1))
# $(call programName,ID): its file name without the extension: ID_NAME, else
# the port's naming rule -- the id itself, unless the port redefines it.
programName = $(or $(call programVar,$(1),NAME),$(call programNameRule,$(1)))
programNameRule = $(1)
# $(call programDir,ID): the folder it is built into: ID_BINDIR or bin/, and
# in there a folder of its own when it ships in one (ID_FOLDER) on a port
# whose programs can.
programDir = $(or $(call programVar,$(1),BINDIR),$(BINDIR))$(if $(and $(PROGRAM_FOLDERS),$(call programVar,$(1),FOLDER)),/$(call programName,$(1)))
# $(call programBin,ID): the program file.
programBin = $(call programDir,$(1))/$(call programName,$(1))$(PROGRAM_EXT)
# $(call programTarget,ID): what the programs goal builds for it.
programTarget = $(call programBin,$(1))
# $(call programDataDir,ID): where its DATA files are staged -- the DATA
# folder beside it, or on a port that packages programs (the IIgs disks, the
# DVX SDK) build/<port>/data/<name>/DATA.
programDataDir = $(if $(PROGRAM_DATA_APART),$(PROGRAM_BUILD)/data/$(call programName,$(1))/DATA,$(call programDir,$(1))/DATA)
# $(call programOptionalDir,ID): where its optional DATA files are staged:
# ones it runs without (a game that JIT-compiles a level's sprites when the
# level's pre-compiled bank is missing). Only the IIgs disk packer treats
# them apart -- they go on last, while there is room -- so elsewhere this is
# the DATA folder itself.
programOptionalDir = $(if $(PROGRAM_OPTIONAL_APART),$(PROGRAM_BUILD)/data/$(call programName,$(1))/OPTIONAL,$(call programDataDir,$(1)))
# Rules that hand a host tool's build to make/tools.mk depend on FORCE, so
# tools.mk -- which owns each tool's source list -- is always asked; it does
# nothing when the tool is current. Without it an existing tool binary looked
# up to date forever, and edits to its sources never reached it (a bake tool
# kept running a stale binary after a source it links changed). Defining it
# must not change any port's default goal (most include this file before
# their `all`), so the goal in force is saved and put back around it.
COMMON_DEFAULT_GOAL := $(.DEFAULT_GOAL)
.PHONY: FORCE
FORCE:
.DEFAULT_GOAL := $(COMMON_DEFAULT_GOAL)