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