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