# 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, X68K_ROOT, ...) 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)))) # $(call requireSet,VARIABLE,WHAT): stop with a clear message when a part of # a program's identity is not set -- who makes it (DVX's vendor and author), # a Mac app's identifier -- which is the project's own and never JoeyLib's. # WHAT says what to set it to. Goals that only clean need none. requireSet = $(if $(filter-out clean%,$(or $(MAKECMDGOALS),all)),$(if $(strip $($(1))),,$(error $(1) is not set: $(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))))))))))))))))))))))))))) # Quoting, for text a recipe writes out: $(call shellQuote,TEXT) is TEXT as # one single-quoted shell word whatever quotes it holds, and # $(call cQuote,TEXT) TEXT as a C string literal. shellQuote = '$(subst ','\'',$(1))' cQuote = "$(subst ",\",$(subst \,\\,$(1)))" # A comma, for text inside a $(call) argument. comma = , 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 macos 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_MACOS := $(if $(and $(wildcard $(ZIG)),$(wildcard $(SDL3_MACOS_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// (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, macos and psp build the same SDL3 backend, natively, for a # browser, for the Mac 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) # The software sound mixer -- the module player and the SFX overlay -- for the # ports that mix on the CPU. The Amiga (PTPlayer), the IIgs (NinjaTrackerPlus) # and DVX (its own music library) play through theirs and build without it. CORE_MIX_C_SRCS := $(SRC_CORE)/audioSfxMix.c $(SRC_CORE)/modPlayer.c CORE_C_SRCS_NO_MIX := $(filter-out $(CORE_MIX_C_SRCS), $(CORE_C_SRCS)) # 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 / jlSpriteCompiledDraw, 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, macOS, Win32, Win16, the # PSP, Haiku, BeOS) is chunky like the DOS one, so they take them too (Win16 # runs a pre-compiled bank's x86 code, as DVX does; the rest take its cels # 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 macos 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__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//data//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))) # $(call programIcon,ID): the picture its icon is baked from (ID_ICON, else # JoeyLib's kangaroo), on a port that shows programs by an icon; and # $(call programIconDir,ID), where the port's bakes of it go, by its name (the # IIgs disk scripts look icons up by it), or by its id for a program built # apart (ID_BINDIR: a probe build may share a shipped program's name). # ICON_BAKE is the baker (tools/iconBake.py says what each port gets); a rule # that runs it depends on ICON_BAKE_DEPS too, so a change to the baker # rebakes. programIcon = $(or $(call programVar,$(1),ICON),$(REPO_DIR)/assets/joeyIcon.png) programIconDir = $(PROGRAM_BUILD)/icon/$(if $(call programVar,$(1),BINDIR),$(1),$(call programName,$(1))) ICON_BAKE := python3 $(REPO_DIR)/tools/iconBake.py ICON_BAKE_DEPS := $(REPO_DIR)/tools/iconBake.py # 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)