# SPDX-License-Identifier: LGPL-2.0-only # # Makefile for managing multiple software repositories in one tree # # (C) Copyright 2001-2025, Jan Lindemann # # This is the top-level Makefile for a software build tree organized by # jw-pkg. It is provided under the terms of the GNU Lesser Public License, # Version 2. # # Current documentation on how this Makefile is meant to be used can be found # under https://janware.com/wiki/pub/en/sw/build/. Running "make help" might # take you there semi-automatically. # # ------------ Makefile and environment variable definitions .NOTPARALLEL: PROJECTS_MAKEFILE_NAME ?= $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) # -- Find JWBDIR DEV_PROJECTS_DIR ?= . JWBDIR_NAME ?= jw-pkg JWBDIR_SEARCH_PATH ?= $(DEV_PROJECTS_DIR) $(BUILD_TOOLS_PREFIX)/opt/$(FLAVOUR_PATH_PREFIX) JWBDIR ?= $(firstword $(wildcard $(addsuffix /$(JWBDIR_NAME),$(JWBDIR_SEARCH_PATH)))) JW_PKG_BINDIR = $(JWBDIR)/bin JWB_SCRIPT_DIR = $(firstword $(wildcard ./$(JWBDIR_NAME)/scripts $(JW_PKG_BINDIR)) jwb-script-dir-not-found) JW_PKG_REMOTE_BINDIR = /opt/jw-pkg/bin SHELL = /bin/bash -o pipefail +H PROJECTS_TXT ?= projects.txt JW_PKG_VERBOSE ?= false BASE_PKGS = git make sudo time xdg-utils python3 PREREQ_RELEASE ?= pull ifneq ($(JANWARE_USER),) export JANWARE_USER endif ifneq ($(GIT_ASKPASS),) GIT_ASKPASS := $(realpath $(GIT_ASKPASS)) endif ifneq ($(SSH_ASKPASS),) SSH_ASKPASS := $(realpath $(SSH_ASKPASS)) endif # ------------ evaluate Makefile and environment variables ifneq ($(wildcard $(PROJECTS_TXT)),) PROJECTS ?= $(shell cat $(PROJECTS_TXT) | sed '/^ *\#/ d') else PROJECTS ?= $(shell ls -d */GNUmakefile */Makefile 2>/dev/null | sed 's%/[^/]*%%' | sort -u) endif ifeq ($(JW_PKG_VERBOSE),true) SSH_WRAPPER_TRACE ?= -x endif export JW_PKG_VERBOSE # ------------ external programs I CWD := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) GET_OS_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/get-os.sh $(JW_PKG_BINDIR)/get-os.sh) get-os-sh-not-found) SSH_WRAPPER_SH := $(CWD)/ssh-wrapper.sh EXCLUDES_FILE ?= exclude.txt EXCLUDES_FILES = $(wildcard exclude-$(shell $(GET_OS_SH) name 2>/dev/null).txt exclude-$(shell $(GET_OS_SH) 2>/dev/null).txt $(EXCLUDES_FILE)) ifneq ($(EXCLUDES_FILES),) EXCLUDE_FROM_BUILD += $(shell sed 's/\#.*//g' $(EXCLUDES_FILES)) endif OFFLINE ?= false OFFLINE_PROJECTS ?= $(EXCLUDE_FROM_BUILD) TEXT_FILES_CACHE ?= text-files.txt ifeq ($(OFFLINE),true) UNAVAILABLE_TARGETS ?= pull.done update.done clone.done else UNAVAILABLE_TARGETS ?= endif ifneq ($(origin JW_PKG_SSH),undefined) export GIT_SSH := $(JW_PKG_SSH) else export GIT_SSH := $(SSH_WRAPPER_SH) endif ifeq ($(filter pkg-%,$(MAKECMDGOALS)),) JW_PKG_SSH_EXTRA_OPTS += -o StrictHostKeyChecking=no -o ControlMaster=auto -o ControlPath=/tmp/%r@jw-pkg:%h:%p -o ControlPersist=3m ifneq ($(JANWARE_USER),) JW_PKG_SSH_EXTRA_OPTS += -l $(JANWARE_USER) endif export JW_PKG_SSH_EXTRA_OPTS endif ifneq ($(EXCLUDE_FROM_BUILD),) PROJECTS_PY_EXTRA_BUILD_OPTS += --exclude "$(EXCLUDE_FROM_BUILD)" endif # non-interactive mode INTERACTIVE ?= true ifneq ($(INTERACTIVE),true) DASH_Y := -y endif # ------------ external programs II BROWSER ?= xdg-open EDITOR ?= xdg-open ifeq ($(TIME),) TIME = $(shell which time) -p endif PROJECTS_PY = $(TIME) python3 $(JWB_SCRIPT_DIR)/jw-projects.py --prefix $(shell pwd) $(PROJECTS_PY_EXTRA_OPTS) PROJECTS_PY_BUILD = $(PROJECTS_PY) build $(PROJECTS_PY_EXTRA_BUILD_OPTS) PGIT_SH := /bin/bash $(JWB_SCRIPT_DIR)/pgit.sh ifneq ($(origin PROJECTS_DIR_REMOTE_BASE),undefined) PGIT_SH += --remote-base $(PROJECTS_DIR_REMOTE_BASE) endif PGIT_SH_CLONE := $(PGIT_SH) clone PGIT_SH_CLONE_DEFAULT = $(PGIT_SH_CLONE) $(PGIT_SH_OPTS_NETWORK) ifneq ($(JANWARE_USER),) PGIT_SH_OPTS_NETWORK += --login $(JANWARE_USER) endif ifneq ($(CLONE_FROM_USER),) PGIT_SH_CLONE_DEFAULT += --refspec $(CLONE_FROM_USER) endif ifneq ($(OFFLINE_PROJECTS),) export PGIT_IGNORE = $(OFFLINE_PROJECTS) endif ifneq ($(JANWARE_USER),) PGIT_SH_CLONE_DEFAULT += --create-remote-user-repos endif PURGE_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/purge-stale-projects.sh $(JW_PKG_BINDIR)/purge-stale-projects.sh) purge-not-found) PKG_MANAGER_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/pkg-manager.sh $(JW_PKG_BINDIR)/pkg-manager.sh) pkg-manager-not-found) CREATE_PROJECT_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/jw-pkg-create-project.sh $(JW_PKG_BINDIR)/jw-pkg-create-project.sh) jw-pkg-create-project-not-found) LIST_VCS_FILES_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/scm.sh $(JW_PKG_BINDIR)/scm.sh) scm-sh-not-found) ls-files GIT_SRV_ADMIN_SH = JW_PKG_SSH_EXTRA_OPTS="$(JW_PKG_SSH_EXTRA_OPTS)" $(GIT_SSH) $(JANWARE_USER)@git.janware.com $(JW_PKG_REMOTE_BINDIR)/git-srv-admin.sh # ------------ projects to be built TARGET_PROJECTS = $(filter-out $(EXCLUDE_FROM_BUILD),$(PROJECTS)) BUILD_PROJECTS = $(shell $(PROJECTS_PY_BUILD) --build-order all $(TARGET_PROJECTS)) GIT_PROJECTS = $(patsubst %/,%,$(dir $(wildcard $(addsuffix /.git,$(BUILD_PROJECTS))))) # ------------ targets # --- mandatory targets all: $(filter-out $(UNAVAILABLE_TARGETS),pull.done) $(PROJECTS_PY_BUILD) $@ $(TARGET_PROJECTS) clean: clean-dirs distclean: clean-all-dirs done.clean install: @echo @echo " Target install is not supported by this Makefile." @echo " Target pkg-rebuild-reinstall might be what you are looking for." @echo @exit 1 # --- build targets rebuild: clean purge pull subdirs-all subdirs-%: FORCE_REBUILD_SUBDIRS=true make $* # --- informative-only targets help doc-project doc-module: $(BROWSER) $(firstword $(shell sed '/https:/ !d; s%.*https%https%; s/ .*//' $(firstword $(MAKEFILE_LIST)))) status: $(SSH_WRAPPER_SH) build-order-%: $(filter-out $(UNAVAILABLE_TARGETS),pull.done) $(PROJECTS_PY_BUILD) --build-order $* $(TARGET_PROJECTS) | sed 's/ */\n/g' build-order: build-order-all echo-build-deps: @$(PROJECTS_PY) required-os-pkg --skip-excluded --flavours "build" $(TARGET_PROJECTS) echo-install-deps: @$(PROJECTS_PY) required-os-pkg --skip-excluded --flavours "build run" $(TARGET_PROJECTS) echo-release-deps: @$(PROJECTS_PY) required-os-pkg --skip-excluded --flavours "build run release" $(TARGET_PROJECTS) echo-os: @$(GET_OS_SH) echo-projects: @echo $(PROJECTS) echo-target-projects: @echo $(TARGET_PROJECTS) echo-excludes: @echo $(EXCLUDE_FROM_BUILD) edit-%: | $(TEXT_FILES_CACHE) $(EDITOR) $(shell grep "/$*$$" $(TEXT_FILES_CACHE)) distclean: clean.text-files-cache clean.text-files-cache: rm -f $(TEXT_FILES_CACHE) list-files: @realpath PROJECTS_MAKEFILE_NAME @for p in $(BUILD_PROJECTS); do \ $(LIST_VCS_FILES_SH) -znf $$p | sed -z "s/^/$$p\//" | \ xargs -0 realpath -q ;\ done $(TEXT_FILES_CACHE): @make -s text-files-update text-files-update: make -s --no-print-directory list-files | tr '\n' '\0' | xargs -0 file -N | sed "/:.*text/I !d; s/:.*//" > $(TEXT_FILES_CACHE).tmp mv $(TEXT_FILES_CACHE).tmp $(TEXT_FILES_CACHE) text-files-update-all: @PROJECTS_TXT= make text-files-update text-files-list list-text-files: | $(TEXT_FILES_CACHE) @cat $(TEXT_FILES_CACHE) text-files-list-0 list-text-files-0: | $(TEXT_FILES_CACHE) @cat $(TEXT_FILES_CACHE) | tr '\n' '\0' cloc: for p in $(GIT_PROJECTS); do \ git -C $$p submodule status | sed "s|^ *\([^ ]\+\) \+\([^ ]\+\) *.*|$$p/\2|" ;\ done > cloc-ignore.txt for p in $(foreach s,dist include bin lib,$(addsuffix /$s,$(BUILD_PROJECTS))); do \ echo $$p >> cloc-ignore.txt ;\ done cloc --exclude-list-file=cloc-ignore.txt $(BUILD_PROJECTS) # --- package-related targets pkg-manager-refresh: $(PKG_MANAGER_SH) refresh $(DASH_Y) pkg-install-build-deps: $(PKG_MANAGER_SH) install $(DASH_Y) "$(BASE_PKGS) $(shell $(PROJECTS_PY) required-os-pkg --skip-excluded --flavours build $(TARGET_PROJECTS))" pkg-install-release-deps: $(PKG_MANAGER_SH) install $(DASH_Y) "$(BASE_PKGS) $(shell $(PROJECTS_PY) required-os-pkg --skip-excluded --flavours 'build run release' $(TARGET_PROJECTS))" pkg-release-reinstall: $(PREREQ_RELEASE) pkg-release-all: /bin/bash ./packager-client/scripts/packager-client-2.sh pkg-init-%: $(CREATE_PROJECT_SH) $* pkg-%: $(filter-out $(UNAVAILABLE_TARGETS),pull.done) $(PROJECTS_PY_BUILD) $@ $(TARGET_PROJECTS) # --- generic cleanup targets clean-dirs: echo $(sort $(dir $(wildcard */*.done))) | xargs -r $(PROJECTS_PY_BUILD) clean clean-all-dirs: $(PROJECTS_PY_BUILD) clean $(PROJECTS) make clean-dirs purge: $(SSH_WRAPPER_SH) ifneq ($(PURGE_SH),/bin/bash purge-not-found) $(PURGE_SH) --vcs git endif $(PROJECTS_TXT): echo $(PROJECTS) | sed 's/ /\n/g; s%/%%g' > $@ done.clean: rm -f *.done # --- collab targets list-maintainers: $(SSH_WRAPPER_SH) $(GIT_SRV_ADMIN_SH) $@ update pull: purge git-clone touch pull.done sync: pull push sync-all: pull-all push-all sync-%: ssh $* make -C $(shell pwd) sync pull-all: purge git-clone git-pull-all touch clone.done touch pull.done diff-all diff: $(SSH_WRAPPER_SH) $(PGIT_SH) diff git-push push: $(SSH_WRAPPER_SH) $(PGIT_SH) push $(PGIT_SH_OPTS_NETWORK) git-push-all: $(SSH_WRAPPER_SH) $(PGIT_SH) push $(PGIT_SH_OPTS_NETWORK) --all --recurse-submodules=on-demand git-diff: $(SSH_WRAPPER_SH) $(PGIT_SH) diff git-short-diff: $(SSH_WRAPPER_SH) $(PGIT_SH) diff --shortstat git-status: $(PGIT_SH) status -uno git-pull: $(SSH_WRAPPER_SH) $(PGIT_SH_CLONE_DEFAULT) git-pull-mini: $(SSH_WRAPPER_SH) PGIT_CLONE_PROJECTS="$(patsubst %/.git,%,$(wildcard $(addsuffix /.git,$(shell make -s build-order))))" $(PGIT_SH_CLONE_DEFAULT) git-pull-all: $(SSH_WRAPPER_SH) $(PGIT_SH) pull $(PGIT_SH_OPTS_NETWORK) --all git-clone: $(SSH_WRAPPER_SH) $(PGIT_SH_CLONE_DEFAULT) touch clone.done git-clone-%: $(SSH_WRAPPER_SH) $(PGIT_SH_CLONE_DEFAULT) git-show-non-master-branches: @$(PGIT_SH) branch 2>&1 | \ sed '/^#\|^*/!d; s/.*git -C //; s/ *branch *//; s/ *\* *//' | \ while read p; do \ read b ;\ if [ "$$b" != "master" ]; then \ echo " * $$p: $$b" ;\ fi ;\ done git-show-pushable-master-branches: @for p in $(BUILD_PROJECTS); do \ if git -C $$p log --oneline origin/master.. | grep . >/dev/null; then \ echo ======================= $$p ;\ git -C $$p log --oneline origin/master.. ;\ fi ;\ done # git-echo-link- returns a string functioning as hyperlink to # matching files in git when embedded into a janware wiki or ticket. git-echo-links-%: | $(TEXT_FILES_CACHE) sed "/$*$$/!d; s%$(CWD)%%; s|^|\n \[\[jgit>/proj/$(JANWARE_USER)/|; s/$$/|$*\]\]\n/" $(TEXT_FILES_CACHE) git-update-project-descriptions: $(SSH_WRAPPER_SH) $(GIT_SRV_ADMIN_SH) -j update-descriptions all git-pull-%: $(SSH_WRAPPER_SH) $(PGIT_SH_CLONE) $(PGIT_SH_OPTS_NETWORK) --refspec "$*:master:master" git-commit: $(PGIT_SH) commit # --- rules $(SSH_WRAPPER_SH): $(PROJECTS_MAKEFILE_NAME) /bin/echo -e '#!/bin/bash $(SSH_WRAPPER_TRACE)\n\nexec /usr/bin/ssh $$JW_PKG_SSH_EXTRA_OPTS "$$@"' > $@.tmp chmod 700 $@.tmp mv $@.tmp $@ ssh-wrapper: $(SSH_WRAPPER_SH) clean.ssh-wrapper: rm -f $(SSH_WRAPPER_SH) distclean: clean.ssh-wrapper pull.done: $(filter-out $(UNAVAILABLE_TARGETS), clone.done) touch $@ clone.done: $(filter-out $(UNAVAILABLE_TARGETS),$(SSH_WRAPPER_SH)) $(PGIT_SH_CLONE_DEFAULT) touch $@