jw-pkg/make/projects-dir.mk

373 lines
12 KiB
Makefile
Raw Normal View History

# SPDX-License-Identifier: LGPL-2.0-only
#
# Makefile for managing multiple software repositories in one tree
#
# (C) Copyright 2001-2025, Jan Lindemann <jan@janware.com>
#
# This is the top-level Makefile for a software build tree organized by
# jw-build. 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-build
JWBDIR_SEARCH_PATH ?= $(DEV_PROJECTS_DIR) $(BUILD_TOOLS_PREFIX)/opt/$(FLAVOUR_PATH_PREFIX)
JWBDIR ?= $(firstword $(wildcard $(addsuffix /$(JWBDIR_NAME),$(JWBDIR_SEARCH_PATH))))
JW_BUILD_BINDIR = $(JWBDIR)/bin
JWB_SCRIPT_DIR = $(firstword $(wildcard ./$(JWBDIR_NAME)/scripts $(JW_BUILD_BINDIR)) jwb-script-dir-not-found)
JW_BUILD_REMOTE_BINDIR = /opt/jw-build/bin
SHELL = /bin/bash -o pipefail +H
PROJECTS_TXT ?= projects.txt
JW_BUILD_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_BUILD_VERBOSE),true)
SSH_WRAPPER_TRACE ?= -x
endif
export JW_BUILD_VERBOSE
# ------------ external programs I
CWD := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
GET_OS_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/get-os.sh $(JW_BUILD_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_BUILD_SSH),undefined)
export GIT_SSH := $(JW_BUILD_SSH)
else
export GIT_SSH := $(SSH_WRAPPER_SH)
endif
ifeq ($(filter pkg-%,$(MAKECMDGOALS)),)
JW_BUILD_SSH_EXTRA_OPTS += -o StrictHostKeyChecking=no -o ControlMaster=auto -o ControlPath=/tmp/%r@jw-build:%h:%p -o ControlPersist=3m
ifneq ($(JANWARE_USER),)
JW_BUILD_SSH_EXTRA_OPTS += -l $(JANWARE_USER)
endif
export JW_BUILD_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
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
ifneq ($(origin PROJECTS_DIR_REMOTE_BASE),undefined)
PGIT_SH += --remote-base $(PROJECTS_DIR_REMOTE_BASE)
endif
PURGE_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/purge-stale-projects.sh $(JW_BUILD_BINDIR)/purge-stale-projects.sh) purge-not-found)
PKG_MANAGER_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/pkg-manager.sh $(JW_BUILD_BINDIR)/pkg-manager.sh) pkg-manager-not-found)
CREATE_PROJECT_SH ?= /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/jw-build-create-project.sh $(JW_BUILD_BINDIR)/jw-build-create-project.sh) jw-build-create-project-not-found)
LIST_VCS_FILES_SH = /bin/bash $(firstword $(wildcard $(JWB_SCRIPT_DIR)/scm.sh $(JW_BUILD_BINDIR)/scm.sh) scm-sh-not-found) ls-files
GIT_SRV_ADMIN_SH = JW_BUILD_SSH_EXTRA_OPTS="$(JW_BUILD_SSH_EXTRA_OPTS)" $(GIT_SSH) $(JANWARE_USER)@git.janware.com $(JW_BUILD_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)
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-echo-link-<filename> 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_BUILD_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 $@