xref: /src/share/mk/meta.stage.mk (revision bc531a96c9b28b1cabcd5deb0c9f8f6d815cfebc)
1# $Id: meta.stage.mk,v 1.75 2025/12/08 17:44:57 sjg Exp $
2#
3#	@(#) Copyright (c) 2011-2025, Simon J. Gerraty
4#
5#	SPDX-License-Identifier: BSD-2-Clause
6#
7#	Please send copies of changes and bug-fixes to:
8#	sjg@crufty.net
9#
10
11.ifndef NO_STAGING
12
13.if !target(__${.PARSEFILE}__)
14# the guard target is defined later
15
16.-include <local.meta.stage.mk>
17
18.if ${.MAKE.DEPENDFILE_PREFERENCE:U${.MAKE.DEPENDFILE}:M*.${MACHINE}} != ""
19# this is generally safer anyway
20_dirdep ?= ${RELDIR}.${TARGET_SPEC:U${MACHINE}}
21.else
22_dirdep ?= ${RELDIR}
23.endif
24
25CLEANFILES+= .dirdep
26
27# this allows us to trace dependencies back to their src dir
28.dirdep: .NOPATH
29.if !commands(.dirdep)
30.dirdep:
31	@echo '${_dirdep}' > $@
32.endif
33
34.if ${isPOSIX_SHELL:U:Nfalse}
35_stage_file_basename = $${f\#\#*/}
36_stage_file_dirname = $${f%/*}
37_stage_target_dirname = $${t%/*}
38.else
39_stage_file_basename = `basename $$f`
40_stage_file_dirname = `dirname $$f`
41_stage_target_dirname = `dirname $$t`
42.endif
43
44_OBJROOT ?= ${OBJROOT:U${OBJTOP:H}}
45.if ${_OBJROOT:M*/} != ""
46_objroot ?= ${_OBJROOT:tA}/
47.else
48_objroot ?= ${_OBJROOT:tA}
49.endif
50
51# make sure this is global
52_STAGED_DIRS ?=
53.export _STAGED_DIRS
54# add each dir we stage to _STAGED_DIRS
55# and make sure we have absolute paths so that bmake
56# will match against .MAKE.META.BAILIWICK
57STAGE_DIR_FILTER = tA:@d@$${_STAGED_DIRS::+=$$d}$$d@
58# convert _STAGED_DIRS into suitable filters
59GENDIRDEPS_FILTER += Nnot-empty-is-important \
60	${_STAGED_DIRS:O:u:M${OBJTOP}*:S,${OBJTOP}/,N,} \
61	${_STAGED_DIRS:O:u:M${_objroot}*:N${OBJTOP}*:S,${_objroot},,:C,^([^/]+)/(.*),N\2.\1,:S,${HOST_TARGET},.host,}
62
63LN_CP_SCRIPT = LnCp() { \
64  rm -f $$2 2> /dev/null; \
65  { [ -z "$$mode" ] && ${LN:Uln} $$1 $$2 2> /dev/null; } || \
66  cp -p $$1 $$2 2> /dev/null || cp $$1 $$2; }
67
68# a staging conflict should cause an error
69# a warning is handy when bootstapping different options.
70STAGE_CONFLICT?= ERROR
71.if ${STAGE_CONFLICT:tl} == "error"
72STAGE_CONFLICT_ACTION= exit 1
73.else
74STAGE_CONFLICT_ACTION=
75.endif
76
77# it is an error for more than one src dir to try and stage
78# the same file
79STAGE_DIRDEP_SCRIPT = ${LN_CP_SCRIPT}; StageDirdep() { \
80  t=$$1; \
81  if [ -s $$t.dirdep ]; then \
82	cmp -s .dirdep $$t.dirdep && return; \
83	x=`cat $$t.dirdep`; \
84	case "${RELDIR}:${_dirdep}" in \
85	$${x%.*}:$${x}*) ;; \
86	*) echo "${STAGE_CONFLICT}: $$t installed by $$x not ${_dirdep}" >&2; \
87	${STAGE_CONFLICT_ACTION} ;; \
88	esac; \
89  fi; \
90  LnCp .dirdep $$t.dirdep || exit 1; }
91
92# common logic for staging files
93# this all relies on RELDIR being set to a subdir of SRCTOP
94# we use ln(1) if we can, else cp(1)
95# if --subdir is given the dirname part of each file will be preserved
96STAGE_FILE_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageFiles() { \
97  mode= subdir=; \
98  while : ; do \
99	case "$$1" in \
100	"") return;; \
101	-m) mode=$$2; shift 2;; \
102	--subdir) subdir=1; shift;; \
103	*) break;; \
104	esac; \
105  done; \
106  dest=$$1; shift; \
107  mkdir -p $$dest; \
108  [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \
109  for f in "$$@"; do \
110	case "$$subdir,$$f" in \
111	1,*/*) t=$$dest/$$f; mkdir -p $$dest/${_stage_file_dirname};; \
112	*/*) t=$$dest/${_stage_file_basename};; \
113	*) t=$$dest/$$f;; \
114	esac; \
115	StageDirdep $$t; \
116	LnCp $$f $$t || exit 1; \
117	[ -z "$$mode" ] || chmod $$mode $$t; \
118  done; :; }
119
120STAGE_LINKS_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageLinks() { \
121  case "$$1" in "") return;; --) shift;; -*) ldest= lnf=$$1; shift;; /*) ldest=$$1/;; esac; \
122  dest=$$1; shift; \
123  mkdir -p $$dest; \
124  [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \
125  while test $$\# -ge 2; do \
126	l=$$ldest$$1; shift; \
127	t=$$dest/$$1; \
128	case "$$1" in */*) mkdir -p ${_stage_target_dirname};; esac; \
129	shift; \
130	StageDirdep $$t; \
131	rm -f $$t 2>/dev/null; \
132	ln $$lnf $$l $$t || exit 1; \
133  done; :; }
134
135STAGE_AS_SCRIPT = ${STAGE_DIRDEP_SCRIPT}; StageAs() { \
136  case "$$1" in "") return;; -m) mode=$$2; shift 2;; *) mode=;; esac; \
137  dest=$$1; shift; \
138  mkdir -p $$dest; \
139  [ -s .dirdep ] || echo '${_dirdep}' > .dirdep; \
140  while test $$\# -ge 2; do \
141	s=$$1; shift; \
142	t=$$dest/$$1; \
143	case "$$1" in */*) mkdir -p ${_stage_target_dirname};; esac; \
144	shift; \
145	StageDirdep $$t; \
146	LnCp $$s $$t || exit 1; \
147	[ -z "$$mode" ] || chmod $$mode $$t; \
148  done; :; }
149
150# this is simple, a list of the "staged" files depends on this,
151_STAGE_BASENAME_USE:	.USE .dirdep ${.TARGET:T}
152	@${STAGE_FILE_SCRIPT}; StageFiles ${.TARGET:H:${STAGE_DIR_FILTER}} ${.TARGET:T}
153
154_STAGE_AS_BASENAME_USE:        .USE .dirdep ${.TARGET:T}
155	@${STAGE_AS_SCRIPT}; StageAs ${.TARGET:H:${STAGE_DIR_FILTER}} ${.TARGET:T} ${STAGE_AS_${.TARGET:T}:U${.TARGET:T}}
156
157
158.endif				# first time
159
160
161.if !empty(STAGE_INCSDIR)
162.if !empty(STAGE_INCS)
163stage_incs: ${STAGE_INCS:N*\**}
164.endif
165.if target(stage_incs) || !empty(.ALLTARGETS:Mstage_includes)
166STAGE_TARGETS += stage_incs
167STAGE_INCS ?= ${.ALLSRC:N.dirdep:Nstage_*}
168stage_includes: stage_incs
169stage_incs:	.dirdep
170	@${STAGE_FILE_SCRIPT}; StageFiles ${STAGE_INCSDIR:${STAGE_DIR_FILTER}} ${STAGE_INCS}
171	@touch $@
172
173.endif
174.endif
175
176.if !empty(STAGE_LIBDIR)
177.if !empty(STAGE_LIBS)
178stage_libs: ${STAGE_LIBS:N*\**}
179.endif
180.if target(stage_libs)
181STAGE_TARGETS += stage_libs
182STAGE_LIBS ?= ${.ALLSRC:N.dirdep:Nstage_*}
183stage_libs:	.dirdep
184	@${STAGE_FILE_SCRIPT}; StageFiles ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} ${STAGE_LIBS}
185.if !defined(NO_SHLIB_LINKS)
186.if !empty(SHLIB_LINKS)
187	@${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} \
188	${SHLIB_LINKS:@t@${STAGE_LIBS:T:M$t.*:${STAGE_SHLIB_LINKS_FILTER:U}} $t@}
189.elif !empty(SHLIB_LINK) && !empty(SHLIB_NAME)
190	@${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_LIBDIR:${STAGE_DIR_FILTER}} ${SHLIB_NAME} ${SHLIB_LINK}
191.endif
192.endif
193	@touch $@
194.endif
195.endif
196
197.if !empty(STAGE_DIR)
198STAGE_SETS += _default
199STAGE_DIR._default = ${STAGE_DIR}
200STAGE_LINKS_DIR._default = ${STAGE_LINKS_DIR:U${STAGE_OBJTOP}}
201STAGE_SYMLINKS_DIR._default = ${STAGE_SYMLINKS_DIR:U${STAGE_OBJTOP}}
202STAGE_FILES._default = ${STAGE_FILES}
203STAGE_LINKS._default = ${STAGE_LINKS}
204STAGE_SYMLINKS._default = ${STAGE_SYMLINKS}
205.endif
206
207.if !empty(STAGE_SETS)
208CLEANFILES += ${STAGE_SETS:@s@stage*$s@}
209
210# some makefiles need to populate multiple directories
211.for s in ${STAGE_SETS:O:u}
212.if !empty(STAGE_FILES.$s)
213stage_files.$s: ${STAGE_FILES.$s:N*\**}
214.endif
215.if target(stage_files.$s) || target(stage_files${s:S,^,.,:N._default})
216STAGE_TARGETS += stage_files
217STAGE_FILES.$s ?= ${.ALLSRC:N.dirdep:Nstage_*}
218.if !target(.stage_files.$s)
219.stage_files.$s:
220.if $s != "_default"
221stage_files:	stage_files.$s
222stage_files.$s:	.dirdep
223.else
224STAGE_FILES ?= ${.ALLSRC:N.dirdep:Nstage_*}
225stage_files:	.dirdep
226.endif
227	@${STAGE_FILE_SCRIPT}; StageFiles ${FLAGS.$@:U} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_FILES.$s:O}
228	@touch $@
229.endif
230.endif
231
232.if !empty(STAGE_LINKS.$s)
233stage_links.$s:
234.endif
235.if target(stage_links.$s) || target(stage_links${s:S,^,.,:N._default})
236STAGE_LINKS_DIR.$s ?= ${STAGE_OBJTOP}
237STAGE_TARGETS += stage_links
238.if !target(.stage_links.$s)
239.stage_links.$s:
240.if $s != "_default"
241stage_links:	stage_links.$s
242stage_links.$s:	.dirdep
243.else
244stage_links:	.dirdep
245.endif
246	@${STAGE_LINKS_SCRIPT}; StageLinks ${STAGE_LINKS_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_LINKS.$s}
247	@touch $@
248.endif
249.endif
250
251.if !empty(STAGE_SYMLINKS.$s)
252stage_symlinks.$s:
253.endif
254.if target(stage_symlinks.$s) || target(stage_symlinks${s:S,^,.,:N._default})
255STAGE_SYMLINKS_DIR.$s ?= ${STAGE_OBJTOP}
256STAGE_TARGETS += stage_symlinks
257.if !target(.stage_symlinks.$s)
258.stage_symlinks.$s:
259.if $s != "_default"
260stage_symlinks:	stage_symlinks.$s
261stage_symlinks.$s:	.dirdep
262.else
263stage_symlinks:	.dirdep
264.endif
265	@${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_SYMLINKS_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_SYMLINKS.$s}
266	@touch $@
267.endif
268.endif
269
270.endfor
271.endif
272
273.if !empty(STAGE_AS_SETS)
274CLEANFILES += ${STAGE_AS_SETS:@s@stage*$s@}
275
276# sometimes things need to be renamed as they are staged
277# each ${file} will be staged as ${STAGE_AS_${file:T}}
278# one could achieve the same with SYMLINKS
279# stage_as_and_symlink makes the original name (or ${STAGE_LINK_AS_${name}})
280# a symlink to the new name
281# it is the same as using stage_as and stage_symlinks but ensures
282# both operations happen together
283.for s in ${STAGE_AS_SETS:O:u}
284.if !empty(STAGE_AS.$s)
285stage_as.$s: ${STAGE_AS.$s:N*\**}
286.endif
287.if target(stage_as.$s)
288STAGE_TARGETS += stage_as
289STAGE_AS.$s ?= ${.ALLSRC:N.dirdep:Nstage_*}
290.if !target(.stage_as.$s)
291.stage_as.$s:
292stage_as:	stage_as.$s
293stage_as.$s:	.dirdep
294	@${STAGE_AS_SCRIPT}; StageAs ${FLAGS.$@} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_AS.$s:O:@f@$f ${STAGE_AS_${f:tA}:U${STAGE_AS_${f:T}:U${f:T}}}@}
295	@touch $@
296.endif
297.endif
298
299.if !empty(STAGE_AS_AND_SYMLINK.$s)
300stage_as_and_symlink.$s: ${STAGE_AS_AND_SYMLINK.$s:N*\**}
301.endif
302.if target(stage_as_and_symlink.$s)
303STAGE_TARGETS += stage_as_and_symlink
304STAGE_AS_AND_SYMLINK.$s ?= ${.ALLSRC:N.dirdep:Nstage_*}
305.if !target(.stage_as_and_symlink.$s)
306.stage_as_and_symlink.$s:
307stage_as_and_symlink:	stage_as_and_symlink.$s
308stage_as_and_symlink.$s:	.dirdep
309	@${STAGE_AS_SCRIPT}; StageAs ${FLAGS.$@} ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_AS_AND_SYMLINK.$s:O:@f@$f ${STAGE_AS_${f:tA}:U${STAGE_AS_${f:T}:U${f:T}}}@}
310	@${STAGE_LINKS_SCRIPT}; StageLinks -s ${STAGE_FILES_DIR.$s:U${STAGE_DIR.$s}:${STAGE_DIR_FILTER}} ${STAGE_AS_AND_SYMLINK.$s:O:@f@${STAGE_AS_${f:tA}:U${STAGE_AS_${f:T}:U${f:T}}} ${STAGE_LINK_AS_${f}:U$f}@}
311	@touch $@
312.endif
313.endif
314
315.endfor
316.endif
317
318CLEANFILES += ${STAGE_TARGETS} stage_incs stage_includes
319
320# this lot also only makes sense the first time...
321.if !target(__${.PARSEFILE}__)
322__${.PARSEFILE}__: .NOTMAIN
323
324# stage_*links usually needs to follow any others.
325# for non-jobs mode the order here matters
326staging: ${STAGE_TARGETS:N*_links} ${STAGE_TARGETS:M*_links}
327
328.if ${.MAKE.JOBS:U0} > 0 && ${STAGE_TARGETS:U:M*_links} != ""
329# the above isn't sufficient
330.for t in ${STAGE_TARGETS:N*links:O:u}
331.ORDER: $t stage_links
332.endfor
333.endif
334
335# generally we want staging to wait until everything else is done
336STAGING_WAIT ?= .WAIT
337
338.if ${.MAKE.LEVEL} > 0
339all: ${STAGING_WAIT} staging
340.endif
341
342.if exists(${.PARSEDIR}/stage-install.sh) && !defined(STAGE_INSTALL)
343# this will run install(1) and then followup with .dirdep files.
344STAGE_INSTALL := sh ${.PARSEDIR:tA}/stage-install.sh INSTALL="${INSTALL}" OBJDIR=${.OBJDIR:tA}
345.endif
346
347# if ${INSTALL} gets run during 'all' assume it is for staging?
348.if ${.TARGETS:Nall} == "" && defined(STAGE_INSTALL)
349INSTALL := ${STAGE_INSTALL}
350.if target(beforeinstall)
351beforeinstall: .dirdep
352.endif
353.endif
354.NOPATH: ${STAGE_FILES}
355
356.if !empty(STAGE_TARGETS)
357# for backwards compat make sure they exist
358${STAGE_TARGETS}:
359
360.NOPATH: ${CLEANFILES}
361
362MK_STALE_STAGED?= no
363.if ${MK_STALE_STAGED} == "yes"
364all: stale_staged
365# get a list of paths that we have just staged
366# get a list of paths that we have previously staged to those same dirs
367# anything in the 2nd list but not the first is stale - remove it.
368stale_staged: staging .NOMETA
369	@${EGREP:Uegrep} '^[WL] .*${STAGE_OBJTOP}' /dev/null ${.MAKE.META.FILES:M*stage_*} | \
370	sed "/\.dirdep/d;s,.* '*\(${STAGE_OBJTOP}/[^ '][^ ']*\).*,\1," | \
371	sort > ${.TARGET}.staged1
372	@grep -l '${_dirdep}' /dev/null ${_STAGED_DIRS:M${STAGE_OBJTOP}*:O:u:@d@$d/*.dirdep@} | \
373	sed 's,\.dirdep,,' | sort > ${.TARGET}.staged2
374	@comm -13 ${.TARGET}.staged1 ${.TARGET}.staged2 > ${.TARGET}.stale
375	@test ! -s ${.TARGET}.stale || { \
376		echo "Removing stale staged files..."; \
377		sed 's,.*,& &.dirdep,' ${.TARGET}.stale | xargs rm -f; }
378
379.endif
380.endif
381.endif
382.endif
383