diff --git a/contrib/bmake/ChangeLog b/contrib/bmake/ChangeLog index 12774bb5598c..212b0fcfaacb 100644 --- a/contrib/bmake/ChangeLog +++ b/contrib/bmake/ChangeLog @@ -1,4743 +1,4882 @@ +2025-01-25 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20250125 + Merge with NetBSD make, sync up below change. + + * unit-tests/Makefile: remove $TMPDIR via .END + to avoid failure in test on NFS - since the temp file for the + target script is open and thus gets renamed by the server rather + than removed. + +2025-01-20 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20250120 + Merge with NetBSD make, pick up + o use FORK_FUNCTION so it can be forced to fork when doing coverage. + o main.c: avoid memory allocation in error path after exec failure. + +2025-01-16 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20250116 + Merge with NetBSD make, pick up + o clarify that undefined expressions are allowed in dependencies + o simplify code for evaluating the '!=' variable assignment + +2025-01-11 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20250111 + Merge with NetBSD make, pick up + o replace "Malformed conditional" with "Variable is undefined" + when appropriate + +2025-01-10 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20250110 + Merge with NetBSD make, pick up + o job.c: remove some unnecessary layers in job handling + o unit-tests: test expressions based on undefined variables + +2025-01-01 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20250101 + Merge with NetBSD make, pick up + o var.c: reduce pointer indirections when unexporting a variable + +2024-12-12 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20241212 + * mk/ updates + +2024-11-24 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20241124 + Merge with NetBSD make, pick up + o var.c: fix confusing error message when overriding a read-only + variable + +2024-11-22 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20241122 + Merge with NetBSD make, pick up + o unit-tests/Makefile: optimize running of tests skip extra cat + in 99% of cases. + +2024-11-15 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20241114 + Merge with NetBSD make, pick up + o make.1: note that MAKEOBJPREFIX should be absolute path + also that it can be set via makefile if suitable care taken. + +2024-11-10 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20241110 + Merge with NetBSD make, pick up + o make: allow .../ (search here and above) in + .MAKE.MAKEFILE_PREFERENCE and -f argument. + +2024-11-03 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20241101 + Merge with NetBSD make, pick up + o parse.c: report filename:linenumber in parse debug output + +2024-09-21 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240921 + Merge with NetBSD make, pick up + o make.1: Only list the defaults for MAKEFILE_PREFERENCE once. + + * Makefile: use genfiles.mk to generate ${MAN} + so that it can be tuned for local site. + Ensure MAN is defined before including Makefile.inc + + * Makefile: use MK_GEN_MAN to make it easier to control whether we + generate ${MAN} + +2024-09-16 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240909 + + * arch.c: fix NetBSD PR 58597 + +2024-09-01 Simon J Gerraty + + * Makefile: use SUBDIR.${MK_TESTS} so that we skip + unit-tests for obj and clean when FreeBSD is building WITHOUT_TESTS + + * VERSION (_MAKE_VERSION): 20240901 + Merge with NetBSD make, pick up + o reduce line length in error messages + o var.c: simplify printing of an evaluation stack element + +2024-08-29 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240828 + Merge with NetBSD make, pick up + o add more context to error message about recursive variables + o treat recursive variables non-fatally - continue parsing to end + of makefile + +2024-08-12 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240808 + Merge with NetBSD make, pick up + o improve some error messages for better clarify and readability + +2024-07-22 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240722 + Merge with NetBSD make, pick up + o job.c: remove dead code + +2024-07-21 Simon J Gerraty + + * VERSION (_MAKE_VERSION): 20240720 + Merge with NetBSD make, pick up + o compat.c: do not run commands that have parse or evaluation errors. + o var.c: remove wrong error message about an undefined variable + 2024-07-13 Simon J Gerraty * cleanup redundant differences from NetBSD make o parse.c: no longer uses mmap o var.c: check __STDC_VERSION__ not __STDC__ 2024-07-12 Simon J Gerraty * Apply some patches from NetBSD pkgsrc to reduce divergence o meta.c: requires sys/select.h if available o var.c: ensure SIZE_MAX has a value o util.c: ensure SA_RESTART is defined * configure.in: use *ksh* rather than just *ksh to match ksh shell specification. * unit-tests/Makefile: expand BROKEN_TESTS for ksh and mksh in particular 2024-07-11 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240711 Merge with NetBSD make, pick up o compat.c: allow Compat_RunCommand to also handle very long commands by writing to a temp file when needed. o main.c: extract the temp file logic recently added to Cmd_Exec to Cmd_Argv so it can be leveraged by Compat_RunCommand. 2024-07-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240709 Merge with NetBSD make, pick up o error out on parse/evaluation errors in shell commands o var.c: error out on syntax errors in ':M' and ':N' modifiers 2024-07-07 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240707 Merge with NetBSD make, pick up o only generate code for cleanup functions in CLEANUP mode o hash.c: don't track hash table chain lengths during lookup unless debugging o main.c: move initialization of variable scopes to targ.c o var.c: remove Var_End as it is now unnecessary 2024-07-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240706 Merge with NetBSD make, pick up o reduce lint comments about ARGSUSED o cond.c: error out on conditions containing the operators '&' and '|' o str.c: error out on a matching malformed matching pattern '[[' o var.c: in error messages, distinguish parsing from evaluating in error messages for anonymous variables, log the value error out on unclosed expressions during parse time 2024-07-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240704 Merge with NetBSD make, pick up o add more context information to error messages o main.c: on error, print the targets to be made add detailed exit status to message for failed sub-commands o var.c: error out on the "Bad modifier" error message 2024-07-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240701 Merge with NetBSD make, pick up o var.c: add :tt for Title case 2024-06-30 Simon J Gerraty * configure.in: 20240630 further refine check for whether TZ=Europe/Berlin works * VERSION (_MAKE_VERSION): 20240630 Merge with NetBSD make, pick up o job.c: reduce use of UNCONST o main.c: add detailed exit status to message for failed sub-commands o var.c: error out on some more syntax errors add more context to "returned non-zero status" message 2024-06-25 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240625 Merge with NetBSD make, pick up o job.c: ensure shellPath is always duped, avoid upsetting free() 2024-06-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240616 Merge with NetBSD make, pick up o clean up collection of context information for error messages o in warnings, move the word "warning" to the front o var.c: throw an error on attempt to override an internal read-only variable 2024-06-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240610 Merge with NetBSD make, pick up o for.c: remove redundant shortcut for building the .for loop body 2024-06-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240602 Merge with NetBSD make, pick up o rename some VarEvalMode constants to better match debug names. o var.c: avoid out-of-bounds read when parsing indirect modifiers. 2024-06-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240601 Merge with NetBSD make, pick up o add .export-all rather than allow .export with no argument which can happen accidentally. o if lua is available, run check-expect.lua after unit-tests o main.c: use snprintf rather than strncpy fix memory leak when purging realpath cache. 2024-05-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240528 Merge with NetBSD make, pick up o fix a number of memory leaks o replace magic numbers with POSIX FILENO constants o hash.c: remove dead code from HashTable_DeleteEntry o main.c: when complaining about unusable .OBJDIR call PrintOnError if MAKE_DEBUG_OBJDIR_CHECK_WRITABLE is true. o parse.c: use fewer technical terms in debug message for dependency 2024-05-20 Simon J Gerraty * VERSION (_MAKE_VERSION): Merge with NetBSD make, pick up o dir.c: in FindFile restore last search of .CURDIR even for includes, as a number of existing makefiles are broken otherwise. 2024-05-19 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240519 Merge with NetBSD make, pick up o dir.c: Add Dir_FindInclude, FindFile without looking in .CURDIR. Also fix Dir_SetSYSPATH to use defSysIncPath if sysIncPath is empty. o main.c: no need to set .DOTLAST in sysIncPath 2024-05-07 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240508 Merge with NetBSD make, pick up o make: ensure variables set on command line get added to .MAKEOVERRIDES (even if they start with '.') so they are passed to sub-makes. 2024-04-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240430 Merge with NetBSD make, pick up o main.c: ensure '.include ' respects MAKESYSPATH. Dir_FindFile will search .CURDIR first unless ".DOTLAST" is seen. 2024-04-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240428 Merge with NetBSD make, pick up o simplify freeing of lists o arch.c: trim pointless comments o var.c: delay variable assignments until actually needed don't reallocate memory after evaluating an expression, result is almost always short-lived. 2024-04-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240426 Merge with NetBSD make, pick up o job.c: in debug output, print the directory in which a job failed at same time as failed target so it is more easily found in build log. 2024-04-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240424 Merge with NetBSD make, pick up o clean up comments, code and tests 2024-04-23 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240422 Merge with NetBSD make, pick up o var.c: avoid LazyBuf for :*time modifiers. LazyBuf's are not nul terminated so not suitable for passing to functions that expect that. These modifiers are used sparingly so an extra allocation is not a problem. 2024-04-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240420 Merge with NetBSD make, pick up o provide more context information for parse/evaluate errors 2024-04-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240414 Merge with NetBSD make, pick up o parse.c: print -dp debug info earlier so we see which .if or .for line is being parsed. 2024-04-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240404 Merge with NetBSD make, pick up o fix some unit tests for Cygwin o parse.c: exit immediately after reading a null byte from a makefile * fix generation of bmake.cat1 2024-03-19 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240314 Add/Improve support for Cygwin o uname -s output isn't useful so allow configure to set FORCE_MAKE_OS - to force the value of .MAKE.OS and use Cygwin which matches uname -o o fix some unit-tests for Cygwin 2024-03-10 Simon J Gerraty * boot-strap: tests can take a long time; use a cookie to skip them if bmake has not been updated since tests last ran successfully. * Makefile: Cygwin handles MANTARGET man * unit-tests/Makefile: set BROKEN_TESTS for Cygwin 2024-03-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240309 Merge with NetBSD make, pick up o set .ERROR_EXIT to the exit status of .ERROR_TARGET this allows a .ERROR target to ignore the case of .ERROR_EXIT==6 which just means that the build actually failed somewhere else. 2024-03-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240303 * var.c: on IRIX we need both inttypes.h and stdint.h 2024-03-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240301 Merge with NetBSD make, pick up o export variables with value from target scope when appropriate. 2024-02-12 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240212 Merge with NetBSD make, pick up o remove unneeded conditional-compilation toggles INCLUDES, LIBRARIES, POSIX, SYSVINCLUDE, SYSVVARSUB, GMAKEEXPORT NO_REGEX and SUNSHCMD * configure.in: add check for regex.h * var.c: replace use of NO_REGEX with HAVE_REGEX_H 2024-02-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240204 Merge with NetBSD make, pick up o var.c: fix some lint (-dL) mode parsing issues 2024-02-02 Simon J Gerraty * VERSION: (_MAKE_VERSION): 20240202 Merge with NetBSD make, pick up o make.1: note that arg to :D and :U can be empty o var.c: $$ is not a parse error when .MAKE.SAVE_DOLLARS=no 2024-01-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240108 Merge with NetBSD make, pick up o miscellaneous cleanups 2024-01-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240106 Merge with NetBSD make, pick up o fix duplicate progname when reporting an unknown target o unit tests for Cmd_Exec using temp file 2024-01-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240105 Merge with NetBSD make, pick up o main.c: Cmd_Exec write cmd to a file if too big avoid blowing commandline/env limits 2024-01-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20240101 o util.c: flesh out more of strftime * configure.in: add --with-bmake-strftime it is not a full implementation but enough to pass all the unit-tests. * parse.c: LoadFile do not append \n to empty buffer. 2023-12-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231230 Merge with NetBSD make, pick up o simplify memory allocation for string buffers o fix declared types of list nodes o suff.c: clean up freeing of suffixes o var.c: simplify debug message for the ':@var@...@' modifier clean up variable handling 2023-12-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231226 Merge with NetBSD make, pick up o compat.c: ensure make's output is correctly ordered with that of the target when not going to a tty o main.c: check for shellPath whether to call Shell_Init() 2023-12-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231224 Merge with NetBSD make, pick up o compat.c: check for shellPath whether to call Shell_Init() tweak the unit test to detect the bug thus fixed. o make.1: do not claim .SHELL is only used by jobs mode. 2023-12-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231220 Merge with NetBSD make, pick up o str.c: speed up pattern matching in the ':M' modifier o var.c: fix confusing debug logging when deleting a variable use consistent debug messages style when ignoring variables 2023-12-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231210 Merge with NetBSD make, pick up o var.c: avoid segfault on empty :C match expression explain in debug log why variable assignment is ignored. 2023-12-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231208 Merge with NetBSD make, pick up o var.c: ensure fromCmd is set correctly for variables set on command line. 2023-11-26 Simon J Gerraty * configure.in: disable generation of 'makefile' for Darwin by default. * boot-strap: docuement --without-makefile 2023-11-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20231124 Merge with NetBSD make, pick up o main.c: cleanup processing of -j fix lint warning about strchr o var.c: more accurate error message for invalid ':mtime' argument cleanup :[...] modifier avoid reading beyond substring when comparing o unit-tests cover all cases of :mtime, test and explain exporting of variables o cleanup comments 2023-09-17 Simon J Gerraty * bsd.after-import.mk (ECHO_TAG): FreeBSD no longer uses $FreeBSD$ tag, so avoid adding it. 2023-09-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230909 Merge with NetBSD make, pick up o main.c: allow -j to compute a multiple of ncpu If _SC_NPROCESSORS_ONLN is supported; and -j arg is a floating point number or ends in 'C' compute .MAKE.JOBS as a multiple of _SC_NPROCESSORS_ONLN .MAKE.JOBS.C will be "yes" if -jC is supported 2023-08-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230820 Merge with NetBSD make, pick up o make.1: note that :localtime is better for %s o parse.c: improve error messages for invalid input. o var.c: fix for %s:L:gmtime - set TZ=UTC and use localtime to get correct result, it is still better to use %s:L:localtime. 2023-08-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230818 Merge with NetBSD make, pick up o meta.c: meta_ignore - check raw path against metaIgnorePaths to potentially skip call to realpath. o var.c: be strict when parsing the argument of the ':mtime' modifier o unit-tests/varmod-mtime.mk: document why '${%s:L:localtime}' should be used to get an equivalent value to time(3). 2023-08-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230816 Merge with NetBSD make, pick up o cond.c: clean up multiple-inclusion guards 2023-07-25 Simon J Gerraty * unit-tests/Makefile: addd varmod-localtime to BROKEN_TESTS if configure cannot work out how to control TZ. Remove varmod-localtime from BROKEN_TESTS for IRIX* 2023-07-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230723 * configure.in: fix the test for wether TZ=Europe/Berlin works. Depending on the time of year, if run between 22:00 and 00:00 UTC the check in configure would fail incorrectly. Take the day into account as well. 2023-07-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230711 Merge with NetBSD make, pick up o make.1: clean up wording, clarify scope of '!' in conditions 2023-07-15 Simon J Gerraty * make-bootstrap.sh.in: set prefix If configure is run using ksh we get unexpanded ${prefix} in DEFAULT_SYS_PATH, by ensuring prefix is set we should still get correct result. 2023-07-13 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230711 bump version for IRIX tweaks * make.h: undef OP_NONE if defined * unit-tests/Makefile: set BROKEN_TESTS for IRIX * configure.in: override INSTALL on IRIX 2023-06-27 Simon J Gerraty * boot-strap op_test: ensure we set TEST_MAKE as we want it. 2023-06-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230622 Merge with NetBSD make, pick up o optimize string matching for ':M' and ':N' o warn about malformed patterns in ':M', ':N' and '.if make(...)' 2023-06-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230621 Merge with NetBSD make, pick up o more extensive tests for include guards o parse.c: if a guard is already defined a file that uses the same guard is still guarded by it. 2023-06-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230620 Merge with NetBSD make, pick up o allow guards to be targets as well as variables The guard targets may include variable references like __${.PARSEDIR:tA}/${.PARSEFILE}__ 2023-06-19 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230619 Merge with NetBSD make, pick up o unit test for .undef of readOnly vars o optimization for makefiles protected from multiple-inclusion skip even opening the file after first include. Initially this only handles makefiles guarded by a variable target guards are next. 2023-06-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230616 Merge with NetBSD make, pick up o var.c: do not allow delete of readOnly variable 2023-06-03 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230601 Merge with NetBSD make, pick up o parse.c: .break takes no args o lots of unit test updates 2023-05-29 Simon J Gerraty * unit-tests/Makefile: skip tests that require /dev/filemon if it does not exists - issue a warning. 2023-05-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230522 Fix building on darwin ppc * os.sh (MACHINE): Darwin powerpc cannot use `uname -m` also recent NetBSD uses x86_64 for MACHINE_ARCH so conform. 2023-05-15 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230515 * Makefile (COPTS.filemon_ktrace.c): NetBSD 7 needs help to compile filemon_ktrace.c 2023-05-13 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230512 o sys.dirdeps.mk - broke after-import target 2023-05-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230510 Merge with NetBSD make, pick up o parse.c: don't print null filename in stack traces o var.c: :mtime operate on each word in variable value 2023-05-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230509 Merge with NetBSD make, pick up o for.c: skip syntactically wrong .for loops o var.c: allow for :gmtime=${mtime} add :mtime[=timestamp] where timestamp is used if stat(2) fails, if :mtime=error stat(2) failure causes error. 2023-05-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230504 Merge with NetBSD make, pick up o compat.c: fix compile on NetBSD 7.2 o make.1: fix documentation of .PREFIX to match reality and POSIX o unit-tests: improved var-scope-local 2023-04-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230414 Merge with NetBSD make, pick up o minor cleanup 2023-03-25 Simon J Gerraty * main.c: on some systems (eg OS/X) setting RLIMIT_NOFILE to unlimited results in an insane number (0x7fffffffffffffff). If BMAKE_NOFILE_MAX is defined, use that instead. 2023-03-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230321 Merge with NetBSD make, pick up * make.1: document seemingly unexplained Error code 6. 2023-03-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230317 Merge with NetBSD make, pick up o compat.c: CompatDeleteTarget skip .PHONY targets to be consistent with JobDeleteTarget. o job.c: fix memory leak in handling sysv :from=to modifiers 2023-03-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230303 Merge with NetBSD make, pick up o several updated unit-tests 2023-02-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230222 Merge with NetBSD make, pick up o unit tests for .MAKE.META.IGNORE_{FILTER,PATHS,PATTERNS} 2023-02-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230218 Merge with NetBSD make, pick up o var.c: fix parsing of unevaluated subexpressions with unbalanced '{}' 2023-02-17 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230215 Merge with NetBSD make, pick up o inline macros for some variable names o cond.c: reduce complexity of evaluating expressions 2023-02-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230208 Merge with NetBSD make, pick up o var.c: always use SCOPE_GLOBAL for :_ to avoid problems when it has been used within conditional expressions 2023-01-27 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230127 * install-sh: if making directories ensure umask is set to match mode. * Makefile: use DIRMODE for directories and NONBINMODE for man pages and mk files 2023-01-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230126 Merge with NetBSD make, pick up o variables like .newline and .MAKE.{GID,PID,PPID,UID} should be read-only. 2023-01-23 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230123 Merge with NetBSD make, pick up o .[NO]READONLY: for control of read-only variables o .SYSPATH: for controlling the path searched for makefiles 2023-01-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230120 Merge with NetBSD make, pick up o allow for white-space between command specifiers @+- o add more details to warning 'Extra targets ignored' 2023-01-12 Simon J Gerraty * machine.sh: leverage os.sh rather than duplicate also dispence with the $OS.$MACHINE values - we have $HOST_TARGET for that purpose for the past decade or so. We invariably get MACHINE and MACHINE_ARCH at runtime anyway. 2023-01-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20230101 Merge with NetBSD make, pick up o cleanup comments, inline some LazyBuf_ methods o unit-tests/ add/improve comments in tests o make.1: sync list of built-in variables with reality sort list of built-in variables reduce indentation of the long list of variable names use consistent markup for boolean flags move description of .MAKE.MODE below the .MAKE.META block clarify in which case an expression may omit braces 2022-11-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20221024 Merge with NetBSD make, pick up o change return type of unlink_file back to int 2022-10-07 Simon J Gerraty * Makefile: Darwin and Linux can handle MANTARGET=man 2022-09-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220928 Merge with NetBSD make, pick up o fix more ignored returns from snprintf o compile with higher warnings 2022-09-26 Simon J Gerraty * main.c meta.c: do not ignore return from snprintf * meta.c strlcpy.c: we need prototype for strlcpy * sigcompat.c: fix unused function warnings 2022-09-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220924 Merge with NetBSD make, pick up o fix bug in .break reset of conditional depth o overhaul and simplify tracking of conditional depth 2022-09-17 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220912 Merge with NetBSD make, pick up o man page updates 2022-09-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220909 Merge with NetBSD make, pick up o update unit-tests to handle deprecation of egrep o cond.c: add more details to error message for numeric comparison * configure.in: allow for deprecation of egrep * Makefile: Linux can handle MANTARGET=man 2022-09-03 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220903 Merge with NetBSD make, pick up o job.c: fix handling of null bytes in output 2022-09-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220902 Merge with NetBSD make, pick up o Allow .break to terminate a .for loop early 2022-09-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220901 Merge with NetBSD make, pick up o var.c: fix out-of-bounds errors when parsing 2022-08-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220824 Merge with NetBSD make, pick up o var.c: revert change to modifier parsing that breaks shell variable references within ':@var@body@' o adjust unit-tests 2022-08-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220818 Merge with NetBSD make, pick up o fix exit status for '-q' (since 1994) 2022-08-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220808 Merge with NetBSD make, pick up o var.c: fix parsing of modifiers containing unbalanced subexpressions extract parsing of ':D' and ':U' modifiers into separate function 2022-07-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220726 * Auto-create objdir for bmake/unit-tests if appropriate 2022-07-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220724 Merge with NetBSD make, pick up o make.1: describe variable assignment and evaluation more precisely o parse.c: fix out-of-bounds read when parsing an invalid line o var.c: simplify return type of IsShortVarnameValid 2022-06-12 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220612 Merge with NetBSD make, pick up o allow to randomize build order of targets .MAKE.MODE += randomize-targets can help uncover dependency bugs within a makefile. o compat.c: rename Compat_Run to Compat_MakeAll o make.c: inline MakeBuildParent inline make_abort, improve error details o parse.c: reorganize Parse_Error fix memory leak in wildcard targets and sources separate cases in HandleDependencyTargetMundane extract HandleSingleDependencyTargetMundane rename loadfile to LoadFile split IncludeFile into separate functions condense code for searching a file in the paths fix off-by-one error in buffer for .WAIT nodes o str.c: condense Str_Match make code for string matching syntactically more consistent 2022-04-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220418 Merge with NetBSD make, pick up o ignore '.POSIX:' if not in first non-comment line of Makefile as specified by POSIX. add unit-tests for above. o meta.c: make it easier to find usage of identifiers o targ.c: add .USEBEFORE to Targ_PrintType 2022-04-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220414 * unit-tests/Makefile: simplify checks for shells with BROKEN_TESTS, this helps with other Linux distros that use dash. 2022-03-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220330 Merge with NetBSD make, pick up o var.c: fix spacing, and a typo in a test 2022-03-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220326 Merge with NetBSD make, pick up o parse.c: try to include 'posix.mk' the first time .POSIX: is encountered, to allow for beter POSIX compliance. o var.c: make debug logs more readable prefer 'long long' over 'long' on 32-bit C99 platforms fix crash on .undef of an environment variable 2022-03-03 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220303 Merge with NetBSD make, pick up o tell meta mode unit tests not to expect filemon o cond.c: make debug logging for comparisons less technical o lst.c: fix mem leak in Lst_Remove o str.c: make code for string matching syntactically more consistent o var.c: simplify ParseModifier_Match 2022-02-14 Simon J Gerraty * unit-tests/Makefile: control MAKESYSPATH for deptgt-phony * VERSION (_MAKE_VERSION): 20220214 Merge with NetBSD make, pick up o cond.c: simplify control flow in CondParser_Comparison o job.c: fix echoing of command with '-' in silent target in jobs mode o main.c: prefix the warning about read-only .OBJDIR with a colon o parse.c: remove redundant conditions o var.c: simplify control flow in ModifyWord_SysVSubst 2022-02-08 Simon J Gerraty * unit-tests/Makefile: disable opt-debug-x-trace on Linux if there is any chance we have dash as .SHELL * VERSION (_MAKE_VERSION): 20220208 Merge with NetBSD make, pick up o more unit tests o meta.c: use a variable to hold command line to be filtered to avoid any side effects from content of command line. 2022-02-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220204 Merge with NetBSD make, pick up o use unsigned consistently for line numbers, avoid the need for %z o parse.c: do not step off end of input in Parse_IsVar when checking for target local variable assignments 2022-02-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220202 Merge with NetBSD make, pick up o remove redundant declaration of HashIter_Init o make DEBUG0 simpler 2022-01-30 Simon J Gerraty * cast gn->lineno to avoid %z * VERSION (_MAKE_VERSION): 20220130 Merge with NetBSD make, pick up o more unit tests o make GNode lineno unsigned to please lint o print location of recursive variable references in commands o print "stack trace" (makefile includes) on fatal errors o make.1: refine documentation for target local assignments 2022-01-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220128 Merge with NetBSD make, pick up o inline functions called only once o for.c: clean up AddEscape for building the body of a .for loop o hash.c: merge duplicate code for finding an entry in a hash table replace HashEntry_KeyEquals with strncmp o make.1: document quirks of target local variable assignments. o parse.c: cleanup white-space 2022-01-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220126 Merge with NetBSD make, pick up o allow setting target local variables o more unit tests o add missing newline after "cannot continue" message o meta.c: clean up eat_dots o parse.c: fix filename in warning about duplicate script o var.c: when expanding nested variables, check simple things first 2022-01-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220116 Merge with NetBSD make, pick up o fix for unit-tests/varname-makeflags on non-BSD systems o use Var_Exists rather than Var_Value where appropriate o remove unnecessary functions for expanding variable names o cond.c: inline EvalBare o main.c: lint cleanup o parse.c: condense code in Parse_IsVar use islower for parsing directives (none have upper case) 2022-01-12 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220112 Merge with NetBSD make, pick up o meta.c: add .MAKE.META.CMP_FILTER for filtering commands before comparion, rarely needed but useful when it is. 2022-01-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220110 Merge with NetBSD make, pick up o inline Buf_Clear o remove redundant braces o rename and inline Targ_Precious o cond.c: remove redundant initializer in CondParser_ComparisonOrLeaf o for.c: clean up handling of .for loops fix reported line numbers of continuation lines add details about .for loop variables to stack traces o job.c: reduce code for initializing error handling in shell o main.c: in Cmd_Exec, return error message instead of format string have as few statements as possible between va_start and va_end add debug logging for capturing the output of external commands o make.c: use consistent variable names for varargs o make_malloc.c: remove duplicate code from bmake_strdup o parse.c: add missing printflike annotations remove redundant lines from stack traces fix stack traces in -dp mode reduce confusing code in ParseForLoop fix line number in debug log after returning from a file rename IFile and its fields to match their actual content clean up ParseDependencySources o var.c: shorten ApplyModifier_Assign rename is_shell_metachar, fix character conversion warning merge calls to ApplyModifier_Time merge duplicate code for modifiers 'gmtime' and 'localtime' 2022-01-04 Simon J Gerraty * parse.c: loadfile restore extra byte in buffer. 2022-01-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20220101 Merge with NetBSD make, pick up o more unit-tests o remove unnecessary words from command line options in CmdOpts o rename eunlink to unlink_file o cond.c: make ParseWord in condition parser simpler internally return false for irrelevant leaves in conditions replace table for function lookup in conditions with simple code merge duplicate types CondEvalResult and CondResult o for.c: clean up handling of .for loops and .include directives o main.c: constify cached_realpath clean up Cmd_Exec o parse.c: sync API documentation fix error message when reading more than 1 GB from stdin clean up parsing of makefiles fix line number in error message about open conditionals unexport types VarAssignOp and VarAssign clean up function names remove redundant parameters in dependency parsing functions reduce scope of the list of wildcard target names extract OP_NOTARGET into separate function clean up variable names for parsing dependency lines make debug logging a bit more human-friendly o var.c: condense code in ApplyModifier_Assign 2021-12-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211221 Merge with NetBSD make, pick up o more unit-tests o style cleanup o in CLEANUP mode, free interned strings at the very end o fix memory leak for filenames in .for loops o buf.c: avoid memory leak o cond.c: condense CondParser_ComparisonOp o hash.c: change return type of HashTable_Set to void o job.c: change return type of Compat_RunCommand from int to bool o main.c: remove bmake_free o parse.c: condense repetetive code in ParseDirective remove dead code for handling traditional include directives clean up parsing of variable assignments remove unreachable code for parsing the dependency operator clean up loading of files fix memory leak in IncludeFile o var.c: fix memory leak when parsing a variable name fix memory leak from ${.SUFFIXES} reduce memory allocation in modifier ':?' and ':C' condense RegexReplace for the modifier ':C' and avoid strlen merge duplicate code for memory handling in Var_Parse distinguish between short-lived and environment variables rename VarFreeEnv to VarFreeShortLived 2021-12-15 Simon J Gerraty * cond.c: fix mem leak in CondParser_Leaf 2021-12-12 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211212 Merge with NetBSD make, pick up o rename Parse_SetInput to Parse_PushInput o remove remove period from end of error messages and warnings to be more consistent o arch.c: use simpler memory management for parsing archive members o cond.c: rework and reduce recursion o for.c: rename some functions to better reflect purpose o suff.c: add Suff_NamesStr to provide .SUFFIXES as a string. o var.c: in parse errors, mark whitespace more clearly inline ParseEmptyArg into CondParser_FuncCallEmpty minimize calls to LazyBuf_Get in ParseVarnameLong treat .SUFFIXES as a read-only variable 2021-12-07 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211207 Merge with NetBSD make, pick up o inline HashIter_Init o parse.c: inline common subexpression in ParseRawLine o var.c: merge branches for modifiers ':D' and ':U' extract common code into Expr_Words extract common code into Expr_Str move low-level implementation details out of Var_Parse 2021-12-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211206 Merge with NetBSD make, pick up o add unit-tests/varmod-loop-delete o for.c: inline Str_Words - reduce memory allocation o parse.c: do not try to expand fixed variable names only allocate the name of an included file if necessary clean up ParseInclude o var.c: fix use-after-free in modifier ':@' save a memory allocation in each modifier ':O' and ':u' save a memory allocation in the modifier ':[...]' in UnexportVars, replace Str_Words with Substring_Words to reduce allocations and copying. 2021-12-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211204 Merge with NetBSD make, pick up o flesh out a number of tests o replace enums with bitfields, this simplifies a lot of code. o var.c: refactor ParseModifierPartSubst 2021-10-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211024 Merge with NetBSD make, pick up o Punt on write errors - ENOSPC etc. 2021-10-22 Simon J Gerraty * configure.in: use_defshell, set both DEFSHELL_INDEX and defshell_path if appropriate. This makes it easier to use say the KSH specification with and alternate path for the shell. * configure.in compat.c: for SCO we need to force UseShell * configure.in: SCO /bin/sh is not usable, provide a list of alternatives for use as .SHELL. We still have to mark some tests as broken, plus more if we end up with ksh as .SHELL. Issue a warning about skipped tests. * boot-strap: leave TOOL_DIFF to configure * configure.in: on SCO native cc is not usable, gcc is to be found in /usr/gnu/bin and while ancient is at least able to compile bmake. Thus we add /usr/gnu/bin to PATH if it exists, and later check if $CC would have been found via $PATH. If not we set CC to the full path of $CC. Also gnu diff is known to support -u, so if it exists use it. * configure.in: move getopt to AC_REPLACE_FUNCS also add AC_C_INLINE - in an attempt to compile using native cc on SCO. * configure.in: check for stresep as well as strsep, since we define the later to the former if necessary, and if we have to provide stresep we also need to provide a prototype. * configure.in: we no longer need to worry about sys/cdefs.h providing __RCSID which simplifies things quite a bit. * make.h: make sure we have __RCSID * unit-tests/Makefile.config.in: add TOOL_DIFF so configure can control it. 2021-10-20 Simon J Gerraty * VERSION: 20211020 Merge with NetBSD make, pick up o confirm sync of unit-tests 2021-10-18 Simon J Gerraty * configure.in: check if timezone Europe/Berlin is supported if not try UTC-1 * configure.in: if .OBJDIR is $srcdir/obj we need to create a symlink unit-tests -> ../unit-tests/obj so that unit-tests/Makefile.config is put in the right place. * refine filtering of .OBJDIR in unit-tests 2021-10-16 Simon J Gerraty * Fix unit-tests on Minix 3.2.0 o job.c: do not punt if read of token pipe fails for EAGAIN. On Minix at least, we are not ready to read the childExitJob pipe when poll says we are. There should actually be no reason for this pipe to be non-blocking, but while that works fine on {Net,Free}BSD it breaks another test case on Minix. o unit-tests/Makefile: deal with variants of error messages and use of obj as .OBJDIR 2021-10-14 Simon J Gerraty * configure.in: add sigaction to AC_REPLACE_FUNCS we also need to check for sigaddset etc just for the benefit of sigact.c * Add sigact.c as sigaction.c so this "just works". This should have been done back when bmake_signal started using sigaction (I only just noticed that sigact.c wasn't here ;-) Note: I no longer have access to any system where this would matter. 2021-10-13 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211011 * Makefile: cleanup a little * configure.in: check for sigsetmask 2021-10-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20211001 Merge with NetBSD make, pick up o reduce locations reducing text size o remove unnecessary const o cond.c: fix lint warning on i386 do not allow unquoted 'left == right' after modifier ':?' o hash.c: fix build for DEBUG_HASH_LOOKUP o var.c: fix memory leak in error case of the ':?' modifier 2021-09-11 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210911 Merge with NetBSD make, pick up o var.c: replace remaining ModChain_ShouldEval with Expr_ShouldEval 2021-09-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210906 Merge with NetBSD make, pick up o more unit tests o lint cleanup o rename some functions to better fit purpose o for.c: cleanup - remove unnecessary optimization fix embedded newlines o parse.c: correct case for CVS/RCS 2021-08-11 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210808 Merge with NetBSD make, pick up o var.c: remove redundant initialization in ApplyModifier_Order * mk/options.mk: issue warning for incorrect usage 2021-08-03 Simon J Gerraty * var.c: use long for :On if we don't have a 64bit int type * VERSION (_MAKE_VERSION): 20210803 Merge with NetBSD make, pick up o rework varmod-order tests to avoid qsort instability o make.1: clarify :On entry 2021-07-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210731 Merge with NetBSD make, pick up o fix some lint issues o more unit tests o var.c: rework of ApplyModifier_Order 2021-07-30 Simon J Gerraty * util.c: add strto*l if HAVE_STRTO*L not defined * VERSION (_MAKE_VERSION): 20210730 Merge with NetBSD make, pick up o var.c: add :On and :Orn for numeric sort disabled if no 64bit type available. o _strtol.h: to implement strto*l functions 2021-07-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210704 Merge with NetBSD make, pick up o unit-tests: fix some tests to be more portable - job-output-null not all shells do the same number of write calls - objdir-writable if TMPDIR is set; /tmp may not be usable 2021-07-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210701 Merge with NetBSD make, pick up o unit-tests: allow for BROKEN_TESTS to list TESTS to be skipped; some tests just cannot work in some environments. o buf.c: simpler upper bound for length in Buf_AddInt o cond.c: fix grammar in error message for malformed conditional o for.c: prevent newline injection (from ${.newline}) in .for loops o var.c: use more practical data type in RegexReplace (avoid need for %zu) extract RegexReplace from ModifyWord_SubstRegex 2021-06-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210621 Merge with NetBSD make, pick up o var.c: only report error for unmatched regex subexpression when linting (-dL) since we cannot tell when an unmatched subexpression is an expected result. o move unmatched regex subexpression tests to varmod-subst-regex.mk and enable strict (lint) mode 2021-06-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210616 Merge with NetBSD make, pick up o more unit tests o cond.c: rename If_Eval to EvalBare improve function names for parsing conditions o job.c: fix error handling of targets that cannot be made o var.c: uncompress code in ApplyModifier_Unique 2021-05-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210518 Merge with NetBSD make, pick up o fix unit-tests/opt-chdir to cope with /nonexistent existing. o job.c: Print -de error information when running multiple jobs 2021-04-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210420 Merge with NetBSD make, pick up o use C99 bool type o convert VarEvalFlags back into an enum o cond.c: do not complain when skipping the condition 'no >= 10' o hash.c: avoid allocating memory for simple variable names o job.c: use distinct wording for writing to the shell commands file remove type name for the abort status in job handling rename PrintOutput to PrintFilteredOutput to avoid confusion o main.c: avoid double slash in name of temporary directory o var.c: use straight quotes for error 'Bad conditional expression' reduce memory allocations in the modifiers ':D' and ':U' rename members of ModifyWord_LoopArgs clean up pattern flags for the modifiers ':S' and ':C' reduce memory allocation and strlen calls in modifier ':from=to' in the ':Q' modifier, only allocate memory if necessary improve performance for LazyBuf remove redundant parameter from ParseVarnameLong migrate ParseModifierPart to use Substring avoid unnecessary calls to strlen when evaluating modifiers migrate ModifyWord functions to use Substring migrate handling of the modifier ':S,from,to,' to Substring reduce debug logging and memory allocation for ${:U...} reduce verbosity of the -dv debug logging for standard cases clean up debug logging for ':M' and ':N' disallow '$' in the variable name of the modifier ':@' simplify access to the name of an expression during evaluation 2021-03-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210330 Merge with NetBSD make, pick up o replace enum bit-field with struct bit-field for VarEvalFlags o rename VARE_NONE to VARE_PARSE_ONLY o var.c: rename ApplyModifiersState to ModChain fix double varname expansion in the variable modifier '::=' change debug log for variable evaluation flags to lowercase 2021-03-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210314 Merge with NetBSD make, pick up o var.c: avoid evaluating many modifiers in parse only mode in strict mode (-dL) many variable references are parsed twice, the first time just to report parse errors early, so we want to avoid side effects and wasted effort to the extent possible. 2021-02-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210226 Merge with NetBSD make, pick up o remove freestanding freeIt variables link via FStr o var.c: restructure code in ParseVarname to target human readers improve error message for; bad modifier in variable expression unclosed modifier unknown modifier remove redundant parameter of ApplySingleModifier explain non-obvious code around indirect variable modifiers quote ':S' in error message about missing delimiter extract ParseModifier_Match into separate function add context information to error message about ':range' modifier add quotes around variable name in an error message reorder code in ModifyWords use more common parameter order for VarSelectWords make ModifyWord_Subst a little easier to understand do not expand variable name from the command line twice extract ExistsInCmdline from Var_SetWithFlags save a hash map lookup when defining a cmdline variable clean up VarAdd, Var_Delete, Var_ReexportVars use bit-shift expressions for VarFlags constants rename constants for VarFlags rename ExprDefined constants for debug logging rename ExprStatus to ExprDefined split parameters for evaluating variable expressions reduce redundant code around ModifyWords print error about failed shell command before overwriting variable clean up ValidShortVarname, ParseVarnameShort rename VarExprStatus to ExprStatus add functions for assigning the value of an expression rename ApplyModifiersState_Define to Expr_Define condense the code for parsing :S and :C modifiers 2021-02-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210206 Merge with NetBSD make, pick up o unit-tests: use private TMPDIR to avoid errors from other users 2021-02-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210205 Merge with NetBSD make, pick up o avoid strdup in mkTempFile o always use vfork o rename context and ctxt to scope o rename some VAR constants to SCOPE o Var_ functions, move the scope to the front o use shortcut functions Global_Set and Global_Append o add shortcut Global_Delete for deleting a global variable o rename Var_Delete to Var_DeleteExpand, Var_DeleteVar to Var_Delete o compat.c: when exiting due to an error, print graph information o enum.c: remove overengineered Enum_ValueToString o make.c: remove unused INTERNAL flag remove unused return type of MakeBuildParent o parse.c: replace parse error "Need an operator" with better message o var.c: improve documentation about variable scopes rename Var_ValueDirect to GNode_ValueDirect rename old Var_SetWithFlags to Var_SetExpandWithFlags merge SetVar into Var_SetWithFlags split Var_Exists into plain Var_Exists and Var_ExistsExpand split Var_Append into Var_Append and Var_AppendExpand replace enum bit-set with bit-field o unit-tests/var-op-shell: use kill rather than kill -14 which broke on darwin with recent update. 2021-02-01 Simon J Gerraty * configure.in: check for sig_atomic_t and define it as 'int' if missing. * VERSION (_MAKE_VERSION): 20210201 Merge with NetBSD make, pick up o use sig_atomic_t for caught_sigchld 2021-01-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210130 Merge with NetBSD make, pick up o more unit tests o convert SearchPath to struct o split Buf_Destroy into Buf_Done and Buf_DoneData o for.c: split For_Eval into separate functions rename struct For to struct ForLoop o job.c: do not create empty shell files in jobs mode rename JobOpenTmpFile to JobWriteShellCommands reduce unnecessary calls to waitpid o parse.c: in -dp mode, print stack trace with each diagnostic 2021-01-23 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210123 Merge with NetBSD make, pick up o rename Dir_Expand to SearchPath_Expand o rename Dir_AddDir, reorder parameters of SearchPath_ToFlags o cond.c: fix debug output for comparison operators in conditionals o dir.c: split Dir_FindFile into separate functions 2021-01-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210120 Merge with NetBSD make, pick up o fix some more lint nits o refine some unit tests for portability o cond.c: rework parsing 2021-01-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210110 Merge with NetBSD make, pick up o fix lint warnings o consistently use boolean expressions in conditions 2021-01-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210108 Merge with NetBSD make, pick up o job.c: back to polling token pipe if we want a token o main.c: always print 'stopped in' on first call The execption is if we bail because of an abort token in which case just exit 6. 2021-01-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20210101 Merge with NetBSD make, pick up o Happy New Year! o rename CmdOpts.lint to strict o exit 2 on technical errors o replace pointers in controlling conditions with booleans o replace global preserveUndefined with VARE_KEEP_UNDEF o compat.c: re-export variables from the actual make process if using vfork this is the effect anyway o cond.c: clean up VarParseResult constants o for.c: fix undefined behavior in SubstVarLong make control flow in SubstVarLong of .for loops more obvious clean up SubstVarShort in .for loops extract ForSubstBody from ForReadMore clean up ForReadMore simplify termination condition for .for loop add error handling for .for loop items job.c: re-export variables from the actual make process parse.c: remove mmap for loading files, only allow files < 1 GiB fix edge case in := with undefined in variable name skip variable expansion in ParseDependencyTargetWord var.c: split ExportVar into separate functions clean up code in extracted ExportVar functions remove dead code from ApplyModifiersIndirect split Var_Subst into easily understandable functions clean up VarParseResult constants 2020-12-25 Simon J Gerraty * main.c: use .MAKE.DEPENDFILE as set by makefiles 2020-12-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201222 Merge with NetBSD make, pick up o make DEBUG macro return boolean o parse.c: fix assertion failure for files without trailing newline o var.c: allow .undef to undefine multiple variables at once remove excess newline from parse errors 2020-12-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201221 Merge with NetBSD make, pick up o some unit-test updates 2020-12-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201220 Merge with NetBSD make, pick up o more unit tests o return FStr from Var_Parse and Var_Value o spell nonexistent consistently o add str_basename to reduce duplicate code o compat.c: fix .ERROR_TARGET in compat -k mode extract InitSignals from Compat_Run extract UseShell from Compat_RunCommand o cond.c: error out if an '.endif' or '.else' contain extraneous text o for.c: rename ForIterate to ForReadMore o hash.c: clean up hash function for HashTable o lst.c: rename Vector.priv_cap to cap o main.c: remove constant parameter from MakeMode o make.c: use symbolic time for 0 in Make_Recheck extract MakeChildren from MakeStartJobs o parse.c: clean up memory handling in VarAssign_EvalShell, Parse_DoVar fix error message for .info/.warning/.error without argument extract Var_Undef from ParseDirective extract ParseSkippedBranches, ParseForLoop from ParseReadLine rename mode constants for ParseGetLine to be more expressive reduce debugging details in Parse_SetInput fix line numbers in .for loops split ParseGetLine into separate functions fix garbled output for failed shell command var.c: remove redundant assignment in ApplyModifier_SysV error out on unknown variable modifiers at parse time remove wrong error message for indirect modifier in lint mode extract ApplySingleModifier from ApplyModifiers use FStr for memory management in Var_SetWithFlags extract SetVar from Var_SetWithFlags use FStr in VarNew extract string functions from ApplyModifier_To error out if .undef has not exactly 1 argument extract Var_DeleteVar from Var_Delete extract Var_Undef from ParseDirective clean up memory management for expanding variable expressions 2020-12-12 Simon J Gerraty * avoid %zu * lst.c: avoid anonymous union * VERSION (_MAKE_VERSION): 20201212 Merge with NetBSD make, pick up o more unit tests o inline Targ_Ignore and Targ_Silent o split JobFlags into separate fields o remove const from function parameters (left overs from refactoring) o eliminate boolean argument of Var_Export o make API of Buf_Init simpler o rename ParseRunOptions to ParseCommandFlags o replace *line with line[0] o compat.c: fix wrong exit status for multiple failed main targets refactor Compat_Run to show the error condition more clearly don't make .END if the main targets already failed (-k mode) fix exit status in -k mode if a dependency fails o for.c: clean up Buf_AddEscaped in .for loops o job.c: extract ShellWriter_ErrOn from JobPrintCommand make Job_Touch simpler refactor JobFinish rename Shell.exitFlag to errFlag move Job.xtraced to ShellWriter make printing of shell commands independent from the job rename shell flags in struct Shell extract JobOpenTmpFile from JobStart rename RunFlags to CommandFlags split various Job.* into separate fields rename commandShell to shell extract InitShellNameAndPath from Shell_Init replace signal handling macros with local functions replace macro MESSAGE with local function parse.c: error out on null bytes in makefiles error out on misspelled directives rename IFile.nextbuf to readMore fix undefined behavior in ParseEOF str.c: remove redundant call to strlen in Str_Words var.c: error out on misspelled .unexport-env error out on misspelled .export directives extract ExportVars from Var_Export extract ExportVarsExpand from Var_Export eliminate boolean argument of Var_Export fix undefined behavior when exporting ${:U } rename Var_ExportVars to Var_ReexportVars rename Var_Export1 to ExportVar 2020-12-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201206 Merge with NetBSD make, pick up o more unit tests o inline macros for debug logging o use consistent variable names for list nodes o define constants for enum zero-values o dir.c: use fixed format for debug output of the directory cache remove Dir_InitDir o lst.c: inline Lst_Enqueue, Vector_Done o meta.c: remove unused parameter from meta_needed o parse.c: rename parse functions o suff.c: extract ExpandChildrenRegular from ExpandChildren o targ.c: don't concatenate identifiers in Targ_PrintType o var.c: remove comment decoration extract UnexportVars from Var_UnExport extract GetVarnamesToUnexport from Var_UnExport extract UnexportEnv from Var_UnExport extract UnexportVar from Var_UnExport move CleanEnv to UnexportVars replace pointer comparisons with enum add FStr to var.c to make memory handling simpler use FStr in Var_UnExport move type definitions in var.c to the top extract FreeEnvVar from Var_Parse extract ShuffleStrings from ApplyModifier_Order 2020-11-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201130 Merge with NetBSD make, pick up o add unit tests for META MODE o reduce memory allocation for dirSearchPath, GNode.parents, GNode.children, OpenDirs o reduce pointer indirection for GNode.cohorts and GNode.implicitParents o remove pointer indirection from GNode.commands o inline Lst_ForEachUntil in meta mode o dir.c: fix memory leak for lstat cache in -DCLEANUP mode clean up memory management for CachedDirs fix the reference count of dotLast going negative add debug logging for OpenDirs_Done extract CacheNewDir from Dir_AddDir add debug logging for reference counting of CachedDir rename some Dir functions to SearchPath o job.c: rename some global variables o main.c: reduce memory allocation in ReadBuiltinRules reduce memory allocation in CmdOpts.create, CmdOpts.variables, CmdOpts.makefiles Add .MAKE.UID and .MAKE.GID o make.c: reduce memory allocation for/in toBeMade, Make_ProcessWait, Make_ExpandUse o meta.c: reduce memory allocation in meta_oodate o parse.c: reduce memory allocations for parsing dependencies and targets o suff.c: reduce memory allocation in suffix handling 2020-11-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201124 Merge with NetBSD make, pick up o .MAKE.{UID,GID} represent uid and gid running make. o fix error handling for .BEGIN and .END dependency in -k mode o fix missing "Stop." after failed .END node in -k mode o use properly typed comparisons in boolean contexts o replace a few HashTable_CreateEntry with HashTable_Set o add HashSet type o compat.c: split Compat_Make into smaller functions extract DebugFailedTarget from Compat_RunCommand o dir.c: refactor Dir_UpdateMTime migrate CachedDir.files from HashTable to HashSet o make.c: add high-level API for GNode.made 2020-11-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201122 Merge with NetBSD make, pick up o rename GNode.context to vars o suff.c: cleanup and refactor rename some functions and vars to better reflect usage add high-level API for CandidateSearcher o targ.c: add more debug logging for suffix handling o more unit tests o add debug logging for setting and resetting the main target 2020-11-17 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201117 Merge with NetBSD make, pick up o fix some unit-tests when .SHELL is dash o rename Targ_NewGN to GNode_New o make some GNode functions const o main.c: call Targ_Init before Var_Init cleanup PrintOnError, getTmpdir and ParseBoolean o var.c: fix error message of failed :!cmd! modifier 2020-11-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201114 Merge with NetBSD make, pick up o replace a few HashTable_CreateEntry with HashTable_Set o clean up cached_stats o rename DEFAULT to defaultNode o remove redundant struct make_stat o cond.c: in lint mode, check for ".else " use bitset for IfState replace large switch with if-else in Cond_EvalLine o job.c: clean up JobExec, JobStart, JobDoOutput use stderr for error message about failed touch clean up Job_Touch replace macro DBPRINTF with JobPrintln rename JobState to JobStatus main.c: switch cache for realpath from GNode to HashTable clean up Fatal clean up InitDefSysIncPath use progname instead of hard-coded 'make' in warning rename Main_SetVarObjdir to SetVarObjdir make.1: document the -S option make.c: fix debug output for GNode details use symbolic names in debug output of GNodes 2020-11-12 Simon J Gerraty * configure.in: fix --with-force-machine-arch * VERSION (_MAKE_VERSION): 20201112 Merge with NetBSD make, pick up o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable checks in InitObjdir. Explicit .OBJDIR target always allows read-only directory. o cond.c: clean up Cond_EvalLine 2020-11-11 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201111 Merge with NetBSD make, pick up o more unit-tests o style cleanup remove redundant parentheses from sizeof operator replace character literal 0 with '\0'. replace pointer literal 0 with NULL. remove redundant parentheses. replace (expr & mask) == 0 with !(expr & mask). use strict typing in conditions of the form !var o rename Make_OODate to GNode_IsOODate o rename Make_TimeStamp to GNode_UpdateYoungestChild o rename Var_Set_with_flags to Var_SetWithFlags o rename dieQuietly to shouldDieQuietly o buf.c: make API of Buf_Init simpler o compat.c: clean up Compat_Make, Compat_RunCommand, CompatDeleteTarget and CompatInterrupt o cond.c: in lint mode, only allow '&&' and '||', not '&' and '|' clean up CondParser_Comparison o main.c: rename getBoolean and s2Boolean rename MAKEFILE_PREFERENCE for consistency o parse.c: replace strstr in ParseMaybeSubMake with optimized code o var.c: rename VARE_ASSIGN to VARE_KEEP_DOLLAR replace emptyString with allocated empty string error out on unclosed expressions after the colon 2020-11-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201101 Merge with NetBSD make, pick up o negate NoExecute to GNode_ShouldExecute o job.c: rename JobMatchShell to FindShellByName extract EscapeShellDblQuot from JobPrintCommand extract ParseRunOptions from JobPrintCommand o var.c: extract ApplyModifiersIndirect from ApplyModifiers treat malformed :range, :ts and :[...] as errors add tests for the variable modifiers :[words] and :range 2020-10-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201031 Merge with NetBSD make, pick up o format #include directives consistently o do not look up local variables like .TARGET anywhere else o main.c: Main_SetObjdir is first called for curdir which may be readonly reduce the scope where recursive expressions are detected remove redundant :tl from getBoolean clean up mkTempFile o meta.c: simplify memory allocation in meta_create and meta_oodate o parse.c: extract loadedfile_mmap from loadfile o trace.c: document possible undefined behavior with .CURDIR o var.c: make parsing of the :gmtime and :localtime modifiers stricter rename ismeta to is_shell_metachar remove debug logging for the :Q variable modifier rename VarIsDynamic to VarnameIsDynamic use consistent parameter order in varname parsing functions extract ParseVarnameLong from Var_Parse extract ParseVarnameShort from Var_Parse fix type of ParseModifierPart parameter delim extract IsEscapedModifierPart from ParseModifierPart clean up ModifyWords add test for combining the :@ and :? variable modifiers 2020-10-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201030 Merge with NetBSD make, pick up o change char * to void * in Var_Value o make iterating over HashTable simpler o rename VAR_CMD to VAR_CMDLINE o cond.c: clean up is_separator fix parse error in string literal in conditional o main.c: do not use objdir that is not writable in lint mode, exit with error status on errors o parse.c: clean up StrContainsWord fix out-of-bounds pointer in ParseTrackInput o var.c: rename Str_SYSVMatch and its parameters remove unsatisfiable conditions in Var_Set_with_flags document where the variable name is expanded fix documentation for VARP_SUB_ONE rename VAR_EXPORTED_YES to VAR_EXPORTED_SOME document VAR_READONLY prevent appending to read-only variables extract MayExport from Var_Export1 remove redundant evaluations in VarFind replace VarFindFlags with a simple Boolean rename FIND_CMD to FIND_CMDLINE, to match VAR_CMDLINE 2020-10-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201028 Merge with NetBSD make, pick up o rename defIncPath to defSysIncPath o initialize all CmdOpts fields o lst.c: inline Vector_Get o main.c: refactor main extract InitMaxJobs,InitObjdir,InitVarMake,InitRandom, ReadMakefiles,CleanUp,InitVpath,ReadBuiltinRules, InitDefIncPath,CmdOpts_Init,UnlimitFiles o parse.c: merge curFile into includes rename predecessor to order_pred sort ParseSpecial alphabetically remove unused, undocumented .NOEXPORT rename ParseSpecial enum values consistently rename some fields of struct IFile 2020-10-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201026 Merge with NetBSD make, pick up o group the command line options and arguments into a struct o rename GNode.cmgn to youngestChild o rename hash functions to identify the type name o negate OP_NOP and rename it to GNode_IsTarget o add GNode_Path to access the path of a GNode o remove macros MIN and MAX o remove unused Lst_Find and Lst_FindFrom o arch.c: and make Arch_FindLib simpler clean up code layout make Arch_ParseArchive simpler o cond.c: inline CondFindStrMatch into FuncMake o dir.c: replace Dir_CopyDir with Dir_CopyDirSearchPath omit trailing space in debug output for expanding file patterns refactor DirMatchFiles document that the SearchPath of Dir_FindFile may be NULL remove UNCONST from Dir_Expand inline DirFindName o for.c: clean up code for handling .for loops o hash.c: print hash in debug log with fixed width clean up hash table functions reduce amount of string hashing o job.c: refactor JobDeleteTarget use proper enum constants for aborting convert result of JobStart from macros to enum convert abort reason macros to enum rework Job_CheckCommands to reduce indentation rename Shell fields add field names in declaration of DEFSHELL_CUSTOM convert JobState and JobFlags to enum types move handling of the "..." command to JobPrintCommands o lst.c: clean up refactor LstNodeNew remove Lst_Open, Lst_Next, Lst_Close remove code for circular lists from Lst_Next o main.c: do not attempt to read .MAKE.DEPENFILE if set to /dev/null or anything starting with "no" convert macros for debug flags into enum o make.c: inline Lst_Copy in Make_ExpandUse o meta.c: inline Lst_Find in meta_oodate make Lst_RemoveIf simpler in meta_oodate o parse.c: convert error level for Parse_Error to an enum o suff.c: properly terminate debug output with newline add more details to DEBUG_SRC log replace Dir_CopyDir with Dir_CopyDirSearchPath don't modify GNode name while rebuilding the suffix graph o var.c: reduce duplicate code in VarFind 2020-10-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201022 Merge with NetBSD make, pick up o more refactoring and simplification to reduce code size o var.c: extract CanonicalVarname from VarFind o make.c: extract UpdateImplicitParentsVars from Make_Update o main.c: extract PrintVar from doPrintVars extract HandlePWD from main o lst.c: inline simple Lst getters remove unused Lst_ForEach o job.c: move struct Shell from job.h to job.c o more unit tests 2020-10-19 Simon J Gerraty * configure.in: remove inappropriate use of AC_INCLUDES_DEFAULT 2020-10-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201018 Merge with NetBSD make, pick up o remove USE_IOVEC o rename some Hash_* apis to Hash* o replace execError with execDie o rename Lst_Init to Lst_New o add tags to enum types o rename Stack to Vector o parse.c: more refactoring o unit-tests: make some tests use line buffered stdout o unit-tests/Makefile: in meta mode do not make all tests depend on Makefile, it isn't necessary. 2020-10-10 Simon J Gerraty * main.c: check for CTL_HW being defined. * unit-tests/Makefile: ensure export tests output are POSIX compliant disable opt-debug-jobs test until it works on ubuntu * VERSION (_MAKE_VERSION): 20201010 Merge with NetBSD make, pick up o dir.c: remove pathname limit for Dir_FindHereOrAbove o hash.c: replace strcpy with memcpy in Hash_CreateEntry o main.c: extract init_machine and init_machine_arch from main allow to disable debug logging options o parse.c: enable format string truncation warnings extract parsing of sources from ParseDoDependency split ParseDoSrc into smaller functions hide implementation details from Parse_DoVar clean up parsing of variable assignments split Parse_DoVar into manageable pieces don't modify the given line during Parse_DoVar fix out-of-bounds memory access in Parse_DoVar fix parsing of the :sh assignment modifier o var.c: rework memory allocation for the name of variables extract ApplyModifier_Literal into separate function in lint mode, reject modifiers without delimiter do not export variable names starting with '-' o fix double-free bug in -DCLEANUP mode o more cleanup to enable higher warnings level o more unit tests 2020-10-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201002 Merge with NetBSD make, pick up o dir.c: use hash table for looking up open directories by name o main.c: clean up option handling o parse.c: add missing const for Parse_AddIncludeDir o var.c: ApplyModifier_To, update pp in each branch o remove redundant function prototypes o more unit tests 2020-10-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20201001 Merge with NetBSD make, pick up o compat.c: comment about "..." 2020-09-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200930 Merge with NetBSD make, pick up o job.c: split Job.jobPipe into 2 separate fields replace Lst_Open with direct iteration o lst.c: remove redundant assertions o targ.c: replace Lst_Open with direct iteration o var.c: fix bug in evaluation of indirect variable modifiers extract ApplyModifier_Quote into separate function o make debug logging simpler 2020-09-27 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200927 Merge with NetBSD make, pick up o parse.c: ensure parse errors result in 'stopped in' message. o compat.c: make parameter of Compat_RunCommand const o main.c: extract InitVarTarget from main o parse.c: rename ParseFinishLine to FinishDependencyGroup refactor ParseDoDependency o var.c: Var_Subst no longer returns string result rename Var_ParsePP back to Var_Parse in lint mode, improve error handling for undefined variables extract ParseVarname from Var_Parse o rename Lst_ForEach to Lst_ForEachUntil o inline Lst_ForEachUntil in several cases o clean up API for finding and creating GNodes o fix assertion failure in -j mode with .END node o inline and remove LstNode_Prev and LstNode_Next o use fine-grained type names for lists and their nodes o more unit tests 2020-09-11 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200911 Merge with NetBSD make, pick up o cond.c: split EvalComparison into smaller functions reorder parameters of condition parsing functions reduce code size in CondParser_Eval rename CondGetString to CondParser_String add CondLexer_SkipWhitespace group the condition parsing state into a struct in CondGetString, replace repeated Buf_Add with Buf_AddStr o migrate Var_Parse to Var_ParsePP o add wrappers around ctype.h functions o lst.c: use a stack instead of a list for the nested include path o more unit tests 2020-09-04 Simon J Gerraty * make-bootstrap.sh.in: adjust object list 2020-09-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200902 Merge with NetBSD make, pick up o use make_stat to ensure no confusion over valid fields returned by cached_stat o var.c: make VarQuote const-correct o add unit tests for .for 2020-09-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200901 Merge with NetBSD make, pick up o rename Hash_Table fields o make data types in Dir_HasWildcards more precise 2020-08-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200831 Merge with NetBSD make, pick up o suff.c: fix unbalanced Lst_Open/Lst_Close in SuffFindCmds o lst.c: Lst_Open renable assert that list isn't open o unit test for .TARGET dependent flags o var.c: fix aliasing bug in VarUniq o more unit tests for :u 2020-08-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200830 Merge with NetBSD make, pick up o allow for strict type checking for Boolean o Var_Parse never returns NULL o Var_Subst never returns NULL o Lst_Find now takes boolean match function o rename Lst_Memeber to Lst_FindDatum o rename LstNode functions to match their type o rename GNode.iParents to implicitParents o fix assertion failure for .SUFFIXES in archives o compat.c: clean up documentation for CompatInterrupt and Compat_Run remove unreachable code from CompatRunCommand o main.c: simplify getBoolean o stc.c: replace brk_string with simpler Str_Words o suff.c: add debug macros 2020-08-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200828 Merge with NetBSD make, pick up o lst.c: inline LstIsValid and LstNodeIsValid o remove trailing S from Lst function names after migration complete o more comment cleanup/clarification o suff.c: clean up suffix handling o more unit tests 2020-08-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200826 Merge with NetBSD make, pick up o enum.c: distinguish between bitsets containing flags and ordinary enums o var.c: fix error message for ::!= modifier with shell error o fix bugs in -DCLEANUP mode 2020-08-24 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200824 Merge with NetBSD make, pick up o in debug mode, print GNode details in symbols 2020-08-23 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200823 Merge with NetBSD make, pick up o lst.c: more asserts, make args to Lst_Find match others. o var.c: pass flags to VarAdd o arch.c: use Buffer o str.c: brk_string return size_t for nwords o more unit tests 2020-08-22 Simon J Gerraty * VERSION (_MAKE_VERSION): Merge with NetBSD make, pick up o var.c: support for read-only variables eg .SHELL being the shell used to run scripts. o lst.c: more simplification o more documentation and style cleanup o more unit tests o ensure unit-test/Makefile is run by TEST_MAKE o reduce duplication of header inclusion 2020-08-21 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200821 Merge with NetBSD make, pick up o lst.c: revert invalid assertion - but document it o dir.c: split Dir_Init into two functions 2020-08-20 Simon J Gerraty * lst.c: needs inttypes.h on Linux * VERSION (_MAKE_VERSION): 20200820 Merge with NetBSD make, pick up o make.1: clarify some passages o var.c: more cleanup, clarify comments o make_malloc.c: remove unreachable code o cond.c: make CondGetString easier to debug o simplify list usage o unit-tests: more 2020-08-16 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200816 Merge with NetBSD make, pick up o refactor unit-tests to be more fine grained not all tests moved yet 2020-08-14 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200814 Merge with NetBSD make, pick up o more str_concat variants o more enums for flags o var.c: cleanup for higher warnings level 2020-08-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200810 Merge with NetBSD make, pick up o more unit tests o general comment and style cleanup 2020-08-08 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200808 Merge with NetBSD make, pick up o enum.[ch]: streamline, enums for use in flags and debug output o cond.c: cleanup o var.c: reduce duplicate code for modifiers debug logging for Var_Parse more detailed debug output o more unit tests 2020-08-06 Simon J Gerraty * unit-tests/Makefile: -r for recursive and include Makefile.inc so I can run tests in meta mode supress extra noise if in meta mode * VERSION (_MAKE_VERSION): 20200806 Merge with NetBSD make, pick up o parse.c: remove VARE_WANTRES for LINT we just want to check parsing (for now). 2020-08-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200805 Merge with NetBSD make, pick up o make.1: Rework the description of dependence operators 2020-08-03 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200803 Merge with NetBSD make, pick up o revert some C99 usage, for max portability o unit-tests/lint 2020-08-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200802 Merge with NetBSD make, pick up o more unit tests 2020-08-01 Simon J Gerraty * Remove NetBSD specific plumbing from unit-tests/Makefile * VERSION (_MAKE_VERSION): 20200801 Merge with NetBSD make, pick up o make Var_Value return const o size_t for buf sizes o optimize some buffer operations - avoid strlen 2020-07-31 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200731 Merge with NetBSD make, pick up o var.c: fix undefinded behavior for incomplete :t modifier fixes unit-test/moderrs on Ubuntu o parse.c: When parsing variable assignments other than := if DEBUG(LINT) test substition of value, so we get a file and line number in the resulting error. o dir.c: fix parsing of nested braces in dependency lines add unit-tests 2020-07-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200730 Merge with NetBSD make, pick up o var.c: minor cleanup o unit-tests: more tests to improve code coverage 2020-07-28 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200728 Merge with NetBSD make, pick up o var.c: more optimizations 2020-07-26 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200726 Merge with NetBSD make, pick up o collapse lsd.lib into lst.c - reduce code size and allow inlining o lots of function comment updates o var.c: more optimizations o make return of Var_Parse const 2020-07-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200720 Merge with NetBSD make, pick up o DEBUG_HASH report stats at end and tone down the noise o var.c: each flag type gets its own prefix. move SysV string matching to var.c make ampersand in ${VAR:from=to&} an ordinary character cleanup and simplify implementation of modifiers o make.1: move documentation for assignment modifiers 2020-07-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200718 Merge with NetBSD make, pick up o DEBUG_HASH to see how well the hash tables are working 2020-07-11 Simon J Gerraty * bsd.after-import.mk: make sure we update unit-tests/Makefile 2020-07-10 Simon J Gerraty * configure.in: use AC_INCLUDES_DEFAULT rather than AC_HEADER_STDC * VERSION (_MAKE_VERSION): 20200710 Merge with NetBSD make, pick up o filemon/filemon_dev.c: use O_CLOEXEC rather than extra syscall o meta.c: target flagged .META is out-of-date if meta file missing 2020-07-09 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200709 Merge with NetBSD make, pick up o cond.c: fix for compare_expression when doEval=0 o unit-tests/Makefile: rework o filemon/filemon_dev.c: ensure filemon fd is closed on exec. 2020-07-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200704 Merge with NetBSD make, pick up (most of this by rillig@) o lots of style and white-space cleanup o lots more unit tests for variable modifiers o simplified description of some functions o str.c: refactor Str_Match o var.c: debugging output for :@ constify VarModify parameter fix :hash modifier on 16-bit platforms remove unnecessary forward declarations refactor ApplyModifier_SysV to have less indentation simplify code for :E and :R clean up code for :H and :T refactor ApplyModifiers * var.c: we need stdint.h on some platforms to get uint32_t * unit-test/Makefile: we need to supress the specific error for RE substitution error in modmisc, since it varies accross different OS. 2020-07-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200702 Merge with NetBSD make, pick up o var.c: more improvements to avoiding unnecessary evaluation use enums for flags o remove flags arg to Var_Set which outside of var.c is always 0 2020-07-01 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200701 Merge with NetBSD make, pick up o var.c: with change to cond.c; ensure that nested variables within a variable name are expanded. o unit-tests/varmisc.mk: test for nested varname 2020-06-29 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200629 Merge with NetBSD make, pick up o cond.c: do not eval unnecessary terms of conditionals. 2020-06-25 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200625 Merge with NetBSD make, pick up o meta.c: report error if lseek in filemon_read fails 2020-06-22 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200622 Merge with NetBSD make, pick up o dieQuietly: ignore OP_SUBMAKE as too aggressive 2020-06-19 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200619 Merge with NetBSD make, pick up o str.c: performance improvement for Str_Match for multiple '*' o dieQuietly: supress the failure output from make when failing node is a sub-make or a sibling failed. This cuts down greatly on unhelpful noise at the end of build log. Disabled by -dj or .MAKE.DIE_QUIETLY=no 2020-06-10 Simon J Gerraty * FILES: add LICENSE to appease some packagers. This is an attempt to fairly represent the license on almost 200 files, which are almost all BSD-3-Clause The few exceptions being more liberal. * VERSION (_MAKE_VERSION): 20200610 Merge with NetBSD make, pick up o unit test for :Or 2020-06-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200606 Merge with NetBSD make, pick up o make.1: cleanup * Makefile: fix depends for main.o which broke MAKE_VERSION 2020-06-05 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200605 Merge with NetBSD make, pick up o dir.c: cached_stats - don't confuse stat and lstat results. o var.c: add :Or for reverse sort. 2020-05-24 Simon J Gerraty * configure.in: add AC_PROG_CC_C99 for mipspro compiler also if --with-filemon= specifies path to filemon.h set use_filemon=dev * dirname.c: remove include of namespace.h 2020-05-17 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200517 Merge with NetBSD make, pick up o modified dollar tests to avoid shell dependencies o new tests for .INCLUDEFROM 2020-05-16 Simon J Gerraty * unit-tests/dollar.mk: tweak '1 dollar literal' test to not depend so much on shell behavior 2020-05-10 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200510 Merge with NetBSD make, pick up o unit test for dollar handling 2020-05-06 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200506 Merge with NetBSD make, pick up o str.c: empty string does not match % pattern plus unit-test changes 2020-05-04 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200504 May the 4th be with you Merge with NetBSD make, pick up o var.c: import handling of old sysV style modifier using '%' o str.c: refactor brk_string o unit-tests: add test case for lazy conditions 2020-04-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200418 * configure.in: use_makefile=no for Cygwin et al. case insensitive filesystems just don't work if both makefile and Makefile exist. NOTE: bmake does not support Cygwin and likely never will, but if brave souls want to try it - help them out. 2020-04-02 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200402 Merge with NetBSD make, pick up o meta.c: meta_oodate, CHECK_VALID_META is too aggressive for CMD a blank command is perfectly valid. 2020-03-30 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200330 Merge with NetBSD make, pick up o make.h: extern debug_file 2020-03-18 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200318 Merge with NetBSD make, pick up o meta.c: meta_oodate, check for corrupted meta file earlier and more often. 2020-02-20 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200220 2020-02-19 Simon J Gerraty * boot-strap: unset MAKEFLAGS 2020-02-12 Simon J Gerraty * VERSION (_MAKE_VERSION): 20200212 * meta.c: meta_compat_parent check for USE_FILEMON patch from Soeren Tempel 2020-02-05 Simon J Gerraty * VERSION: 20200205 Merge with NetBSD make, pick up o meta.c: fix compat mode, need to call meta_job_output() o job.c: extra fds for meta mode not needed if using filemon_dev 2020-01-22 Simon J Gerraty * VERSION: 20200122 Merge with NetBSD make, pick up o meta.c: avoid passing NULL to filemon_*() when meta_needed() returns FALSE. 2020-01-21 Simon J Gerraty * VERSION: 20200121 Merge with NetBSD make, pick up o filemon/filemon_{dev,ktrace}.c: allow selection of filemon implementation. filemon_dev.c uses the kernel module while filemon_ktrace.c leverages the fktrace api available in NetBSD. filemon_ktrace.c can hopefully form the basis for adding support for other tracing mechanisms such as strace on Linux. o meta.c: when target is out-of-date per normal make rules record value of .OODATE in meta file. 2019-09-26 Simon J Gerraty * VERSION: 20190926 Merge with NetBSD make, pick up o parse.c: don't pass NULL to realpath(3) some versions cannot handle it. 2019-04-09 Simon J Gerraty * VERSION: 20190409 Merge with NetBSD make, pick up o parse.c: ParseDoDependency: free paths rather than assert 2018-12-22 Simon J Gerraty * VERSION: 20181222 * configure.in: add --without-makefile to avoid generating makefile and make-bootstrap.sh * include Makefile.inc if it exists * Use Makefile and Makefile.config.in in unit-tests so we can use just: make obj && make && make test when bmake is already available. We add --without-makefile to CONFIGURE_ARGS in this case. * tweak bsd.after-import.mk (captures Makefile.config etc after import to FreeBSD for example) to cope with all the above. 2018-12-21 Simon J Gerraty * VERSION: 20181221 Merge with NetBSD make, pick up o parse.c: ParseVErrorInternal use .PARSEDIR and apply if relative, and then use .PARSEFILE for consistent result. 2018-12-20 Simon J Gerraty * VERSION: 20181220 Merge with NetBSD make, pick up o parse.c: ParseVErrorInternal use .CURDIR if .PARSEDIR is relative o var.c: avoid SEGFAULT in .unexport-env when MAKELEVEL is not set 2018-12-16 Simon J Gerraty * VERSION: 20181216 Merge with NetBSD make, pick up o fix for unit-tests/varquote.mk on Debian 2018-09-21 Simon J. Gerraty * VERSION: 20180919 Merge with NetBSD make, pick up o var.c: add :q o dir.c: cleanup caching of stats 2018-09-21 Simon J Gerraty * Makefile.config.in: use += where it makes sense. 2018-05-12 Simon J. Gerraty * VERSION: 20180512 Merge with NetBSD make, pick up o job.c: skip polling job token pipe 2018-04-05 Simon J. Gerraty * VERSION: 20180405 Merge with NetBSD make, pick up o parse.c: be more cautious about detecting depenency line rather than sysV style include. 2018-02-22 Simon J. Gerraty * VERSION: 20180222 Merge with NetBSD make, pick up o parse.c: avoid calling sysconf for every call to loadfile 2018-02-18 Simon J. Gerraty * VERSION: 20180218 Merge with NetBSD make, pick up o var.c: Var_Set handle NULL value anytime. 2018-02-12 Simon J. Gerraty * VERSION: 20180212 Merge with NetBSD make, pick up o parse.c: do not treat .info as warning with -W 2017-12-07 Simon J. Gerraty * VERSION: 20171207 Merge with NetBSD make, pick up o var.c: Var_Append use Var_Set if var not previously set so that VAR_CMD is handled correctly. Add a suitable unit-test. 2017-11-26 Simon J. Gerraty * VERSION (_MAKE_VERSION): 20171126 * aclocal.m4: use AC_LINK_IFELSE for AC_C___ATTRIBUTE__ since AC_TRY_COMPILE puts input inside main() which upsets modern compilers. 2017-11-18 Simon J. Gerraty * VERSION: 20171118 Merge with NetBSD make, pick up o var.c: do not append to variable set on command line add unit-test to catch this. 2017-10-28 Simon J. Gerraty * VERSION: 20171028 Merge with NetBSD make, pick up o main.c: ignore empty MAKEOBJDIR * Makefile.config.in: make @prefix@ @machine*@ and @default_sys_path@ defaults. 2017-10-05 Simon J. Gerraty * VERSION: 20171005 * unit-tests/dotwait.mk: redirect stderr through pipe for more consistent result on some platforms. 2017-08-13 Simon J. Gerraty * machine.sh: entry for AIX 2017-08-12 Simon J. Gerraty * VERSION (_MAKE_VERSION): Move the setting of _MAKE_VERSION to a file that can be included by configure as well as make. This allows configure to set set _MAKE_VERSION in make-bootstrap.sh 2017-08-10 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170810 Merge with NetBSD make, pick up o meta.c: if target is in subdir we only need subdir name in meta_name. 2017-07-20 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170720 Merge with NetBSD make, pick up o compat.c: pass SIGINT etc onto child and wait for it to exit before we self-terminate. 2017-07-11 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170711 forgot to update after merge on 20170708 ;-) o main.c: refactor to reduce size of main function. add -v option to always fully expand values. o meta.c: ensure command output in meta file has ending newline even when filemon not being used. When matching ${.MAKE.META.IGNORE_PATTERNS} do not use pathname via ':L' since any ':' in pathname breaks that. Instead set a '${.p.}' to pathname in the target context and use that. 2017-05-10 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170510 Merge with NetBSD make, pick up o main.c: Main_SetObjdir: ensure buf2 is in scope 2017-05-08 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170505 see mk/ChangeLog 2017-05-05 Simon J. Gerraty * parse.c: not everyone has stdint.h 2017-05-01 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170501 see mk/ChangeLog 2017-04-21 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170421 Merge with NetBSD make, pick up o str.c: Str_Match: fix closure tests for [^] and add unit-test. 2017-04-20 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170420 Merge with NetBSD make, pick up o main.c: only use -C arg "as is" if it contains no relative component. 2017-04-18 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170418 Merge with NetBSD make, pick up o main.c: fix Main_SetObjdir() for relative paths (eg obj). 2017-04-17 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170417 Merge with NetBSD make, pick up o fixes a number of coverity complaints - check return value of fseek, fcntl - plug memory leak in Dir_FindFile, Var_LoopExpand, JobPrintCommand, ParseTraditionalInclude - use bmake_malloc() where NULL is not tollerated - use MAKE_ATTR_UNUSED rather that kludges like return(unused ? 0 : 0) - use purge_cached_realpaths() rather than abuse cached_realpath() 2017-04-13 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170413 Merge with NetBSD make, pick up o main.c: when setting .OBJDIR ignore '$' in paths. * job.c: use MALLOC_OPTIONS to set malloc_options. 2017-04-11 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170411 Merge with NetBSD make, pick up o str.c: Str_Match: allow [^a-z] to behave as expected. 2017-03-26 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170326 Merge with NetBSD make, pick up o main.c: purge relative paths from realpath cache when .OBJDIR is changed. 2017-03-11 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170311 Merge with NetBSD make, pick up o main.c: only use -C arg "as is" if it starts with '/'. 2017-03-01 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170301 Merge with NetBSD make, pick up o main.c: use -C arg "as is" rather than getcwd() if they identify the same directory. o parse.c: ensure loadfile buffer is \n terminated in non-mmap case 2017-02-01 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170201 Merge with NetBSD make, pick up o var.c: allow :_=var and avoid use of special context. 2017-01-30 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170130 Merge with NetBSD make, pick up o var.c: add :range and :_ o main.c: partially initialize Dir_* before MainParseArgs() can be called. If -V, skip Main_ExportMAKEFLAGS() 2017-01-14 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20170114 Merge with NetBSD make, pick up o var.c: allow specifying the utc value used by :{gm,local}time 2016-12-12 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20161212 Merge with NetBSD make, pick up o main.c: look for obj.${MACHINE}-${MACHINE_ARCH} too. 2016-12-09 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20161209 Merge with NetBSD make, pick up o main.c: cleanup setting of .OBJDIR o parse.c: avoid coredump from (var)=val 2016-11-26 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20161126 Merge with NetBSD make, pick up o make.c: Make_OODate: report src node name if path not set 2016-09-26 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160926 Merge with NetBSD make, pick up o support for .DELETE_ON_ERROR: (remove targets that fail) 2016-09-26 Simon J. Gerraty * Makefile MAN: tweak .Dt to match ${PROG} 2016-08-18 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160818 its a neater number; pick up whitespace fixes to man page. 2016-08-17 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160817 Merge with NetBSD make, pick up o meta.c: move handling of .MAKE.META.IGNORE_* to meta_ignore() so we can call it before adding entries to missingFiles. Thus we do not track files we have been told to ignore. 2016-08-15 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160815 Merge with NetBSD make, pick up o meta_oodate: apply .MAKE.META.IGNORE_FILTER (if defined) to pathnames, and skip if the expansion is empty. Useful for dirdeps.mk when checking DIRDEPS_CACHE. 2016-08-12 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160812 Merge with NetBSD make, pick up o meta.c: remove all missingFiles entries that match a deleted dir. o main.c: set .ERROR_CMD if possible. 2016-06-06 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160606 Merge with NetBSD make, pick up o dir.c: extend mtimes cache to others via cached_stat() 2016-06-04 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160604 Merge with NetBSD make, pick up o meta.c: missing filemon data is only relevant if we read a meta file. Also do not return oodate for a missing metafile if gn->path points to .CURDIR 2016-06-02 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160602 Merge with NetBSD make, pick up o cached_realpath(): avoid hitting filesystem more than necessary. o meta.c: refactor need_meta decision, add knobs for missing meta file and filemon data wrt out-of-datedness. 2016-05-28 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160528 * boot-strap, make-bootstrap.sh.in: Makefile now uses _MAKE_VERSION 2016-05-12 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160512 Merge with NetBSD make, pick up o meta.c: ignore paths that match .MAKE.META.IGNORE_PATTERNS this is useful for gcov builds. o propagate errors from filemon(4). 2016-05-09 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160509 Merge with NetBSD make, pick up o remove use of non-standard types u_int etc. o meta.c: apply realpath() before matching against metaIgnorePaths 2016-04-04 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160404 Merge with NetBSD make, pick up o allow makefile to set .MAKE.JOBS * Makefile (PROG_NAME): use ${_MAKE_VERSION} 2016-03-15 Simon J. Gerraty * Makefile (_MAKE_VERSION): 20160315 Merge with NetBSD make, pick up o fix handling of archive members 2016-03-13 Simon J. Gerraty * Makefile (_MAKE_VERSION): rename variable to avoid interference with checks for ${MAKE_VERSION} 2016-03-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160310 Merge with NetBSD make, pick up o meta.c: treat missing Read file same as Write, incase we Delete it. 2016-03-07 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160307 Merge with NetBSD make, pick up o var.c: fix :ts\nnn to be octal by default. o meta.c: meta_finish() to cleanup memory. 2016-02-26 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160226 Merge with NetBSD make, pick up o meta.c: allow meta file for makeDepend if makefiles want it. 2016-02-19 Simon J. Gerraty * var.c: default .MAKE.SAVE_DOLLARS to FALSE for backwards compatability. * Makefile (MAKE_VERSION): 20160220 Merge with NetBSD make, pick up o var.c: add knob to control handling of '$$' in := 2016-02-18 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160218 Merge with NetBSD make, pick up o var.c: add .export-literal allows us to fix sys.clean-env.mk post the changes to Var_Subst. Var_Subst now takes flags, and does not consume '$$' in := 2016-02-17 Simon J. Gerraty * Makefile (MAKE_VERSION): 20160217 Merge with NetBSD make, pick up o var.c: preserve '$$' in := o parse.c: add .dinclude for handling included makefile like .depend 2015-12-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151220 Merge with NetBSD make, pick up o suff.c: re-initialize suffNull when clearing suffixes. 2015-12-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151201 Merge with NetBSD make, pick up o cond.c: CondCvtArg: avoid access beyond end of empty buffer. o meta.c: meta_oodate: use lstat(2) for checking link target in case it is a symlink. o var.c: avoid calling brk_string and Var_Export1 with empty strings. 2015-11-26 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151126 Merge with NetBSD make, pick up o parse.c: ParseTrackInput don't access beyond end of old value. 2015-10-22 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151022 * Add support for BSD/OS which lacks inttypes.h and really needs sys/param.h for sys/sysctl.h also 'type' is not a shell builtin. * var.c: eliminate uint32_t and need for inttypes.h * main.c: PrintOnError flush stdout before run .ERROR * parse.c: cope with _SC_PAGESIZE not being defined. 2015-10-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151020 Merge with NetBSD make, pick up o var.c: fix uninitialized var 2015-10-12 Simon J. Gerraty * var.c: the conditional expressions used with ':?' can be expensive, if already discarding do not evaluate or expand anything. 2015-10-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151010 Merge with NetBSD make, pick up o Add Boolean wantit flag to Var_Subst and Var_Parse when FALSE we know we are discarding the result and can skip operations like Cmd_Exec. 2015-10-09 Simon J. Gerraty * Makefile (MAKE_VERSION): 20151009 Merge with NetBSD make, pick up o var.c: don't check for NULL before free() o meta.c: meta_oodate, do not hard code ignore of makeDependfile 2015-09-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150910 Merge with NetBSD make, pick up o main.c: with -w print Enter/Leaving messages for objdir too if necessary. o centralize shell metachar handling * FILES: add metachar.[ch] 2015-06-06 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150606 Merge with NetBSD make, pick up o make.1: document .OBJDIR target 2015-05-05 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150505 Merge with NetBSD make, pick up o cond.c: be strict about lhs of comparison when evaluating .if but less so when called from variable expansion. o unit-tests/cond2.mk: test various error conditions 2015-05-04 Simon J. Gerraty * machine.sh (MACHINE): Add Bitrig patch from joerg@netbsd.org 2015-04-18 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150418 Merge with NetBSD make, pick up o job.c: use memmove() rather than memcpy() * unit-tests/varshell.mk: SunOS cannot handle the TERMINATED_BY_SIGNAL case, so skip it. 2015-04-11 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150411 bump version - only mk/ changes. 2015-04-10 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150410 Merge with NetBSD make, pick up o document different handling of '-' in jobs mode vs compat o fix jobs mode so that '-' only applies to whole job when shell lacks hasErrCtl o meta.c: use separate vars to track lcwd and latestdir (read) per process 2015-04-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20150401 Merge with NetBSD make, pick up o meta.c: close meta file in child * Makefile: use BINDIR.bmake if set. Same for MANDIR and SHAREDIR Handy for testing release candidates in various environments. 2015-03-26 Simon J. Gerraty * move initialization of savederr to block where it is used to avoid spurious warning from gcc5 2014-11-11 Simon J. Gerraty * Makefile (MAKE_VERSION): 20141111 just a cooler number 2014-11-05 Simon J. Gerraty * Makefile (MAKE_VERSION): 20141105 Merge with NetBSD make, pick up o revert major overhaul of suffix handling and POSIX compliance - too much breakage and impossible to make backwards compatible. o we still have the new unit test structure which is ok. o meta.c ensure "-- filemon" is at start of line. 2014-09-17 Simon J. Gerraty * configure.in: test that result of getconf PATH_MAX is numeric and discard if not. Apparently needed for Hurd. 2014-08-30 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140830 Merge with NetBSD make, pick up o major overhaul of suffix handling o improved POSIX compliance o overhauled unit-tests 2014-06-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140620 Merge with NetBSD make, pick up o var.c return varNoError rather than var_Error for ::= modifiers. 2014-05-22 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140522 Merge with NetBSD make, pick up o var.c detect some parse errors. 2014-04-05 Simon J. Gerraty * Fix spelling errors - patch from Pedro Giffuni 2014-02-14 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140214 Merge with NetBSD make, pick up o .INCLUDEFROM* o use Var_Value to get MAKEOBJDIR[PREFIX] o reduced realloc'ign in brk_string. * configure.in: add a check for compiler supporting __func__ 2014-01-03 Simon J. Gerraty * boot-strap: ignore mksrc=none 2014-01-02 Simon J. Gerraty * Makefile (DEFAULT_SYS_PATH?): use just ${prefix}/share/mk 2014-01-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20140101 * configure.in: set bmake_path_max to min(_SC_PATH_MAX,1024) * Makefile.config: defined BMAKE_PATH_MAX to bmake_path_max * make.h: use BMAKE_PATH_MAX if MAXPATHLEN not defined (needed for Hurd) * configure.in: Add AC_PREREQ and check for sysctl; patch from Andrew Shadura andrewsh at debian.org 2013-10-16 Simon J. Gerraty * Makefile (MAKE_VERSION): 20131010 * lose the const from arg to systcl to avoid problems on older BSDs. 2013-10-01 Simon J. Gerraty * Makefile (MAKE_VERSION): 20131001 Merge with NetBSD make, pick up o main.c: for NATIVE build sysctl to get MACHINE_ARCH from hw.machine_arch if necessary. o meta.c: meta_oodate - need to look at src of Link and target of Move as well. * main.c: check that CTL_HW and HW_MACHINE_ARCH exist. provide __arraycount() if needed. 2013-09-04 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130904 Merge with NetBSD make, pick up o Add VAR_INTERNAL context, so that internal setting of MAKEFILE does not override value set by makefiles. 2013-09-02 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130902 Merge with NetBSD make, pick up o CompatRunCommand: only apply shellErrFlag when errCheck is true 2013-08-28 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130828 Merge with NetBSD make, pick up o Fix VAR :sh = syntax from Will Andrews at freebsd.org o Call Job_SetPrefix() from Job_Init() so makefiles have opportunity to set .MAKE.JOB.PREFIX 2013-07-30 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130730 Merge with NetBSD make, pick up o Allow suppression of --- job -- tokens by setting .MAKE.JOB.PREFIX empty. 2013-07-16 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130716 Merge with NetBSD make, pick up o number of gmake compatibility tweaks -w for gmake style entering/leaving messages if .MAKE.LEVEL > 0 indicate it in progname "make[1]" etc. handle MAKEFLAGS containing only letters. o when overriding a GLOBAL variable on the command line, delete it from GLOBAL context so -V doesn't show the wrong value. 2013-07-06 Simon J. Gerraty * configure.in: We don't need MAKE_LEVEL_SAFE anymore. * Makefile (MAKE_VERSION): 20130706 Merge with NetBSD make, pick up o Shell_Init(): export shellErrFlag if commandShell hasErrCtl is true so that CompatRunCommand() can use it, to ensure consistent behavior with jobs mode. o use MAKE_LEVEL_ENV to define the variable to propagate .MAKE.LEVEL - currently set to MAKELEVEL (same as gmake). o meta.c: use .MAKE.META.IGNORE_PATHS to allow customization of paths to ignore. 2013-06-04 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130604 Merge with NetBSD make, pick up o job.c: JobCreatePipe: do fcntl() after any tweaking of fd's to avoid leaking descriptors. 2013-05-28 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130528 Merge with NetBSD make, pick up o var.c: cleanup some left-overs in VarHash() 2013-05-20 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130520 generate manifest from component FILES rather than have to update FILES when mk/FILES changes. 2013-05-18 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130518 Merge with NetBSD make, pick up o suff.c: don't skip all processsing for .PHONY targets else wildcard srcs do not get expanded. o var.c: expand name of variable to delete if necessary. 2013-03-30 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130330 Merge with NetBSD make, pick up o meta.c: refine the handling of .OODATE in commands. Rather than suppress command comparison for the entire script as though .NOMETA_CMP had been used, only suppress it for the one command line. This allows something like ${.OODATE:M.NOMETA_CMP} to be used to suppress comparison of a command without otherwise affecting it. o make.1: document that 2013-03-22 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130321 yes, not quite right but its a cooler number. Merge with NetBSD make, pick up o parse.c: fix ParseGmakeExport to be portable and add a unit-test. * meta.c: call meta_init() before makefiles are read and if built with filemon support set .MAKE.PATH_FILEMON to _PATH_FILEMON this let's makefiles test for support. Call meta_mode_init() to process .MAKE.MODE. 2013-03-13 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130305 Merge with NetBSD make, pick up o run .STALE: target when a dependency from .depend is missing. o job.c: add Job_RunTarget() for the above and .BEGIN 2013-03-03 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130303 Merge with NetBSD make, pick up o main.c: set .MAKE.OS to utsname.sysname o job.c: more checks for read and poll errors o var.c: lose VarChangeCase() saves 4% time 2013-03-02 Simon J. Gerraty * boot-strap: remove MAKEOBJDIRPREFIX from environment since we want to use MAKEOBJDIR 2013-01-27 Simon J. Gerraty * Merge with NetBSD make, pick up o make.1: more info on how shell commands are handled. o job.c,main.c: detect write errors to job pipes. 2013-01-25 Simon J. Gerraty * Makefile (MAKE_VERSION): 20130123 Merge with NetBSD make, pick up o meta.c: if script uses .OODATE and meta_oodate() decides rebuild is needed, .OODATE will be empty - set it to .ALLSRC. o var.c: in debug output indicate which variabale modifiers apply to. o remove Check_Cwd logic the makefiles have been fixed. 2012-12-12 Simon J. Gerraty * makefile.in: add a simple makefile for folk who insist on ./configure; make; make install it just runs boot-strap * include mk/* to accommodate the above * boot-strap: re-work to accommodate the above mksrc defaults to $Mydir/mk allow op={configure,build,install,clean,all} add options to facilitate install * Makefile.config.in: just the bits set by configure * Makefile: bump version to 20121212 abandon Makefile.in (NetBSD Makefile) leverage mk/* instead * configure.in: ensure srcdir is absolute 2012-11-11 Simon J. Gerraty * Makefile.in (MAKE_VERSION): 20121111 fix generation of bmake.cat1 2012-11-09 Simon J. Gerraty * Makefile.in (MAKE_VERSION): 20121109 Merge with NetBSD make, pick up o make.c: MakeBuildChild: return 0 so search continues if a .ORDER dependency is detected. o unit-tests/order: test the above 2012-11-02 Simon J. Gerraty * Makefile.in (MAKE_VERSION): 20121102 Merge with NetBSD make, pick up o cond.c: allow cond_state[] to grow. In meta mode with a very large tree, we can hit the limit while processing dirdeps. 2012-10-25 Simon J. Gerraty * Makefile.in: we need to use ${srcdir} not ${.CURDIR} 2012-10-10 Simon J. Gerraty * Makefile.in (MAKE_VERSION): 20121010 o protect syntax that only bmake parses correctly. o remove auto setting of FORCE_MACHINE, use configure's --with-force-machine=whatever if that is desired. 2012-10-08 Simon J. Gerraty * Makefile.in: do not lose history from make.1 when generating bmake.1 2012-10-07 Simon J. Gerraty * Makefile.in (MAKE_VERSION): 20121007 Merge with NetBSD make, pick up o compat.c: ignore empty commands - same as jobs mode. o make.1: document meta chars that cause use of shell 2012-09-11 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120911 * bsd.after-import.mk: include Makefile.inc early and allow it to override PROG 2012-08-31 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120831 Merge with NetBSD make, pick up o cast sizeof() to int for comparison o minor make.1 tweak 2012-08-30 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120830 Merge with NetBSD make, pick up o .MAKE.EXPAND_VARIABLES knob can control default behavior of -V o debug flag -dV causes -V to show raw value regardless. 2012-07-05 Simon J. Gerraty * bsd.after-import.mk (after-import): ensure unit-tests/Makefile gets SRCTOP set. 2012-07-04 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120704 Merge with NetBSD make, pick up o Job_ParseShell should call Shell_Init if it has been previously called. * Makefile.in: set USE_META based on configure result. also .PARSEDIR is safer indicator of bmake. 2012-06-26 Simon J. Gerraty * Makefile.in: bump version to 20120626 ensure CPPFLAGS is in CFLAGS * meta.c: avoid nested externs * bsd.after-import.mk: avoid ${.CURDIR}/Makefile as target 2012-06-20 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120620 Merge with NetBSD make, pick up o make_malloc.c: avoid including make_malloc.h again * Makefile.in: avoid bmake only syntax or protect with .if defined(.MAKE.LEVEL) * bsd.after-import.mk: replace .-include with .sinclude ensure? SRCTOP gets a value * configure.in: look for filemon.h in /usr/include/dev/filemon first. 2012-06-19 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120612 Merge with NetBSD make, pick up o use MAKE_ATTR_* rather than those defined by cdefs.h or compiler for greater portability. o unit-tests/forloop: check that .for works as expected wrt number of times and with "quoted strings". 2012-06-06 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120606 Merge with NetBSD make, pick up o compat.c: use kill(2) rather than raise(3). * configure.in: look for sys/dev/filemon * bsd.after-import.mk: add a .-include "Makefile.inc" to Makefile and pass BOOTSTRAP_XTRAS to boot-strap. 2012-06-04 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120604 Merge with NetBSD make, pick up o util.c and var.c share same var for tracking if environ has been reallocated. o util.c provide getenv with setenv. * Add MAKE_LEVEL_SAFE as an alternate means of passing MAKE_LEVEL when the shell actively strips .MAKE.* from the environment. We still refer to the variable always as .MAKE.LEVEL * util.c fix bug in findenv() was finding prefix of name. * compat.c: re-raising SIGINT etc after running .INTERRUPT results in more reliable termination of all activity on many platforms. 2012-06-02 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120602 Merge with NetBSD make, pick up o for.c: handle quoted items in .for list 2012-05-30 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120530 Merge with NetBSD make, pick up o compat.c: ignore empty command. 2012-05-24 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120524 * FILES: add bsd.after-import.mk: A simple means of integrating bmake into a BSD build system. 2012-05-20 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120520 Merge with NetBSD make, pick up o increased limit for nested conditionals. 2012-05-18 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120518 Merge with NetBSD make, pick up o use _exit(2) in signal hanlder o Don't use the [dir] cache when building nodes that might have changed since the last exec. o Avoid nested extern declaration warnings. 2012-04-27 Simon J. Gerraty * meta.c (fgetLine): avoid %z - not portable. * parse.c: Since we moved include of sys/mman.h and def's of MAP_COPY etc. we got dups from a merge. 2012-04-24 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120420 Merge with NetBSD make, pick up o restore duplicate supression in .MAKE.MAKEFILES runtime saving can be significant. o Var_Subst() uses Buf_DestroyCompact() to reduce memory consumption up to 20%. 2012-04-20 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120420 Merge with NetBSD make, pick up o remove duplicate supression in .MAKE.MAKEFILES o improved dir cache behavior o gmake'ish export command 2012-03-25 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20120325 Merge with NetBSD make, pick up o fix parsing of :[#] in conditionals. 2012-02-10 Simon J. Gerraty * Makefile.in: replace use of .Nx in bmake.1 with NetBSD since some systems cannot cope with .Nx 2011-11-14 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20111111 Merge with NetBSD make, pick up o debug output for .PARSEDIR and .PARSEFILE 2011-10-10 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20111010 2011-10-09 Simon J. Gerraty * boot-strap: check for an expected file in the dirs we look for. * make-bootstrap.sh: pass on LDSTATIC 2011-10-01 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20111001 Merge with NetBSD make, pick up o ensure .PREFIX is set for .PHONY and .TARGET set for .PHONY run via .END o __dead used consistently 2011-09-10 Simon J. Gerraty * Makefile.in (MAKE_VERSION): 20110909 is a better number ;-) 2011-09-05 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110905 Merge with NetBSD make, pick up o meta_oodate: ignore makeDependfile 2011-08-28 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110828 Merge with NetBSD make, pick up o silent=yes in .MAKE.MODE causes meta mode to mark targets as SILENT if a .meta file is created 2011-08-18 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110818 Merge with NetBSD make, pick up o in meta mode, if target flagged .META a missing .meta file means target is out-of-date o fixes for gcc 4.5 warnings o simplify job printing code 2011-08-09 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110808 Merge with NetBSD make, pick up o do not touch OP_SPECIAL targets when doing make -t 2011-06-22 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110622 Merge with NetBSD make, pick up o meta_oodate detect corrupted .meta file and declare oodate. * configure.in: add check for setsid 2011-06-07 Simon J. Gerraty * Merge with NetBSD make, pick up o unit-tests/modts now works on MirBSD 2011-06-04 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110606 Merge with NetBSD make, pick up o ApplyModifiers: when we parse a variable which is not the entire modifier string, or not followed by ':', do not consider it as containing modifiers. o loadfile: ensure newline at end of mapped file. 2011-05-05 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110505 Merge with NetBSD make, pick up o .MAKE.META.BAILIWICK - list of prefixes which define the scope of make's control. In meta mode, any generated file within said bailiwick, which is found to be missing, causes current target to be out-of-date. 2011-04-11 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110411 Merge with NetBSD make, pick up o when long modifiers fail to match, check sysV style. - add a test case 2011-04-10 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110410 Merge with NetBSD make, pick up o :hash - cheap 32bit hash of value o :localtime, :gmtime - use value as format string for strftime. 2011-03-30 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110330 mostly because its a cooler version. Merge with NetBSD make, pick up o NetBSD tags for meta.[ch] o job.c call meta_job_finish() after meta_job_error(). o meta_job_error() should call meta_job_finish() to ensure .meta file is closed, and safe to copy - if .ERROR target wants. meta_job_finish() is safe to call repeatedly. 2011-03-29 Simon J. Gerraty * unit-tests/modts: use printf if it is a builtin, to save us from MirBSD * Makefile.in (MAKE_VERSION): bump version to 20110329 Merge with NetBSD make, pick up o fix for use after free() in CondDoExists(). o meta_oodate() report extra commands and return earlier. 2011-03-27 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110327 Merge with NetBSD make, pick up o meta.c, if .MAKE.MODE contains curdirOk=yes allow creating .meta files in .CURDIR * boot-strap (TOOL_DIFF): aparently at least on linux distro formats the output of 'type' differently - so eat any "()" 2011-03-06 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110306 Merge with NetBSD make, pick up o meta.c, only do getcwd() once 2011-03-05 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110305 Merge with NetBSD make, pick up o correct sysV substitution handling of empty lhs and variable o correct exists() check for dir with trailing / o correct handling of modifiers for non-existant variables during evaluation of conditionals. o ensure MAP_FILE is defined. o meta.c use curdir[] now exported by main.c 2011-02-25 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110225 Merge with NetBSD make, pick up o fix for incorrect .PARSEDIR when .OBJDIR is re-computed after makefiles have been read. o fix example of :? modifier in man page. 2011-02-13 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110214 Merge with NetBSD make, pick up o meta.c handle realpath() failing when generating meta file name. * sigcompat.c: convert to ansi so we can use higher warning levels. 2011-02-07 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110207 Merge with NetBSD make, pick up o fix for bug in meta mode. 2011-01-03 Simon J. Gerraty * parse.c: SunOS 5.8 at least does not have MAP_FILE 2011-01-01 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20110101 Merge with NetBSD make, pick up o use mmap(2) if available, for reading makefiles 2010-12-15 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20101215 Merge with NetBSD make, pick up o ensure meta_job_error() does not report a previous .meta file as being culprit. 2010-12-10 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20101210 Merge with NetBSD make, pick up o meta_oodate: track cwd per process, and only consider target out-of-date if missing file is outside make's CWD. Ignore files in /tmp/ etc. o to ensure unit-tests results match, need to control LC_ALL as well as LANG. o fix for parsing bug in var.c 2010-11-26 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20101126 Merge with NetBSD make, pick up o if stale dependency is an IMPSRC, search via .PATH o meta_oodate: if a referenced file is missing, target is out-of-date. o meta_oodate: if a target uses .OODATE in its commands, it (.OODATE) needs to be recomputed. o keep a pointer to youngest child node, rather than just its mtime. 2010-11-02 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20101101 2010-10-16 Simon J. Gerraty * machine.sh: like os.sh, allow for uname -p producing useless drivel 2010-09-13 Simon J. Gerraty * boot-strap: document configure knobs for meta and filemon. * Makefile.in (MAKE_VERSION): bump version to 20100911 Merge with NetBSD make, pick up o meta.c - meta mode * make-bootstrap.sh.in: handle meta.c * configure.in: add knobs for use_meta and filemon_h also, look for dirname, str[e]sep and strlcpy * util.c: add simple err[x] and warn[x] 2010-08-08 Simon J. Gerraty * boot-strap (TOOL_DIFF): set this to ensure tests use the same version of diff that configure tested * Makefile.in (MAKE_VERSION): bump version to 20100808 Merge with NetBSD make, pick up o in jobs mode, when we discover we cannot make something, call PrintOnError before exit. 2010-08-06 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100806 Merge with NetBSD make, pick up o formatting fixes for ignored errors o ensure jobs are cleaned up regardless of where wait() was called. 2010-06-28 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100618 * os.sh (MACHINE_ARCH): watch out for drivel from uname -p 2010-06-16 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100616 Merge with NetBSD make, pick up o man page update o call PrintOnError from JobFinish when we detect an error we are not ignoring. 2010-06-06 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100606 Merge with NetBSD make, pick up o man page update 2010-06-05 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100605 Merge with NetBSD make, pick up o use bmake_signal() which is a wrapper around sigaction() in place of signal() o add .export-env to allow exporting variables to environment without tracking (so no re-export when the internal value is changed). 2010-05-24 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100524 Merge with NetBSD make, pick up o fix for .info et al being greedy. 2010-05-23 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100520 Merge with NetBSD make, pick up o back to using realpath on argv[0] but only if contains '/' and does not start with '/'. 2010-05-10 Simon J. Gerraty * boot-strap: use absolute path for bmake when running tests. * Makefile.in (MAKE_VERSION): bump version to 20100510 Merge with NetBSD make, pick up o revert use of realpath on argv[0] too many corner cases. o print MAKE_PRINT_VAR_ON_ERROR before running .ERROR target. 2010-05-05 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100505 Merge with NetBSD make, pick up o fix for missed SIGCHLD when compiled with SunPRO actually for bmake, defining FORCE_POSIX_SIGNALS would have done the job. 2010-04-30 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100430 Merge with NetBSD make, pick up o fflush stdout before writing to stdout 2010-04-23 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100423 Merge with NetBSD make, pick up o updated unit tests for Haiku (this time for sure). * boot-strap: based on patch from joerg honor --with-default-sys-path better. * boot-strap: remove mention of --with-prefix-sys-path 2010-04-22 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100422 * Merge with NetBSD make, pick up o fix for vfork() on Darwin. o fix for bogus $TMPDIR. o set .MAKE.MODE=compat for -B o set .MAKE.JOBS=max_jobs for -j max_jobs o allow unit-tests to run without any *.mk o unit-tests/modmisc be more conservative in dirs presumed to exist. * boot-strap: ignore /usr/share/mk except on NetBSD. * unit-tests/Makefile.in: set LANG=C when running unit-tests to ensure sort(1) behaves as expected. 2010-04-21 Simon J. Gerraty * boot-strap: add FindHereOrAbove so we can use -m .../mk 2010-04-20 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100420 * Merge with NetBSD make, pick up o fix for variable realpath() behavior. we have to stat(2) the result to be sure. o fix for .export (all) when nested vars use :sh 2010-04-14 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100414 * Merge with NetBSD make, pick up o use realpath to resolve argv[0] (for .MAKE) if needed. o add realpath from libc. o add :tA to resolve variable via realpath(3) if possible. 2010-04-08 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100408 * Merge with NetBSD make, pick up o unit tests for .ERROR, .error o fix for .ERROR to ensure it cannot be default target. 2010-04-06 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100406 * Merge with NetBSD make, pick up o fix for compat mode "Error code" going to debug_file. o fix for .ALLSRC being populated twice. o support for .info, .warning and .error directives o .MAKE.MODE to control make's operational mode o .MAKE.MAKEFILE_PREFERENCE to control the preferred makefile name(s). o .MAKE.DEPENDFILE to control the name of the depend file o .ERROR target - run on failure. 2010-03-18 Simon J. Gerraty * make-bootstrap.sh.in: extract MAKE_VERSION from Makefile * os.sh,arch.c: patch for Haiku from joerg at netbsd 2010-03-17 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100222 * Merge with NetBSD make, pick up o better error msg for .for with mutiple inter vars * boot-strap: o use make-bootstrap.sh from joerg at netbsd to avoid the need for a native make when bootstrapping. o add "" everywhere ;-) o if /usr/share/tmac/andoc.tmac exists install nroff bmake.1 otherwise the pre-formated version. 2010-01-04 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20100102 * Merge with NetBSD make, pick up: o fix for -m .../ 2009-11-18 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20091118 * Merge with NetBSD make, pick up: o .unexport o report lines that start with '.' and should have ':' (catch typo's of .el*if). 2009-10-30 Simon J. Gerraty * configure.in: Ensure that srcdir and mksrc are absolute paths. 2009-10-09 Simon J. Gerraty * Makefile.in (MAKE_VERSION): fix version to 20091007 2009-10-07 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 200910007 * Merge with NetBSD make, pick up: o fix for parsing of :S;...;...; applied to .for loop iterator appearing in a dependency line. 2009-09-09 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20090909 * Merge with NetBSD make, pick up: o fix for -C, .CURDIR and .OBJDIR * boot-strap: o allow share_dir to be set independent of prefix. o select default share_dir better when prefix ends in $HOST_TARGET o if FORCE_BSD_MK etc were set, include them in the suggested install-mk command. 2009-09-08 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20090908 * Merge with NetBSD make, pick up: o .MAKE.LEVEL for recursion tracking o fix for :M scanning \: 2009-09-03 Simon J. Gerraty * configure.in: Don't -D__EXTENSIONS__ if AC_USE_SYSTEM_EXTENSIONS says "no". 2009-08-26 Simon J. Gerraty * Makefile.in (MAKE_VERSION): bump version to 20090826 Simplify MAKE_VERSION to just the bare date. * Merge with NetBSD make, pick up: o -C directory support. o support for SIGINFO o use $TMPDIR for temp files. o child of vfork should be careful about modifying parent's state. 2009-03-26 Simon J. Gerraty * Appy some patches for MiNT from David Brownlee 2009-02-26 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20090222 * Merge with NetBSD make, pick up: o Possible null pointer de-ref in Var_Set. 2009-02-08 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20090204 * Merge with NetBSD make, pick up: o bmake_malloc et al moved to their own .c o Count both () and {} when looking for the end of a :M pattern o Change 'Buffer' so that it is the actual struct, not a pointer to it. o strlist.c - functions for processing extendable arrays of pointers to strings. o ClientData replaced with void *, so const void * can be used. o New debug flag C for DEBUG_CWD 2008-11-11 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20081111 Apply patch from Joerg Sonnenberge to configure.in: o remove some redundant checks o check for emlloc etc only in libutil and require the whole family. util.c: o remove [v]asprintf which is no longer used. 2008-11-04 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20081101 * Merge with NetBSD make, pick up: o util.c: avoid use of putenv() - christos 2008-10-30 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20081030 pick up man page tweaks. 2008-10-29 Simon J. Gerraty * Makefile.in: move processing of LIBOBJS to after is definition! thus we'll have getenv.c in SRCS only if needed. * make.1: add examples of how to use :? * Makefile.in (BMAKE_VERSION): bump version to 20081029 * Merge with NetBSD make, pick up: o fix for .END processing with -j o segfault from Parse_Error when no makefile is open o handle numeric expressions in any variable expansion o debug output now defaults to stderr, -dF to change it - apb o make now uses bmake_malloc etc so that it can build natively on A/UX - wasn't an issue for bmake, but we want to keep in sync. 2008-09-27 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20080808 * Merge with NetBSD make, pick up: o fix for PR/38840: Pierre Pronchery: make crashes while parsing long lines in Makefiles o optimizations for VarQuote by joerg o fix for PR/38756: dominik: make dumps core on invalid makefile 2008-05-15 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20080515 * Merge with NetBSD make, pick up: o fix skip setting vars in VAR_GLOBAL context, to handle cases where VAR_CMD is used for other than command line vars. 2008-05-14 Simon J. Gerraty * boot-strap (make_version): we may need to look in $prefix/share/mk for sys.mk * Makefile.in (BMAKE_VERSION): bump version to 20080514 * Merge with NetBSD make, pick up: o skip setting vars in VAR_GLOBAL context, when already set in VAR_CMD which takes precedence. 2008-03-30 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20080330 * Merge with NetBSD make, pick up: o fix for ?= when LHS contains variable reference. 2008-02-15 Simon J. Gerraty * merge some patches from NetBSD pkgsrc. * makefile.boot.in (BOOTSTRAP_SYS_PATH): Allow better control of the MAKSYSPATH used during bootstrap. * Makefile.in (BMAKE_VERSION): bump version to 20080215 * Merge with NetBSD make, pick up: o warn if non-space chars follow 'empty' in a conditional. 2008-01-18 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20080118 * Merge with NetBSD make, pick up: o consider dependencies read from .depend as optional - dsl o remember when buffer for reading makefile grows - dsl o add -dl (aka LOUD) - David O'Brien 2007-10-22 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20071022 * Merge with NetBSD make, pick up: o Allow .PATH to be used for .include "" * boot-strap: source default settings from .bmake-boot-strap.rc 2007-10-16 Simon J. Gerraty * Makefile.in: fix maninstall on various systems provided that our man.mk is used. For non-BSD systems we install the preformatted page into $MANDIR/cat1 2007-10-15 Simon J. Gerraty * boot-strap: make bmake.1 too, so maninstall works. 2007-10-14 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20071014 * Merge with NetBSD make, pick up: o revamped handling of defshell - configure no longer needs to know the content of the shells array - apb o stop Var_Subst modifying its input - apb o avoid calling ParseTrackInput too often - dsl 2007-10-11 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20071011 * Merge with NetBSD make, pick up: o fix Shell_Init for case that _BASENAME_DEFSHELL is absolute path. * sigcompat.c: some tweaks for HP-UX 11.x based on patch from Tobias Nygren * configure.in: update handling of --with-defshell to match new make behavior. --with-defshell=/usr/xpg4/bin/sh will now do what one might hope - provided the chosen shell behaves enough like sh. 2007-10-08 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20071008 * Merge with NetBSD make, pick up: o .MAKE.JOB.PREFIX - control the token output before jobs - sjg o .export/.MAKE.EXPORTED - export of variables - sjg o .MAKE.MAKEFILES - track all makefiles read - sjg o performance improvements - dsl o revamp parallel job scheduling - dsl 2006-07-28 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20060728 * Merge with NetBSD make, pick up: o extra debug info during variable and cond processing - sjg o shell definition now covers newline - rillig o minor mem leak in PrintOnError - sjg 2006-05-11 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20060511 * Merge with NetBSD make, pick up: o more memory leaks - coverity o possible overflow in ArchFindMember - coverity o extract variable modifier code out of Var_Parse() so it can be called recursively - sjg o unit-tests/moderrs - sjg 2006-04-12 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20060412 * Merge with NetBSD make, pick up: o fixes for some memory leaks - coverity o only read first sys.mk etc when searching sysIncPath - sjg * main.c (ReadMakefile): remove hack for __INTERIX that prevented setting ${MAKEFILE} - OBATA Akio 2006-03-18 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20060318 * Merge with NetBSD make, pick up: o cleanup of job.c to remove remote handling, distcc is more useful and this code was likely bit-rotting - dsl o fix for :P modifier - sjg * boot-strap: set default prefix to something reasonable (for me anyway). 2006-03-01 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20060301 * Merge with NetBSD make, pick up: o make .WAIT apply recursively, document and test case - apb o allow variable modifiers in a variable appear anywhere in modifier list, document and test case - sjg 2006-02-22 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20060222 * Merge with NetBSD make, pick up: o improved job token handling - dsl o SIG_DFL the correct signal before exec - dsl o more debug info during parsing - dsl o allow variable modifiers to be specified via variable - sjg * boot-strap: explain why we died if no mksrc 2005-11-05 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20051105 * configure.in: always set default_sys_path default is ${prefix}/share/mk - remove prefix_sys_path, anyone wanting more than above needs to set it manually. 2005-11-04 Simon J. Gerraty * boot-strap: make this a bit easier for pkgsrc folk. bootstrap still fails on IRIX64 since MACHINE_ARCH gets set to 'mips' while pkgsrc wants 'mipseb' or 'mipsel' 2005-11-02 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20051102 * job.c (JobFinish): fix likely ancient merge lossage fix from Todd Vierling. * boot-strap (srcdir): allow setting mksrc=none 2005-10-31 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20051031 * ranlib.h: skip on OSF too. (NetBSD PR 31864) 2005-10-10 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20051002 fix a silly typo 2005-10-09 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20051001 support for UnixWare and some other systems, based on patches from pkgsrc/bootstrap 2005-09-03 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20050901 * Merge with NetBSD make, pick up: o possible parse error causing us to wander off. 2005-06-06 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20050606 * Merge with NetBSD make, pick up: o :0x modifier for randomizing a list o fixes for a number of -Wuninitialized issues. 2005-05-30 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20050530 * Merge with NetBSD make, pick up: o Handle dependencies for .BEGIN, .END and .INTERRUPT * README: was seriously out of date. 2005-03-22 Simon J. Gerraty * Important to use .MAKE rather than MAKE. 2005-03-15 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20050315 * Merge with NetBSD make, pick up: o don't mistake .elsefoo for .else o use suffix-specific search path correctly o bunch of style nits 2004-05-11 Simon J. Gerraty * boot-strap: o ensure that args to --src and --with-mksrc are resolved before giving them to configure. o add -o "objdir" so that builder can control it, default is $OS as determined by os.sh o add -q to suppress all the install instructions. 2004-05-08 Simon J. Gerraty * Remove __IDSTRING() * Makefile.in (BMAKE_VERSION): bump to 20040508 * Merge with NetBSD make, pick up: o posix fixes - remove '-e' from compat mode - add support for '+' command-line prefix. o fix for handling '--' on command-line. o fix include in lst.lib/lstInt.h to simplify '-I's o we also picked up replacement of MAKE_BOOTSTRAP with !MAKE_NATIVE which is a noop, but possibly confusing. 2004-04-14 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20040414 * Merge with NetBSD make, pick up: o allow quoted strings on lhs of conditionals o issue warning when extra .else is seen o print line numer when errors encountered during parsing from string. 2004-02-20 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20040220 * Merge with NetBSD make, pick up: o fix for old :M parsing bug. o re-jigged unit-tests 2004-02-15 Simon J. Gerraty * Makefile.in (accept test): use ${.MAKE:S,^./,${.CURDIR}/,} so that './bmake -f Makefile test' works. 2004-02-14 Simon J. Gerraty * Makefile.in: (BMAKE_VERSION): bump to 20040214 * Merge with NetBSD make, pick up: o search upwards for *.mk o fix for double free of var substitution buffers o use of getopt replaced with custom code, since the usage (re-scanning) isn't posix compatible. 2004-02-12 Simon J. Gerraty * arch.c: don't include ranlib.h on ELF systems (thanks to Chuck Cranor ). 2004-01-18 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump to 20040118 * boot-strap (while): export vars we assign to on cmdline * unit-test/Makefile.in: ternary is .PHONY 2004-01-08 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20040108 * Merge with NetBSD make, pick up: o fix for ternary modifier 2004-01-06 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20040105 * Merge with NetBSD make, pick up: o fix for cond.c to handle compound expressions better o variable expansion within sysV style replacements 2003-12-22 Simon J. Gerraty * Make portable snprintf safer - output to /dev/null first to check space needed. * Makefile.in (BMAKE_VERSION): bump version to 20031222 * Merge with NetBSD make, pick up: o -dg3 to show input graph when things go wrong. o explicitly look for makefiles in objdir if not found in curdir so that errors in .depend etc will be reported accurarely. o avoid use of -e in shell scripts in jobs mode, use '|| exit $?' instead as it more accurately reflects the expected behavior and is more consistently implemented. o avoid use of asprintf. 2003-09-28 Simon J. Gerraty * util.c: Add asprintf and vasprintf. * Makefile.in (BMAKE_VERSION): bump version to 20030928 * Merge with NetBSD make, pick up: :[] modifier - allows picking words from a variable. :tW modifier - allows treating value as one big word. W flag for :C and :S - allows treating value as one big word. 2003-09-12 Simon J. Gerraty * Merge with NetBSD make pick up -de flag to enable printing failed command. don't skip 1st two dir entries (normally . and ..) since coda does not have them. 2003-09-09 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20030909 * Merge with NetBSD make, pick up: - changes for -V '${VAR}' to print fully expanded value cf. -V VAR - CompatRunCommand now prints the command that failed. - several files got updated 3 clause Berkeley license. 2003-08-02 Simon J. Gerraty * boot-strap: Allow setting configure args on command line. 2003-07-31 Simon J. Gerraty * configure.in: add --with-defshell to allow sh or ksh to be selected as default shell. * Makefile.in: bump version to 20030731 * Merge with NetBSD make Pick up .SHELL spec for ksh and associate man page changes. Also compat mode now uses the same shell specs. 2003-07-29 Simon J. Gerraty * var.c (Var_Parse): ensure delim is initialized. * unit-tests/Makefile.in: use single quotes to avoid problems from some shells. * makefile.boot.in: Run the unit-tests as part of the bootstrap procedure. 2003-07-28 Simon J. Gerraty * unit-tests/Makefile.in: always force complaints from ${TEST_MAKE} to be from 'make'. * configure.in: add check for 'diff -u' also fix some old autoconf'isms * Makefile.in (BMAKE_VERSION): bump version to 20030728. if using GCC add -Wno-cast-qual to CFLAGS for var.o * Merge with NetBSD make Pick up fix for :ts parsing error in some cases. Pick unit-tests. 2003-07-23 Simon J. Gerraty * Makefile.in (BMAKE_VERSION): bump version to 20030723. * var.c (Var_Parse): fix bug in :ts modifier, after const correctness fixes, must pass nstr to VarModify. 2003-07-14 Simon J. Gerraty * Makefile.in: BMAKE_VERSION switch to a date based version. We'll generally use the date of last import from NetBSD. * Merge with NetBSD make Pick up fixes for const-correctness, now passes WARNS=3 on NetBSD. Pick up :ts modifier, allows controlling the separator used between words in variable expansion. 2003-07-11 Simon J. Gerraty * FILES: include boot-strap and os.sh * Makefile.in: only set WARNS if we are NetBSD, the effect on FreeBSD is known to be bad. * makefile.boot.in (bootstrap): make this the default target. * Makefile.in: bump version to 3.1.19 * machine.sh: avoid A-Z with tr as it is bound to lose. 2003-07-10 Simon J. Gerraty * Merge with NetBSD make Pick up fix for PR/19781 - unhelpful error msg on unclosed ${var:foo Plus some doc fixes. 2003-04-27 Simon J. Gerraty * Merge with NetBSD make Pick up fix for PR/1523 - don't count a library as built, if there is no way to build it * Bump version to 3.1.18 2003-03-23 Simon J. Gerraty * Merge with NetBSD make Pick up fix for ParseDoSpecialSrc - we only use it if .WAIT appears in src list. 2003-03-21 Simon J. Gerraty * Merge with NetBSD make (mmm 10th anniversary!) pick up fix for .WAIT in srcs that refer to $@ or $* (PR#20828) pick up -X which tells us to not export VAR=val via setenv if we are already doing so via MAKEFLAGS. This saves valuable env space on systems like Darwin. set MAKE_VERSION to 3.1.17 * parse.c: pix up fix for suffix rules 2003-03-06 Simon J. Gerraty * Merge with NetBSD make. pick up fix for propagating -B via MAKEFLAGS. set MAKE_VERSION to 3.1.16 * Apply some patches from pkgsrc-bootstrap/bmake Originally by Grant Beattie I may have missed some - since they are based on bmake-3.1.12 2002-12-03 Simon J. Gerraty * makefile.boot.in (bmake): update install targets for those that use them, also clear MAKEFLAGS when invoking bmake.boot to avoid havoc from gmake -w. Thanks to Harlan Stenn . * bmake.cat1: update the pre-formatted man page! 2002-11-30 Simon J. Gerraty * Merge with NetBSD make. pick up fix for premature free of pointer used in call to Dir_InitCur(). set MAKE_VERSION to 3.1.15 2002-11-26 Simon J. Gerraty * configure.in: determine suitable value for MKSRC. override using --with-mksrc=PATH. * machine.sh: use `uname -p` for MACHINE_ARCH on modern SunOS systems. configs(8) will use 'sun4' as an alias for 'sparc'. 2002-11-25 Simon J. Gerraty * Merge with NetBSD make. pick up ${.PATH} pick up fix for finding ../cat.c via .PATH when .CURDIR=.. set MAKE_VERSION to 3.1.14 add configure checks for killpg and sys/socket.h 2002-09-16 Simon J. Gerraty * tag bmake-3-1-13 * makefile.boot.in (bmake): use install-mk Also setup ./mk before trying to invoke bmake.boot incase we needed install-mk to create a sys.mk for us. * configure.in: If we need to add -I${srcdir}/missing, make it an absolute path so that it works for lst.lib too. * make.h: always include sys/cdefs.h since we provide one if the host does not. * Makefile.in (install-mk): use MKSRC/install-mk which will do the right thing. use uname -p for ARCH if possible. since install-mk will setup links bsd.prog.mk -> prog.mk if needed, just .include bsd.prog.mk * Merge with NetBSD make (NetBSD-1.6) Code is ansi-C only now. Bug in handling of dotLast is fixed. Can now assign .OBJDIR and make will reset its notions of life. New modifiers :tu :tl for toUpper and toLower. Tue Oct 16 12:18:42 2001 Simon J. Gerraty * Merge with NetBSD make pick up fix for .END failure in compat mode. pick up fix for extra va_end() in ParseVErrorInternal. Thu Oct 11 13:20:06 2001 Simon J. Gerraty * configure.in: for systems that have sys/cdefs.h check if it is compatible. If not, include the one under missing, but tell it to include the native one too - necessary on Linux. * missing/sys/cdefs.h: if NEED_HOST_CDEFS_H is defined, use include_next (for gcc) to get the native sys/cdefs.h Tue Aug 21 02:29:34 2001 Simon J. Gerraty * job.c (JobFinish): Fix an earlier merge bug that resulted in leaking descriptors when using -jN. * job.c (JobPrintCommand): See if "curdir" exists before attempting to chdir(). Doing the chdir directly in make (when in compat mode) fails silently, so let the -jN version do the same. This can happen when building kernels in an object tree and playing clever games to reset .CURDIR. * Merged with NetBSD make pick up .USEBEFORE Tue Jun 26 23:45:11 2001 Simon J. Gerraty * makefile.boot.in: Give bmake.boot a MAKESYSPATH that might work. Tue Jun 12 16:48:57 2001 Simon J. Gerraty * var.c (Var_Set): Add 4th (flags) arg so VarLoopExpand can tell us not to export the iterator variable when using VAR_CMD context. Sun Jun 10 21:55:21 2001 Simon J. Gerraty * job.c (Job_CatchChildren): don't call Job_CatchOutput() here, its the wrong "fix". Sat Jun 9 00:11:24 2001 Simon J. Gerraty * Redesigned export of VAR_CMD's via MAKEFLAGS. We now simply append the variable names to .MAKEOVERRIDES, and handle duplicate suppression and quoting in ExportMAKEFLAGS using: ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@} Apart from fixing quoting bugs in previous version, this allows us to export vars to the environment by simply doing: .MAKEOVERRIDES+= PATH Merged again with NetBSD make, but the above is the only change. * configure.in: added --disable-pwd-override disable $PWD overriding getcwd() --disable-check-make-chdir disable make trying to guess when it should automatically cd ${.CURDIR} * Merge with NetBSD make, changes include: parse.c (ParseDoDependency): Spot that the syntax error is caused by an unresolved cvs/rcs conflict and say so. var.c: most of Var* functions now take a ctxt as 1st arg. now does variable substituion on rhs of sysv style modifiers. * var.c (Var_Set): exporting of command line variables (VAR_CMD) is now done here. We append the name='value' to .MAKEOVERRIDES rather than directly into MAKEFLAGS as this allows a Makefile to use .MAKEOVERRIDES= to disable this behaviour. GNU make uses a very similar mechanism. Note that in adding name='value' to .MAKEOVERRIDES we do the moral equivalent of: .MAKEOVERRIDES:= ${.MAKEOVERRIDES:Nname=*} name='val' Fri Jun 1 14:08:02 2001 Simon J. Gerraty * make-conf.h (USE_IOVEC): make it conditional on HAVE_SYS_UIO_H * Merged with NetBSD make make -dx can now be used to run commands via sh -x better error messages on exec failures. Thu May 31 01:44:54 2001 Simon J. Gerraty * Makefile.in (main.o): depends on ${SRCS} ${MAKEFILE} so that MAKE_VERSION gets updated. Also don't use ?= for MAKE_VERSION, MACHINE etc otherwise they propagate from the previous bmake. * configure.in (machine): allow --with-machine=generic to make configure use machine.sh to set MACHINE. * job.c (JobInterrupt): convert to using WAIT_T and friends. * Makefile.in: mention in bmake.1 that we use autoconf. * make.1: mention MAKE_PRINT_VAR_ON_ERROR. Wed May 30 23:17:18 2001 Simon J. Gerraty * main.c (ReadMakefile): don't set MAKEFILE if reading ".depend" as that rather defeats the usefulness of ${MAKEFILE}. * main.c (MainParseArgs): append command line variable assignments to MAKEFLAGS so that they get propagated to child make's. Apparently this is required POSIX behaviour? Its useful anyway. Tue May 29 02:20:07 2001 Simon J. Gerraty * compat.c (CompatRunCommand): don't use perror() since stdio may cause problems in child of vfork(). * compat.c, main.c: Call PrintOnError() when we are going to bail. This routine prints out the .curdir where we stopped and will also display any vars listed in ${MAKE_PRINT_VAR_ON_ERROR}. * main.c: add ${.newline} to hold a "\n" - sometimes handy in :@ expansion. * var.c: VarLoopExpand: ignore addSpace if a \n is present. * Added RCSid's for the files we've touched. Thu May 24 15:41:37 2001 Simon J. Gerraty * configure.in: Thanks to some clues from mdb@juniper.net, added autoconf magic to control setting of MACHINE, MACHINE_ARCH as well as what ends up in _PATH_DEFSYSPATH. We now have: --with-machine=MACHINE explicitly set MACHINE --with-force-machine=MACHINE set FORCE_MACHINE --with-machine_arch=MACHINE_ARCH explicitly set MACHINE_ARCH --with-default-sys-path=PATH:DIR:LIST use an explicit _PATH_DEFSYSPATH --with-prefix-sys-path=PATH:DIR:LIST prefix _PATH_PREFIX_SYSPATH --with-path-objdirprefix=PATH override _PATH_OBJDIRPREFIX If _PATH_OBJDIRPREFIX is set to "no" we won't define it. * makefile: added a pathetically simple makefile to drive bootstrapping. Running configure by hand is more useful. * Makefile.in: added MAKE_VERSION, and reworked things to be less dependent on NetBSD bsd.*.mk * pathnames.h: allow NO_PATH_OBJDIRPREFIX to stop us defining _PATH_OBJDIRPREFIX for those that don't want a default. construct _PATH_DEFSYSPATH from the info we get from configure. * main.c: allow for no _PATH_OBJDIRPREFIX, set ${MAKE_VERSION} if MAKE_VERSION is defined. * compat.c: when we bail, print out the .CURDIR we were in. Sat May 12 00:34:12 2001 Simon J. Gerraty * Merged with NetBSD make * var.c: fixed a bug in the handling of the modifier :P if the node as found but the path was null, we segfault trying to duplicate it. Mon Mar 5 16:20:33 2001 Simon J. Gerraty * Merged with NetBSD make * make.c: Make_OODate's test for a library out of date was using cmtime where it should have used mtime (my bug). * compat.c: Use perror() to tell us what really went wrong when we cannot exec a command. Fri Dec 15 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Sat Jun 10 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Thu Jun 1 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Tue May 30 10:11:08 2000 Simon J. Gerraty * Merged with NetBSD make Thu Apr 27 00:07:47 2000 Simon J. Gerraty * util.c: don't provide signal() since we use sigcompat.c * Makefile.in: added a build target. * var.c (Var_Parse): added ODE modifiers :U, :D, :L, :P, :@ and :! These allow some quite clever magic. * main.c (main): added support for getenv(MAKESYSPATH). Mon Apr 2 16:25:13 2000 Simon J. Gerraty * Disable $PWD overriding getcwd() if MAKEOBJDIRPREFIX is set. This avoids objdir having a different value depending on how a directory was reached (via command line, or subdir.mk). * If FORCE_MACHINE is defined, ignore getenv("MACHINE"). Mon Apr 2 23:15:31 2000 Simon J. Gerraty * Do a chdir(${.CURDIR}) before invoking ${.MAKE} or ${.MAKE:T} if MAKEOBJDIRPREFIX is set and NOCHECKMAKECHDIR is not. I've been testing this in NetBSD's make for some weeks. * Turn Makefile into Makefile.in and make it useful. Tue Feb 29 22:08:00 2000 Simon J. Gerraty * Imported NetBSD's -current make(1) and resolve conflicts. * Applied autoconf patches from bmake v2 * Imported clean code base from NetBSD-1.0 diff --git a/contrib/bmake/FILES b/contrib/bmake/FILES index bfe95a9b6b47..c8937c610a4d 100644 --- a/contrib/bmake/FILES +++ b/contrib/bmake/FILES @@ -1,874 +1,876 @@ ChangeLog FILES LICENSE Makefile Makefile.config.in PSD.doc/Makefile PSD.doc/tutorial.ms README VERSION _strtol.h aclocal.m4 arch.c bmake.1 bmake.cat1 boot-strap bsd.after-import.mk buf.c buf.h compat.c cond.c config.h.in configure configure.in dir.c dir.h dirname.c filemon/filemon.h filemon/filemon_dev.c filemon/filemon_ktrace.c find_lib.sh for.c getopt.c hash.c hash.h install-sh job.c job.h lst.c lst.h machine.sh main.c make-bootstrap.sh.in make-conf.h make.1 make.c make.h make_malloc.c make_malloc.h makefile.in meta.c meta.h metachar.c metachar.h missing/sys/cdefs.h mkdeps.sh os.sh parse.c pathnames.h ranlib.h realpath.c setenv.c sigact.h sigaction.c sigcompat.c str.c str.h stresep.c strlcpy.c suff.c targ.c trace.c trace.h unit-tests/Makefile unit-tests/Makefile.config.in unit-tests/archive-suffix.exp unit-tests/archive-suffix.mk unit-tests/archive.exp unit-tests/archive.mk unit-tests/cmd-errors-jobs.exp unit-tests/cmd-errors-jobs.mk unit-tests/cmd-errors-lint.exp unit-tests/cmd-errors-lint.mk unit-tests/cmd-errors.exp unit-tests/cmd-errors.mk unit-tests/cmd-interrupt.exp unit-tests/cmd-interrupt.mk unit-tests/cmdline-redirect-stdin.exp unit-tests/cmdline-redirect-stdin.mk unit-tests/cmdline-undefined.exp unit-tests/cmdline-undefined.mk unit-tests/cmdline.exp unit-tests/cmdline.mk unit-tests/comment.exp unit-tests/comment.mk unit-tests/compat-error.exp unit-tests/compat-error.mk unit-tests/cond-cmp-numeric-eq.exp unit-tests/cond-cmp-numeric-eq.mk unit-tests/cond-cmp-numeric-ge.exp unit-tests/cond-cmp-numeric-ge.mk unit-tests/cond-cmp-numeric-gt.exp unit-tests/cond-cmp-numeric-gt.mk unit-tests/cond-cmp-numeric-le.exp unit-tests/cond-cmp-numeric-le.mk unit-tests/cond-cmp-numeric-lt.exp unit-tests/cond-cmp-numeric-lt.mk unit-tests/cond-cmp-numeric-ne.exp unit-tests/cond-cmp-numeric-ne.mk unit-tests/cond-cmp-numeric.exp unit-tests/cond-cmp-numeric.mk unit-tests/cond-cmp-string.exp unit-tests/cond-cmp-string.mk unit-tests/cond-cmp-unary.exp unit-tests/cond-cmp-unary.mk unit-tests/cond-eof.exp unit-tests/cond-eof.mk unit-tests/cond-func-commands.exp unit-tests/cond-func-commands.mk unit-tests/cond-func-defined.exp unit-tests/cond-func-defined.mk unit-tests/cond-func-empty.exp unit-tests/cond-func-empty.mk unit-tests/cond-func-exists.exp unit-tests/cond-func-exists.mk unit-tests/cond-func-make-main.exp unit-tests/cond-func-make-main.mk unit-tests/cond-func-make.exp unit-tests/cond-func-make.mk unit-tests/cond-func-target.exp unit-tests/cond-func-target.mk unit-tests/cond-func.exp unit-tests/cond-func.mk unit-tests/cond-late.exp unit-tests/cond-late.mk unit-tests/cond-op-and-lint.exp unit-tests/cond-op-and-lint.mk unit-tests/cond-op-and.exp unit-tests/cond-op-and.mk unit-tests/cond-op-not.exp unit-tests/cond-op-not.mk unit-tests/cond-op-or-lint.exp unit-tests/cond-op-or-lint.mk unit-tests/cond-op-or.exp unit-tests/cond-op-or.mk unit-tests/cond-op-parentheses.exp unit-tests/cond-op-parentheses.mk unit-tests/cond-op.exp unit-tests/cond-op.mk unit-tests/cond-short.exp unit-tests/cond-short.mk unit-tests/cond-token-number.exp unit-tests/cond-token-number.mk unit-tests/cond-token-plain.exp unit-tests/cond-token-plain.mk unit-tests/cond-token-string.exp unit-tests/cond-token-string.mk unit-tests/cond-token-var.exp unit-tests/cond-token-var.mk unit-tests/cond-undef-lint.exp unit-tests/cond-undef-lint.mk unit-tests/counter-append.exp unit-tests/counter-append.mk unit-tests/counter.exp unit-tests/counter.mk unit-tests/dep-colon-bug-cross-file.exp unit-tests/dep-colon-bug-cross-file.mk unit-tests/dep-colon.exp unit-tests/dep-colon.mk unit-tests/dep-double-colon-indep.exp unit-tests/dep-double-colon-indep.mk unit-tests/dep-double-colon.exp unit-tests/dep-double-colon.mk unit-tests/dep-duplicate.exp unit-tests/dep-duplicate.mk unit-tests/dep-exclam.exp unit-tests/dep-exclam.mk unit-tests/dep-none.exp unit-tests/dep-none.mk unit-tests/dep-op-missing.exp unit-tests/dep-op-missing.mk unit-tests/dep-percent.exp unit-tests/dep-percent.mk unit-tests/dep-var.exp unit-tests/dep-var.mk unit-tests/dep-wildcards.exp unit-tests/dep-wildcards.mk unit-tests/dep.exp unit-tests/dep.mk unit-tests/depsrc-end.exp unit-tests/depsrc-end.mk unit-tests/depsrc-exec.exp unit-tests/depsrc-exec.mk unit-tests/depsrc-ignore.exp unit-tests/depsrc-ignore.mk unit-tests/depsrc-made.exp unit-tests/depsrc-made.mk unit-tests/depsrc-make.exp unit-tests/depsrc-make.mk unit-tests/depsrc-meta.exp unit-tests/depsrc-meta.mk unit-tests/depsrc-nometa.exp unit-tests/depsrc-nometa.mk unit-tests/depsrc-nometa_cmp.exp unit-tests/depsrc-nometa_cmp.mk unit-tests/depsrc-nopath.exp unit-tests/depsrc-nopath.mk unit-tests/depsrc-notmain.exp unit-tests/depsrc-notmain.mk unit-tests/depsrc-optional.exp unit-tests/depsrc-optional.mk unit-tests/depsrc-phony.exp unit-tests/depsrc-phony.mk unit-tests/depsrc-precious.exp unit-tests/depsrc-precious.mk unit-tests/depsrc-recursive.exp unit-tests/depsrc-recursive.mk unit-tests/depsrc-silent.exp unit-tests/depsrc-silent.mk unit-tests/depsrc-use.exp unit-tests/depsrc-use.mk unit-tests/depsrc-usebefore-double-colon.exp unit-tests/depsrc-usebefore-double-colon.mk unit-tests/depsrc-usebefore.exp unit-tests/depsrc-usebefore.mk unit-tests/depsrc-wait.exp unit-tests/depsrc-wait.mk unit-tests/depsrc.exp unit-tests/depsrc.mk unit-tests/deptgt-begin-fail-indirect.exp unit-tests/deptgt-begin-fail-indirect.mk unit-tests/deptgt-begin-fail.exp unit-tests/deptgt-begin-fail.mk unit-tests/deptgt-begin.exp unit-tests/deptgt-begin.mk unit-tests/deptgt-default.exp unit-tests/deptgt-default.mk unit-tests/deptgt-delete_on_error.exp unit-tests/deptgt-delete_on_error.mk unit-tests/deptgt-end-fail-all.exp unit-tests/deptgt-end-fail-all.mk unit-tests/deptgt-end-fail-indirect.exp unit-tests/deptgt-end-fail-indirect.mk unit-tests/deptgt-end-fail.exp unit-tests/deptgt-end-fail.mk unit-tests/deptgt-end-jobs.exp unit-tests/deptgt-end-jobs.mk unit-tests/deptgt-end.exp unit-tests/deptgt-end.mk unit-tests/deptgt-error.exp unit-tests/deptgt-error.mk unit-tests/deptgt-ignore.exp unit-tests/deptgt-ignore.mk unit-tests/deptgt-interrupt.exp unit-tests/deptgt-interrupt.mk unit-tests/deptgt-main.exp unit-tests/deptgt-main.mk unit-tests/deptgt-makeflags.exp unit-tests/deptgt-makeflags.mk unit-tests/deptgt-no_parallel.exp unit-tests/deptgt-no_parallel.mk unit-tests/deptgt-nopath.exp unit-tests/deptgt-nopath.mk unit-tests/deptgt-notparallel.exp unit-tests/deptgt-notparallel.mk unit-tests/deptgt-objdir.exp unit-tests/deptgt-objdir.mk unit-tests/deptgt-order.exp unit-tests/deptgt-order.mk unit-tests/deptgt-path-suffix.exp unit-tests/deptgt-path-suffix.mk unit-tests/deptgt-path.exp unit-tests/deptgt-path.mk unit-tests/deptgt-phony.exp unit-tests/deptgt-phony.mk unit-tests/deptgt-posix.exp unit-tests/deptgt-posix.mk unit-tests/deptgt-precious.exp unit-tests/deptgt-precious.mk unit-tests/deptgt-shell.exp unit-tests/deptgt-shell.mk unit-tests/deptgt-silent-jobs.exp unit-tests/deptgt-silent-jobs.mk unit-tests/deptgt-silent.exp unit-tests/deptgt-silent.mk unit-tests/deptgt-stale.exp unit-tests/deptgt-stale.mk unit-tests/deptgt-suffixes.exp unit-tests/deptgt-suffixes.mk unit-tests/deptgt.exp unit-tests/deptgt.mk unit-tests/dir-expand-path.exp unit-tests/dir-expand-path.mk unit-tests/dir.exp unit-tests/dir.mk unit-tests/directive-dinclude.exp unit-tests/directive-dinclude.mk unit-tests/directive-elif.exp unit-tests/directive-elif.mk unit-tests/directive-elifdef.exp unit-tests/directive-elifdef.mk unit-tests/directive-elifmake.exp unit-tests/directive-elifmake.mk unit-tests/directive-elifndef.exp unit-tests/directive-elifndef.mk unit-tests/directive-elifnmake.exp unit-tests/directive-elifnmake.mk unit-tests/directive-else.exp unit-tests/directive-else.mk unit-tests/directive-endfor.exp unit-tests/directive-endfor.mk unit-tests/directive-endif.exp unit-tests/directive-endif.mk unit-tests/directive-error.exp unit-tests/directive-error.mk unit-tests/directive-export-env.exp unit-tests/directive-export-env.mk unit-tests/directive-export-gmake.exp unit-tests/directive-export-gmake.mk unit-tests/directive-export-impl.exp unit-tests/directive-export-impl.mk unit-tests/directive-export-literal.exp unit-tests/directive-export-literal.mk unit-tests/directive-export.exp unit-tests/directive-export.mk unit-tests/directive-for-break.exp unit-tests/directive-for-break.mk unit-tests/directive-for-empty.exp unit-tests/directive-for-empty.mk unit-tests/directive-for-errors.exp unit-tests/directive-for-errors.mk unit-tests/directive-for-escape.exp unit-tests/directive-for-escape.mk unit-tests/directive-for-generating-endif.exp unit-tests/directive-for-generating-endif.mk unit-tests/directive-for-if.exp unit-tests/directive-for-if.mk unit-tests/directive-for-lines.exp unit-tests/directive-for-lines.mk unit-tests/directive-for-null.exp unit-tests/directive-for-null.mk unit-tests/directive-for.exp unit-tests/directive-for.mk unit-tests/directive-hyphen-include.exp unit-tests/directive-hyphen-include.mk unit-tests/directive-if-nested.exp unit-tests/directive-if-nested.mk unit-tests/directive-if.exp unit-tests/directive-if.mk unit-tests/directive-ifdef.exp unit-tests/directive-ifdef.mk unit-tests/directive-ifmake.exp unit-tests/directive-ifmake.mk unit-tests/directive-ifndef.exp unit-tests/directive-ifndef.mk unit-tests/directive-ifnmake.exp unit-tests/directive-ifnmake.mk unit-tests/directive-include-fatal.exp unit-tests/directive-include-fatal.mk unit-tests/directive-include-guard.exp unit-tests/directive-include-guard.mk unit-tests/directive-include.exp unit-tests/directive-include.mk unit-tests/directive-info.exp unit-tests/directive-info.mk unit-tests/directive-misspellings.exp unit-tests/directive-misspellings.mk unit-tests/directive-sinclude.exp unit-tests/directive-sinclude.mk unit-tests/directive-undef.exp unit-tests/directive-undef.mk unit-tests/directive-unexport-env.exp unit-tests/directive-unexport-env.mk unit-tests/directive-unexport.exp unit-tests/directive-unexport.mk unit-tests/directive-warning.exp unit-tests/directive-warning.mk unit-tests/directive.exp unit-tests/directive.mk unit-tests/dollar.exp unit-tests/dollar.mk unit-tests/doterror.exp unit-tests/doterror.mk unit-tests/dotwait.exp unit-tests/dotwait.mk unit-tests/error.exp unit-tests/error.mk unit-tests/escape.exp unit-tests/escape.mk unit-tests/export-all.exp unit-tests/export-all.mk unit-tests/export-env.exp unit-tests/export-env.mk unit-tests/export-variants.exp unit-tests/export-variants.mk unit-tests/export.exp unit-tests/export.mk unit-tests/gnode-submake.exp unit-tests/gnode-submake.mk unit-tests/hanoi-include.exp unit-tests/hanoi-include.mk unit-tests/impsrc.exp unit-tests/impsrc.mk unit-tests/include-main.exp unit-tests/include-main.mk unit-tests/include-sub.inc unit-tests/include-subsub.inc unit-tests/job-flags.exp unit-tests/job-flags.mk unit-tests/job-output-long-lines.exp unit-tests/job-output-long-lines.mk unit-tests/job-output-null.exp unit-tests/job-output-null.mk unit-tests/jobs-empty-commands-error.exp unit-tests/jobs-empty-commands-error.mk unit-tests/jobs-empty-commands.exp unit-tests/jobs-empty-commands.mk unit-tests/jobs-error-indirect.exp unit-tests/jobs-error-indirect.mk unit-tests/jobs-error-nested-make.exp unit-tests/jobs-error-nested-make.mk unit-tests/jobs-error-nested.exp unit-tests/jobs-error-nested.mk unit-tests/lint.exp unit-tests/lint.mk unit-tests/make-exported.exp unit-tests/make-exported.mk unit-tests/meta-cmd-cmp.exp unit-tests/meta-cmd-cmp.mk unit-tests/meta-ignore.inc unit-tests/moderrs.exp unit-tests/moderrs.mk unit-tests/modmisc.exp unit-tests/modmisc.mk unit-tests/objdir-writable.exp unit-tests/objdir-writable.mk unit-tests/opt-backwards.exp unit-tests/opt-backwards.mk unit-tests/opt-chdir.exp unit-tests/opt-chdir.mk unit-tests/opt-debug-all.exp unit-tests/opt-debug-all.mk unit-tests/opt-debug-archive.exp unit-tests/opt-debug-archive.mk unit-tests/opt-debug-cond.exp unit-tests/opt-debug-cond.mk unit-tests/opt-debug-curdir.exp unit-tests/opt-debug-curdir.mk unit-tests/opt-debug-dir.exp unit-tests/opt-debug-dir.mk unit-tests/opt-debug-errors-jobs.exp unit-tests/opt-debug-errors-jobs.mk unit-tests/opt-debug-errors.exp unit-tests/opt-debug-errors.mk unit-tests/opt-debug-file.exp unit-tests/opt-debug-file.mk unit-tests/opt-debug-for.exp unit-tests/opt-debug-for.mk unit-tests/opt-debug-graph1.exp unit-tests/opt-debug-graph1.mk unit-tests/opt-debug-graph2.exp unit-tests/opt-debug-graph2.mk unit-tests/opt-debug-graph3.exp unit-tests/opt-debug-graph3.mk unit-tests/opt-debug-hash.exp unit-tests/opt-debug-hash.mk unit-tests/opt-debug-jobs.exp unit-tests/opt-debug-jobs.mk unit-tests/opt-debug-lint.exp unit-tests/opt-debug-lint.mk unit-tests/opt-debug-loud.exp unit-tests/opt-debug-loud.mk unit-tests/opt-debug-making.exp unit-tests/opt-debug-making.mk unit-tests/opt-debug-meta.exp unit-tests/opt-debug-meta.mk unit-tests/opt-debug-no-rm.exp unit-tests/opt-debug-no-rm.mk unit-tests/opt-debug-parse.exp unit-tests/opt-debug-parse.mk unit-tests/opt-debug-suff.exp unit-tests/opt-debug-suff.mk unit-tests/opt-debug-targets.exp unit-tests/opt-debug-targets.mk unit-tests/opt-debug-var.exp unit-tests/opt-debug-var.mk unit-tests/opt-debug-varraw.exp unit-tests/opt-debug-varraw.mk unit-tests/opt-debug-x-trace.exp unit-tests/opt-debug-x-trace.mk unit-tests/opt-debug.exp unit-tests/opt-debug.mk unit-tests/opt-define.exp unit-tests/opt-define.mk unit-tests/opt-env.exp unit-tests/opt-env.mk unit-tests/opt-file.exp unit-tests/opt-file.mk unit-tests/opt-ignore.exp unit-tests/opt-ignore.mk unit-tests/opt-include-dir.exp unit-tests/opt-include-dir.mk unit-tests/opt-jobs-internal.exp unit-tests/opt-jobs-internal.mk unit-tests/opt-jobs-no-action.exp unit-tests/opt-jobs-no-action.mk unit-tests/opt-jobs.exp unit-tests/opt-jobs.mk unit-tests/opt-keep-going-indirect.exp unit-tests/opt-keep-going-indirect.mk unit-tests/opt-keep-going-multiple.exp unit-tests/opt-keep-going-multiple.mk unit-tests/opt-keep-going.exp unit-tests/opt-keep-going.mk unit-tests/opt-m-include-dir.exp unit-tests/opt-m-include-dir.mk unit-tests/opt-no-action-at-all.exp unit-tests/opt-no-action-at-all.mk unit-tests/opt-no-action-runflags.exp unit-tests/opt-no-action-runflags.mk unit-tests/opt-no-action-touch.exp unit-tests/opt-no-action-touch.mk unit-tests/opt-no-action.exp unit-tests/opt-no-action.mk unit-tests/opt-query.exp unit-tests/opt-query.mk unit-tests/opt-raw.exp unit-tests/opt-raw.mk unit-tests/opt-silent.exp unit-tests/opt-silent.mk unit-tests/opt-touch-jobs.exp unit-tests/opt-touch-jobs.mk unit-tests/opt-touch.exp unit-tests/opt-touch.mk unit-tests/opt-tracefile.exp unit-tests/opt-tracefile.mk unit-tests/opt-var-expanded.exp unit-tests/opt-var-expanded.mk unit-tests/opt-var-literal.exp unit-tests/opt-var-literal.mk unit-tests/opt-version.exp unit-tests/opt-version.mk unit-tests/opt-warnings-as-errors.exp unit-tests/opt-warnings-as-errors.mk unit-tests/opt-where-am-i.exp unit-tests/opt-where-am-i.mk unit-tests/opt-x-reduce-exported.exp unit-tests/opt-x-reduce-exported.mk unit-tests/opt.exp unit-tests/opt.mk unit-tests/order.exp unit-tests/order.mk unit-tests/parse-var.exp unit-tests/parse-var.mk unit-tests/parse.exp unit-tests/parse.mk unit-tests/phony-end.exp unit-tests/phony-end.mk unit-tests/posix.exp unit-tests/posix.mk unit-tests/posix1.exp unit-tests/posix1.mk unit-tests/recursive.exp unit-tests/recursive.mk unit-tests/sh-dots.exp unit-tests/sh-dots.mk unit-tests/sh-errctl.exp unit-tests/sh-errctl.mk unit-tests/sh-flags.exp unit-tests/sh-flags.mk unit-tests/sh-jobs-error.exp unit-tests/sh-jobs-error.mk unit-tests/sh-jobs.exp unit-tests/sh-jobs.mk unit-tests/sh-leading-at.exp unit-tests/sh-leading-at.mk unit-tests/sh-leading-hyphen.exp unit-tests/sh-leading-hyphen.mk unit-tests/sh-leading-plus.exp unit-tests/sh-leading-plus.mk unit-tests/sh-meta-chars.exp unit-tests/sh-meta-chars.mk unit-tests/sh-multi-line.exp unit-tests/sh-multi-line.mk unit-tests/sh-single-line.exp unit-tests/sh-single-line.mk unit-tests/sh.exp unit-tests/sh.mk unit-tests/shell-csh.exp unit-tests/shell-csh.mk unit-tests/shell-custom.exp unit-tests/shell-custom.mk unit-tests/shell-ksh.exp unit-tests/shell-ksh.mk unit-tests/shell-sh.exp unit-tests/shell-sh.mk +unit-tests/suff.exp +unit-tests/suff.mk unit-tests/suff-add-later.exp unit-tests/suff-add-later.mk unit-tests/suff-clear-regular.exp unit-tests/suff-clear-regular.mk unit-tests/suff-clear-single.exp unit-tests/suff-clear-single.mk unit-tests/suff-incomplete.exp unit-tests/suff-incomplete.mk unit-tests/suff-lookup.exp unit-tests/suff-lookup.mk unit-tests/suff-main-several.exp unit-tests/suff-main-several.mk unit-tests/suff-main.exp unit-tests/suff-main.mk unit-tests/suff-phony.exp unit-tests/suff-phony.mk unit-tests/suff-rebuild.exp unit-tests/suff-rebuild.mk unit-tests/suff-self.exp unit-tests/suff-self.mk unit-tests/suff-transform-debug.exp unit-tests/suff-transform-debug.mk unit-tests/suff-transform-endless.exp unit-tests/suff-transform-endless.mk unit-tests/suff-transform-expand.exp unit-tests/suff-transform-expand.mk unit-tests/suff-transform-select.exp unit-tests/suff-transform-select.mk unit-tests/suff-use.exp unit-tests/suff-use.mk unit-tests/sunshcmd.exp unit-tests/sunshcmd.mk unit-tests/ternary.exp unit-tests/ternary.mk unit-tests/unexport-env.exp unit-tests/unexport-env.mk unit-tests/unexport.exp unit-tests/unexport.mk unit-tests/use-inference.exp unit-tests/use-inference.mk unit-tests/var-eval-short.exp unit-tests/var-eval-short.mk unit-tests/var-op-append.exp unit-tests/var-op-append.mk unit-tests/var-op-assign.exp unit-tests/var-op-assign.mk unit-tests/var-op-default.exp unit-tests/var-op-default.mk unit-tests/var-op-expand.exp unit-tests/var-op-expand.mk unit-tests/var-op-shell.exp unit-tests/var-op-shell.mk unit-tests/var-op-sunsh.exp unit-tests/var-op-sunsh.mk unit-tests/var-op.exp unit-tests/var-op.mk unit-tests/var-readonly.exp unit-tests/var-readonly.mk unit-tests/var-recursive.exp unit-tests/var-recursive.mk unit-tests/var-scope-cmdline.exp unit-tests/var-scope-cmdline.mk unit-tests/var-scope-env.exp unit-tests/var-scope-env.mk unit-tests/var-scope-global.exp unit-tests/var-scope-global.mk unit-tests/var-scope-local-legacy.exp unit-tests/var-scope-local-legacy.mk unit-tests/var-scope-local.exp unit-tests/var-scope-local.mk unit-tests/var-scope.exp unit-tests/var-scope.mk unit-tests/varcmd.exp unit-tests/varcmd.mk unit-tests/vardebug.exp unit-tests/vardebug.mk unit-tests/varfind.exp unit-tests/varfind.mk unit-tests/varmisc.exp unit-tests/varmisc.mk unit-tests/varmod-assign-shell.exp unit-tests/varmod-assign-shell.mk unit-tests/varmod-assign.exp unit-tests/varmod-assign.mk unit-tests/varmod-defined.exp unit-tests/varmod-defined.mk unit-tests/varmod-edge.exp unit-tests/varmod-edge.mk unit-tests/varmod-exclam-shell.exp unit-tests/varmod-exclam-shell.mk unit-tests/varmod-extension.exp unit-tests/varmod-extension.mk unit-tests/varmod-gmtime.exp unit-tests/varmod-gmtime.mk unit-tests/varmod-hash.exp unit-tests/varmod-hash.mk unit-tests/varmod-head.exp unit-tests/varmod-head.mk unit-tests/varmod-ifelse.exp unit-tests/varmod-ifelse.mk unit-tests/varmod-indirect.exp unit-tests/varmod-indirect.mk unit-tests/varmod-l-name-to-value.exp unit-tests/varmod-l-name-to-value.mk unit-tests/varmod-localtime.exp unit-tests/varmod-localtime.mk unit-tests/varmod-loop-delete.exp unit-tests/varmod-loop-delete.mk unit-tests/varmod-loop-varname.exp unit-tests/varmod-loop-varname.mk unit-tests/varmod-loop.exp unit-tests/varmod-loop.mk unit-tests/varmod-match-escape.exp unit-tests/varmod-match-escape.mk unit-tests/varmod-match.exp unit-tests/varmod-match.mk unit-tests/varmod-mtime.exp unit-tests/varmod-mtime.mk unit-tests/varmod-no-match.exp unit-tests/varmod-no-match.mk unit-tests/varmod-order-numeric.exp unit-tests/varmod-order-numeric.mk unit-tests/varmod-order-reverse.exp unit-tests/varmod-order-reverse.mk unit-tests/varmod-order-shuffle.exp unit-tests/varmod-order-shuffle.mk unit-tests/varmod-order-string.exp unit-tests/varmod-order-string.mk unit-tests/varmod-order.exp unit-tests/varmod-order.mk unit-tests/varmod-path.exp unit-tests/varmod-path.mk unit-tests/varmod-quote-dollar.exp unit-tests/varmod-quote-dollar.mk unit-tests/varmod-quote.exp unit-tests/varmod-quote.mk unit-tests/varmod-range.exp unit-tests/varmod-range.mk unit-tests/varmod-remember.exp unit-tests/varmod-remember.mk unit-tests/varmod-root.exp unit-tests/varmod-root.mk unit-tests/varmod-select-words.exp unit-tests/varmod-select-words.mk unit-tests/varmod-shell.exp unit-tests/varmod-shell.mk unit-tests/varmod-subst-regex.exp unit-tests/varmod-subst-regex.mk unit-tests/varmod-subst.exp unit-tests/varmod-subst.mk unit-tests/varmod-sun-shell.exp unit-tests/varmod-sun-shell.mk unit-tests/varmod-sysv.exp unit-tests/varmod-sysv.mk unit-tests/varmod-tail.exp unit-tests/varmod-tail.mk unit-tests/varmod-to-abs.exp unit-tests/varmod-to-abs.mk unit-tests/varmod-to-lower.exp unit-tests/varmod-to-lower.mk unit-tests/varmod-to-many-words.exp unit-tests/varmod-to-many-words.mk unit-tests/varmod-to-one-word.exp unit-tests/varmod-to-one-word.mk unit-tests/varmod-to-separator.exp unit-tests/varmod-to-separator.mk unit-tests/varmod-to-title.exp unit-tests/varmod-to-title.mk unit-tests/varmod-to-upper.exp unit-tests/varmod-to-upper.mk unit-tests/varmod-undefined.exp unit-tests/varmod-undefined.mk unit-tests/varmod-unique.exp unit-tests/varmod-unique.mk unit-tests/varmod.exp unit-tests/varmod.mk unit-tests/varname-dollar.exp unit-tests/varname-dollar.mk unit-tests/varname-dot-alltargets.exp unit-tests/varname-dot-alltargets.mk unit-tests/varname-dot-curdir.exp unit-tests/varname-dot-curdir.mk unit-tests/varname-dot-includedfromdir.exp unit-tests/varname-dot-includedfromdir.mk unit-tests/varname-dot-includedfromfile.exp unit-tests/varname-dot-includedfromfile.mk unit-tests/varname-dot-includes.exp unit-tests/varname-dot-includes.mk unit-tests/varname-dot-libs.exp unit-tests/varname-dot-libs.mk unit-tests/varname-dot-make-dependfile.exp unit-tests/varname-dot-make-dependfile.mk unit-tests/varname-dot-make-expand_variables.exp unit-tests/varname-dot-make-expand_variables.mk unit-tests/varname-dot-make-exported.exp unit-tests/varname-dot-make-exported.mk unit-tests/varname-dot-make-jobs-prefix.exp unit-tests/varname-dot-make-jobs-prefix.mk unit-tests/varname-dot-make-jobs.exp unit-tests/varname-dot-make-jobs.mk unit-tests/varname-dot-make-level.exp unit-tests/varname-dot-make-level.mk unit-tests/varname-dot-make-makefile_preference.exp unit-tests/varname-dot-make-makefile_preference.mk unit-tests/varname-dot-make-makefiles.exp unit-tests/varname-dot-make-makefiles.mk unit-tests/varname-dot-make-meta-bailiwick.exp unit-tests/varname-dot-make-meta-bailiwick.mk unit-tests/varname-dot-make-meta-created.exp unit-tests/varname-dot-make-meta-created.mk unit-tests/varname-dot-make-meta-files.exp unit-tests/varname-dot-make-meta-files.mk unit-tests/varname-dot-make-meta-ignore_filter.exp unit-tests/varname-dot-make-meta-ignore_filter.mk unit-tests/varname-dot-make-meta-ignore_paths.exp unit-tests/varname-dot-make-meta-ignore_paths.mk unit-tests/varname-dot-make-meta-ignore_patterns.exp unit-tests/varname-dot-make-meta-ignore_patterns.mk unit-tests/varname-dot-make-meta-prefix.exp unit-tests/varname-dot-make-meta-prefix.mk unit-tests/varname-dot-make-mode.exp unit-tests/varname-dot-make-mode.mk unit-tests/varname-dot-make-path_filemon.exp unit-tests/varname-dot-make-path_filemon.mk unit-tests/varname-dot-make-pid.exp unit-tests/varname-dot-make-pid.mk unit-tests/varname-dot-make-ppid.exp unit-tests/varname-dot-make-ppid.mk unit-tests/varname-dot-make-save_dollars.exp unit-tests/varname-dot-make-save_dollars.mk unit-tests/varname-dot-makeflags.exp unit-tests/varname-dot-makeflags.mk unit-tests/varname-dot-makeoverrides.exp unit-tests/varname-dot-makeoverrides.mk unit-tests/varname-dot-newline.exp unit-tests/varname-dot-newline.mk unit-tests/varname-dot-objdir.exp unit-tests/varname-dot-objdir.mk unit-tests/varname-dot-parsedir.exp unit-tests/varname-dot-parsedir.mk unit-tests/varname-dot-parsefile.exp unit-tests/varname-dot-parsefile.mk unit-tests/varname-dot-path.exp unit-tests/varname-dot-path.mk unit-tests/varname-dot-shell.exp unit-tests/varname-dot-shell.mk unit-tests/varname-dot-suffixes.exp unit-tests/varname-dot-suffixes.mk unit-tests/varname-dot-targets.exp unit-tests/varname-dot-targets.mk unit-tests/varname-empty.exp unit-tests/varname-empty.mk unit-tests/varname-make.exp unit-tests/varname-make.mk unit-tests/varname-make_print_var_on_error-jobs.exp unit-tests/varname-make_print_var_on_error-jobs.mk unit-tests/varname-make_print_var_on_error.exp unit-tests/varname-make_print_var_on_error.mk unit-tests/varname-makefile.exp unit-tests/varname-makefile.mk unit-tests/varname-makeflags.exp unit-tests/varname-makeflags.mk unit-tests/varname-pwd.exp unit-tests/varname-pwd.mk unit-tests/varname-vpath.exp unit-tests/varname-vpath.mk unit-tests/varname.exp unit-tests/varname.mk unit-tests/varparse-dynamic.exp unit-tests/varparse-dynamic.mk unit-tests/varparse-errors.exp unit-tests/varparse-errors.mk unit-tests/varparse-mod.exp unit-tests/varparse-mod.mk unit-tests/varparse-undef-partial.exp unit-tests/varparse-undef-partial.mk util.c var.c wait.h diff --git a/contrib/bmake/Makefile b/contrib/bmake/Makefile index 65730df7e3df..939b71c5e12c 100644 --- a/contrib/bmake/Makefile +++ b/contrib/bmake/Makefile @@ -1,237 +1,266 @@ -# $Id: Makefile,v 1.127 2024/03/19 16:03:23 sjg Exp $ +# $Id: Makefile,v 1.132 2024/09/22 19:56:26 sjg Exp $ -PROG= bmake +PROG = bmake -SRCS= \ +SRCS = \ arch.c \ buf.c \ compat.c \ cond.c \ dir.c \ for.c \ hash.c \ job.c \ lst.c \ main.c \ make.c \ make_malloc.c \ meta.c \ metachar.c \ parse.c \ str.c \ suff.c \ targ.c \ trace.c \ util.c \ var.c +.MAIN: all + +MAN = ${PROG}.1 +SRCS.${MAN} = ${srcdir}/make.1 + .-include "VERSION" .-include "Makefile.inc" # this file gets generated by configure .-include "Makefile.config" .if !empty(LIBOBJS) -SRCS+= ${LIBOBJS:T:.o=.c} +SRCS += ${LIBOBJS:T:.o=.c} .endif # just in case -prefix?= /usr -srcdir?= ${.CURDIR} +prefix ?= /usr +srcdir ?= ${.PARSEDIR} +srcdir := ${srcdir} -DEFAULT_SYS_PATH?= ${prefix}/share/mk +DEFAULT_SYS_PATH ?= ${prefix}/share/mk -CPPFLAGS+= -DUSE_META -CFLAGS+= ${CPPFLAGS} -CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\" -CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE -CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} -COPTS.main.c+= "-DMAKE_VERSION=\"${_MAKE_VERSION}\"" +CPPFLAGS += -DUSE_META +CFLAGS += ${CPPFLAGS} +CFLAGS += -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\" +CFLAGS += -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE +CFLAGS += ${COPTS.${.ALLSRC:M*.c:T:u}} +COPTS.main.c += "-DMAKE_VERSION=\"${_MAKE_VERSION}\"" .for x in FORCE_MAKE_OS FORCE_MACHINE FORCE_MACHINE_ARCH .ifdef $x -COPTS.main.c+= "-D$x=\"${$x}\"" +COPTS.main.c += "-D$x=\"${$x}\"" .endif .endfor # meta mode can be useful even without filemon # should be set by now USE_FILEMON ?= no .if ${USE_FILEMON:tl} != "no" .PATH: ${srcdir}/filemon SRCS+= filemon_${USE_FILEMON}.c -COPTS.meta.c+= -DUSE_FILEMON -DUSE_FILEMON_${USE_FILEMON:tu} -COPTS.job.c+= ${COPTS.meta.c} +COPTS.meta.c += -DUSE_FILEMON -DUSE_FILEMON_${USE_FILEMON:tu} +COPTS.job.c += ${COPTS.meta.c} .if ${USE_FILEMON} == "dev" FILEMON_H ?= /usr/include/dev/filemon/filemon.h .if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" COPTS.filemon_dev.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} .endif .elif ${USE_FILEMON} == "ktrace" COPTS.filemon_ktrace.c += -Wno-error=unused-parameter .endif .endif # USE_FILEMON .PATH: ${srcdir} -.if make(obj) || make(clean) -SUBDIR+= unit-tests -.endif - # start-delete1 for bsd.after-import.mk # we skip a lot of this when building as part of FreeBSD etc. # list of OS's which are derrived from BSD4.4 -BSD44_LIST= NetBSD FreeBSD OpenBSD DragonFly MirBSD Bitrig +BSD44_LIST = NetBSD FreeBSD OpenBSD DragonFly MirBSD Bitrig # we are... OS := ${.MAKE.OS:U${uname -s:L:sh}} # are we 4.4BSD ? -isBSD44:=${BSD44_LIST:M${OS}} +isBSD44 := ${BSD44_LIST:M${OS}} .if ${isBSD44} == "" && ${OS:NCygwin:NDarwin:NLinux} != "" -MANTARGET?= cat +MANTARGET ?= cat .if ${MACHINE} == "sun386" # even I don't have one of these anymore :-) -CFLAGS+= -DPORTAR +CFLAGS += -DPORTAR .elif ${OS} != "SunOS" # assume the worst -SRCS+= sigcompat.c -CFLAGS+= -DSIGNAL_FLAGS=SA_RESTART +SRCS += sigcompat.c +CFLAGS += -DSIGNAL_FLAGS=SA_RESTART .endif .else -MANTARGET?= man +MANTARGET ?= man .endif # turn this on by default - ignored if we are root -WITH_INSTALL_AS_USER= +WITH_INSTALL_AS_USER = # suppress with -DWITHOUT_* -OPTIONS_DEFAULT_YES+= \ +OPTIONS_DEFAULT_YES += \ AUTOCONF_MK \ INSTALL_MK \ - PROG_LINK + PROG_LINK \ + TESTS \ + +OPTIONS_DEFAULT_NO += \ + GEN_MAN \ + PROG_VERSION \ -OPTIONS_DEFAULT_NO+= \ - PROG_VERSION +.if ${PROG} != "make" || ${srcdir} != ${.CURDIR} || !exists(${srcdir}/${MAN}) +WITH_GEN_MAN = 1 +.endif # process options now .include .if ${MK_PROG_VERSION} == "yes" -PROG_NAME= ${PROG}-${_MAKE_VERSION} +PROG_NAME = ${PROG}-${_MAKE_VERSION} .if ${MK_PROG_LINK} == "yes" -SYMLINKS+= ${PROG_NAME} ${BINDIR}/${PROG} +SYMLINKS += ${PROG_NAME} ${BINDIR}/${PROG} .endif .endif EXTRACT_MAN=no # end-delete1 -MAN= ${PROG}.1 -MAN1= ${MAN} +.if make(obj) || make(clean) +SUBDIR.${MK_TESTS} += unit-tests +.endif + +MAN1 = ${MAN} + +.if ${MK_GEN_MAN:Uno} == "yes" + +# we use this to generate ${MAN} +.include <${srcdir}/mk/genfiles.mk> .if ${PROG} != "make" -CLEANFILES+= my.history -.if make(${MAN}) || !exists(${srcdir}/${MAN}) +CLEANFILES += my.history +SED_CMDS.${MAN} += \ + -e '/^.Dt/s/MAKE/${PROG:tu}/' \ + -e '/^.Nm/s/make/${PROG}/' \ + +.endif + +.if ${CLEANFILES:U:Mmy.history} != "" +${MAN}: my.history my.history: @(echo ".Nm"; \ echo "is derived from NetBSD"; \ echo ".Xr make 1 ."; \ echo "It uses autoconf to facilitate portability to other platforms."; \ echo ".Pp") > $@ -.NOPATH: ${MAN} -${MAN}: make.1 my.history - @echo making $@ - @sed \ - -e '/^.Dt/s/MAKE/${PROG:tu}/' \ - -e 's/^.Nx/NetBSD/' \ - -e '/^.Nm/s/make/${PROG}/' \ +SED_CMDS.${MAN} += \ -e '/^.Sh HISTORY/rmy.history' \ - -e '/^.Sh HISTORY/,$$s,^.Nm,make,' ${srcdir}/make.1 > $@ + -e '/^.Sh HISTORY/,/BUGS/s,^.Nm,make,' \ -all beforeinstall: ${MAN} -_mfromdir=. .endif + +.if ${.MAKE.OS:N*BSD} != "" +# assume .Nx is not supported +SED_CMDS.${MAN} += -e 's/^\.Nx/NetBSD/' +.endif + +# watch out for a late change of PROG +.if !empty(SRCS.${MAN}) +.NOPATH: ${MAN} +${MAN}: ${SRCS.${MAN}} _GENFILES_USE + +all man beforeinstall: ${MAN} +_mfromdir=. .endif +.endif # MK_GEN_MAN -MANTARGET?= cat -MANDEST?= ${MANDIR}/${MANTARGET}1 +MANTARGET ?= cat +MANDEST ?= ${MANDIR}/${MANTARGET}1 .if ${MANTARGET} == "cat" -_mfromdir=${srcdir} +_mfromdir = ${srcdir} .endif .include -CPPFLAGS+= -DMAKE_NATIVE -DHAVE_CONFIG_H +CPPFLAGS += -DMAKE_NATIVE -DHAVE_CONFIG_H COPTS.var.c += -Wno-cast-qual COPTS.job.c += -Wno-format-nonliteral COPTS.parse.c += -Wno-format-nonliteral COPTS.var.c += -Wno-format-nonliteral # Force these -SHAREDIR= ${SHAREDIR.bmake:U${prefix}/share} -BINDIR= ${BINDIR.bmake:U${prefix}/bin} -MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man} +SHAREDIR = ${SHAREDIR.bmake:U${prefix}/share} +BINDIR = ${BINDIR.bmake:U${prefix}/bin} +MANDIR = ${MANDIR.bmake:U${SHAREDIR}/man} ${OBJS}: config.h # start-delete2 for bsd.after-import.mk # make sure that MAKE_VERSION gets updated. main.o: ${srcdir}/VERSION .if ${MK_AUTOCONF_MK} == "yes" CONFIGURE_DEPS += ${.CURDIR}/VERSION # we do not need or want the generated makefile CONFIGURE_ARGS += --without-makefile AUTOCONF_GENERATED_MAKEFILE = Makefile.config .include .endif SHARE_MK ?= ${SHAREDIR}/mk MKSRC = ${srcdir}/mk INSTALL ?= ${srcdir}/install-sh .if ${MK_INSTALL_MK} == "yes" install: install-mk .endif beforeinstall: test -d ${DESTDIR}${BINDIR} || ${INSTALL} -m ${DIRMODE} -d ${DESTDIR}${BINDIR} test -d ${DESTDIR}${MANDEST} || ${INSTALL} -m ${DIRMODE} -d ${DESTDIR}${MANDEST} install-mk: .if exists(${MKSRC}/install-mk) test -d ${DESTDIR}${SHARE_MK} || ${INSTALL} -m ${DIRMODE} -d ${DESTDIR}${SHARE_MK} sh ${MKSRC}/install-mk -v -m ${NONBINMODE} ${DESTDIR}${SHARE_MK} .else @echo need to unpack mk.tar.gz under ${srcdir} or set MKSRC; false .endif # end-delete2 # A simple unit-test driver to help catch regressions TEST_MAKE ?= ${.OBJDIR}/${PROG:T} accept test: .NOMETA cd ${.CURDIR}/unit-tests && \ MAKEFLAGS= ${TEST_MAKE} -r -m / ${.TARGET} ${TESTS:DTESTS=${TESTS:Q}} .if make(test) && ${MK_AUTO_OBJ} == "yes" # The test target above visits unit-tests with -r -m / # which prevents MK_AUTO_OBJ doing its job # so do it here .if defined(MAKEOBJDIRPREFIX) || ${MAKEOBJDIR:U:M*/*} != "" _utobj = ${.OBJDIR}/unit-tests .else _utobj = ${.CURDIR}/unit-tests/${MAKEOBJDIR:Uobj} .endif utobj: .NOMETA @test -d ${_utobj} && exit 0; \ echo "[Creating ${_utobj}...]"; \ umask ${OBJDIR_UMASK:U002}; \ mkdir -p ${_utobj} test: utobj .endif diff --git a/contrib/bmake/VERSION b/contrib/bmake/VERSION index f55bfabc9103..9d1a987f65d6 100644 --- a/contrib/bmake/VERSION +++ b/contrib/bmake/VERSION @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20240711 +_MAKE_VERSION=20250125 diff --git a/contrib/bmake/arch.c b/contrib/bmake/arch.c index 00c72261707f..77ac7f3c5707 100644 --- a/contrib/bmake/arch.c +++ b/contrib/bmake/arch.c @@ -1,1026 +1,1025 @@ -/* $NetBSD: arch.c,v 1.221 2024/07/07 07:50:57 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.222 2024/08/06 17:46:01 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Manipulate libraries, archives and their members. * * The first time an archive is referenced, all of its members' headers are * read and cached and the archive closed again. All cached archives are kept * on a list which is searched each time an archive member is referenced. * * The interface to this module is: * * Arch_Init Initialize this module. * * Arch_End Clean up this module. * * Arch_ParseArchive * Parse an archive specification such as * "archive.a(member1 member2)". * * Arch_Touch Alter the modification time of the archive * member described by the given node to be * the time when make was started. * * Arch_TouchLib Update the modification time of the library * described by the given node. This is special * because it also updates the modification time * of the library's table of contents. * * Arch_UpdateMTime * Find the modification time of a member of * an archive *in the archive* and place it in the * member's GNode. * * Arch_UpdateMemberMTime * Find the modification time of a member of * an archive. Called when the member doesn't * already exist. Looks in the archive for the * modification time. Returns the modification * time. * * Arch_FindLib Search for a library along a path. The * library name in the GNode should be in * -l format. * * Arch_LibOODate Decide if a library node is out-of-date. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef HAVE_AR_H #include #else struct ar_hdr { char ar_name[16]; /* name */ char ar_date[12]; /* modification time */ char ar_uid[6]; /* user id */ char ar_gid[6]; /* group id */ char ar_mode[8]; /* octal file permissions */ char ar_size[10]; /* size in bytes */ #ifndef ARFMAG #define ARFMAG "`\n" #endif char ar_fmag[2]; /* consistency check */ }; #endif #if defined(HAVE_RANLIB_H) && !(defined(__ELF__) || defined(NO_RANLIB)) #include #endif #ifdef HAVE_UTIME_H #include #endif #include "make.h" #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.221 2024/07/07 07:50:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.222 2024/08/06 17:46:01 rillig Exp $"); typedef struct List ArchList; typedef struct ListNode ArchListNode; static ArchList archives; /* The archives we've already examined */ typedef struct Arch { char *name; HashTable members; /* All the members of the archive described * by key/value pairs */ char *fnametab; /* Extended name table strings */ size_t fnamesize; /* Size of the string table */ } Arch; static FILE *ArchFindMember(const char *, const char *, struct ar_hdr *, const char *); #if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) #define SVR4ARCHIVES static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); #endif #if defined(_AIX) # define AR_NAME _ar_name.ar_name # define AR_FMAG _ar_name.ar_fmag # define SARMAG SAIAMAG # define ARMAG AIAMAG # define ARFMAG AIAFMAG #endif #ifndef AR_NAME # define AR_NAME ar_name #endif #ifndef AR_DATE # define AR_DATE ar_date #endif #ifndef AR_SIZE # define AR_SIZE ar_size #endif #ifndef AR_FMAG # define AR_FMAG ar_fmag #endif #ifndef ARMAG # define ARMAG "!\n" #endif #ifndef SARMAG # define SARMAG 8 #endif #ifdef CLEANUP static void ArchFree(Arch *a) { HashIter hi; HashIter_Init(&hi, &a->members); while (HashIter_Next(&hi)) free(hi.entry->value); free(a->name); free(a->fnametab); HashTable_Done(&a->members); free(a); } #endif /* Return "archive(member)". */ MAKE_ATTR_NOINLINE static char * FullName(const char *archive, const char *member) { Buffer buf; Buf_Init(&buf); Buf_AddStr(&buf, archive); Buf_AddStr(&buf, "("); Buf_AddStr(&buf, member); Buf_AddStr(&buf, ")"); return Buf_DoneData(&buf); } /* * Parse an archive specification such as "archive.a(member1 member2.${EXT})", * adding nodes for the expanded members to gns. If successful, advance pp * beyond the archive specification and any trailing whitespace. */ bool Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) { char *spec; /* For modifying some bytes of *pp */ const char *cp; /* Pointer into line */ GNode *gn; /* New node */ FStr lib; /* Library-part of specification */ FStr mem; /* Member-part of specification */ char saveChar; /* Ending delimiter of member-name */ bool expandLib; /* Whether the parsed lib contains * expressions that need to be expanded */ spec = *pp; lib = FStr_InitRefer(spec); expandLib = false; for (cp = lib.str; *cp != '(' && *cp != '\0';) { if (*cp == '$') { /* Expand nested expressions. */ /* XXX: This code can probably be shortened. */ const char *nested_p = cp; FStr result; bool isError; /* XXX: is expanded twice: once here and once below */ result = Var_Parse(&nested_p, scope, VARE_EVAL_DEFINED); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); if (isError) return false; expandLib = true; cp += nested_p - cp; } else cp++; } spec[cp++ - spec] = '\0'; if (expandLib) Var_Expand(&lib, scope, VARE_EVAL_DEFINED); for (;;) { /* * First skip to the start of the member's name, mark that * place and skip to the end of it (either white-space or * a close paren). */ bool doSubst = false; cpp_skip_whitespace(&cp); mem = FStr_InitRefer(cp); while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { if (*cp == '$') { /* Expand nested expressions. */ /* * XXX: This code can probably be shortened. */ FStr result; bool isError; const char *nested_p = cp; result = Var_Parse(&nested_p, scope, VARE_EVAL_DEFINED); /* TODO: handle errors */ isError = result.str == var_Error; FStr_Done(&result); if (isError) return false; doSubst = true; cp += nested_p - cp; } else { cp++; } } if (*cp == '\0') { Parse_Error(PARSE_FATAL, - "No closing parenthesis " - "in archive specification"); + "Missing ')' in archive specification"); return false; } if (cp == mem.str) break; saveChar = *cp; spec[cp - spec] = '\0'; /* * XXX: This should be taken care of intelligently by * SuffExpandChildren, both for the archive and the member * portions. */ /* * If member contains variables, try and substitute for them. * This slows down archive specs with dynamic sources, since * they are (non-)substituted three times, but we need to do * this since SuffExpandChildren calls us, otherwise we could * assume the substitutions would be taken care of later. */ if (doSubst) { char *fullName; char *p; const char *unexpandedMem = mem.str; Var_Expand(&mem, scope, VARE_EVAL_DEFINED); /* * Now form an archive spec and recurse to deal with * nested variables and multi-word variable values. */ fullName = FullName(lib.str, mem.str); p = fullName; if (strcmp(mem.str, unexpandedMem) == 0) { /* * Must contain dynamic sources, so we can't * deal with it now. Just create an ARCHV node * and let SuffExpandChildren handle it. */ gn = Targ_GetNode(fullName); gn->type |= OP_ARCHV; Lst_Append(gns, gn); } else if (!Arch_ParseArchive(&p, gns, scope)) { /* Error in nested call. */ free(fullName); /* XXX: does unexpandedMemName leak? */ return false; } free(fullName); /* XXX: does unexpandedMemName leak? */ } else if (Dir_HasWildcards(mem.str)) { StringList members = LST_INIT; SearchPath_Expand(&dirSearchPath, mem.str, &members); while (!Lst_IsEmpty(&members)) { char *member = Lst_Dequeue(&members); char *fullname = FullName(lib.str, member); free(member); gn = Targ_GetNode(fullname); free(fullname); gn->type |= OP_ARCHV; Lst_Append(gns, gn); } Lst_Done(&members); } else { char *fullname = FullName(lib.str, mem.str); gn = Targ_GetNode(fullname); free(fullname); gn->type |= OP_ARCHV; Lst_Append(gns, gn); } FStr_Done(&mem); spec[cp - spec] = saveChar; } FStr_Done(&lib); cp++; /* skip the ')' */ cpp_skip_whitespace(&cp); *pp += cp - *pp; return true; } /* * Locate a member in an archive. * * See ArchFindMember for an almost identical copy of this code. */ static struct ar_hdr * ArchStatMember(const char *archive, const char *member, bool addToCache) { -#define AR_MAX_NAME_LEN (sizeof arh.ar_name - 1) +#define AR_MAX_NAME_LEN (sizeof arh.AR_NAME - 1) FILE *arch; size_t size; /* Size of archive member */ char magic[SARMAG]; ArchListNode *ln; Arch *ar; struct ar_hdr arh; char memName[MAXPATHLEN + 1]; /* Current member name while hashing. */ member = str_basename(member); for (ln = archives.first; ln != NULL; ln = ln->next) { const Arch *a = ln->datum; if (strcmp(a->name, archive) == 0) break; } if (ln != NULL) { struct ar_hdr *hdr; ar = ln->datum; hdr = HashTable_FindValue(&ar->members, member); if (hdr != NULL) return hdr; { /* Try truncated name */ char copy[AR_MAX_NAME_LEN + 1]; size_t len = strlen(member); if (len > AR_MAX_NAME_LEN) { snprintf(copy, sizeof copy, "%s", member); hdr = HashTable_FindValue(&ar->members, copy); } return hdr; } } if (!addToCache) { /* * Since the archive is not to be cached, assume there's no * need to allocate the header, so just declare it static. */ static struct ar_hdr sarh; arch = ArchFindMember(archive, member, &sarh, "r"); if (arch == NULL) return NULL; fclose(arch); return &sarh; } arch = fopen(archive, "r"); if (arch == NULL) return NULL; if (fread(magic, SARMAG, 1, arch) != 1 || strncmp(magic, ARMAG, SARMAG) != 0) { (void)fclose(arch); return NULL; } ar = bmake_malloc(sizeof *ar); ar->name = bmake_strdup(archive); ar->fnametab = NULL; ar->fnamesize = 0; HashTable_Init(&ar->members); memName[AR_MAX_NAME_LEN] = '\0'; while (fread(&arh, sizeof arh, 1, arch) == 1) { char *nameend; if (strncmp(arh.AR_FMAG, ARFMAG, sizeof arh.AR_FMAG) != 0) goto bad_archive; arh.AR_SIZE[sizeof arh.AR_SIZE - 1] = '\0'; size = (size_t)strtol(arh.AR_SIZE, NULL, 10); memcpy(memName, arh.AR_NAME, sizeof arh.AR_NAME); nameend = memName + AR_MAX_NAME_LEN; while (nameend > memName && *nameend == ' ') nameend--; nameend[1] = '\0'; #ifdef SVR4ARCHIVES /* * svr4 names are slash-terminated. * Also svr4 extended the AR format. */ if (memName[0] == '/') { /* svr4 magic mode; handle it */ switch (ArchSVR4Entry(ar, memName, size, arch)) { case -1: /* Invalid data */ goto bad_archive; case 0: /* List of files entry */ continue; default: /* Got the entry */ break; } } else { if (nameend[0] == '/') nameend[0] = '\0'; } #endif #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && ch_isdigit(memName[sizeof AR_EFMT1 - 1])) { size_t elen = (size_t)atoi( memName + sizeof AR_EFMT1 - 1); if (elen > MAXPATHLEN) goto bad_archive; if (fread(memName, elen, 1, arch) != 1) goto bad_archive; memName[elen] = '\0'; if (fseek(arch, -(long)elen, SEEK_CUR) != 0) goto bad_archive; if (DEBUG(ARCH) || DEBUG(MAKE)) debug_printf( "ArchStatMember: " "Extended format entry for %s\n", memName); } #endif { struct ar_hdr *cached_hdr = bmake_malloc( sizeof *cached_hdr); memcpy(cached_hdr, &arh, sizeof arh); HashTable_Set(&ar->members, memName, cached_hdr); } /* Files are padded with newlines to an even-byte boundary. */ if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0) goto bad_archive; } fclose(arch); Lst_Append(&archives, ar); return HashTable_FindValue(&ar->members, member); bad_archive: fclose(arch); HashTable_Done(&ar->members); free(ar->fnametab); free(ar); return NULL; } #ifdef SVR4ARCHIVES /* * Parse an SVR4 style entry that begins with a slash. * If it is "//", then load the table of filenames. * If it is "/", then try to substitute the long file name * from offset of a table previously read. * If a table is read, the file pointer is moved to the next archive member. * * Results: * -1: Bad data in archive * 0: A table was loaded from the file * 1: Name was successfully substituted from table * 2: Name was not successfully substituted from table */ static int ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) { #define ARLONGNAMES1 "//" #define ARLONGNAMES2 "/ARFILENAMES" size_t entry; char *ptr, *eptr; if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 || strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) { if (ar->fnametab != NULL) { DEBUG0(ARCH, "Attempted to redefine an SVR4 name table\n"); return -1; } /* * This is a table of archive names, so we build one for * ourselves */ ar->fnametab = bmake_malloc(size); ar->fnamesize = size; if (fread(ar->fnametab, size, 1, arch) != 1) { DEBUG0(ARCH, "Reading an SVR4 name table failed\n"); return -1; } eptr = ar->fnametab + size; for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) if (*ptr == '/') { entry++; *ptr = '\0'; } DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n", (unsigned long)entry); return 0; } if (inout_name[1] == ' ' || inout_name[1] == '\0') return 2; entry = (size_t)strtol(&inout_name[1], &eptr, 0); if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) { DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name); return 2; } if (entry >= ar->fnamesize) { DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", inout_name, (unsigned long)ar->fnamesize); return 2; } DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]); snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); return 1; } #endif static bool ArchiveMember_HasName(const struct ar_hdr *hdr, const char *name, size_t namelen) { const size_t ar_name_len = sizeof hdr->AR_NAME; const char *ar_name = hdr->AR_NAME; if (strncmp(ar_name, name, namelen) != 0) return false; if (namelen >= ar_name_len) return namelen == ar_name_len; /* hdr->AR_NAME is space-padded to the right. */ if (ar_name[namelen] == ' ') return true; /* * In archives created by GNU binutils 2.27, the member names end * with a slash. */ if (ar_name[namelen] == '/' && ar_name[namelen + 1] == ' ') return true; return false; } /* * Load the header of an archive member. The mode is "r" for read-only * access, "r+" for read-write access. * * Upon successful return, the archive file is positioned at the start of the * member's struct ar_hdr. In case of a failure or if the member doesn't * exist, return NULL. * * See ArchStatMember for an almost identical copy of this code. */ static FILE * ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh, const char *mode) { FILE *arch; int size; /* Size of archive member */ char magic[SARMAG]; size_t len; arch = fopen(archive, mode); if (arch == NULL) return NULL; if (fread(magic, SARMAG, 1, arch) != 1 || strncmp(magic, ARMAG, SARMAG) != 0) { fclose(arch); return NULL; } /* Files are archived using their basename, not the entire path. */ member = str_basename(member); len = strlen(member); while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) { if (strncmp(out_arh->AR_FMAG, ARFMAG, sizeof out_arh->AR_FMAG) != 0) { fclose(arch); return NULL; } DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n", archive, (int)sizeof out_arh->AR_NAME, out_arh->AR_NAME, (int)sizeof out_arh->ar_date, out_arh->ar_date); if (ArchiveMember_HasName(out_arh, member, len)) { if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != 0) { fclose(arch); return NULL; } return arch; } #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(out_arh->AR_NAME, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && (ch_isdigit(out_arh->AR_NAME[sizeof AR_EFMT1 - 1]))) { size_t elen = (size_t)atoi( &out_arh->AR_NAME[sizeof AR_EFMT1 - 1]); char ename[MAXPATHLEN + 1]; if (elen > MAXPATHLEN) { fclose(arch); return NULL; } if (fread(ename, elen, 1, arch) != 1) { fclose(arch); return NULL; } ename[elen] = '\0'; if (DEBUG(ARCH) || DEBUG(MAKE)) debug_printf( "ArchFindMember: " "Extended format entry for %s\n", ename); if (strncmp(ename, member, len) == 0) { /* Found as extended name */ if (fseek(arch, -(long)(sizeof(struct ar_hdr) - elen), SEEK_CUR) != 0) { fclose(arch); return NULL; } return arch; } if (fseek(arch, -(long)elen, SEEK_CUR) != 0) { fclose(arch); return NULL; } } #endif /* Advance to the next member. */ out_arh->AR_SIZE[sizeof out_arh->AR_SIZE - 1] = '\0'; size = (int)strtol(out_arh->AR_SIZE, NULL, 10); /* Files are padded with newlines to an even-byte boundary. */ if (fseek(arch, (size + 1) & ~1L, SEEK_CUR) != 0) { fclose(arch); return NULL; } } fclose(arch); return NULL; } /* * Update the ar_date of the member of an archive, on disk but not in the * GNode. Update the st_mtime of the entire archive as well. For a library, * it may be required to run ranlib after this. */ void Arch_Touch(GNode *gn) { FILE *f; struct ar_hdr arh; f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, "r+"); if (f == NULL) return; snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); (void)fwrite(&arh, sizeof arh, 1, f); fclose(f); /* TODO: handle errors */ } /* * Given a node which represents a library, touch the thing, making sure that * the table of contents is also touched. * * Both the modification time of the library and of the RANLIBMAG member are * set to 'now'. */ void Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED) { #ifdef RANLIBMAG FILE *f; struct ar_hdr arh; /* Header describing table of contents */ struct utimbuf times; f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); if (f == NULL) return; snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); (void)fwrite(&arh, sizeof arh, 1, f); fclose(f); /* TODO: handle errors */ times.actime = times.modtime = now; utime(gn->path, ×); /* TODO: handle errors */ #endif } /* * Update the mtime of the GNode with the mtime from the archive member on * disk (or in the cache). */ void Arch_UpdateMTime(GNode *gn) { struct ar_hdr *arh; arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), true); if (arh != NULL) gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10); else gn->mtime = 0; } /* * Given a nonexistent archive member's node, update gn->mtime from its * archived form, if it exists. */ void Arch_UpdateMemberMTime(GNode *gn) { GNodeListNode *ln; for (ln = gn->parents.first; ln != NULL; ln = ln->next) { GNode *pgn = ln->datum; if (pgn->type & OP_ARCHV) { /* * If the parent is an archive specification and is * being made and its member's name matches the name * of the node we were given, record the modification * time of the parent in the child. We keep searching * its parents in case some other parent requires this * child to exist. */ const char *nameStart = strchr(pgn->name, '(') + 1; const char *nameEnd = strchr(nameStart, ')'); size_t nameLen = (size_t)(nameEnd - nameStart); if (pgn->flags.remake && strncmp(nameStart, gn->name, nameLen) == 0) { Arch_UpdateMTime(pgn); gn->mtime = pgn->mtime; } } else if (pgn->flags.remake) { /* * Something which isn't a library depends on the * existence of this target, so it needs to exist. */ gn->mtime = 0; break; } } } /* * Search for a library along the given search path. * * The node's 'path' field is set to the found path (including the * actual file name, not -l...). If the system can handle the -L * flag when linking (or we cannot find the library), we assume that * the user has placed the .LIBS variable in the final linking * command (or the linker will know where to find it) and set the * TARGET variable for this node to be the node's name. Otherwise, * we set the TARGET variable to be the full path of the library, * as returned by Dir_FindFile. */ void Arch_FindLib(GNode *gn, SearchPath *path) { char *libName = str_concat3("lib", gn->name + 2, ".a"); gn->path = Dir_FindFile(libName, path); free(libName); Var_Set(gn, TARGET, gn->name); } static bool RanlibOODate(const GNode *gn MAKE_ATTR_UNUSED) { #ifdef RANLIBMAG struct ar_hdr *arh; /* Header for __.SYMDEF */ int tocModTime; /* The table-of-contents' mod time */ arh = ArchStatMember(gn->path, RANLIBMAG, false); if (arh == NULL) { /* A library without a table of contents is out-of-date. */ if (DEBUG(ARCH) || DEBUG(MAKE)) debug_printf("no toc..."); return true; } tocModTime = (int)strtol(arh->ar_date, NULL, 10); if (DEBUG(ARCH) || DEBUG(MAKE)) debug_printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(tocModTime)); return gn->youngestChild == NULL || gn->youngestChild->mtime > tocModTime; #else return false; #endif } /* * Decide if a node with the OP_LIB attribute is out-of-date. * The library is cached if it hasn't been already. * * There are several ways for a library to be out-of-date that are not * available to ordinary files. In addition, there are ways that are open to * regular files that are not available to libraries. * * A library that is only used as a source is never considered out-of-date by * itself. This does not preclude the library's modification time from making * its parent be out-of-date. A library will be considered out-of-date for * any of these reasons, given that it is a target on a dependency line * somewhere: * * Its modification time is less than that of one of its sources * (gn->mtime < gn->youngestChild->mtime). * * Its modification time is greater than the time at which the make * began (i.e. it's been modified in the course of the make, probably * by archiving). * * The modification time of one of its sources is greater than the one * of its RANLIBMAG member (i.e. its table of contents is out-of-date). * We don't compare the archive time vs. TOC time because they can be * too close. In my opinion we should not bother with the TOC at all * since this is used by 'ar' rules that affect the data contents of the * archive, not by ranlib rules, which affect the TOC. */ bool Arch_LibOODate(GNode *gn) { if (gn->type & OP_PHONY) return true; if (!GNode_IsTarget(gn) && Lst_IsEmpty(&gn->children)) return false; if ((!Lst_IsEmpty(&gn->children) && gn->youngestChild == NULL) || (gn->mtime > now) || (gn->youngestChild != NULL && gn->mtime < gn->youngestChild->mtime)) return true; return RanlibOODate(gn); } /* Initialize the archives module. */ void Arch_Init(void) { Lst_Init(&archives); } #ifdef CLEANUP /* Clean up the archives module. */ void Arch_End(void) { ArchListNode *ln; for (ln = archives.first; ln != NULL; ln = ln->next) ArchFree(ln->datum); Lst_Done(&archives); } #endif bool Arch_IsLib(GNode *gn) { char buf[8]; int fd; bool isLib; if ((fd = open(gn->path, O_RDONLY)) == -1) return false; isLib = read(fd, buf, sizeof buf) == sizeof buf && memcmp(buf, "!\n", sizeof buf) == 0; (void)close(fd); return isLib; } diff --git a/contrib/bmake/bmake.1 b/contrib/bmake/bmake.1 index f7cc15b16c41..eef2d799b960 100644 --- a/contrib/bmake/bmake.1 +++ b/contrib/bmake/bmake.1 @@ -1,2788 +1,2801 @@ -.\" $NetBSD: make.1,v 1.378 2024/07/01 21:02:26 sjg Exp $ +.\" $NetBSD: make.1,v 1.381 2024/11/14 19:30:13 sjg Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd July 1, 2024 +.Dd November 14, 2024 .Dt BMAKE 1 .Os .Sh NAME .Nm bmake .Nd maintain program dependencies .Sh SYNOPSIS .Nm .Op Fl BeikNnqrSstWwX .Op Fl C Ar directory .Op Fl D Ar variable .Op Fl d Ar flags .Op Fl f Ar makefile .Op Fl I Ar directory .Op Fl J Ar private .Op Fl j Ar max_jobs .Op Fl m Ar directory .Op Fl T Ar file .Op Fl V Ar variable .Op Fl v Ar variable .Op Ar variable\| Ns Cm \&= Ns Ar value .Op Ar target No ... .Sh DESCRIPTION .Nm is a program designed to simplify the maintenance of other programs. Its input is a list of specifications as to the files upon which programs and other files depend. If no .Fl f Ar makefile option is given, .Nm -tries to open -.Sq Pa makefile -then -.Sq Pa Makefile +looks for the makefiles listed in +.Va .MAKE.MAKEFILE_PREFERENCE +(default +.Sq Pa makefile , +.Sq Pa Makefile ) in order to find the specifications. If the file .Sq Pa .depend exists, it is read, see .Xr mkdep 1 . .Pp This manual page is intended as a reference document only. For a more thorough description of .Nm and makefiles, please refer to .%T "PMake \- A Tutorial" (from 1993). .Pp .Nm prepends the contents of the .Ev MAKEFLAGS environment variable to the command line arguments before parsing them. .Pp The options are as follows: .Bl -tag -width Ds .It Fl B Try to be backwards compatible by executing a single shell per command and by making the sources of a dependency line in sequence. .It Fl C Ar directory Change to .Ar directory before reading the makefiles or doing anything else. If multiple .Fl C options are specified, each is interpreted relative to the previous one: .Fl C Pa / Fl C Pa etc is equivalent to .Fl C Pa /etc . .It Fl D Ar variable Define .Ar variable to be 1, in the global scope. .It Fl d Oo Cm \- Oc Ns Ar flags Turn on debugging, and specify which portions of .Nm are to print debugging information. Unless the flags are preceded by .Ql \- , they are added to the .Ev MAKEFLAGS environment variable and are passed on to any child make processes. By default, debugging information is printed to standard error, but this can be changed using the .Cm F debugging flag. The debugging output is always unbuffered; in addition, if debugging is enabled but debugging output is not directed to standard output, the standard output is line buffered. The available .Ar flags are: .Bl -tag -width Ds .It Cm A Print all possible debugging information; equivalent to specifying all of the debugging flags. .It Cm a Print debugging information about archive searching and caching. .It Cm C Print debugging information about the current working directory. .It Cm c Print debugging information about conditional evaluation. .It Cm d Print debugging information about directory searching and caching. .It Cm e Print debugging information about failed commands and targets. .It Cm F Ns Oo Cm \&+ Oc Ns Ar filename Specify where debugging output is written. This must be the last flag, because it consumes the remainder of the argument. If the character immediately after the .Cm F flag is .Ql \&+ , the file is opened in append mode; otherwise the file is overwritten. If the file name is .Ql stdout or .Ql stderr , debugging output is written to the standard output or standard error output respectively (and the .Ql \&+ option has no effect). Otherwise, the output is written to the named file. If the file name ends with .Ql .%d , the .Ql %d is replaced by the pid. .It Cm f Print debugging information about loop evaluation. .It Cm g1 Print the input graph before making anything. .It Cm g2 Print the input graph after making everything, or before exiting on error. .It Cm g3 Print the input graph before exiting on error. .It Cm h Print debugging information about hash table operations. .It Cm j Print debugging information about running multiple shells. .It Cm L Turn on lint checks. This throws errors for variable assignments that do not parse correctly, at the time of assignment, so the file and line number are available. .It Cm l Print commands in Makefiles regardless of whether or not they are prefixed by .Ql @ or other .Dq quiet flags. Also known as .Dq loud behavior. .It Cm M Print debugging information about .Dq meta mode decisions about targets. .It Cm m Print debugging information about making targets, including modification dates. .It Cm n Don't delete the temporary command scripts created when running commands. These temporary scripts are created in the directory referred to by the .Ev TMPDIR environment variable, or in .Pa /tmp if .Ev TMPDIR is unset or set to the empty string. The temporary scripts are created by .Xr mkstemp 3 , and have names of the form .Pa makeXXXXXX . .Em NOTE : This can create many files in .Ev TMPDIR or .Pa /tmp , so use with care. .It Cm p Print debugging information about makefile parsing. .It Cm s Print debugging information about suffix-transformation rules. .It Cm t Print debugging information about target list maintenance. .It Cm V Force the .Fl V option to print raw values of variables, overriding the default behavior set via .Va .MAKE.EXPAND_VARIABLES . .It Cm v Print debugging information about variable assignment and expansion. .It Cm x Run shell commands with .Fl x so the actual commands are printed as they are executed. .El .It Fl e Let environment variables override global variables within makefiles. .It Fl f Ar makefile -Specify a makefile to read instead of the default -.Pa makefile -or -.Pa Makefile . +Specify a makefile to read instead of one of the defaults listed in +.Va .MAKE.MAKEFILE_PREFERENCE . If .Ar makefile is .Ql \&- , standard input is read. +If +.Ar makefile +starts with the string +.Ql \&.../ , +.Nm +searches for the specified path in the rest of the argument +in the current directory and its parents. Multiple makefiles may be specified, and are read in the order specified. .It Fl I Ar directory Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the .Fl m option) is automatically included as part of this list. .It Fl i Ignore non-zero exit of shell commands in the makefile. Equivalent to specifying .Ql \&- before each command line in the makefile. .It Fl J Ar private This option should .Em not be specified by the user. .Pp When the .Fl j option is in use in a recursive build, this option is passed by a make to child makes to allow all the make processes in the build to cooperate to avoid overloading the system. .It Fl j Ar max_jobs Specify the maximum number of jobs that .Nm may have running at any one time. If .Ar max_jobs is a floating point number, or ends with .Ql C , then the value is multiplied by the number of CPUs reported online by .Xr sysconf 3 . The value of .Ar max_jobs is saved in .Va .MAKE.JOBS . Turns compatibility mode off, unless the .Fl B option is also specified. When compatibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the traditional one shell invocation per line. This can break traditional scripts which change directories on each command invocation and then expect to start with a fresh environment on the next line. It is more efficient to correct the scripts rather than turn backwards compatibility on. .Pp A job token pool with .Ar max_jobs tokens is used to control the total number of jobs running. Each instance of .Nm will wait for a token from the pool before running a new job. .It Fl k Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. .It Fl m Ar directory Specify a directory in which to search for .Pa sys.mk and makefiles included via the .Li \&< Ns Ar file Ns Li \&> Ns -style include statement. The .Fl m option can be used multiple times to form a search path. This path overrides the default system include path .Pa /usr/share/mk . Furthermore, the system include path is appended to the search path used for .Li \*q Ns Ar file Ns Li \*q Ns -style include statements (see the .Fl I option). The system include path can be referenced via the read-only variable .Va .SYSPATH . .Pp If a directory name in the .Fl m argument (or the .Ev MAKESYSPATH environment variable) starts with the string .Ql \&.../ , .Nm searches for the specified file or directory named in the remaining part of the argument string. The search starts with the current directory and then works upward towards the root of the file system. If the search is successful, the resulting directory replaces the .Ql \&.../ specification in the .Fl m argument. This feature allows .Nm to easily search in the current source tree for customized .Pa sys.mk files (e.g., by using .Ql \&.../mk/sys.mk as an argument). .It Fl n Display the commands that would have been executed, but do not actually execute them unless the target depends on the .Va .MAKE special source (see below) or the command is prefixed with .Sq Cm + . .It Fl N Display the commands that would have been executed, but do not actually execute any of them; useful for debugging top-level makefiles without descending into subdirectories. .It Fl q Do not execute any commands, instead exit 0 if the specified targets are up to date, and 1 otherwise. .It Fl r Do not use the built-in rules specified in the system makefile. .It Fl S Stop processing if an error is encountered. This is the default behavior and the opposite of .Fl k . .It Fl s Do not echo any commands as they are executed. Equivalent to specifying .Sq Ic @ before each command line in the makefile. .It Fl T Ar tracefile When used with the .Fl j flag, append a trace record to .Ar tracefile for each job started and completed. .It Fl t Rather than re-building a target as specified in the makefile, create it or update its modification time to make it appear up-to-date. .It Fl V Ar variable Print the value of .Ar variable . Do not build any targets. Multiple instances of this option may be specified; the variables are printed one per line, with a blank line for each null or undefined variable. The value printed is extracted from the global scope after all makefiles have been read. .Pp By default, the raw variable contents (which may include additional unexpanded variable references) are shown. If .Ar variable contains a .Ql \&$ , it is not interpreted as a variable name but rather as an expression. Its value is expanded before printing. The value is also expanded before printing if .Va .MAKE.EXPAND_VARIABLES is set to true and the .Fl dV option has not been used to override it. .Pp Note that loop-local and target-local variables, as well as values taken temporarily by global variables during makefile processing, are not accessible via this option. The .Fl dv debug mode can be used to see these at the cost of generating substantial extraneous output. .It Fl v Ar variable Like .Fl V , but all printed variables are always expanded to their complete value. The last occurrence of .Fl V or .Fl v decides whether all variables are expanded or not. .It Fl W Treat any warnings during makefile parsing as errors. .It Fl w Print entering and leaving directory messages, pre and post processing. .It Fl X Don't export variables passed on the command line to the environment individually. Variables passed on the command line are still exported via the .Ev MAKEFLAGS environment variable. This option may be useful on systems which have a small limit on the size of command arguments. .It Ar variable\| Ns Cm \&= Ns Ar value Set the value of the variable .Ar variable to .Ar value . Normally, all values passed on the command line are also exported to sub-makes in the environment. The .Fl X flag disables this behavior. Variable assignments should follow options for POSIX compatibility but no ordering is enforced. .El .Pp There are several different types of lines in a makefile: dependency specifications, shell commands, variable assignments, include statements, conditional directives, for loops, other directives, and comments. .Pp Lines may be continued from one line to the next by ending them with a backslash .Pq Ql \e . The trailing newline character and initial whitespace on the following line are compressed into a single space. .Sh FILE DEPENDENCY SPECIFICATIONS Dependency lines consist of one or more targets, an operator, and zero or more sources. This creates a relationship where the targets .Dq depend on the sources and are customarily created from them. A target is considered out of date if it does not exist, or if its modification time is less than that of any of its sources. An out-of-date target is re-created, but not until all sources have been examined and themselves re-created as needed. Three operators may be used: .Bl -tag -width flag .It Ic \&: Many dependency lines may name this target but only one may have attached shell commands. All sources named in all dependency lines are considered together, and if needed the attached shell commands are run to create or re-create the target. If .Nm is interrupted, the target is removed. .It Ic \&! The same, but the target is always re-created whether or not it is out of date. .It Ic \&:: Any dependency line may have attached shell commands, but each one is handled independently: its sources are considered and the attached shell commands are run if the target is out of date with respect to (only) those sources. Thus, different groups of the attached shell commands may be run depending on the circumstances. Furthermore, unlike .Ic \&: , for dependency lines with no sources, the attached shell commands are always run. Also unlike .Ic \&: , the target is not removed if .Nm is interrupted. .El .Pp All dependency lines mentioning a particular target must use the same operator. .Pp Targets and sources may contain the shell wildcard values .Ql \&? , .Ql * , .Ql [] , and .Ql {} . The values .Ql \&? , .Ql * , and .Ql [] may only be used as part of the final component of the target or source, and only match existing files. The value .Ql {} need not necessarily be used to describe existing files. Expansion is in directory order, not alphabetically as done in the shell. .Sh SHELL COMMANDS Each target may have associated with it one or more lines of shell commands, normally used to create the target. Each of the lines in this script .Em must be preceded by a tab. (For historical reasons, spaces are not accepted.) While targets can occur in many dependency lines if desired, by default only one of these rules may be followed by a creation script. If the .Sq Ic \&:: operator is used, however, all rules may include scripts, and the respective scripts are executed in the order found. .Pp Each line is treated as a separate shell command, unless the end of line is escaped with a backslash .Ql \e , in which case that line and the next are combined. If the first characters of the command are any combination of .Sq Ic @ , .Sq Ic + , or .Sq Ic \- , the command is treated specially. .Bl -tag -offset indent -width indent .It Ic @ causes the command not to be echoed before it is executed. .It Ic + causes the command to be executed even when .Fl n is given. This is similar to the effect of the .Va .MAKE special source, except that the effect can be limited to a single line of a script. .It Ic \- in compatibility mode causes any non-zero exit status of the command line to be ignored. .El .Pp When .Nm is run in jobs mode with .Fl j Ar max_jobs , the entire script for the target is fed to a single instance of the shell. In compatibility (non-jobs) mode, each command is run in a separate process. If the command contains any shell meta characters .Pq Ql #=|^(){};&<>*?[]:$`\e\en , it is passed to the shell; otherwise .Nm attempts direct execution. If a line starts with .Sq Ic \- and the shell has ErrCtl enabled, failure of the command line is ignored as in compatibility mode. Otherwise .Sq Ic \- affects the entire job; the script stops at the first command line that fails, but the target is not deemed to have failed. .Pp Makefiles should be written so that the mode of .Nm operation does not change their behavior. For example, any command which uses .Dq cd or .Dq chdir without the intention of changing the directory for subsequent commands should be put in parentheses so it executes in a subshell. To force the use of a single shell, escape the line breaks so as to make the whole script one command. For example: .Bd -literal -offset indent avoid-chdir-side-effects: @echo "Building $@ in $$(pwd)" @(cd ${.CURDIR} && ${MAKE} $@) @echo "Back in $$(pwd)" ensure-one-shell-regardless-of-mode: @echo "Building $@ in $$(pwd)"; \e (cd ${.CURDIR} && ${MAKE} $@); \e echo "Back in $$(pwd)" .Ed .Pp Since .Nm changes the current working directory to .Sq Va .OBJDIR before executing any targets, each child process starts with that as its current working directory. .Sh VARIABLE ASSIGNMENTS Variables in make behave much like macros in the C preprocessor. .Pp Variable assignments have the form .Sq Ar NAME Ar op Ar value , where: .Bl -tag -offset Ds -width Ds .It Ar NAME is a single-word variable name, consisting, by tradition, of all upper-case letters, .It Ar op is one of the variable assignment operators described below, and .It Ar value is interpreted according to the variable assignment operator. .El .Pp Whitespace around .Ar NAME , .Ar op and .Ar value is discarded. .Ss Variable assignment operators The five operators that assign values to variables are: .Bl -tag -width Ds .It Ic \&= Assign the value to the variable. Any previous value is overwritten. .It Ic \&+= Append the value to the current value of the variable, separating them by a single space. .It Ic \&?= Assign the value to the variable if it is not already defined. .It Ic \&:= Expand the value, then assign it to the variable. .Pp .Em NOTE : References to undefined variables are .Em not expanded. This can cause problems when variable modifiers are used. .\" See var-op-expand.mk, the section with LATER and INDIRECT. .It Ic \&!= Expand the value and pass it to the shell for execution, then assign the output from the child's standard output to the variable. Any newlines in the result are replaced with spaces. .El .Ss Expansion of variables In most contexts where variables are expanded, .Ql \&$$ expands to a single dollar sign. In other contexts (most variable modifiers, string literals in conditions), .Ql \&\e$ expands to a single dollar sign. .Pp References to variables have the form .Cm \&${ Ns Ar name Ns Oo Ns Cm \&: Ns Ar modifiers Oc Ns Cm \&} or .Cm \&$( Ns Ar name Ns Oo Ns Cm \&: Ns Ar modifiers Oc Ns Cm \&) . If the variable name consists of only a single character and the expression contains no modifiers, the surrounding curly braces or parentheses are not required. This shorter form is not recommended. .Pp If the variable name contains a dollar, the name itself is expanded first. This allows almost arbitrary variable names, however names containing dollar, braces, parentheses or whitespace are really best avoided. .Pp If the result of expanding a nested variable expression contains a dollar sign .Pq Ql \&$ , the result is subject to further expansion. .Pp Variable substitution occurs at four distinct times, depending on where the variable is being used. .Bl -enum .It Variables in dependency lines are expanded as the line is read. .It Variables in conditionals are expanded individually, but only as far as necessary to determine the result of the conditional. .It Variables in shell commands are expanded when the shell command is executed. .It .Ic .for loop index variables are expanded on each loop iteration. Note that other variables are not expanded when composing the body of a loop, so the following example code: .Bd -literal -offset indent \&.for i in 1 2 3 a+= ${i} j= ${i} b+= ${j} \&.endfor all: @echo ${a} @echo ${b} .Ed .Pp prints: .Bd -literal -offset indent 1 2 3 3 3 3 .Ed .Pp After the loop is executed: .Bl -tag -offset indent -width indent .It Va a contains .Ql ${:U1} ${:U2} ${:U3} , which expands to .Ql 1 2 3 . .It Va j contains .Ql ${:U3} , which expands to .Ql 3 . .It Va b contains .Ql ${j} ${j} ${j} , which expands to .Ql ${:U3} ${:U3} ${:U3} and further to .Ql 3 3 3 . .El .El .Ss Variable classes The four different classes of variables (in order of increasing precedence) are: .Bl -tag -width Ds .It Environment variables Variables defined as part of .Nm Ns 's environment. .It Global variables Variables defined in the makefile or in included makefiles. .It Command line variables Variables defined as part of the command line. .It Local variables Variables that are defined specific to a certain target. .El .Pp Local variables can be set on a dependency line, unless .Va .MAKE.TARGET_LOCAL_VARIABLES is set to .Ql false . The rest of the line (which already has had global variables expanded) is the variable value. For example: .Bd -literal -offset indent COMPILER_WRAPPERS= ccache distcc icecc ${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,} .Ed .Pp Only the targets .Ql ${OBJS} are impacted by that filter (in .Dq meta mode) and simply enabling/disabling any of the compiler wrappers does not render all of those targets out-of-date. .Pp .Em NOTE : target-local variable assignments behave differently in that; .Bl -tag -width Ds -offset indent .It Ic \&+= Only appends to a previous local assignment for the same target and variable. .It Ic \&:= Is redundant with respect to global variables, which have already been expanded. .El .Pp The seven built-in local variables are: .Bl -tag -width ".Va .ARCHIVE" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as .Sq Va \&> . .It Va .ARCHIVE The name of the archive file; also known as .Sq Va \&! . .It Va .IMPSRC In suffix-transformation rules, the name/path of the source from which the target is to be transformed (the .Dq implied source); also known as .Sq Va \&< . It is not defined in explicit rules. .It Va .MEMBER The name of the archive member; also known as .Sq Va % . .It Va .OODATE The list of sources for this target that were deemed out-of-date; also known as .Sq Va \&? . .It Va .PREFIX The name of the target with suffix (if declared in .Ic .SUFFIXES ) removed; also known as .Sq Va * . .It Va .TARGET The name of the target; also known as .Sq Va @ . For compatibility with other makes this is an alias for .Va .ARCHIVE in archive member rules. .El .Pp The shorter forms .Po .Sq Va \&> , .Sq Va \&! , .Sq Va \&< , .Sq Va \&% , .Sq Va \&? , .Sq Va \&* , and .Sq Va \&@ .Pc are permitted for backward compatibility with historical makefiles and legacy POSIX make and are not recommended. .Pp Variants of these variables with the punctuation followed immediately by .Ql D or .Ql F , e.g.\& .Ql $(@D) , are legacy forms equivalent to using the .Ql :H and .Ql :T modifiers. These forms are accepted for compatibility with .At V makefiles and POSIX but are not recommended. .Pp Four of the local variables may be used in sources on dependency lines because they expand to the proper value for each target on the line. These variables are .Sq Va .TARGET , .Sq Va .PREFIX , .Sq Va .ARCHIVE , and .Sq Va .MEMBER . .Ss Additional built-in variables In addition, .Nm sets or knows about the following variables: .Bl -tag .\" NB: This list is sorted case-insensitive, ignoring punctuation. .\" NB: To find all built-in variables in make's source code, .\" NB: search for Var_*, Global_*, SetVarObjdir, GetBooleanExpr, .\" NB: and the implementation of Var_SetWithFlags. .\" NB: Last synced on 2023-01-01. .It Va .ALLTARGETS The list of all targets encountered in the makefiles. If evaluated during makefile parsing, lists only those targets encountered thus far. .It Va .CURDIR A path to the directory where .Nm was executed. Refer to the description of .Sq Va PWD for more details. .It Va .ERROR_CMD Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_CWD Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_EXIT Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_META_FILE Is used in error handling in .Dq meta mode, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_TARGET Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .INCLUDEDFROMDIR The directory of the file this makefile was included from. .It Va .INCLUDEDFROMFILE The filename of the file this makefile was included from. .\" .INCLUDES is intentionally undocumented, as it is obsolete. .\" .LIBS is intentionally undocumented, as it is obsolete. .It Va MACHINE The machine hardware name, see .Xr uname 1 . .It Va MACHINE_ARCH The machine processor architecture name, see .Xr uname 1 . .It Va MAKE The name that .Nm was executed with .Pq Va argv[0] . .It Va .MAKE The same as .Va MAKE , for compatibility. The preferred variable to use is the environment variable .Ev MAKE because it is more compatible with other make variants and cannot be confused with the special target with the same name. .\" '.MAKE.cmd_filtered' is intentionally undocumented, .\" as it is an internal implementation detail. .It Va .MAKE.DEPENDFILE Names the makefile (default .Sq Pa .depend ) from which generated dependencies are read. .It Va .MAKE.DIE_QUIETLY If set to .Ql true , do not print error information at the end. .It Va .MAKE.EXPAND_VARIABLES A boolean that controls the default behavior of the .Fl V option. If true, variable values printed with .Fl V are fully expanded; if false, the raw variable contents (which may include additional unexpanded variable references) are shown. .It Va .MAKE.EXPORTED The list of variables exported by .Nm . .It Va MAKEFILE The top-level makefile that is currently read, as given in the command line. .It Va .MAKEFLAGS The environment variable .Sq Ev MAKEFLAGS may contain anything that may be specified on .Nm Ns 's command line. Anything specified on .Nm Ns 's command line is appended to the .Va .MAKEFLAGS variable, which is then added to the environment for all programs that .Nm executes. .It Va .MAKE.GID The numeric group ID of the user running .Nm . It is read-only. .It Va .MAKE.JOB.PREFIX If .Nm is run with .Fl j , the output for each target is prefixed with a token .Dl --- Ar target Li --- the first part of which can be controlled via .Va .MAKE.JOB.PREFIX . If .Va .MAKE.JOB.PREFIX is empty, no token is printed. For example, setting .Va .MAKE.JOB.PREFIX to .Ql ${.newline}---${.MAKE:T}[${.MAKE.PID}] would produce tokens like .Dl ---make[1234] Ar target Li --- making it easier to track the degree of parallelism being achieved. .It Va .MAKE.JOBS The argument to the .Fl j option. .It Va .MAKE.JOBS.C A read-only boolean that indicates whether the .Fl j option supports use of .Ql C . .It Va .MAKE.LEVEL The recursion depth of .Nm . The top-level instance of .Nm has level 0, and each child make has its parent level plus 1. This allows tests like: .Li .if ${.MAKE.LEVEL} == 0 to protect things which should only be evaluated in the top-level instance of .Nm . .It Va .MAKE.LEVEL.ENV The name of the environment variable that stores the level of nested calls to .Nm . .It Va .MAKE.MAKEFILE_PREFERENCE The ordered list of makefile names (default .Sq Pa makefile , .Sq Pa Makefile ) that .Nm looks for. .It Va .MAKE.MAKEFILES The list of makefiles read by .Nm , which is useful for tracking dependencies. Each makefile is recorded only once, regardless of the number of times read. .It Va .MAKE.META.BAILIWICK In .Dq meta mode, provides a list of prefixes which match the directories controlled by .Nm . If a file that was generated outside of .Va .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. .It Va .MAKE.META.CMP_FILTER In .Dq meta mode, it can (very rarely!) be useful to filter command lines before comparison. This variable can be set to a set of modifiers that are applied to each line of the old and new command that differ, if the filtered commands still differ, the target is considered out-of-date. .It Va .MAKE.META.CREATED In .Dq meta mode, this variable contains a list of all the meta files updated. If not empty, it can be used to trigger processing of .Va .MAKE.META.FILES . .It Va .MAKE.META.FILES In .Dq meta mode, this variable contains a list of all the meta files used (updated or not). This list can be used to process the meta files to extract dependency information. .It Va .MAKE.META.IGNORE_FILTER Provides a list of variable modifiers to apply to each pathname. Ignore if the expansion is an empty string. .It Va .MAKE.META.IGNORE_PATHS Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: .Sq Pa /dev /etc /proc /tmp /var/run /var/tmp .It Va .MAKE.META.IGNORE_PATTERNS Provides a list of patterns to match against pathnames. Ignore any that match. .It Va .MAKE.META.PREFIX Defines the message printed for each meta file updated in .Dq meta verbose mode. The default value is: .Dl Building ${.TARGET:H:tA}/${.TARGET:T} .It Va .MAKE.MODE Processed after reading all makefiles. Affects the mode that .Nm runs in. It can contain these keywords: .Bl -tag -width indent .It Cm compat Like .Fl B , puts .Nm into .Dq compat mode. .It Cm meta Puts .Nm into .Dq meta mode, where meta files are created for each target to capture the command run, the output generated, and if .Xr filemon 4 is available, the system calls which are of interest to .Nm . The captured output can be useful when diagnosing errors. .It Cm curdirOk= Ns Ar bf By default, .Nm does not create .Pa .meta files in .Sq Va .CURDIR . This can be overridden by setting .Ar bf to a value which represents true. .It Cm missing-meta= Ns Ar bf If .Ar bf is true, a missing .Pa .meta file makes the target out-of-date. .It Cm missing-filemon= Ns Ar bf If .Ar bf is true, missing filemon data makes the target out-of-date. .It Cm nofilemon Do not use .Xr filemon 4 . .It Cm env For debugging, it can be useful to include the environment in the .Pa .meta file. .It Cm verbose If in .Dq meta mode, print a clue about the target being built. This is useful if the build is otherwise running silently. The message printed is the expanded value of .Va .MAKE.META.PREFIX . .It Cm ignore-cmd Some makefiles have commands which are simply not stable. This keyword causes them to be ignored for determining whether a target is out of date in .Dq meta mode. See also .Ic .NOMETA_CMP . .It Cm silent= Ns Ar bf If .Ar bf is true, when a .meta file is created, mark the target .Ic .SILENT . .It Cm randomize-targets In both compat and parallel mode, do not make the targets in the usual order, but instead randomize their order. This mode can be used to detect undeclared dependencies between files. .El .It Va MAKEOBJDIR Used to create files in a separate directory, see .Va .OBJDIR . .It Va MAKE_OBJDIR_CHECK_WRITABLE When true, .Nm will check that .Va .OBJDIR is writable, and issue a warning if not. .It Va MAKE_DEBUG_OBJDIR_CHECK_WRITABLE When true and .Nm is warning about an unwritable .Va .OBJDIR , report the variables listed in .Va MAKE_PRINT_VAR_ON_ERROR to help debug. .It Va MAKEOBJDIRPREFIX Used to create files in a separate directory, see .Va .OBJDIR . +It should be an absolute path. .It Va .MAKE.OS The name of the operating system, see .Xr uname 1 . It is read-only. .It Va .MAKEOVERRIDES This variable is used to record the names of variables assigned to on the command line, so that they may be exported as part of .Sq Ev MAKEFLAGS . This behavior can be disabled by assigning an empty value to .Sq Va .MAKEOVERRIDES within a makefile. Extra variables can be exported from a makefile by appending their names to .Sq Va .MAKEOVERRIDES . .Sq Ev MAKEFLAGS is re-exported whenever .Sq Va .MAKEOVERRIDES is modified. .It Va .MAKE.PATH_FILEMON If .Nm was built with .Xr filemon 4 support, this is set to the path of the device node. This allows makefiles to test for this support. .It Va .MAKE.PID The process ID of .Nm . It is read-only. .It Va .MAKE.PPID The parent process ID of .Nm . It is read-only. .It Va MAKE_PRINT_VAR_ON_ERROR When .Nm stops due to an error, it sets .Sq Va .ERROR_TARGET to the name of the target that failed, .Sq Va .ERROR_EXIT to the exit status of the failed target, .Sq Va .ERROR_CMD to the commands of the failed target, and in .Dq meta mode, it also sets .Sq Va .ERROR_CWD to the .Xr getcwd 3 , and .Sq Va .ERROR_META_FILE to the path of the meta file (if any) describing the failed target. It then prints its name and the value of .Sq Va .CURDIR as well as the value of any variables named in .Sq Va MAKE_PRINT_VAR_ON_ERROR . .It Va .MAKE.SAVE_DOLLARS If true, .Ql $$ are preserved when doing .Ql := assignments. The default is false, for backwards compatibility. Set to true for compatability with other makes. If set to false, .Ql $$ becomes .Ql $ per normal evaluation rules. .It Va .MAKE.TARGET_LOCAL_VARIABLES If set to .Ql false , apparent variable assignments in dependency lines are treated as normal sources. .It Va .MAKE.UID The numeric ID of the user running .Nm . It is read-only. .\" 'MAKE_VERSION' is intentionally undocumented .\" since it is only defined in the bmake distribution, .\" but not in NetBSD's native make. .\" '.meta.%d.lcwd' is intentionally undocumented .\" since it is an internal implementation detail. .\" '.meta.%d.ldir' is intentionally undocumented .\" since it is an internal implementation detail. .\" 'MFLAGS' is intentionally undocumented .\" since it is obsolete. .It Va .newline This variable is simply assigned a newline character as its value. It is read-only. This allows expansions using the .Cm \&:@ modifier to put a newline between iterations of the loop rather than a space. For example, in case of an error, .Nm prints the variable names and their values using: .Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} .It Va .OBJDIR A path to the directory where the targets are built. Its value is determined by trying to .Xr chdir 2 to the following directories in order and using the first match: .Bl -enum .It .Cm ${MAKEOBJDIRPREFIX} Ns Cm ${.CURDIR} .Pp (Only if .Sq Ev MAKEOBJDIRPREFIX is set in the environment or on the command line.) .It .Cm ${MAKEOBJDIR} .Pp (Only if .Sq Ev MAKEOBJDIR is set in the environment or on the command line.) .It .Cm ${.CURDIR} Ns Pa /obj. Ns Cm ${MACHINE} .It .Cm ${.CURDIR} Ns Pa /obj .It .Pa /usr/obj/ Ns Cm ${.CURDIR} .It .Cm ${.CURDIR} .El .Pp Variable expansion is performed on the value before it is used, so expressions such as .Cm ${.CURDIR:S,^/usr/src,/var/obj,} may be used. This is especially useful with .Sq Ev MAKEOBJDIR . .Pp .Sq Va .OBJDIR may be modified in the makefile via the special target .Sq Ic .OBJDIR . In all cases, .Nm changes to the specified directory if it exists, and sets .Sq Va .OBJDIR and .Sq Va PWD to that directory before executing any targets. .Pp Except in the case of an explicit .Sq Ic .OBJDIR target, .Nm checks that the specified directory is writable and ignores it if not. This check can be skipped by setting the environment variable .Sq Ev MAKE_OBJDIR_CHECK_WRITABLE to .Dq no . .It Va .PARSEDIR The directory name of the current makefile being parsed. .It Va .PARSEFILE The basename of the current makefile being parsed. This variable and .Sq Va .PARSEDIR are both set only while the makefiles are being parsed. To retain their current values, assign them to a variable using assignment with expansion .Sq Cm \&:= . .It Va .PATH The space-separated list of directories that .Nm searches for files. To update this search list, use the special target .Sq Ic .PATH rather than modifying the variable directly. .It Va %POSIX Is set in POSIX mode, see the special .Ql Va .POSIX target. .\" XXX: There is no make variable named 'PWD', .\" XXX: make only reads and writes the environment variable 'PWD'. .It Va PWD Alternate path to the current directory. .Nm normally sets .Sq Va .CURDIR to the canonical path given by .Xr getcwd 3 . However, if the environment variable .Sq Ev PWD is set and gives a path to the current directory, .Nm sets .Sq Va .CURDIR to the value of .Sq Ev PWD instead. This behavior is disabled if .Sq Ev MAKEOBJDIRPREFIX is set or .Sq Ev MAKEOBJDIR contains a variable transform. .Sq Va PWD is set to the value of .Sq Va .OBJDIR for all programs which .Nm executes. .It Va .SHELL The pathname of the shell used to run target scripts. It is read-only. .It Va .SUFFIXES The list of known suffixes. It is read-only. .It Va .SYSPATH The space-separated list of directories that .Nm searches for makefiles, referred to as the system include path. To update this search list, use the special target .Sq Ic .SYSPATH rather than modifying the variable which is read-only. .It Va .TARGETS The list of targets explicitly specified on the command line, if any. .It Va VPATH The colon-separated .Pq Dq \&: list of directories that .Nm searches for files. This variable is supported for compatibility with old make programs only, use .Sq Va .PATH instead. .El .Ss Variable modifiers The general format of a variable expansion is: .Pp .Sm off .D1 Ic \&${ Ar variable\| Oo Ic \&: Ar modifier\| Oo Ic \&: No ... Oc Oc Ic \&} .Sm on .Pp Each modifier begins with a colon. To escape a colon, precede it with a backslash .Ql \e . .Pp A list of indirect modifiers can be specified via a variable, as follows: .Pp .Bd -literal -offset indent .Ar modifier_variable\^ Li \&= Ar modifier Ns Oo Ic \&: Ns No ... Oc .Sm off .Ic \&${ Ar variable Ic \&:${ Ar modifier_variable Ic \&} Oo Ic \&: No ... Oc Ic \&} .Sm on .Ed .Pp In this case, the first modifier in the .Ar modifier_variable does not start with a colon, since that colon already occurs in the referencing variable. If any of the modifiers in the .Ar modifier_variable contains a dollar sign .Pq Ql $ , these must be doubled to avoid early expansion. .Pp Some modifiers interpret the expression value as a single string, others treat the expression value as a whitespace-separated list of words. When splitting a string into words, whitespace can be escaped using double quotes, single quotes and backslashes, like in the shell. The quotes and backslashes are retained in the words. .Pp The supported modifiers are: .Bl -tag -width EEE .It Cm \&:E Replaces each word with its suffix. .It Cm \&:H Replaces each word with its dirname. .It Cm \&:M\| Ns Ar pattern Selects only those words that match .Ar pattern . The standard shell wildcard characters .Pf ( Ql * , .Ql \&? , and .Ql \&[] ) may be used. The wildcard characters may be escaped with a backslash .Pq Ql \e . As a consequence of the way values are split into words, matched, and then joined, the construct .Ql ${VAR:M*} removes all leading and trailing whitespace and normalizes the inter-word spacing to a single space. .It Cm \&:N\| Ns Ar pattern This is the opposite of .Sq Cm \&:M , selecting all words which do .Em not match .Ar pattern . .It Cm \&:O Orders the words lexicographically. .It Cm \&:On Orders the words numerically. A number followed by one of .Ql k , .Ql M or .Ql G is multiplied by the appropriate factor, which is 1024 for .Ql k , 1048576 for .Ql M , or 1073741824 for .Ql G . Both upper- and lower-case letters are accepted. .It Cm \&:Or Orders the words in reverse lexicographical order. .It Cm \&:Orn Orders the words in reverse numerical order. .It Cm \&:Ox Shuffles the words. The results are different each time you are referring to the modified variable; use the assignment with expansion .Sq Cm \&:= to prevent such behavior. For example, .Bd -literal -offset indent LIST= uno due tre quattro RANDOM_LIST= ${LIST:Ox} STATIC_RANDOM_LIST:= ${LIST:Ox} all: @echo "${RANDOM_LIST}" @echo "${RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" .Ed may produce output similar to: .Bd -literal -offset indent quattro due tre uno tre due quattro uno due uno quattro tre due uno quattro tre .Ed .It Cm \&:Q Quotes every shell meta-character in the value, so that it can be passed safely to the shell. .It Cm \&:q Quotes every shell meta-character in the value, and also doubles .Sq $ characters so that it can be passed safely through recursive invocations of .Nm . This is equivalent to .Sq Cm \&:S/\e\&$/&&/g:Q . .It Cm \&:R Replaces each word with everything but its suffix. .It Cm \&:range Ns Oo Cm = Ns Ar count Oc The value is an integer sequence representing the words of the original value, or the supplied .Ar count . .It Cm \&:gmtime Ns Oo Cm = Ns Ar timestamp Oc The value is interpreted as a format string for .Xr strftime 3 , using .Xr gmtime 3 , producing the formatted timestamp. Note: the .Ql %s format should only be used with .Sq Cm \&:localtime . If a .Ar timestamp value is not provided or is 0, the current time is used. .It Cm \&:hash Computes a 32-bit hash of the value and encodes it as 8 hex digits. .It Cm \&:localtime Ns Oo Cm = Ns Ar timestamp Oc The value is interpreted as a format string for .Xr strftime 3 , using .Xr localtime 3 , producing the formatted timestamp. If a .Ar timestamp value is not provided or is 0, the current time is used. .It Cm \&:mtime Ns Oo Cm = Ns Ar timestamp Oc Call .Xr stat 2 with each word as pathname; use .Ql st_mtime as the new value. If .Xr stat 2 fails; use .Ar timestamp or current time. If .Ar timestamp is set to .Ql error , then .Xr stat 2 failure will cause an error. .It Cm \&:tA Attempts to convert the value to an absolute path using .Xr realpath 3 . If that fails, the value is unchanged. .It Cm \&:tl Converts the value to lower-case letters. .It Cm \&:ts Ns Ar c When joining the words after a modifier that treats the value as words, the words are normally separated by a space. This modifier changes the separator to the character .Ar c . If .Ar c is omitted, no separator is used. The common escapes (including octal numeric codes) work as expected. .It Cm \&:tt Converts the first character of each word to upper-case, and the rest to lower-case letters. .It Cm \&:tu Converts the value to upper-case letters. .It Cm \&:tW Causes subsequent modifiers to treat the value as a single word (possibly containing embedded whitespace). See also .Sq Cm \&:[*] . .It Cm \&:tw Causes the value to be treated as a list of words. See also .Sq Cm \&:[@] . .Sm off .It Cm \&:S\| No \&/ Ar old_string\| No \&/ Ar new_string\| No \&/ Op Cm 1gW .Sm on Modifies the first occurrence of .Ar old_string in each word of the value, replacing it with .Ar new_string . If a .Ql g is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a .Ql 1 is appended to the last delimiter of the pattern, only the first occurrence is affected. If a .Ql W is appended to the last delimiter of the pattern, the value is treated as a single word. If .Ar old_string begins with a caret .Pq Ql ^ , .Ar old_string is anchored at the beginning of each word. If .Ar old_string ends with a dollar sign .Pq Ql \&$ , it is anchored at the end of each word. Inside .Ar new_string , an ampersand .Pq Ql & is replaced by .Ar old_string (without the anchoring .Ql ^ or .Ql \&$ ) . Any character may be used as the delimiter for the parts of the modifier string. The anchoring, ampersand and delimiter characters can be escaped with a backslash .Pq Ql \e . .Pp Both .Ar old_string and .Ar new_string may contain nested expressions. To prevent a dollar sign from starting a nested expression, escape it with a backslash. .Sm off .It Cm \&:C\| No \&/ Ar pattern\| No \&/ Ar replacement\| No \&/ Op Cm 1gW .Sm on The .Cm \&:C modifier works like the .Cm \&:S modifier except that the old and new strings, instead of being simple strings, are an extended regular expression .Ar pattern (see .Xr regex 3 ) and an .Xr ed 1 Ns \-style .Ar replacement . Normally, the first occurrence of the pattern .Ar pattern in each word of the value is substituted with .Ar replacement . The .Ql 1 modifier causes the substitution to apply to at most one word; the .Ql g modifier causes the substitution to apply to as many instances of the search pattern .Ar pattern as occur in the word or words it is found in; the .Ql W modifier causes the value to be treated as a single word (possibly containing embedded whitespace). .Pp As for the .Cm \&:S modifier, the .Ar pattern and .Ar replacement are subjected to variable expansion before being parsed as regular expressions. .It Cm \&:T Replaces each word with its last path component (basename). .It Cm \&:u Removes adjacent duplicate words (like .Xr uniq 1 ) . .Sm off .It Cm \&:\&?\| Ar true_string\| Cm \&: Ar false_string .Sm on If the variable name (not its value), when parsed as a .Cm .if conditional expression, evaluates to true, return as its value the .Ar true_string , otherwise return the .Ar false_string . Since the variable name is used as the expression, \&:\&? must be the first modifier after the variable name .No itself Ns \^\(em\^ Ns which, of course, usually contains variable expansions. A common error is trying to use expressions like .Dl ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS). To determine if any words match .Dq 42 , you need to use something like: .Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} . .It Cm :\| Ns Ar old_string\| Ns Cm = Ns Ar new_string This is the .At V style substitution. It can only be the last modifier specified, as a .Ql \&: in either .Ar old_string or .Ar new_string is treated as a regular character, not as the end of the modifier. .Pp If .Ar old_string does not contain the pattern matching character .Ql % , and the word ends with .Ar old_string or equals it, that suffix is replaced with .Ar new_string . .Pp Otherwise, the first .Ql % in .Ar old_string matches a possibly empty substring of arbitrary characters, and if the whole pattern is found in the word, the matching part is replaced with .Ar new_string , and the first occurrence of .Ql % in .Ar new_string (if any) is replaced with the substring matched by the .Ql % . .Pp Both .Ar old_string and .Ar new_string may contain nested expressions. To prevent a dollar sign from starting a nested expression, escape it with a backslash. .Sm off .It Cm \&:@ Ar varname\| Cm @ Ar string\| Cm @ .Sm on This is the loop expansion mechanism from the OSF Development Environment (ODE) make. Unlike .Cm \&.for loops, expansion occurs at the time of reference. For each word in the value, assign the word to the variable named .Ar varname and evaluate .Ar string . The ODE convention is that .Ar varname should start and end with a period, for example: .Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@} .Pp However, a single-letter variable is often more readable: .Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} .It Cm \&:_ Ns Oo Cm = Ns Ar var Oc Saves the current variable value in .Ql $_ or the named .Ar var for later reference. Example usage: .Bd -literal -offset indent M_cmpv.units = 1 1000 1000000 M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\ \\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} .Ed Here .Ql $_ is used to save the result of the .Ql :S modifier which is later referenced using the index values from .Ql :range . .It Cm \&:U\| Ns Ar newval If the variable is undefined, the optional .Ar newval (which may be empty) is the value. If the variable is defined, the existing value is returned. This is another ODE make feature. It is handy for setting per-target CFLAGS for instance: .Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} If a value is only required if the variable is undefined, use: .Dl ${VAR:D:Unewval} .It Cm \&:D\| Ns Ar newval If the variable is defined, .Ar newval (which may be empty) is the value. .It Cm \&:L The name of the variable is the value. .It Cm \&:P The path of the node which has the same name as the variable is the value. If no such node exists or its path is null, the name of the variable is used. In order for this modifier to work, the name (node) must at least have appeared on the right-hand side of a dependency. .Sm off .It Cm \&:\&! Ar cmd\| Cm \&! .Sm on The output of running .Ar cmd is the value. .It Cm \&:sh The value is run as a command, and the output becomes the new value. .It Cm \&::= Ns Ar str The variable is assigned the value .Ar str after substitution. This modifier and its variations are useful in obscure situations such as wanting to set a variable at a point where a target's shell commands are being parsed. These assignment modifiers always expand to nothing. .Pp The .Sq Cm \&:: helps avoid false matches with the .At V style .Ql \&:= modifier and since substitution always occurs, the .Ql \&::= form is vaguely appropriate. .It Cm \&::?= Ns Ar str As for .Cm \&::= but only if the variable does not already have a value. .It Cm \&::+= Ns Ar str Append .Ar str to the variable. .It Cm \&::!= Ns Ar cmd Assign the output of .Ar cmd to the variable. .It Cm \&:\&[ Ns Ar range Ns Cm \&] Selects one or more words from the value, or performs other operations related to the way in which the value is split into words. .Pp An empty value, or a value that consists entirely of white-space, is treated as a single word. For the purposes of the .Sq Cm \&:[] modifier, the words are indexed both forwards using positive integers (where index 1 represents the first word), and backwards using negative integers (where index \-1 represents the last word). .Pp The .Ar range is subjected to variable expansion, and the expanded result is then interpreted as follows: .Bl -tag -width index .\" :[n] .It Ar index Selects a single word from the value. .\" :[start..end] .It Ar start Ns Cm \&.. Ns Ar end Selects all words from .Ar start to .Ar end , inclusive. For example, .Sq Cm \&:[2..-1] selects all words from the second word to the last word. If .Ar start is greater than .Ar end , the words are output in reverse order. For example, .Sq Cm \&:[-1..1] selects all the words from last to first. If the list is already ordered, this effectively reverses the list, but it is more efficient to use .Sq Cm \&:Or instead of .Sq Cm \&:O:[-1..1] . .\" :[*] .It Cm \&* Causes subsequent modifiers to treat the value as a single word (possibly containing embedded whitespace). Analogous to the effect of .Li \&$* in Bourne shell. .\" :[0] .It 0 Means the same as .Sq Cm \&:[*] . .\" :[*] .It Cm \&@ Causes subsequent modifiers to treat the value as a sequence of words delimited by whitespace. Analogous to the effect of .Li \&$@ in Bourne shell. .\" :[#] .It Cm \&# Returns the number of words in the value. .El \" :[range] .El .Sh DIRECTIVES .Nm offers directives for including makefiles, conditionals and for loops. All these directives are identified by a line beginning with a single dot .Pq Ql \&. character, followed by the keyword of the directive, such as .Cm include or .Cm if . .Ss File inclusion Files are included with either .Cm \&.include \&< Ns Ar file Ns Cm \&> or .Cm \&.include \&\*q Ns Ar file Ns Cm \&\*q . Variables between the angle brackets or double quotes are expanded to form the file name. If angle brackets are used, the included makefile is expected to be in the system makefile directory. If double quotes are used, the including makefile's directory and any directories specified using the .Fl I option are searched before the system makefile directory. .Pp For compatibility with other make variants, .Sq Cm include Ar file No ... (without leading dot) is also accepted. .Pp If the include statement is written as .Cm .-include or as .Cm .sinclude , errors locating and/or opening include files are ignored. .Pp If the include statement is written as .Cm .dinclude , not only are errors locating and/or opening include files ignored, but stale dependencies within the included file are ignored just like in .Va .MAKE.DEPENDFILE . .Ss Exporting variables The directives for exporting and unexporting variables are: .Bl -tag -width Ds .It Ic .export Ar variable No ... Export the specified global variable. .Pp For compatibility with other make programs, .Cm export Ar variable\| Ns Cm \&= Ns Ar value (without leading dot) is also accepted. .Pp Appending a variable name to .Va .MAKE.EXPORTED is equivalent to exporting a variable. .It Ic .export-all Export all globals except for internal variables (those that start with .Ql \&. ) . This is not affected by the .Fl X flag, so should be used with caution. .It Ic .export-env Ar variable No ... The same as .Ql .export , except that the variable is not appended to .Va .MAKE.EXPORTED . This allows exporting a value to the environment which is different from that used by .Nm internally. .It Ic .export-literal Ar variable No ... The same as .Ql .export-env , except that variables in the value are not expanded. .It Ic .unexport Ar variable No ... The opposite of .Ql .export . The specified global .Ar variable is removed from .Va .MAKE.EXPORTED . If no variable list is provided, all globals are unexported, and .Va .MAKE.EXPORTED deleted. .It Ic .unexport-env Unexport all globals previously exported and clear the environment inherited from the parent. This operation causes a memory leak of the original environment, so should be used sparingly. Testing for .Va .MAKE.LEVEL being 0 would make sense. Also note that any variables which originated in the parent environment should be explicitly preserved if desired. For example: .Bd -literal -offset indent .Li .if ${.MAKE.LEVEL} == 0 PATH := ${PATH} .Li .unexport-env .Li .export PATH .Li .endif .Pp .Ed Would result in an environment containing only .Sq Ev PATH , which is the minimal useful environment. .\" TODO: Check the below sentence, environment variables don't start with '.'. Actually .Sq Va .MAKE.LEVEL is also pushed into the new environment. .El .Ss Messages The directives for printing messages to the output are: .Bl -tag -width Ds .It Ic .info Ar message The message is printed along with the name of the makefile and line number. .It Ic .warning Ar message The message prefixed by .Sq Li warning: is printed along with the name of the makefile and line number. .It Ic .error Ar message The message is printed along with the name of the makefile and line number, .Nm exits immediately. .El .Ss Conditionals The directives for conditionals are: .ds maybenot Oo Ic \&! Oc Ns .Bl -tag .It Ic .if \*[maybenot] Ar expression Op Ar operator expression No ... Test the value of an expression. .It Ic .ifdef \*[maybenot] Ar variable Op Ar operator variable No ... Test whether a variable is defined. .It Ic .ifndef \*[maybenot] Ar variable Op Ar operator variable No ... Test whether a variable is not defined. .It Ic .ifmake \*[maybenot] Ar target Op Ar operator target No ... Test the target being requested. .It Ic .ifnmake \*[maybenot] Ar target Op Ar operator target No ... Test the target being requested. .It Ic .else Reverse the sense of the last conditional. .It Ic .elif \*[maybenot] Ar expression Op Ar operator expression No ... A combination of .Sq Ic .else followed by .Sq Ic .if . .It Ic .elifdef \*[maybenot] Ar variable Op Ar operator variable No ... A combination of .Sq Ic .else followed by .Sq Ic .ifdef . .It Ic .elifndef \*[maybenot] Ar variable Op Ar operator variable No ... A combination of .Sq Ic .else followed by .Sq Ic .ifndef . .It Ic .elifmake \*[maybenot] Ar target Op Ar operator target No ... A combination of .Sq Ic .else followed by .Sq Ic .ifmake . .It Ic .elifnmake \*[maybenot] Ar target Op Ar operator target No ... A combination of .Sq Ic .else followed by .Sq Ic .ifnmake . .It Ic .endif End the body of the conditional. .El .Pp The .Ar operator may be any one of the following: .Bl -tag .It Ic \&|\&| Logical OR. .It Ic \&&& Logical AND; of higher precedence than .Sq Ic \&|\&| . .El .Pp .Nm only evaluates a conditional as far as is necessary to determine its value. Parentheses can be used to override the operator precedence. The boolean operator .Sq Ic \&! may be used to logically negate an expression, typically a function call. It is of higher precedence than .Sq Ic \&&& . .Pp The value of .Ar expression may be any of the following function call expressions: .Bl -tag .Sm off .It Ic defined Li \&( Ar varname Li \&) .Sm on Evaluates to true if the variable .Ar varname has been defined. .Sm off .It Ic make Li \&( Ar target Li \&) .Sm on Evaluates to true if the target was specified as part of .Nm Ns 's command line or was declared the default target (either implicitly or explicitly, see .Va .MAIN ) before the line containing the conditional. .Sm off .It Ic empty Li \&( Ar varname Oo Li : Ar modifiers Oc Li \&) .Sm on Evaluates to true if the expansion of the variable, after applying the modifiers, results in an empty string. .Sm off .It Ic exists Li \&( Ar pathname Li \&) .Sm on Evaluates to true if the given pathname exists. If relative, the pathname is searched for on the system search path (see .Va .PATH ) . .Sm off .It Ic target Li \&( Ar target Li \&) .Sm on Evaluates to true if the target has been defined. .Sm off .It Ic commands Li \&( Ar target Li \&) .Sm on Evaluates to true if the target has been defined and has commands associated with it. .El .Pp .Ar Expression may also be an arithmetic or string comparison. Variable expansion is performed on both sides of the comparison. If both sides are numeric and neither is enclosed in quotes, the comparison is done numerically, otherwise lexicographically. A string is interpreted as a hexadecimal integer if it is preceded by .Li 0x , otherwise it is interpreted as a decimal floating-point number; octal numbers are not supported. .Pp All comparisons may use the operators .Sq Ic \&== and .Sq Ic \&!= . Numeric comparisons may also use the operators .Sq Ic \&< , .Sq Ic \&<= , .Sq Ic \&> and .Sq Ic \&>= . .Pp If the comparison has neither a comparison operator nor a right side, the expression evaluates to true if it is nonempty and its numeric value (if any) is not zero. .Pp When .Nm is evaluating one of these conditional expressions, and it encounters a (whitespace-separated) word it doesn't recognize, either the .Dq make or .Dq defined function is applied to it, depending on the form of the conditional. If the form is .Sq Ic .ifdef , .Sq Ic .ifndef or .Sq Ic .if , the .Dq defined function is applied. Similarly, if the form is .Sq Ic .ifmake or .Sq Ic .ifnmake , the .Dq make function is applied. .Pp If the conditional evaluates to true, parsing of the makefile continues as before. If it evaluates to false, the following lines until the corresponding .Sq Ic .elif variant, .Sq Ic .else or .Sq Ic .endif are skipped. .Ss For loops For loops are typically used to apply a set of rules to a list of files. The syntax of a for loop is: .Pp .Bl -tag -compact -width Ds .It Ic \&.for Ar variable Oo Ar variable No ... Oc Ic in Ar expression .It Aq Ar make-lines .It Ic \&.endfor .El .Pp The .Ar expression is expanded and then split into words. On each iteration of the loop, one word is taken and assigned to each .Ar variable , in order, and these .Ar variables are substituted into the .Ar make-lines inside the body of the for loop. The number of words must come out even; that is, if there are three iteration variables, the number of words provided must be a multiple of three. .Pp If .Sq Ic .break is encountered within a .Cm \&.for loop, it causes early termination of the loop, otherwise a parse error. .\" TODO: Describe limitations with defined/empty. .Ss Other directives .Bl -tag -width Ds .It Ic .undef Ar variable No ... Un-define the specified global variables. Only global variables can be un-defined. .El .Sh COMMENTS Comments begin with a hash .Pq Ql \&# character, anywhere but in a shell command line, and continue to the end of an unescaped new line. .Sh SPECIAL SOURCES (ATTRIBUTES) .Bl -tag -width .IGNOREx .It Ic .EXEC Target is never out of date, but always execute commands anyway. .It Ic .IGNORE Ignore any errors from the commands associated with this target, exactly as if they all were preceded by a dash .Pq Ql \- . .\" .It Ic .INVISIBLE .\" XXX .\" .It Ic .JOIN .\" XXX .It Ic .MADE Mark all sources of this target as being up to date. .It Ic .MAKE Execute the commands associated with this target even if the .Fl n or .Fl t options were specified. Normally used to mark recursive .Nm Ns s . .It Ic .META Create a meta file for the target, even if it is flagged as .Ic .PHONY , .Ic .MAKE , or .Ic .SPECIAL . Usage in conjunction with .Ic .MAKE is the most likely case. In .Dq meta mode, the target is out-of-date if the meta file is missing. .It Ic .NOMETA Do not create a meta file for the target. Meta files are also not created for .Ic .PHONY , .Ic .MAKE , or .Ic .SPECIAL targets. .It Ic .NOMETA_CMP Ignore differences in commands when deciding if target is out of date. This is useful if the command contains a value which always changes. If the number of commands change, though, the target is still considered out of date. The same effect applies to any command line that uses the variable .Va .OODATE , which can be used for that purpose even when not otherwise needed or desired: .Bd -literal -offset indent skip-compare-for-some: @echo this is compared @echo this is not ${.OODATE:M.NOMETA_CMP} @echo this is also compared .Ed The .Cm \&:M pattern suppresses any expansion of the unwanted variable. .It Ic .NOPATH Do not search for the target in the directories specified by .Va .PATH . .It Ic .NOTMAIN Normally .Nm selects the first target it encounters as the default target to be built if no target was specified. This source prevents this target from being selected. .It Ic .OPTIONAL If a target is marked with this attribute and .Nm can't figure out how to create it, it ignores this fact and assumes the file isn't needed or already exists. .It Ic .PHONY The target does not correspond to an actual file; it is always considered to be out of date, and is not created with the .Fl t option. Suffix-transformation rules are not applied to .Ic .PHONY targets. .It Ic .PRECIOUS When .Nm is interrupted, it normally removes any partially made targets. This source prevents the target from being removed. .It Ic .RECURSIVE Synonym for .Ic .MAKE . .It Ic .SILENT Do not echo any of the commands associated with this target, exactly as if they all were preceded by an at sign .Pq Ql @ . .It Ic .USE Turn the target into .Nm Ns 's version of a macro. When the target is used as a source for another target, the other target acquires the commands, sources, and attributes (except for .Ic .USE ) of the source. If the target already has commands, the .Ic .USE target's commands are appended to them. .It Ic .USEBEFORE Like .Ic .USE , but instead of appending, prepend the .Ic .USEBEFORE target commands to the target. .It Ic .WAIT If .Ic .WAIT appears in a dependency line, the sources that precede it are made before the sources that succeed it in the line. Since the dependents of files are not made until the file itself could be made, this also stops the dependents being built unless they are needed for another branch of the dependency tree. So given: .Bd -literal x: a .WAIT b echo x a: echo a b: b1 echo b b1: echo b1 .Ed the output is always .Ql a , .Ql b1 , .Ql b , .Ql x . .Pp The ordering imposed by .Ic .WAIT is only relevant for parallel makes. .El .Sh SPECIAL TARGETS Special targets may not be included with other targets, i.e. they must be the only target specified. .Bl -tag -width .BEGINx .It Ic .BEGIN Any command lines attached to this target are executed before anything else is done. .It Ic .DEFAULT This is sort of a .Ic .USE rule for any target (that was used only as a source) that .Nm can't figure out any other way to create. Only the shell script is used. The .Va .IMPSRC variable of a target that inherits .Ic .DEFAULT Ns 's commands is set to the target's own name. .It Ic .DELETE_ON_ERROR If this target is present in the makefile, it globally causes make to delete targets whose commands fail. (By default, only targets whose commands are interrupted during execution are deleted. This is the historical behavior.) This setting can be used to help prevent half-finished or malformed targets from being left around and corrupting future rebuilds. .It Ic .END Any command lines attached to this target are executed after everything else is done successfully. .It Ic .ERROR Any command lines attached to this target are executed when another target fails. See .Va MAKE_PRINT_VAR_ON_ERROR for the variables that will be set. .It Ic .IGNORE Mark each of the sources with the .Ic .IGNORE attribute. If no sources are specified, this is the equivalent of specifying the .Fl i option. .It Ic .INTERRUPT If .Nm is interrupted, the commands for this target are executed. .It Ic .MAIN If no target is specified when .Nm is invoked, this target is built. .It Ic .MAKEFLAGS This target provides a way to specify flags for .Nm at the time when the makefiles are read. The flags are as if typed to the shell, though the .Fl f option has no effect. .\" XXX: NOT YET!!!! .\" .It Ic .NOTPARALLEL .\" The named targets are executed in non parallel mode. .\" If no targets are .\" specified, all targets are executed in non parallel mode. .It Ic .NOPATH Apply the .Ic .NOPATH attribute to any specified sources. .It Ic .NOTPARALLEL Disable parallel mode. .It Ic .NO_PARALLEL Synonym for .Ic .NOTPARALLEL , for compatibility with other pmake variants. .It Ic .NOREADONLY clear the read-only attribute from the global variables specified as sources. .It Ic .OBJDIR The source is a new value for .Sq Va .OBJDIR . If it exists, .Nm changes the current working directory to it and updates the value of .Sq Va .OBJDIR . .It Ic .ORDER In parallel mode, the named targets are made in sequence. This ordering does not add targets to the list of targets to be made. .Pp Since the dependents of a target do not get built until the target itself could be built, unless .Ql a is built by another part of the dependency graph, the following is a dependency loop: .Bd -literal \&.ORDER: b a b: a .Ed .Pp .\" XXX: NOT YET!!!! .\" .It Ic .PARALLEL .\" The named targets are executed in parallel mode. .\" If no targets are .\" specified, all targets are executed in parallel mode. .It Ic .PATH The sources are directories which are to be searched for files not found in the current directory. If no sources are specified, any previously specified directories are removed from the search path. If the source is the special .Ic .DOTLAST target, the current working directory is searched last. .It Ic .PATH. Ns Ar suffix Like .Ic .PATH but applies only to files with a particular suffix. The suffix must have been previously declared with .Ic .SUFFIXES . .It Ic .PHONY Apply the .Ic .PHONY attribute to any specified sources. .It Ic .POSIX If this is the first non-comment line in the main makefile, the variable .Va %POSIX is set to the value .Ql 1003.2 and the makefile .Ql is included if it exists, to provide POSIX-compatible default rules. If .Nm is run with the .Fl r flag, only .Ql posix.mk contributes to the default rules. .It Ic .PRECIOUS Apply the .Ic .PRECIOUS attribute to any specified sources. If no sources are specified, the .Ic .PRECIOUS attribute is applied to every target in the file. .It Ic .READONLY set the read-only attribute on the global variables specified as sources. .It Ic .SHELL Sets the shell that .Nm uses to execute commands. The sources are a set of .Ar field\| Ns Cm \&= Ns Ar value pairs. .Bl -tag -width ".Li hasErrCtls" .It Li name This is the minimal specification, used to select one of the built-in shell specs; .Li sh , .Li ksh , and .Li csh . .It Li path Specifies the absolute path to the shell. .It Li hasErrCtl Indicates whether the shell supports exit on error. .It Li check The command to turn on error checking. .It Li ignore The command to disable error checking. .It Li echo The command to turn on echoing of commands executed. .It Li quiet The command to turn off echoing of commands executed. .It Li filter The output to filter after issuing the .Li quiet command. It is typically identical to .Li quiet . .It Li errFlag The flag to pass the shell to enable error checking. .It Li echoFlag The flag to pass the shell to enable command echoing. .It Li newline The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. .El Example: .Bd -literal \&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e check="set \-e" ignore="set +e" \e echo="set \-v" quiet="set +v" filter="set +v" \e echoFlag=v errFlag=e newline="'\en'" .Ed .It Ic .SILENT Apply the .Ic .SILENT attribute to any specified sources. If no sources are specified, the .Ic .SILENT attribute is applied to every command in the file. .It Ic .STALE This target gets run when a dependency file contains stale entries, having .Va .ALLSRC set to the name of that dependency file. .It Ic .SUFFIXES Each source specifies a suffix to .Nm . If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. .Pp Example: .Bd -literal \&.SUFFIXES: .c .o \&.c.o: cc \-o ${.TARGET} \-c ${.IMPSRC} .Ed .It Ic .SYSPATH The sources are directories which are to be added to the system include path which .Nm searches for makefiles. If no sources are specified, any previously specified directories are removed from the system include path. .El .Sh ENVIRONMENT .Nm uses the following environment variables, if they exist: .Ev MACHINE , .Ev MACHINE_ARCH , .Ev MAKE , .Ev MAKEFLAGS , .Ev MAKEOBJDIR , .Ev MAKEOBJDIRPREFIX , .Ev MAKESYSPATH , .Ev PWD , and .Ev TMPDIR . .Pp .Ev MAKEOBJDIRPREFIX and .Ev MAKEOBJDIR -may only be set in the environment or on the command line to +should be set in the environment or on the command line to .Nm and not as makefile variables; see the description of .Sq Va .OBJDIR for more details. +It is possible to set these via makefile variables but unless done +very early and the +.Sq Ic .OBJDIR +target is used to reset +.Sq Va .OBJDIR , +there may be unexpected side effects. .Sh FILES .Bl -tag -width /usr/share/mk -compact .It .depend list of dependencies .It makefile first default makefile if no makefile is specified on the command line .It Makefile second default makefile if no makefile is specified on the command line .It sys.mk system makefile .It /usr/share/mk system makefile directory .El .Sh COMPATIBILITY The basic make syntax is compatible between different make variants; however the special variables, variable modifiers and conditionals are not. .Ss Older versions An incomplete list of changes in older versions of .Nm : .Pp The way that .for loop variables are substituted changed after -NetBSD 5.0 +.Nx 5.0 so that they still appear to be variable expansions. In particular this stops them being treated as syntax, and removes some obscure problems using them in .if statements. .Pp The way that parallel makes are scheduled changed in -NetBSD 4.0 +.Nx 4.0 so that .ORDER and .WAIT apply recursively to the dependent nodes. The algorithms used may change again in the future. .Ss Other make dialects Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not support most of the features of .Nm as described in this manual. Most notably: .Bl -bullet -offset indent .It The .Ic .WAIT and .Ic .ORDER declarations and most functionality pertaining to parallelization. (GNU make supports parallelization but lacks the features needed to control it effectively.) .It Directives, including for loops and conditionals and most of the forms of include files. (GNU make has its own incompatible and less powerful syntax for conditionals.) .\" The "less powerful" above means that GNU make does not have the .\" make(target), target(target) and commands(target) functions. .It All built-in variables that begin with a dot. .It Most of the special sources and targets that begin with a dot, with the notable exception of .Ic .PHONY , .Ic .PRECIOUS , and .Ic .SUFFIXES . .It Variable modifiers, except for the .Ql :old=new string substitution, which does not portably support globbing with .Ql % and historically only works on declared suffixes. .It The .Ic $> variable even in its short form; most makes support this functionality but its name varies. .El .Pp Some features are somewhat more portable, such as assignment with .Ic += , .Ic ?= , and .Ic != . The .Va .PATH functionality is based on an older feature .Ic VPATH found in GNU make and many versions of SVR4 make; however, historically its behavior is too ill-defined (and too buggy) to rely upon. .Pp The .Ic $@ and .Ic $< variables are more or less universally portable, as is the .Ic $(MAKE) variable. Basic use of suffix rules (for files only in the current directory, not trying to chain transformations together, etc.) is also reasonably portable. .Sh SEE ALSO .Xr mkdep 1 .Sh HISTORY .Nm is derived from NetBSD .Xr make 1 . It uses autoconf to facilitate portability to other platforms. .Pp A make command appeared in .At v7 . This make implementation is based on Adam de Boor's pmake program, which was written for Sprite at Berkeley. It was designed to be a parallel distributed make running jobs on different machines using a daemon called .Dq customs . .Pp Historically the target/dependency .Ic FRC has been used to FoRCe rebuilding (since the target/dependency does not exist ... unless someone creates an .Pa FRC file). .Sh BUGS The -make +.Nm syntax is difficult to parse. For instance, finding the end of a variable's use should involve scanning each of the modifiers, using the correct terminator for each field. In many places -make +.Nm just counts {} and () in order to find the end of a variable expansion. .Pp There is no way of escaping a space character in a filename. .Pp In jobs mode, when a target fails; -make +.Nm will put an error token into the job token pool. This will cause all other instances of -make +.Nm using that token pool to abort the build and exit with error code 6. Sometimes the attempt to suppress a cascade of unnecessary errors, can result in a seemingly unexplained .Ql *** Error code 6 diff --git a/contrib/bmake/bmake.cat1 b/contrib/bmake/bmake.cat1 index f46f6681320a..8a1909e684ed 100644 --- a/contrib/bmake/bmake.cat1 +++ b/contrib/bmake/bmake.cat1 @@ -1,1790 +1,1796 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) NNAAMMEE bbmmaakkee - maintain program dependencies SSYYNNOOPPSSIISS bbmmaakkee [--BBeeiikkNNnnqqrrSSssttWWwwXX] [--CC _d_i_r_e_c_t_o_r_y] [--DD _v_a_r_i_a_b_l_e] [--dd _f_l_a_g_s] [--ff _m_a_k_e_f_i_l_e] [--II _d_i_r_e_c_t_o_r_y] [--JJ _p_r_i_v_a_t_e] [--jj _m_a_x___j_o_b_s] [--mm _d_i_r_e_c_t_o_r_y] [--TT _f_i_l_e] [--VV _v_a_r_i_a_b_l_e] [--vv _v_a_r_i_a_b_l_e] [_v_a_r_i_a_b_l_e==_v_a_l_u_e] [_t_a_r_g_e_t ...] DDEESSCCRRIIPPTTIIOONN bbmmaakkee is a program designed to simplify the maintenance of other programs. Its input is a list of specifications as to the files upon which programs and other files depend. If no --ff _m_a_k_e_f_i_l_e option is - given, bbmmaakkee tries to open `_m_a_k_e_f_i_l_e' then `_M_a_k_e_f_i_l_e' in order to find - the specifications. If the file `_._d_e_p_e_n_d' exists, it is read, see - mkdep(1). + given, bbmmaakkee looks for the makefiles listed in _._M_A_K_E_._M_A_K_E_F_I_L_E___P_R_E_F_E_R_E_N_C_E + (default `_m_a_k_e_f_i_l_e', `_M_a_k_e_f_i_l_e') in order to find the specifications. If + the file `_._d_e_p_e_n_d' exists, it is read, see mkdep(1). This manual page is intended as a reference document only. For a more thorough description of bbmmaakkee and makefiles, please refer to _P_M_a_k_e _- _A _T_u_t_o_r_i_a_l (from 1993). bbmmaakkee prepends the contents of the MAKEFLAGS environment variable to the command line arguments before parsing them. The options are as follows: --BB Try to be backwards compatible by executing a single shell per command and by making the sources of a dependency line in sequence. --CC _d_i_r_e_c_t_o_r_y Change to _d_i_r_e_c_t_o_r_y before reading the makefiles or doing anything else. If multiple --CC options are specified, each is interpreted relative to the previous one: --CC _/ --CC _e_t_c is equivalent to --CC _/_e_t_c. --DD _v_a_r_i_a_b_l_e Define _v_a_r_i_a_b_l_e to be 1, in the global scope. --dd [--]_f_l_a_g_s Turn on debugging, and specify which portions of bbmmaakkee are to print debugging information. Unless the flags are preceded by `-', they are added to the MAKEFLAGS environment variable and are passed on to any child make processes. By default, debugging information is printed to standard error, but this can be changed using the FF debugging flag. The debugging output is always unbuffered; in addition, if debugging is enabled but debugging output is not directed to standard output, the standard output is line buffered. The available _f_l_a_g_s are: AA Print all possible debugging information; equivalent to specifying all of the debugging flags. aa Print debugging information about archive searching and caching. CC Print debugging information about the current working directory. cc Print debugging information about conditional evaluation. dd Print debugging information about directory searching and caching. ee Print debugging information about failed commands and targets. FF[++]_f_i_l_e_n_a_m_e Specify where debugging output is written. This must be the last flag, because it consumes the remainder of the argument. If the character immediately after the FF flag is `+', the file is opened in append mode; otherwise the file is overwritten. If the file name is `stdout' or `stderr', debugging output is written to the standard output or standard error output respectively (and the `+' option has no effect). Otherwise, the output is written to the named file. If the file name ends with `.%d', the `%d' is replaced by the pid. ff Print debugging information about loop evaluation. gg11 Print the input graph before making anything. gg22 Print the input graph after making everything, or before exiting on error. gg33 Print the input graph before exiting on error. hh Print debugging information about hash table operations. jj Print debugging information about running multiple shells. LL Turn on lint checks. This throws errors for variable assignments that do not parse correctly, at the time of assignment, so the file and line number are available. ll Print commands in Makefiles regardless of whether or not they are prefixed by `@' or other "quiet" flags. Also known as "loud" behavior. MM Print debugging information about "meta" mode decisions about targets. mm Print debugging information about making targets, including modification dates. nn Don't delete the temporary command scripts created when running commands. These temporary scripts are created in the directory referred to by the TMPDIR environment variable, or in _/_t_m_p if TMPDIR is unset or set to the empty string. The temporary scripts are created by mkstemp(3), and have names of the form _m_a_k_e_X_X_X_X_X_X. _N_O_T_E: This can create many files in TMPDIR or _/_t_m_p, so use with care. pp Print debugging information about makefile parsing. ss Print debugging information about suffix-transformation rules. tt Print debugging information about target list maintenance. VV Force the --VV option to print raw values of variables, overriding the default behavior set via _._M_A_K_E_._E_X_P_A_N_D___V_A_R_I_A_B_L_E_S. vv Print debugging information about variable assignment and expansion. xx Run shell commands with --xx so the actual commands are printed as they are executed. --ee Let environment variables override global variables within makefiles. --ff _m_a_k_e_f_i_l_e - Specify a makefile to read instead of the default _m_a_k_e_f_i_l_e or - _M_a_k_e_f_i_l_e. If _m_a_k_e_f_i_l_e is `-', standard input is read. Multiple - makefiles may be specified, and are read in the order specified. + Specify a makefile to read instead of one of the defaults listed + in _._M_A_K_E_._M_A_K_E_F_I_L_E___P_R_E_F_E_R_E_N_C_E. If _m_a_k_e_f_i_l_e is `-', standard input + is read. If _m_a_k_e_f_i_l_e starts with the string `.../', bbmmaakkee + searches for the specified path in the rest of the argument in + the current directory and its parents. Multiple makefiles may be + specified, and are read in the order specified. --II _d_i_r_e_c_t_o_r_y Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the --mm option) is automatically included as part of this list. --ii Ignore non-zero exit of shell commands in the makefile. Equivalent to specifying `-' before each command line in the makefile. --JJ _p_r_i_v_a_t_e This option should _n_o_t be specified by the user. When the --jj option is in use in a recursive build, this option is passed by a make to child makes to allow all the make processes in the build to cooperate to avoid overloading the system. --jj _m_a_x___j_o_b_s Specify the maximum number of jobs that bbmmaakkee may have running at any one time. If _m_a_x___j_o_b_s is a floating point number, or ends with `C', then the value is multiplied by the number of CPUs reported online by sysconf(3). The value of _m_a_x___j_o_b_s is saved in _._M_A_K_E_._J_O_B_S. Turns compatibility mode off, unless the --BB option is also specified. When compatibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the traditional one shell invocation per line. This can break traditional scripts which change directories on each command invocation and then expect to start with a fresh environment on the next line. It is more efficient to correct the scripts rather than turn backwards compatibility on. A job token pool with _m_a_x___j_o_b_s tokens is used to control the total number of jobs running. Each instance of bbmmaakkee will wait for a token from the pool before running a new job. --kk Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. --mm _d_i_r_e_c_t_o_r_y Specify a directory in which to search for _s_y_s_._m_k and makefiles included via the <_f_i_l_e>-style include statement. The --mm option can be used multiple times to form a search path. This path overrides the default system include path _/_u_s_r_/_s_h_a_r_e_/_m_k. Furthermore, the system include path is appended to the search path used for "_f_i_l_e"-style include statements (see the --II option). The system include path can be referenced via the read- only variable _._S_Y_S_P_A_T_H. If a directory name in the --mm argument (or the MAKESYSPATH environment variable) starts with the string `.../', bbmmaakkee searches for the specified file or directory named in the remaining part of the argument string. The search starts with the current directory and then works upward towards the root of the file system. If the search is successful, the resulting directory replaces the `.../' specification in the --mm argument. This feature allows bbmmaakkee to easily search in the current source tree for customized _s_y_s_._m_k files (e.g., by using `.../mk/sys.mk' as an argument). --nn Display the commands that would have been executed, but do not actually execute them unless the target depends on the _._M_A_K_E special source (see below) or the command is prefixed with `++'. --NN Display the commands that would have been executed, but do not actually execute any of them; useful for debugging top-level makefiles without descending into subdirectories. --qq Do not execute any commands, instead exit 0 if the specified targets are up to date, and 1 otherwise. --rr Do not use the built-in rules specified in the system makefile. --SS Stop processing if an error is encountered. This is the default behavior and the opposite of --kk. --ss Do not echo any commands as they are executed. Equivalent to specifying `@@' before each command line in the makefile. --TT _t_r_a_c_e_f_i_l_e When used with the --jj flag, append a trace record to _t_r_a_c_e_f_i_l_e for each job started and completed. --tt Rather than re-building a target as specified in the makefile, create it or update its modification time to make it appear up- to-date. --VV _v_a_r_i_a_b_l_e Print the value of _v_a_r_i_a_b_l_e. Do not build any targets. Multiple instances of this option may be specified; the variables are printed one per line, with a blank line for each null or undefined variable. The value printed is extracted from the global scope after all makefiles have been read. By default, the raw variable contents (which may include additional unexpanded variable references) are shown. If _v_a_r_i_a_b_l_e contains a `$', it is not interpreted as a variable name but rather as an expression. Its value is expanded before printing. The value is also expanded before printing if _._M_A_K_E_._E_X_P_A_N_D___V_A_R_I_A_B_L_E_S is set to true and the --ddVV option has not been used to override it. Note that loop-local and target-local variables, as well as values taken temporarily by global variables during makefile processing, are not accessible via this option. The --ddvv debug mode can be used to see these at the cost of generating substantial extraneous output. --vv _v_a_r_i_a_b_l_e Like --VV, but all printed variables are always expanded to their complete value. The last occurrence of --VV or --vv decides whether all variables are expanded or not. --WW Treat any warnings during makefile parsing as errors. --ww Print entering and leaving directory messages, pre and post processing. --XX Don't export variables passed on the command line to the environment individually. Variables passed on the command line are still exported via the MAKEFLAGS environment variable. This option may be useful on systems which have a small limit on the size of command arguments. _v_a_r_i_a_b_l_e==_v_a_l_u_e Set the value of the variable _v_a_r_i_a_b_l_e to _v_a_l_u_e. Normally, all values passed on the command line are also exported to sub-makes in the environment. The --XX flag disables this behavior. Variable assignments should follow options for POSIX compatibility but no ordering is enforced. There are several different types of lines in a makefile: dependency specifications, shell commands, variable assignments, include statements, conditional directives, for loops, other directives, and comments. Lines may be continued from one line to the next by ending them with a backslash (`\'). The trailing newline character and initial whitespace on the following line are compressed into a single space. FFIILLEE DDEEPPEENNDDEENNCCYY SSPPEECCIIFFIICCAATTIIOONNSS Dependency lines consist of one or more targets, an operator, and zero or more sources. This creates a relationship where the targets "depend" on the sources and are customarily created from them. A target is considered out of date if it does not exist, or if its modification time is less than that of any of its sources. An out-of-date target is re- created, but not until all sources have been examined and themselves re- created as needed. Three operators may be used: :: Many dependency lines may name this target but only one may have attached shell commands. All sources named in all dependency lines are considered together, and if needed the attached shell commands are run to create or re-create the target. If bbmmaakkee is interrupted, the target is removed. !! The same, but the target is always re-created whether or not it is out of date. :::: Any dependency line may have attached shell commands, but each one is handled independently: its sources are considered and the attached shell commands are run if the target is out of date with respect to (only) those sources. Thus, different groups of the attached shell commands may be run depending on the circumstances. Furthermore, unlike ::, for dependency lines with no sources, the attached shell commands are always run. Also unlike ::, the target is not removed if bbmmaakkee is interrupted. All dependency lines mentioning a particular target must use the same operator. Targets and sources may contain the shell wildcard values `?', `*', `[]', and `{}'. The values `?', `*', and `[]' may only be used as part of the final component of the target or source, and only match existing files. The value `{}' need not necessarily be used to describe existing files. Expansion is in directory order, not alphabetically as done in the shell. SSHHEELLLL CCOOMMMMAANNDDSS Each target may have associated with it one or more lines of shell commands, normally used to create the target. Each of the lines in this script _m_u_s_t be preceded by a tab. (For historical reasons, spaces are not accepted.) While targets can occur in many dependency lines if desired, by default only one of these rules may be followed by a creation script. If the `::::' operator is used, however, all rules may include scripts, and the respective scripts are executed in the order found. Each line is treated as a separate shell command, unless the end of line is escaped with a backslash `\', in which case that line and the next are combined. If the first characters of the command are any combination of `@@', `++', or `--', the command is treated specially. @@ causes the command not to be echoed before it is executed. ++ causes the command to be executed even when --nn is given. This is similar to the effect of the _._M_A_K_E special source, except that the effect can be limited to a single line of a script. -- in compatibility mode causes any non-zero exit status of the command line to be ignored. When bbmmaakkee is run in jobs mode with --jj _m_a_x___j_o_b_s, the entire script for the target is fed to a single instance of the shell. In compatibility (non-jobs) mode, each command is run in a separate process. If the command contains any shell meta characters (`#=|^(){};&<>*?[]:$`\\n'), it is passed to the shell; otherwise bbmmaakkee attempts direct execution. If a line starts with `--' and the shell has ErrCtl enabled, failure of the command line is ignored as in compatibility mode. Otherwise `--' affects the entire job; the script stops at the first command line that fails, but the target is not deemed to have failed. Makefiles should be written so that the mode of bbmmaakkee operation does not change their behavior. For example, any command which uses "cd" or "chdir" without the intention of changing the directory for subsequent commands should be put in parentheses so it executes in a subshell. To force the use of a single shell, escape the line breaks so as to make the whole script one command. For example: avoid-chdir-side-effects: @echo "Building $@ in $$(pwd)" @(cd ${.CURDIR} && ${MAKE} $@) @echo "Back in $$(pwd)" ensure-one-shell-regardless-of-mode: @echo "Building $@ in $$(pwd)"; \ (cd ${.CURDIR} && ${MAKE} $@); \ echo "Back in $$(pwd)" Since bbmmaakkee changes the current working directory to `_._O_B_J_D_I_R' before executing any targets, each child process starts with that as its current working directory. VVAARRIIAABBLLEE AASSSSIIGGNNMMEENNTTSS Variables in make behave much like macros in the C preprocessor. Variable assignments have the form `_N_A_M_E _o_p _v_a_l_u_e', where: _N_A_M_E is a single-word variable name, consisting, by tradition, of all upper-case letters, _o_p is one of the variable assignment operators described below, and _v_a_l_u_e is interpreted according to the variable assignment operator. Whitespace around _N_A_M_E, _o_p and _v_a_l_u_e is discarded. VVaarriiaabbllee aassssiiggnnmmeenntt ooppeerraattoorrss The five operators that assign values to variables are: == Assign the value to the variable. Any previous value is overwritten. ++== Append the value to the current value of the variable, separating them by a single space. ??== Assign the value to the variable if it is not already defined. ::== Expand the value, then assign it to the variable. _N_O_T_E: References to undefined variables are _n_o_t expanded. This can cause problems when variable modifiers are used. !!== Expand the value and pass it to the shell for execution, then assign the output from the child's standard output to the variable. Any newlines in the result are replaced with spaces. EExxppaannssiioonn ooff vvaarriiaabblleess In most contexts where variables are expanded, `$$' expands to a single dollar sign. In other contexts (most variable modifiers, string literals in conditions), `\$' expands to a single dollar sign. References to variables have the form $${{_n_a_m_e[::_m_o_d_i_f_i_e_r_s]}} or $$((_n_a_m_e[::_m_o_d_i_f_i_e_r_s])). If the variable name consists of only a single character and the expression contains no modifiers, the surrounding curly braces or parentheses are not required. This shorter form is not recommended. If the variable name contains a dollar, the name itself is expanded first. This allows almost arbitrary variable names, however names containing dollar, braces, parentheses or whitespace are really best avoided. If the result of expanding a nested variable expression contains a dollar sign (`$'), the result is subject to further expansion. Variable substitution occurs at four distinct times, depending on where the variable is being used. 1. Variables in dependency lines are expanded as the line is read. 2. Variables in conditionals are expanded individually, but only as far as necessary to determine the result of the conditional. 3. Variables in shell commands are expanded when the shell command is executed. 4. ..ffoorr loop index variables are expanded on each loop iteration. Note that other variables are not expanded when composing the body of a loop, so the following example code: .for i in 1 2 3 a+= ${i} j= ${i} b+= ${j} .endfor all: @echo ${a} @echo ${b} prints: 1 2 3 3 3 3 After the loop is executed: _a contains `${:U1} ${:U2} ${:U3}', which expands to `1 2 3'. _j contains `${:U3}', which expands to `3'. _b contains `${j} ${j} ${j}', which expands to `${:U3} ${:U3} ${:U3}' and further to `3 3 3'. VVaarriiaabbllee ccllaasssseess The four different classes of variables (in order of increasing precedence) are: Environment variables Variables defined as part of bbmmaakkee's environment. Global variables Variables defined in the makefile or in included makefiles. Command line variables Variables defined as part of the command line. Local variables Variables that are defined specific to a certain target. Local variables can be set on a dependency line, unless _._M_A_K_E_._T_A_R_G_E_T___L_O_C_A_L___V_A_R_I_A_B_L_E_S is set to `false'. The rest of the line (which already has had global variables expanded) is the variable value. For example: COMPILER_WRAPPERS= ccache distcc icecc ${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,} Only the targets `${OBJS}' are impacted by that filter (in "meta" mode) and simply enabling/disabling any of the compiler wrappers does not render all of those targets out-of-date. _N_O_T_E: target-local variable assignments behave differently in that; ++== Only appends to a previous local assignment for the same target and variable. ::== Is redundant with respect to global variables, which have already been expanded. The seven built-in local variables are: _._A_L_L_S_R_C The list of all sources for this target; also known as `_>'. _._A_R_C_H_I_V_E The name of the archive file; also known as `_!'. _._I_M_P_S_R_C In suffix-transformation rules, the name/path of the source from which the target is to be transformed (the "implied" source); also known as `_<'. It is not defined in explicit rules. _._M_E_M_B_E_R The name of the archive member; also known as `_%'. _._O_O_D_A_T_E The list of sources for this target that were deemed out- of-date; also known as `_?'. _._P_R_E_F_I_X The name of the target with suffix (if declared in ..SSUUFFFFIIXXEESS) removed; also known as `_*'. _._T_A_R_G_E_T The name of the target; also known as `_@'. For compatibility with other makes this is an alias for _._A_R_C_H_I_V_E in archive member rules. The shorter forms (`_>', `_!', `_<', `_%', `_?', `_*', and `_@') are permitted for backward compatibility with historical makefiles and legacy POSIX make and are not recommended. Variants of these variables with the punctuation followed immediately by `D' or `F', e.g. `$(@D)', are legacy forms equivalent to using the `:H' and `:T' modifiers. These forms are accepted for compatibility with AT&T System V UNIX makefiles and POSIX but are not recommended. Four of the local variables may be used in sources on dependency lines because they expand to the proper value for each target on the line. These variables are `_._T_A_R_G_E_T', `_._P_R_E_F_I_X', `_._A_R_C_H_I_V_E', and `_._M_E_M_B_E_R'. AAddddiittiioonnaall bbuuiilltt--iinn vvaarriiaabblleess In addition, bbmmaakkee sets or knows about the following variables: _._A_L_L_T_A_R_G_E_T_S The list of all targets encountered in the makefiles. If evaluated during makefile parsing, lists only those targets encountered thus far. _._C_U_R_D_I_R A path to the directory where bbmmaakkee was executed. Refer to the description of `_P_W_D' for more details. _._E_R_R_O_R___C_M_D Is used in error handling, see _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R. _._E_R_R_O_R___C_W_D Is used in error handling, see _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R. _._E_R_R_O_R___E_X_I_T Is used in error handling, see _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R. _._E_R_R_O_R___M_E_T_A___F_I_L_E Is used in error handling in "meta" mode, see _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R. _._E_R_R_O_R___T_A_R_G_E_T Is used in error handling, see _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R. _._I_N_C_L_U_D_E_D_F_R_O_M_D_I_R The directory of the file this makefile was included from. _._I_N_C_L_U_D_E_D_F_R_O_M_F_I_L_E The filename of the file this makefile was included from. _M_A_C_H_I_N_E The machine hardware name, see uname(1). _M_A_C_H_I_N_E___A_R_C_H The machine processor architecture name, see uname(1). _M_A_K_E The name that bbmmaakkee was executed with (_a_r_g_v_[_0_]). _._M_A_K_E The same as _M_A_K_E, for compatibility. The preferred variable to use is the environment variable MAKE because it is more compatible with other make variants and cannot be confused with the special target with the same name. _._M_A_K_E_._D_E_P_E_N_D_F_I_L_E Names the makefile (default `_._d_e_p_e_n_d') from which generated dependencies are read. _._M_A_K_E_._D_I_E___Q_U_I_E_T_L_Y If set to `true', do not print error information at the end. _._M_A_K_E_._E_X_P_A_N_D___V_A_R_I_A_B_L_E_S A boolean that controls the default behavior of the --VV option. If true, variable values printed with --VV are fully expanded; if false, the raw variable contents (which may include additional unexpanded variable references) are shown. _._M_A_K_E_._E_X_P_O_R_T_E_D The list of variables exported by bbmmaakkee. _M_A_K_E_F_I_L_E The top-level makefile that is currently read, as given in the command line. _._M_A_K_E_F_L_A_G_S The environment variable `MAKEFLAGS' may contain anything that may be specified on bbmmaakkee's command line. Anything specified on bbmmaakkee's command line is appended to the _._M_A_K_E_F_L_A_G_S variable, which is then added to the environment for all programs that bbmmaakkee executes. _._M_A_K_E_._G_I_D The numeric group ID of the user running bbmmaakkee. It is read-only. _._M_A_K_E_._J_O_B_._P_R_E_F_I_X If bbmmaakkee is run with --jj, the output for each target is prefixed with a token --- _t_a_r_g_e_t --- the first part of which can be controlled via _._M_A_K_E_._J_O_B_._P_R_E_F_I_X. If _._M_A_K_E_._J_O_B_._P_R_E_F_I_X is empty, no token is printed. For example, setting _._M_A_K_E_._J_O_B_._P_R_E_F_I_X to `${.newline}---${.MAKE:T}[${.MAKE.PID}]' would produce tokens like ---make[1234] _t_a_r_g_e_t --- making it easier to track the degree of parallelism being achieved. _._M_A_K_E_._J_O_B_S The argument to the --jj option. _._M_A_K_E_._J_O_B_S_._C A read-only boolean that indicates whether the --jj option supports use of `C'. _._M_A_K_E_._L_E_V_E_L The recursion depth of bbmmaakkee. The top-level instance of bbmmaakkee has level 0, and each child make has its parent level plus 1. This allows tests like: .if ${.MAKE.LEVEL} == 0 to protect things which should only be evaluated in the top-level instance of bbmmaakkee. _._M_A_K_E_._L_E_V_E_L_._E_N_V The name of the environment variable that stores the level of nested calls to bbmmaakkee. _._M_A_K_E_._M_A_K_E_F_I_L_E___P_R_E_F_E_R_E_N_C_E The ordered list of makefile names (default `_m_a_k_e_f_i_l_e', `_M_a_k_e_f_i_l_e') that bbmmaakkee looks for. _._M_A_K_E_._M_A_K_E_F_I_L_E_S The list of makefiles read by bbmmaakkee, which is useful for tracking dependencies. Each makefile is recorded only once, regardless of the number of times read. _._M_A_K_E_._M_E_T_A_._B_A_I_L_I_W_I_C_K In "meta" mode, provides a list of prefixes which match the directories controlled by bbmmaakkee. If a file that was generated outside of _._O_B_J_D_I_R but within said bailiwick is missing, the current target is considered out-of-date. _._M_A_K_E_._M_E_T_A_._C_M_P___F_I_L_T_E_R In "meta" mode, it can (very rarely!) be useful to filter command lines before comparison. This variable can be set to a set of modifiers that are applied to each line of the old and new command that differ, if the filtered commands still differ, the target is considered out-of-date. _._M_A_K_E_._M_E_T_A_._C_R_E_A_T_E_D In "meta" mode, this variable contains a list of all the meta files updated. If not empty, it can be used to trigger processing of _._M_A_K_E_._M_E_T_A_._F_I_L_E_S. _._M_A_K_E_._M_E_T_A_._F_I_L_E_S In "meta" mode, this variable contains a list of all the meta files used (updated or not). This list can be used to process the meta files to extract dependency information. _._M_A_K_E_._M_E_T_A_._I_G_N_O_R_E___F_I_L_T_E_R Provides a list of variable modifiers to apply to each pathname. Ignore if the expansion is an empty string. _._M_A_K_E_._M_E_T_A_._I_G_N_O_R_E___P_A_T_H_S Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: `_/_d_e_v _/_e_t_c _/_p_r_o_c _/_t_m_p _/_v_a_r_/_r_u_n _/_v_a_r_/_t_m_p' _._M_A_K_E_._M_E_T_A_._I_G_N_O_R_E___P_A_T_T_E_R_N_S Provides a list of patterns to match against pathnames. Ignore any that match. _._M_A_K_E_._M_E_T_A_._P_R_E_F_I_X Defines the message printed for each meta file updated in "meta verbose" mode. The default value is: Building ${.TARGET:H:tA}/${.TARGET:T} _._M_A_K_E_._M_O_D_E Processed after reading all makefiles. Affects the mode that bbmmaakkee runs in. It can contain these keywords: ccoommppaatt Like --BB, puts bbmmaakkee into "compat" mode. mmeettaa Puts bbmmaakkee into "meta" mode, where meta files are created for each target to capture the command run, the output generated, and if filemon(4) is available, the system calls which are of interest to bbmmaakkee. The captured output can be useful when diagnosing errors. ccuurrddiirrOOkk==_b_f By default, bbmmaakkee does not create _._m_e_t_a files in `_._C_U_R_D_I_R'. This can be overridden by setting _b_f to a value which represents true. mmiissssiinngg--mmeettaa==_b_f If _b_f is true, a missing _._m_e_t_a file makes the target out- of-date. mmiissssiinngg--ffiilleemmoonn==_b_f If _b_f is true, missing filemon data makes the target out- of-date. nnooffiilleemmoonn Do not use filemon(4). eennvv For debugging, it can be useful to include the environment in the _._m_e_t_a file. vveerrbboossee If in "meta" mode, print a clue about the target being built. This is useful if the build is otherwise running silently. The message printed is the expanded value of _._M_A_K_E_._M_E_T_A_._P_R_E_F_I_X. iiggnnoorree--ccmmdd Some makefiles have commands which are simply not stable. This keyword causes them to be ignored for determining whether a target is out of date in "meta" mode. See also ..NNOOMMEETTAA__CCMMPP. ssiilleenntt==_b_f If _b_f is true, when a .meta file is created, mark the target ..SSIILLEENNTT. rraannddoommiizzee--ttaarrggeettss In both compat and parallel mode, do not make the targets in the usual order, but instead randomize their order. This mode can be used to detect undeclared dependencies between files. _M_A_K_E_O_B_J_D_I_R Used to create files in a separate directory, see _._O_B_J_D_I_R. _M_A_K_E___O_B_J_D_I_R___C_H_E_C_K___W_R_I_T_A_B_L_E When true, bbmmaakkee will check that _._O_B_J_D_I_R is writable, and issue a warning if not. _M_A_K_E___D_E_B_U_G___O_B_J_D_I_R___C_H_E_C_K___W_R_I_T_A_B_L_E When true and bbmmaakkee is warning about an unwritable _._O_B_J_D_I_R, report the variables listed in _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R to help debug. _M_A_K_E_O_B_J_D_I_R_P_R_E_F_I_X - Used to create files in a separate directory, see _._O_B_J_D_I_R. + Used to create files in a separate directory, see _._O_B_J_D_I_R. It + should be an absolute path. _._M_A_K_E_._O_S The name of the operating system, see uname(1). It is read-only. _._M_A_K_E_O_V_E_R_R_I_D_E_S This variable is used to record the names of variables assigned to on the command line, so that they may be exported as part of `MAKEFLAGS'. This behavior can be disabled by assigning an empty value to `_._M_A_K_E_O_V_E_R_R_I_D_E_S' within a makefile. Extra variables can be exported from a makefile by appending their names to `_._M_A_K_E_O_V_E_R_R_I_D_E_S'. `MAKEFLAGS' is re-exported whenever `_._M_A_K_E_O_V_E_R_R_I_D_E_S' is modified. _._M_A_K_E_._P_A_T_H___F_I_L_E_M_O_N If bbmmaakkee was built with filemon(4) support, this is set to the path of the device node. This allows makefiles to test for this support. _._M_A_K_E_._P_I_D The process ID of bbmmaakkee. It is read-only. _._M_A_K_E_._P_P_I_D The parent process ID of bbmmaakkee. It is read-only. _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R When bbmmaakkee stops due to an error, it sets `_._E_R_R_O_R___T_A_R_G_E_T' to the name of the target that failed, `_._E_R_R_O_R___E_X_I_T' to the exit status of the failed target, `_._E_R_R_O_R___C_M_D' to the commands of the failed target, and in "meta" mode, it also sets `_._E_R_R_O_R___C_W_D' to the getcwd(3), and `_._E_R_R_O_R___M_E_T_A___F_I_L_E' to the path of the meta file (if any) describing the failed target. It then prints its name and the value of `_._C_U_R_D_I_R' as well as the value of any variables named in `_M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R'. _._M_A_K_E_._S_A_V_E___D_O_L_L_A_R_S If true, `$$' are preserved when doing `:=' assignments. The default is false, for backwards compatibility. Set to true for compatability with other makes. If set to false, `$$' becomes `$' per normal evaluation rules. _._M_A_K_E_._T_A_R_G_E_T___L_O_C_A_L___V_A_R_I_A_B_L_E_S If set to `false', apparent variable assignments in dependency lines are treated as normal sources. _._M_A_K_E_._U_I_D The numeric ID of the user running bbmmaakkee. It is read-only. _._n_e_w_l_i_n_e This variable is simply assigned a newline character as its value. It is read-only. This allows expansions using the ::@@ modifier to put a newline between iterations of the loop rather than a space. For example, in case of an error, bbmmaakkee prints the variable names and their values using: ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} _._O_B_J_D_I_R A path to the directory where the targets are built. Its value is determined by trying to chdir(2) to the following directories in order and using the first match: 1. $${{MMAAKKEEOOBBJJDDIIRRPPRREEFFIIXX}}$${{..CCUURRDDIIRR}} (Only if `MAKEOBJDIRPREFIX' is set in the environment or on the command line.) 2. $${{MMAAKKEEOOBBJJDDIIRR}} (Only if `MAKEOBJDIR' is set in the environment or on the command line.) 3. $${{..CCUURRDDIIRR}}_/_o_b_j_.$${{MMAACCHHIINNEE}} 4. $${{..CCUURRDDIIRR}}_/_o_b_j 5. _/_u_s_r_/_o_b_j_/$${{..CCUURRDDIIRR}} 6. $${{..CCUURRDDIIRR}} Variable expansion is performed on the value before it is used, so expressions such as $${{..CCUURRDDIIRR::SS,,^^//uussrr//ssrrcc,,//vvaarr//oobbjj,,}} may be used. This is especially useful with `MAKEOBJDIR'. `_._O_B_J_D_I_R' may be modified in the makefile via the special target `..OOBBJJDDIIRR'. In all cases, bbmmaakkee changes to the specified directory if it exists, and sets `_._O_B_J_D_I_R' and `_P_W_D' to that directory before executing any targets. Except in the case of an explicit `..OOBBJJDDIIRR' target, bbmmaakkee checks that the specified directory is writable and ignores it if not. This check can be skipped by setting the environment variable `MAKE_OBJDIR_CHECK_WRITABLE' to "no". _._P_A_R_S_E_D_I_R The directory name of the current makefile being parsed. _._P_A_R_S_E_F_I_L_E The basename of the current makefile being parsed. This variable and `_._P_A_R_S_E_D_I_R' are both set only while the makefiles are being parsed. To retain their current values, assign them to a variable using assignment with expansion `::=='. _._P_A_T_H The space-separated list of directories that bbmmaakkee searches for files. To update this search list, use the special target `..PPAATTHH' rather than modifying the variable directly. _%_P_O_S_I_X Is set in POSIX mode, see the special `_._P_O_S_I_X' target. _P_W_D Alternate path to the current directory. bbmmaakkee normally sets `_._C_U_R_D_I_R' to the canonical path given by getcwd(3). However, if the environment variable `PWD' is set and gives a path to the current directory, bbmmaakkee sets `_._C_U_R_D_I_R' to the value of `PWD' instead. This behavior is disabled if `MAKEOBJDIRPREFIX' is set or `MAKEOBJDIR' contains a variable transform. `_P_W_D' is set to the value of `_._O_B_J_D_I_R' for all programs which bbmmaakkee executes. _._S_H_E_L_L The pathname of the shell used to run target scripts. It is read-only. _._S_U_F_F_I_X_E_S The list of known suffixes. It is read-only. _._S_Y_S_P_A_T_H The space-separated list of directories that bbmmaakkee searches for makefiles, referred to as the system include path. To update this search list, use the special target `..SSYYSSPPAATTHH' rather than modifying the variable which is read-only. _._T_A_R_G_E_T_S The list of targets explicitly specified on the command line, if any. _V_P_A_T_H The colon-separated (":") list of directories that bbmmaakkee searches for files. This variable is supported for compatibility with old make programs only, use `_._P_A_T_H' instead. VVaarriiaabbllee mmooddiiffiieerrss The general format of a variable expansion is: $${{_v_a_r_i_a_b_l_e[::_m_o_d_i_f_i_e_r[::...]]}} Each modifier begins with a colon. To escape a colon, precede it with a backslash `\'. A list of indirect modifiers can be specified via a variable, as follows: _m_o_d_i_f_i_e_r___v_a_r_i_a_b_l_e = _m_o_d_i_f_i_e_r[::...] $${{_v_a_r_i_a_b_l_e::$${{_m_o_d_i_f_i_e_r___v_a_r_i_a_b_l_e}}[::...]}} In this case, the first modifier in the _m_o_d_i_f_i_e_r___v_a_r_i_a_b_l_e does not start with a colon, since that colon already occurs in the referencing variable. If any of the modifiers in the _m_o_d_i_f_i_e_r___v_a_r_i_a_b_l_e contains a dollar sign (`$'), these must be doubled to avoid early expansion. Some modifiers interpret the expression value as a single string, others treat the expression value as a whitespace-separated list of words. When splitting a string into words, whitespace can be escaped using double quotes, single quotes and backslashes, like in the shell. The quotes and backslashes are retained in the words. The supported modifiers are: ::EE Replaces each word with its suffix. ::HH Replaces each word with its dirname. ::MM_p_a_t_t_e_r_n Selects only those words that match _p_a_t_t_e_r_n. The standard shell wildcard characters (`*', `?', and `[]') may be used. The wildcard characters may be escaped with a backslash (`\'). As a consequence of the way values are split into words, matched, and then joined, the construct `${VAR:M*}' removes all leading and trailing whitespace and normalizes the inter-word spacing to a single space. ::NN_p_a_t_t_e_r_n This is the opposite of `::MM', selecting all words which do _n_o_t match _p_a_t_t_e_r_n. ::OO Orders the words lexicographically. ::OOnn Orders the words numerically. A number followed by one of `k', `M' or `G' is multiplied by the appropriate factor, which is 1024 for `k', 1048576 for `M', or 1073741824 for `G'. Both upper- and lower- case letters are accepted. ::OOrr Orders the words in reverse lexicographical order. ::OOrrnn Orders the words in reverse numerical order. ::OOxx Shuffles the words. The results are different each time you are referring to the modified variable; use the assignment with expansion `::==' to prevent such behavior. For example, LIST= uno due tre quattro RANDOM_LIST= ${LIST:Ox} STATIC_RANDOM_LIST:= ${LIST:Ox} all: @echo "${RANDOM_LIST}" @echo "${RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" may produce output similar to: quattro due tre uno tre due quattro uno due uno quattro tre due uno quattro tre ::QQ Quotes every shell meta-character in the value, so that it can be passed safely to the shell. ::qq Quotes every shell meta-character in the value, and also doubles `$' characters so that it can be passed safely through recursive invocations of bbmmaakkee. This is equivalent to `::SS//\\$$//&&&&//gg::QQ'. ::RR Replaces each word with everything but its suffix. ::rraannggee[==_c_o_u_n_t] The value is an integer sequence representing the words of the original value, or the supplied _c_o_u_n_t. ::ggmmttiimmee[==_t_i_m_e_s_t_a_m_p] The value is interpreted as a format string for strftime(3), using gmtime(3), producing the formatted timestamp. Note: the `%s' format should only be used with `::llooccaallttiimmee'. If a _t_i_m_e_s_t_a_m_p value is not provided or is 0, the current time is used. ::hhaasshh Computes a 32-bit hash of the value and encodes it as 8 hex digits. ::llooccaallttiimmee[==_t_i_m_e_s_t_a_m_p] The value is interpreted as a format string for strftime(3), using localtime(3), producing the formatted timestamp. If a _t_i_m_e_s_t_a_m_p value is not provided or is 0, the current time is used. ::mmttiimmee[==_t_i_m_e_s_t_a_m_p] Call stat(2) with each word as pathname; use `st_mtime' as the new value. If stat(2) fails; use _t_i_m_e_s_t_a_m_p or current time. If _t_i_m_e_s_t_a_m_p is set to `error', then stat(2) failure will cause an error. ::ttAA Attempts to convert the value to an absolute path using realpath(3). If that fails, the value is unchanged. ::ttll Converts the value to lower-case letters. ::ttss_c When joining the words after a modifier that treats the value as words, the words are normally separated by a space. This modifier changes the separator to the character _c. If _c is omitted, no separator is used. The common escapes (including octal numeric codes) work as expected. ::tttt Converts the first character of each word to upper-case, and the rest to lower-case letters. ::ttuu Converts the value to upper-case letters. ::ttWW Causes subsequent modifiers to treat the value as a single word (possibly containing embedded whitespace). See also `::[[**]]'. ::ttww Causes the value to be treated as a list of words. See also `::[[@@]]'. ::SS/_o_l_d___s_t_r_i_n_g/_n_e_w___s_t_r_i_n_g/[11ggWW] Modifies the first occurrence of _o_l_d___s_t_r_i_n_g in each word of the value, replacing it with _n_e_w___s_t_r_i_n_g. If a `g' is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a `1' is appended to the last delimiter of the pattern, only the first occurrence is affected. If a `W' is appended to the last delimiter of the pattern, the value is treated as a single word. If _o_l_d___s_t_r_i_n_g begins with a caret (`^'), _o_l_d___s_t_r_i_n_g is anchored at the beginning of each word. If _o_l_d___s_t_r_i_n_g ends with a dollar sign (`$'), it is anchored at the end of each word. Inside _n_e_w___s_t_r_i_n_g, an ampersand (`&') is replaced by _o_l_d___s_t_r_i_n_g (without the anchoring `^' or `$'). Any character may be used as the delimiter for the parts of the modifier string. The anchoring, ampersand and delimiter characters can be escaped with a backslash (`\'). Both _o_l_d___s_t_r_i_n_g and _n_e_w___s_t_r_i_n_g may contain nested expressions. To prevent a dollar sign from starting a nested expression, escape it with a backslash. ::CC/_p_a_t_t_e_r_n/_r_e_p_l_a_c_e_m_e_n_t/[11ggWW] The ::CC modifier works like the ::SS modifier except that the old and new strings, instead of being simple strings, are an extended regular expression _p_a_t_t_e_r_n (see regex(3)) and an ed(1)-style _r_e_p_l_a_c_e_m_e_n_t. Normally, the first occurrence of the pattern _p_a_t_t_e_r_n in each word of the value is substituted with _r_e_p_l_a_c_e_m_e_n_t. The `1' modifier causes the substitution to apply to at most one word; the `g' modifier causes the substitution to apply to as many instances of the search pattern _p_a_t_t_e_r_n as occur in the word or words it is found in; the `W' modifier causes the value to be treated as a single word (possibly containing embedded whitespace). As for the ::SS modifier, the _p_a_t_t_e_r_n and _r_e_p_l_a_c_e_m_e_n_t are subjected to variable expansion before being parsed as regular expressions. ::TT Replaces each word with its last path component (basename). ::uu Removes adjacent duplicate words (like uniq(1)). ::??_t_r_u_e___s_t_r_i_n_g::_f_a_l_s_e___s_t_r_i_n_g If the variable name (not its value), when parsed as a ..iiff conditional expression, evaluates to true, return as its value the _t_r_u_e___s_t_r_i_n_g, otherwise return the _f_a_l_s_e___s_t_r_i_n_g. Since the variable name is used as the expression, :? must be the first modifier after the variable name itself--which, of course, usually contains variable expansions. A common error is trying to use expressions like ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS). To determine if any words match "42", you need to use something like: ${"${NUMBERS:M42}" != "":?match:no}. ::_o_l_d___s_t_r_i_n_g==_n_e_w___s_t_r_i_n_g This is the AT&T System V UNIX style substitution. It can only be the last modifier specified, as a `:' in either _o_l_d___s_t_r_i_n_g or _n_e_w___s_t_r_i_n_g is treated as a regular character, not as the end of the modifier. If _o_l_d___s_t_r_i_n_g does not contain the pattern matching character `%', and the word ends with _o_l_d___s_t_r_i_n_g or equals it, that suffix is replaced with _n_e_w___s_t_r_i_n_g. Otherwise, the first `%' in _o_l_d___s_t_r_i_n_g matches a possibly empty substring of arbitrary characters, and if the whole pattern is found in the word, the matching part is replaced with _n_e_w___s_t_r_i_n_g, and the first occurrence of `%' in _n_e_w___s_t_r_i_n_g (if any) is replaced with the substring matched by the `%'. Both _o_l_d___s_t_r_i_n_g and _n_e_w___s_t_r_i_n_g may contain nested expressions. To prevent a dollar sign from starting a nested expression, escape it with a backslash. ::@@_v_a_r_n_a_m_e@@_s_t_r_i_n_g@@ This is the loop expansion mechanism from the OSF Development Environment (ODE) make. Unlike ..ffoorr loops, expansion occurs at the time of reference. For each word in the value, assign the word to the variable named _v_a_r_n_a_m_e and evaluate _s_t_r_i_n_g. The ODE convention is that _v_a_r_n_a_m_e should start and end with a period, for example: ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@} However, a single-letter variable is often more readable: ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} ::__[==_v_a_r] Saves the current variable value in `$_' or the named _v_a_r for later reference. Example usage: M_cmpv.units = 1 1000 1000000 M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \ \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} Here `$_' is used to save the result of the `:S' modifier which is later referenced using the index values from `:range'. ::UU_n_e_w_v_a_l If the variable is undefined, the optional _n_e_w_v_a_l (which may be empty) is the value. If the variable is defined, the existing value is returned. This is another ODE make feature. It is handy for setting per-target CFLAGS for instance: ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} If a value is only required if the variable is undefined, use: ${VAR:D:Unewval} ::DD_n_e_w_v_a_l If the variable is defined, _n_e_w_v_a_l (which may be empty) is the value. ::LL The name of the variable is the value. ::PP The path of the node which has the same name as the variable is the value. If no such node exists or its path is null, the name of the variable is used. In order for this modifier to work, the name (node) must at least have appeared on the right-hand side of a dependency. ::!!_c_m_d!! The output of running _c_m_d is the value. ::sshh The value is run as a command, and the output becomes the new value. ::::==_s_t_r The variable is assigned the value _s_t_r after substitution. This modifier and its variations are useful in obscure situations such as wanting to set a variable at a point where a target's shell commands are being parsed. These assignment modifiers always expand to nothing. The `::::' helps avoid false matches with the AT&T System V UNIX style `:=' modifier and since substitution always occurs, the `::=' form is vaguely appropriate. ::::??==_s_t_r As for ::::== but only if the variable does not already have a value. ::::++==_s_t_r Append _s_t_r to the variable. ::::!!==_c_m_d Assign the output of _c_m_d to the variable. ::[[_r_a_n_g_e]] Selects one or more words from the value, or performs other operations related to the way in which the value is split into words. An empty value, or a value that consists entirely of white-space, is treated as a single word. For the purposes of the `::[[]]' modifier, the words are indexed both forwards using positive integers (where index 1 represents the first word), and backwards using negative integers (where index -1 represents the last word). The _r_a_n_g_e is subjected to variable expansion, and the expanded result is then interpreted as follows: _i_n_d_e_x Selects a single word from the value. _s_t_a_r_t...._e_n_d Selects all words from _s_t_a_r_t to _e_n_d, inclusive. For example, `::[[22....--11]]' selects all words from the second word to the last word. If _s_t_a_r_t is greater than _e_n_d, the words are output in reverse order. For example, `::[[--11....11]]' selects all the words from last to first. If the list is already ordered, this effectively reverses the list, but it is more efficient to use `::OOrr' instead of `::OO::[[--11....11]]'. ** Causes subsequent modifiers to treat the value as a single word (possibly containing embedded whitespace). Analogous to the effect of $* in Bourne shell. 0 Means the same as `::[[**]]'. @@ Causes subsequent modifiers to treat the value as a sequence of words delimited by whitespace. Analogous to the effect of $@ in Bourne shell. ## Returns the number of words in the value. DDIIRREECCTTIIVVEESS bbmmaakkee offers directives for including makefiles, conditionals and for loops. All these directives are identified by a line beginning with a single dot (`.') character, followed by the keyword of the directive, such as iinncclluuddee or iiff. FFiillee iinncclluussiioonn Files are included with either ..iinncclluuddee <<_f_i_l_e>> or ..iinncclluuddee ""_f_i_l_e"". Variables between the angle brackets or double quotes are expanded to form the file name. If angle brackets are used, the included makefile is expected to be in the system makefile directory. If double quotes are used, the including makefile's directory and any directories specified using the --II option are searched before the system makefile directory. For compatibility with other make variants, `iinncclluuddee _f_i_l_e ...' (without leading dot) is also accepted. If the include statement is written as ..--iinncclluuddee or as ..ssiinncclluuddee, errors locating and/or opening include files are ignored. If the include statement is written as ..ddiinncclluuddee, not only are errors locating and/or opening include files ignored, but stale dependencies within the included file are ignored just like in _._M_A_K_E_._D_E_P_E_N_D_F_I_L_E. EExxppoorrttiinngg vvaarriiaabblleess The directives for exporting and unexporting variables are: ..eexxppoorrtt _v_a_r_i_a_b_l_e ... Export the specified global variable. For compatibility with other make programs, eexxppoorrtt _v_a_r_i_a_b_l_e==_v_a_l_u_e (without leading dot) is also accepted. Appending a variable name to _._M_A_K_E_._E_X_P_O_R_T_E_D is equivalent to exporting a variable. ..eexxppoorrtt--aallll Export all globals except for internal variables (those that start with `.'). This is not affected by the --XX flag, so should be used with caution. ..eexxppoorrtt--eennvv _v_a_r_i_a_b_l_e ... The same as `.export', except that the variable is not appended to _._M_A_K_E_._E_X_P_O_R_T_E_D. This allows exporting a value to the environment which is different from that used by bbmmaakkee internally. ..eexxppoorrtt--lliitteerraall _v_a_r_i_a_b_l_e ... The same as `.export-env', except that variables in the value are not expanded. ..uunneexxppoorrtt _v_a_r_i_a_b_l_e ... The opposite of `.export'. The specified global _v_a_r_i_a_b_l_e is removed from _._M_A_K_E_._E_X_P_O_R_T_E_D. If no variable list is provided, all globals are unexported, and _._M_A_K_E_._E_X_P_O_R_T_E_D deleted. ..uunneexxppoorrtt--eennvv Unexport all globals previously exported and clear the environment inherited from the parent. This operation causes a memory leak of the original environment, so should be used sparingly. Testing for _._M_A_K_E_._L_E_V_E_L being 0 would make sense. Also note that any variables which originated in the parent environment should be explicitly preserved if desired. For example: .if ${.MAKE.LEVEL} == 0 PATH := ${PATH} .unexport-env .export PATH .endif Would result in an environment containing only `PATH', which is the minimal useful environment. Actually `_._M_A_K_E_._L_E_V_E_L' is also pushed into the new environment. MMeessssaaggeess The directives for printing messages to the output are: ..iinnffoo _m_e_s_s_a_g_e The message is printed along with the name of the makefile and line number. ..wwaarrnniinngg _m_e_s_s_a_g_e The message prefixed by `warning:' is printed along with the name of the makefile and line number. ..eerrrroorr _m_e_s_s_a_g_e The message is printed along with the name of the makefile and line number, bbmmaakkee exits immediately. CCoonnddiittiioonnaallss The directives for conditionals are: ..iiff [!!]_e_x_p_r_e_s_s_i_o_n [_o_p_e_r_a_t_o_r _e_x_p_r_e_s_s_i_o_n ...] Test the value of an expression. ..iiffddeeff [!!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e ...] Test whether a variable is defined. ..iiffnnddeeff [!!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e ...] Test whether a variable is not defined. ..iiffmmaakkee [!!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t ...] Test the target being requested. ..iiffnnmmaakkee [!!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t ...] Test the target being requested. ..eellssee Reverse the sense of the last conditional. ..eelliiff [!!]_e_x_p_r_e_s_s_i_o_n [_o_p_e_r_a_t_o_r _e_x_p_r_e_s_s_i_o_n ...] A combination of `..eellssee' followed by `..iiff'. ..eelliiffddeeff [!!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e ...] A combination of `..eellssee' followed by `..iiffddeeff'. ..eelliiffnnddeeff [!!]_v_a_r_i_a_b_l_e [_o_p_e_r_a_t_o_r _v_a_r_i_a_b_l_e ...] A combination of `..eellssee' followed by `..iiffnnddeeff'. ..eelliiffmmaakkee [!!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t ...] A combination of `..eellssee' followed by `..iiffmmaakkee'. ..eelliiffnnmmaakkee [!!]_t_a_r_g_e_t [_o_p_e_r_a_t_o_r _t_a_r_g_e_t ...] A combination of `..eellssee' followed by `..iiffnnmmaakkee'. ..eennddiiff End the body of the conditional. The _o_p_e_r_a_t_o_r may be any one of the following: |||| Logical OR. &&&& Logical AND; of higher precedence than `||||'. bbmmaakkee only evaluates a conditional as far as is necessary to determine its value. Parentheses can be used to override the operator precedence. The boolean operator `!!' may be used to logically negate an expression, typically a function call. It is of higher precedence than `&&&&'. The value of _e_x_p_r_e_s_s_i_o_n may be any of the following function call expressions: ddeeffiinneedd(_v_a_r_n_a_m_e) Evaluates to true if the variable _v_a_r_n_a_m_e has been defined. mmaakkee(_t_a_r_g_e_t) Evaluates to true if the target was specified as part of bbmmaakkee's command line or was declared the default target (either implicitly or explicitly, see _._M_A_I_N) before the line containing the conditional. eemmppttyy(_v_a_r_n_a_m_e[:_m_o_d_i_f_i_e_r_s]) Evaluates to true if the expansion of the variable, after applying the modifiers, results in an empty string. eexxiissttss(_p_a_t_h_n_a_m_e) Evaluates to true if the given pathname exists. If relative, the pathname is searched for on the system search path (see _._P_A_T_H). ttaarrggeett(_t_a_r_g_e_t) Evaluates to true if the target has been defined. ccoommmmaannddss(_t_a_r_g_e_t) Evaluates to true if the target has been defined and has commands associated with it. _E_x_p_r_e_s_s_i_o_n may also be an arithmetic or string comparison. Variable expansion is performed on both sides of the comparison. If both sides are numeric and neither is enclosed in quotes, the comparison is done numerically, otherwise lexicographically. A string is interpreted as a hexadecimal integer if it is preceded by 0x, otherwise it is interpreted as a decimal floating-point number; octal numbers are not supported. All comparisons may use the operators `====' and `!!=='. Numeric comparisons may also use the operators `<<', `<<==', `>>' and `>>=='. If the comparison has neither a comparison operator nor a right side, the expression evaluates to true if it is nonempty and its numeric value (if any) is not zero. When bbmmaakkee is evaluating one of these conditional expressions, and it encounters a (whitespace-separated) word it doesn't recognize, either the "make" or "defined" function is applied to it, depending on the form of the conditional. If the form is `..iiffddeeff', `..iiffnnddeeff' or `..iiff', the "defined" function is applied. Similarly, if the form is `..iiffmmaakkee' or `..iiffnnmmaakkee', the "make" function is applied. If the conditional evaluates to true, parsing of the makefile continues as before. If it evaluates to false, the following lines until the corresponding `..eelliiff' variant, `..eellssee' or `..eennddiiff' are skipped. FFoorr llooooppss For loops are typically used to apply a set of rules to a list of files. The syntax of a for loop is: ..ffoorr _v_a_r_i_a_b_l_e [_v_a_r_i_a_b_l_e ...] iinn _e_x_p_r_e_s_s_i_o_n <_m_a_k_e_-_l_i_n_e_s> ..eennddffoorr The _e_x_p_r_e_s_s_i_o_n is expanded and then split into words. On each iteration of the loop, one word is taken and assigned to each _v_a_r_i_a_b_l_e, in order, and these _v_a_r_i_a_b_l_e_s are substituted into the _m_a_k_e_-_l_i_n_e_s inside the body of the for loop. The number of words must come out even; that is, if there are three iteration variables, the number of words provided must be a multiple of three. If `..bbrreeaakk' is encountered within a ..ffoorr loop, it causes early termination of the loop, otherwise a parse error. OOtthheerr ddiirreeccttiivveess ..uunnddeeff _v_a_r_i_a_b_l_e ... Un-define the specified global variables. Only global variables can be un-defined. CCOOMMMMEENNTTSS Comments begin with a hash (`#') character, anywhere but in a shell command line, and continue to the end of an unescaped new line. SSPPEECCIIAALL SSOOUURRCCEESS ((AATTTTRRIIBBUUTTEESS)) ..EEXXEECC Target is never out of date, but always execute commands anyway. ..IIGGNNOORREE Ignore any errors from the commands associated with this target, exactly as if they all were preceded by a dash (`-'). ..MMAADDEE Mark all sources of this target as being up to date. ..MMAAKKEE Execute the commands associated with this target even if the --nn or --tt options were specified. Normally used to mark recursive bbmmaakkees. ..MMEETTAA Create a meta file for the target, even if it is flagged as ..PPHHOONNYY, ..MMAAKKEE, or ..SSPPEECCIIAALL. Usage in conjunction with ..MMAAKKEE is the most likely case. In "meta" mode, the target is out-of- date if the meta file is missing. ..NNOOMMEETTAA Do not create a meta file for the target. Meta files are also not created for ..PPHHOONNYY, ..MMAAKKEE, or ..SSPPEECCIIAALL targets. ..NNOOMMEETTAA__CCMMPP Ignore differences in commands when deciding if target is out of date. This is useful if the command contains a value which always changes. If the number of commands change, though, the target is still considered out of date. The same effect applies to any command line that uses the variable _._O_O_D_A_T_E, which can be used for that purpose even when not otherwise needed or desired: skip-compare-for-some: @echo this is compared @echo this is not ${.OODATE:M.NOMETA_CMP} @echo this is also compared The ::MM pattern suppresses any expansion of the unwanted variable. ..NNOOPPAATTHH Do not search for the target in the directories specified by _._P_A_T_H. ..NNOOTTMMAAIINN Normally bbmmaakkee selects the first target it encounters as the default target to be built if no target was specified. This source prevents this target from being selected. ..OOPPTTIIOONNAALL If a target is marked with this attribute and bbmmaakkee can't figure out how to create it, it ignores this fact and assumes the file isn't needed or already exists. ..PPHHOONNYY The target does not correspond to an actual file; it is always considered to be out of date, and is not created with the --tt option. Suffix-transformation rules are not applied to ..PPHHOONNYY targets. ..PPRREECCIIOOUUSS When bbmmaakkee is interrupted, it normally removes any partially made targets. This source prevents the target from being removed. ..RREECCUURRSSIIVVEE Synonym for ..MMAAKKEE. ..SSIILLEENNTT Do not echo any of the commands associated with this target, exactly as if they all were preceded by an at sign (`@'). ..UUSSEE Turn the target into bbmmaakkee's version of a macro. When the target is used as a source for another target, the other target acquires the commands, sources, and attributes (except for ..UUSSEE) of the source. If the target already has commands, the ..UUSSEE target's commands are appended to them. ..UUSSEEBBEEFFOORREE Like ..UUSSEE, but instead of appending, prepend the ..UUSSEEBBEEFFOORREE target commands to the target. ..WWAAIITT If ..WWAAIITT appears in a dependency line, the sources that precede it are made before the sources that succeed it in the line. Since the dependents of files are not made until the file itself could be made, this also stops the dependents being built unless they are needed for another branch of the dependency tree. So given: x: a .WAIT b echo x a: echo a b: b1 echo b b1: echo b1 the output is always `a', `b1', `b', `x'. The ordering imposed by ..WWAAIITT is only relevant for parallel makes. SSPPEECCIIAALL TTAARRGGEETTSS Special targets may not be included with other targets, i.e. they must be the only target specified. ..BBEEGGIINN Any command lines attached to this target are executed before anything else is done. ..DDEEFFAAUULLTT This is sort of a ..UUSSEE rule for any target (that was used only as a source) that bbmmaakkee can't figure out any other way to create. Only the shell script is used. The _._I_M_P_S_R_C variable of a target that inherits ..DDEEFFAAUULLTT's commands is set to the target's own name. ..DDEELLEETTEE__OONN__EERRRROORR If this target is present in the makefile, it globally causes make to delete targets whose commands fail. (By default, only targets whose commands are interrupted during execution are deleted. This is the historical behavior.) This setting can be used to help prevent half-finished or malformed targets from being left around and corrupting future rebuilds. ..EENNDD Any command lines attached to this target are executed after everything else is done successfully. ..EERRRROORR Any command lines attached to this target are executed when another target fails. See _M_A_K_E___P_R_I_N_T___V_A_R___O_N___E_R_R_O_R for the variables that will be set. ..IIGGNNOORREE Mark each of the sources with the ..IIGGNNOORREE attribute. If no sources are specified, this is the equivalent of specifying the --ii option. ..IINNTTEERRRRUUPPTT If bbmmaakkee is interrupted, the commands for this target are executed. ..MMAAIINN If no target is specified when bbmmaakkee is invoked, this target is built. ..MMAAKKEEFFLLAAGGSS This target provides a way to specify flags for bbmmaakkee at the time when the makefiles are read. The flags are as if typed to the shell, though the --ff option has no effect. ..NNOOPPAATTHH Apply the ..NNOOPPAATTHH attribute to any specified sources. ..NNOOTTPPAARRAALLLLEELL Disable parallel mode. ..NNOO__PPAARRAALLLLEELL Synonym for ..NNOOTTPPAARRAALLLLEELL, for compatibility with other pmake variants. ..NNOORREEAADDOONNLLYY clear the read-only attribute from the global variables specified as sources. ..OOBBJJDDIIRR The source is a new value for `_._O_B_J_D_I_R'. If it exists, bbmmaakkee changes the current working directory to it and updates the value of `_._O_B_J_D_I_R'. ..OORRDDEERR In parallel mode, the named targets are made in sequence. This ordering does not add targets to the list of targets to be made. Since the dependents of a target do not get built until the target itself could be built, unless `a' is built by another part of the dependency graph, the following is a dependency loop: .ORDER: b a b: a ..PPAATTHH The sources are directories which are to be searched for files not found in the current directory. If no sources are specified, any previously specified directories are removed from the search path. If the source is the special ..DDOOTTLLAASSTT target, the current working directory is searched last. ..PPAATTHH.._s_u_f_f_i_x Like ..PPAATTHH but applies only to files with a particular suffix. The suffix must have been previously declared with ..SSUUFFFFIIXXEESS. ..PPHHOONNYY Apply the ..PPHHOONNYY attribute to any specified sources. ..PPOOSSIIXX If this is the first non-comment line in the main makefile, the variable _%_P_O_S_I_X is set to the value `1003.2' and the makefile `' is included if it exists, to provide POSIX- compatible default rules. If bbmmaakkee is run with the --rr flag, only `posix.mk' contributes to the default rules. ..PPRREECCIIOOUUSS Apply the ..PPRREECCIIOOUUSS attribute to any specified sources. If no sources are specified, the ..PPRREECCIIOOUUSS attribute is applied to every target in the file. ..RREEAADDOONNLLYY set the read-only attribute on the global variables specified as sources. ..SSHHEELLLL Sets the shell that bbmmaakkee uses to execute commands. The sources are a set of _f_i_e_l_d==_v_a_l_u_e pairs. name This is the minimal specification, used to select one of the built-in shell specs; sh, ksh, and csh. path Specifies the absolute path to the shell. hasErrCtl Indicates whether the shell supports exit on error. check The command to turn on error checking. ignore The command to disable error checking. echo The command to turn on echoing of commands executed. quiet The command to turn off echoing of commands executed. filter The output to filter after issuing the quiet command. It is typically identical to quiet. errFlag The flag to pass the shell to enable error checking. echoFlag The flag to pass the shell to enable command echoing. newline The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. Example: .SHELL: name=ksh path=/bin/ksh hasErrCtl=true \ check="set -e" ignore="set +e" \ echo="set -v" quiet="set +v" filter="set +v" \ echoFlag=v errFlag=e newline="'\n'" ..SSIILLEENNTT Apply the ..SSIILLEENNTT attribute to any specified sources. If no sources are specified, the ..SSIILLEENNTT attribute is applied to every command in the file. ..SSTTAALLEE This target gets run when a dependency file contains stale entries, having _._A_L_L_S_R_C set to the name of that dependency file. ..SSUUFFFFIIXXEESS Each source specifies a suffix to bbmmaakkee. If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. Example: .SUFFIXES: .c .o .c.o: cc -o ${.TARGET} -c ${.IMPSRC} ..SSYYSSPPAATTHH The sources are directories which are to be added to the system include path which bbmmaakkee searches for makefiles. If no sources are specified, any previously specified directories are removed from the system include path. EENNVVIIRROONNMMEENNTT bbmmaakkee uses the following environment variables, if they exist: MACHINE, MACHINE_ARCH, MAKE, MAKEFLAGS, MAKEOBJDIR, MAKEOBJDIRPREFIX, MAKESYSPATH, PWD, and TMPDIR. - MAKEOBJDIRPREFIX and MAKEOBJDIR may only be set in the environment or on + MAKEOBJDIRPREFIX and MAKEOBJDIR should be set in the environment or on the command line to bbmmaakkee and not as makefile variables; see the - description of `_._O_B_J_D_I_R' for more details. + description of `_._O_B_J_D_I_R' for more details. It is possible to set these + via makefile variables but unless done very early and the `..OOBBJJDDIIRR' + target is used to reset `_._O_B_J_D_I_R', there may be unexpected side effects. FFIILLEESS .depend list of dependencies makefile first default makefile if no makefile is specified on the command line Makefile second default makefile if no makefile is specified on the command line sys.mk system makefile /usr/share/mk system makefile directory CCOOMMPPAATTIIBBIILLIITTYY The basic make syntax is compatible between different make variants; however the special variables, variable modifiers and conditionals are not. OOllddeerr vveerrssiioonnss An incomplete list of changes in older versions of bbmmaakkee: The way that .for loop variables are substituted changed after NetBSD 5.0 so that they still appear to be variable expansions. In particular this stops them being treated as syntax, and removes some obscure problems using them in .if statements. The way that parallel makes are scheduled changed in NetBSD 4.0 so that .ORDER and .WAIT apply recursively to the dependent nodes. The algorithms used may change again in the future. OOtthheerr mmaakkee ddiiaalleeccttss Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not support most of the features of bbmmaakkee as described in this manual. Most notably: ++oo The ..WWAAIITT and ..OORRDDEERR declarations and most functionality pertaining to parallelization. (GNU make supports parallelization but lacks the features needed to control it effectively.) ++oo Directives, including for loops and conditionals and most of the forms of include files. (GNU make has its own incompatible and less powerful syntax for conditionals.) ++oo All built-in variables that begin with a dot. ++oo Most of the special sources and targets that begin with a dot, with the notable exception of ..PPHHOONNYY, ..PPRREECCIIOOUUSS, and ..SSUUFFFFIIXXEESS. ++oo Variable modifiers, except for the `:old=new' string substitution, which does not portably support globbing with `%' and historically only works on declared suffixes. ++oo The $$>> variable even in its short form; most makes support this functionality but its name varies. Some features are somewhat more portable, such as assignment with ++==, ??==, and !!==. The _._P_A_T_H functionality is based on an older feature VVPPAATTHH found in GNU make and many versions of SVR4 make; however, historically its behavior is too ill-defined (and too buggy) to rely upon. The $$@@ and $$<< variables are more or less universally portable, as is the $$((MMAAKKEE)) variable. Basic use of suffix rules (for files only in the current directory, not trying to chain transformations together, etc.) is also reasonably portable. SSEEEE AALLSSOO mkdep(1) HHIISSTTOORRYY bbmmaakkee is derived from NetBSD make(1). It uses autoconf to facilitate portability to other platforms. A make command appeared in Version 7 AT&T UNIX. This make implementation is based on Adam de Boor's pmake program, which was written for Sprite at Berkeley. It was designed to be a parallel distributed make running jobs on different machines using a daemon called "customs". Historically the target/dependency FFRRCC has been used to FoRCe rebuilding (since the target/dependency does not exist ... unless someone creates an _F_R_C file). BBUUGGSS - The make syntax is difficult to parse. For instance, finding the end of + The bbmmaakkee syntax is difficult to parse. For instance, finding the end of a variable's use should involve scanning each of the modifiers, using the - correct terminator for each field. In many places make just counts {} + correct terminator for each field. In many places bbmmaakkee just counts {} and () in order to find the end of a variable expansion. There is no way of escaping a space character in a filename. - In jobs mode, when a target fails; make will put an error token into the - job token pool. This will cause all other instances of make using that + In jobs mode, when a target fails; bbmmaakkee will put an error token into the + job token pool. This will cause all other instances of bbmmaakkee using that token pool to abort the build and exit with error code 6. Sometimes the attempt to suppress a cascade of unnecessary errors, can result in a seemingly unexplained `*** Error code 6' -FreeBSD 14.1-RELEASE July 1, 2024 FreeBSD 14.1-RELEASE +FreeBSD 14.1-RELEASE-p5 November 14, 2024 FreeBSD 14.1-RELEASE-p5 diff --git a/contrib/bmake/bsd.after-import.mk b/contrib/bmake/bsd.after-import.mk index 418caeaa58a6..f2948b855753 100644 --- a/contrib/bmake/bsd.after-import.mk +++ b/contrib/bmake/bsd.after-import.mk @@ -1,134 +1,133 @@ -# $Id: bsd.after-import.mk,v 1.18 2023/09/18 05:29:23 sjg Exp $ +# $Id: bsd.after-import.mk,v 1.19 2024/09/21 22:44:55 sjg Exp $ # This makefile is for use when integrating bmake into a BSD build # system. Use this makefile after importing bmake. # It will bootstrap the new version, # capture the generated files we need, and add an after-import # target to allow the process to be easily repeated. # The goal is to allow the benefits of autoconf without # the overhead of running configure. all: _makefile _utmakefile all: after-import # we rely on bmake .if !defined(.PARSEDIR) .error this makefile requires bmake .endif _this := ${MAKEFILE:tA} BMAKE_SRC := ${.PARSEDIR} # it helps to know where the top of the tree is. .if !defined(SRCTOP) srctop := ${.MAKE.MAKEFILES:M*src/share/mk/sys.mk:H:H:H} .if empty(srctop) # likely locations? .for d in contrib/bmake external/bsd/bmake/dist .if ${BMAKE_SRC:M*/$d} != "" srctop := ${BMAKE_SRC:tA:S,/$d,,} .endif .endfor .endif .if !empty(srctop) SRCTOP := ${srctop} .endif .endif # This lets us match what boot-strap does .if defined(.MAKE.OS) HOST_OS:= ${.MAKE.OS} .elif !defined(HOST_OS) HOST_OS!= uname .endif BOOTSTRAP_ARGS = \ --prefix /usr \ --share /usr/share .if !empty(DEFAULT_SYS_PATH) BOOTSTRAP_ARGS += --with-default-sys-path='${DEFAULT_SYS_PATH}' .endif # run boot-strap with minimal influence bootstrap: ${BMAKE_SRC}/boot-strap ${MAKEFILE} HOME=/ ${BMAKE_SRC}/boot-strap -o ${HOST_OS} ${BOOTSTRAP_ARGS} ${BOOTSTRAP_XTRAS} touch ${.TARGET} # Makefiles need a little more tweaking than say config.h MAKEFILE_SED = sed -e '/^MACHINE/d' \ -e '/include.*VERSION/d' \ -e '/^CC=/s,=,?=,' \ -e '/^PROG/ { s,=,?=,;s,bmake,$${.CURDIR:T},; }' \ - -e 's,^.-include,.sinclude,' \ - -e '/^\..*include * ${.CURDIR}/$f .endfor .for f in ${configured_files:M*Makefile*} @echo Capturing $f @mkdir -p ${${.CURDIR}/$f:L:H} @(echo '# This is a generated file, do NOT edit!'; \ echo '# See ${_this:S,${SRCTOP}/,,}'; \ echo '#'; ${ECHO_TAG} '# $$${HOST_OS}$$'; echo; \ echo 'SRCTOP?= $${.CURDIR:${${.CURDIR}/$f:L:H:S,${SRCTOP}/,,:C,[^/]+,H,g:S,/,:,g}}'; echo; \ ${MAKEFILE_SED} ${HOST_OS}/$f ) > ${.CURDIR}/$f .endfor # this needs the most work _makefile: bootstrap ${MAKEFILE} @echo Generating ${.CURDIR}/Makefile @(echo '# This is a generated file, do NOT edit!'; \ echo '# See ${_this:S,${SRCTOP}/,,}'; \ echo '#'; ${ECHO_TAG} '# $$${HOST_OS}$$'; \ echo; echo 'SRCTOP?= $${.CURDIR:${.CURDIR:S,${SRCTOP}/,,:C,[^/]+,H,g:S,/,:,g}}'; \ echo; echo '# look here first for config.h'; \ echo 'CFLAGS+= -I$${.CURDIR}'; echo; \ echo '# for after-import'; \ echo 'CLEANDIRS+= ${HOST_OS}'; \ echo 'CLEANFILES+= bootstrap'; echo; \ ${MAKEFILE_SED} \ ${1 2:L:@n@-e '/start-delete$n/,/end-delete$n/d'@} \ ${BMAKE_SRC}/Makefile; \ echo; echo '# override some simple things'; \ echo 'BINDIR= /usr/bin'; \ echo 'MANDIR= ${MANDIR:U/usr/share/man}'; \ echo; echo '# make sure we get this'; \ echo 'CFLAGS+= $${COPTS.$${.IMPSRC:T}}'; \ echo; echo 'after-import: ${_this:S,${SRCTOP},\${SRCTOP},}'; \ echo ' cd $${.CURDIR} && $${.MAKE} -f ${_this:S,${SRCTOP},\${SRCTOP},}'; \ echo ) > ${.TARGET} @cmp -s ${.TARGET} ${.CURDIR}/Makefile || \ mv ${.TARGET} ${.CURDIR}/Makefile _utmakefile: bootstrap ${MAKEFILE} @echo Generating ${.CURDIR}/unit-tests/Makefile @mkdir -p ${.CURDIR}/unit-tests @(echo '# This is a generated file, do NOT edit!'; \ echo '# See ${_this:S,${SRCTOP}/,,}'; \ echo '#'; ${ECHO_TAG} '# $$${HOST_OS}$$'; \ ${MAKEFILE_SED} \ -e '/^UNIT_TESTS/s,=.*,= $${srcdir},' \ ${BMAKE_SRC}/unit-tests/Makefile ) > ${.TARGET} @cmp -s ${.TARGET} ${.CURDIR}/unit-tests/Makefile || \ mv ${.TARGET} ${.CURDIR}/unit-tests/Makefile .include diff --git a/contrib/bmake/compat.c b/contrib/bmake/compat.c index 23ece245c8a6..0da929e35b82 100644 --- a/contrib/bmake/compat.c +++ b/contrib/bmake/compat.c @@ -1,782 +1,787 @@ -/* $NetBSD: compat.c,v 1.260 2024/07/11 20:09:16 sjg Exp $ */ +/* $NetBSD: compat.c,v 1.262 2025/01/19 10:57:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This file implements the full-compatibility mode of make, which makes the * targets without parallelism and without a custom shell. * * Interface: * Compat_MakeAll Initialize this module and make the given targets. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "wait.h" #include #include #include "make.h" #include "dir.h" #include "job.h" #include "metachar.h" #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.260 2024/07/11 20:09:16 sjg Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.262 2025/01/19 10:57:10 rillig Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; static int compatSigno; /* * Delete the file of a failed, interrupted, or otherwise duffed target, * unless inhibited by .PRECIOUS. */ static void CompatDeleteTarget(GNode *gn) { if (gn != NULL && !GNode_IsPrecious(gn) && (gn->type & OP_PHONY) == 0) { const char *file = GNode_VarTarget(gn); if (!opts.noExecute && unlink_file(file) == 0) Error("*** %s removed", file); } } /* * Interrupt the creation of the current target and remove it if it ain't * precious. Then exit. * * If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED. * * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've * left the logic alone for now. - dholland 20160826 */ static void CompatInterrupt(int signo) { CompatDeleteTarget(curTarg); if (curTarg != NULL && !GNode_IsPrecious(curTarg)) { /* Run .INTERRUPT only if hit with interrupt signal. */ if (signo == SIGINT) { GNode *gn = Targ_FindNode(".INTERRUPT"); if (gn != NULL) Compat_Make(gn, gn); } } if (signo == SIGQUIT) _exit(signo); /* * If there is a child running, pass the signal on. * We will exist after it has exited. */ compatSigno = signo; if (compatChild > 0) { KILLPG(compatChild, signo); } else { bmake_signal(signo, SIG_DFL); kill(myPid, signo); } } static void DebugFailedTarget(const char *cmd, const GNode *gn) { const char *p = cmd; debug_printf("\n*** Failed target: %s\n*** Failed command: ", gn->name); /* * Replace runs of whitespace with a single space, to reduce the * amount of whitespace for multi-line command lines. */ while (*p != '\0') { if (ch_isspace(*p)) { debug_printf(" "); cpp_skip_whitespace(&p); } else { debug_printf("%c", *p); p++; } } debug_printf("\n"); } static bool UseShell(const char *cmd MAKE_ATTR_UNUSED) { #if defined(FORCE_USE_SHELL) || !defined(MAKE_NATIVE) /* * In a non-native build, the host environment might be weird enough * that it's necessary to go through a shell to get the correct * behaviour. Or perhaps the shell has been replaced with something * that does extra logging, and that should not be bypassed. */ return true; #else /* * Search for meta characters in the command. If there are no meta * characters, there's no need to execute a shell to execute the * command. * * Additionally variable assignments and empty commands * go to the shell. Therefore treat '=' and ':' like shell * meta characters as documented in make(1). */ return needshell(cmd); #endif } static int Compat_Spawn(const char **av) { - int pid = vfork(); + int pid = FORK_FUNCTION(); if (pid < 0) Fatal("Could not fork"); if (pid == 0) { #ifdef USE_META if (useMeta) meta_compat_child(); #endif (void)execvp(av[0], (char *const *)UNCONST(av)); execDie("exec", av[0]); } return pid; } /* * Execute the next command for a target. If the command returns an error, * the node's made field is set to ERROR and creation stops. * * Input: * cmdp Command to execute * gn Node from which the command came * ln List node that contains the command * * Results: * true if the command succeeded. */ bool Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) { char *cmdStart; /* Start of expanded command */ char *volatile bp; bool silent; /* Don't print command */ bool doIt; /* Execute even if -n */ volatile bool errCheck; /* Check errors */ WAIT_T reason; /* Reason for child's death */ WAIT_T status; /* Description of child's death */ pid_t retstat; /* Result of wait */ const char **av; /* Arguments for the child process */ char **volatile mav; /* Copy of the argument vector for freeing */ bool useShell; /* True if command should be executed using a * shell */ const char *cmd = cmdp; char cmd_file[MAXPATHLEN]; size_t cmd_len; + int parseErrorsBefore; silent = (gn->type & OP_SILENT) != OP_NONE; errCheck = !(gn->type & OP_IGNORE); doIt = false; + parseErrorsBefore = parseErrors; cmdStart = Var_SubstInTarget(cmd, gn); - /* TODO: handle errors */ + if (parseErrors != parseErrorsBefore) { + free(cmdStart); + return false; + } if (cmdStart[0] == '\0') { free(cmdStart); return true; } cmd = cmdStart; LstNode_Set(ln, cmdStart); if (gn->type & OP_SAVE_CMDS) { GNode *endNode = Targ_GetEndNode(); if (gn != endNode) { /* * Append the expanded command, to prevent the * local variables from being interpreted in the * scope of the .END node. * * A probably unintended side effect of this is that * the expanded command will be expanded again in the * .END node. Therefore, a literal '$' in these * commands must be written as '$$$$' instead of the * usual '$$'. */ Lst_Append(&endNode->commands, cmdStart); goto register_command; } } if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; register_command: Parse_RegisterCommand(cmdStart); return true; } for (;;) { if (*cmd == '@') silent = !DEBUG(LOUD); else if (*cmd == '-') errCheck = false; else if (*cmd == '+') doIt = true; else if (!ch_isspace(*cmd)) /* Ignore whitespace for compatibility with gnu make */ break; cmd++; } while (ch_isspace(*cmd)) cmd++; if (cmd[0] == '\0') goto register_command; useShell = UseShell(cmd); if (!silent || !GNode_ShouldExecute(gn)) { printf("%s\n", cmd); fflush(stdout); } if (!doIt && !GNode_ShouldExecute(gn)) goto register_command; DEBUG1(JOB, "Execute: '%s'\n", cmd); cmd_len = strlen(cmd); if (cmd_len > MAKE_CMDLEN_LIMIT) useShell = true; else cmd_file[0] = '\0'; if (useShell) { static const char *shargv[5]; if (Cmd_Argv(cmd, cmd_len, shargv, 5, cmd_file, sizeof(cmd_file), (errCheck && shellErrFlag != NULL), DEBUG(SHELL)) < 0) Fatal("cannot run \"%s\"", cmd); av = shargv; bp = NULL; mav = NULL; } else { Words words = Str_Words(cmd, false); mav = words.words; bp = words.freeIt; av = (void *)mav; } #ifdef USE_META if (useMeta) meta_compat_start(); #endif Var_ReexportVars(gn); compatChild = Compat_Spawn(av); free(mav); free(bp); /* XXX: Memory management looks suspicious here. */ /* XXX: Setting a list item to NULL is unexpected. */ LstNode_SetNull(ln); #ifdef USE_META if (useMeta) meta_compat_parent(compatChild); #endif /* The child is off and running. Now all we can do is wait... */ while ((retstat = wait(&reason)) != compatChild) { if (retstat > 0) JobReapChild(retstat, reason, false); /* not ours? */ if (retstat == -1 && errno != EINTR) break; } if (retstat < 0) Fatal("error in wait: %d: %s", retstat, strerror(errno)); if (WIFSTOPPED(reason)) { status = WSTOPSIG(reason); } else if (WIFEXITED(reason)) { status = WEXITSTATUS(reason); #if defined(USE_META) && defined(USE_FILEMON_ONCE) if (useMeta) meta_cmd_finish(NULL); #endif if (status != 0) { if (DEBUG(ERROR)) DebugFailedTarget(cmd, gn); printf("*** Error code %d", status); } } else { status = WTERMSIG(reason); printf("*** Signal %d", status); } if (!WIFEXITED(reason) || status != 0) { if (errCheck) { #ifdef USE_META if (useMeta) meta_job_error(NULL, gn, false, status); #endif gn->made = ERROR; if (WIFEXITED(reason)) gn->exit_status = status; if (opts.keepgoing) { /* * Abort the current target, * but let others continue. */ printf(" (continuing)\n"); } else { printf("\n"); } if (deleteOnError) CompatDeleteTarget(gn); } else { /* * Continue executing commands for this target. * If we return 0, this will happen... */ printf(" (ignored)\n"); status = 0; } fflush(stdout); } free(cmdStart); if (cmd_file[0] != '\0') unlink(cmd_file); compatChild = 0; if (compatSigno != 0) { bmake_signal(compatSigno, SIG_DFL); kill(myPid, compatSigno); } return status == 0; } static void RunCommands(GNode *gn) { StringListNode *ln; for (ln = gn->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; if (!Compat_RunCommand(cmd, gn, ln)) break; } } static void MakeInRandomOrder(GNode **gnodes, GNode **end, GNode *pgn) { GNode **it; size_t r; for (r = (size_t)(end - gnodes); r >= 2; r--) { /* Biased, but irrelevant in practice. */ size_t i = (size_t)random() % r; GNode *t = gnodes[r - 1]; gnodes[r - 1] = gnodes[i]; gnodes[i] = t; } for (it = gnodes; it != end; it++) Compat_Make(*it, pgn); } static void MakeWaitGroupsInRandomOrder(GNodeList *gnodes, GNode *pgn) { Vector vec; GNodeListNode *ln; GNode **nodes; size_t i, n, start; Vector_Init(&vec, sizeof(GNode *)); for (ln = gnodes->first; ln != NULL; ln = ln->next) *(GNode **)Vector_Push(&vec) = ln->datum; nodes = vec.items; n = vec.len; start = 0; for (i = 0; i < n; i++) { if (nodes[i]->type & OP_WAIT) { MakeInRandomOrder(nodes + start, nodes + i, pgn); Compat_Make(nodes[i], pgn); start = i + 1; } } MakeInRandomOrder(nodes + start, nodes + i, pgn); Vector_Done(&vec); } static void MakeNodes(GNodeList *gnodes, GNode *pgn) { GNodeListNode *ln; if (Lst_IsEmpty(gnodes)) return; if (opts.randomizeTargets) { MakeWaitGroupsInRandomOrder(gnodes, pgn); return; } for (ln = gnodes->first; ln != NULL; ln = ln->next) { GNode *cgn = ln->datum; Compat_Make(cgn, pgn); } } static bool MakeUnmade(GNode *gn, GNode *pgn) { assert(gn->made == UNMADE); /* * First mark ourselves to be made, then apply whatever transformations * the suffix module thinks are necessary. Once that's done, we can * descend and make all our children. If any of them has an error * but the -k flag was given, our 'make' field will be set to false * again. This is our signal to not attempt to do anything but abort * our parent as well. */ gn->flags.remake = true; gn->made = BEINGMADE; if (!(gn->type & OP_MADE)) Suff_FindDeps(gn); MakeNodes(&gn->children, gn); if (!gn->flags.remake) { gn->made = ABORTED; pgn->flags.remake = false; return false; } if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) Var_Set(pgn, IMPSRC, GNode_VarTarget(gn)); /* * All the children were made ok. Now youngestChild->mtime contains the * modification time of the newest child, we need to find out if we * exist and when we were modified last. The criteria for datedness * are defined by GNode_IsOODate. */ DEBUG1(MAKE, "Examining %s...", gn->name); if (!GNode_IsOODate(gn)) { gn->made = UPTODATE; DEBUG0(MAKE, "up-to-date.\n"); return false; } /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ DEBUG0(MAKE, "out-of-date.\n"); if (opts.query && gn != Targ_GetEndNode()) exit(1); /* * We need to be re-made. * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set. */ GNode_SetLocalVars(gn); /* * Alter our type to tell if errors should be ignored or things * should not be printed so Compat_RunCommand knows what to do. */ if (opts.ignoreErrors) gn->type |= OP_IGNORE; if (opts.silent) gn->type |= OP_SILENT; if (Job_CheckCommands(gn, Fatal)) { if (!opts.touch || (gn->type & OP_MAKE)) { curTarg = gn; #ifdef USE_META if (useMeta && GNode_ShouldExecute(gn)) meta_job_start(NULL, gn); #endif RunCommands(gn); curTarg = NULL; } else { Job_Touch(gn, (gn->type & OP_SILENT) != OP_NONE); } } else { gn->made = ERROR; } #ifdef USE_META if (useMeta && GNode_ShouldExecute(gn)) { if (meta_job_finish(NULL) != 0) gn->made = ERROR; } #endif if (gn->made != ERROR) { /* * If the node was made successfully, mark it so, update * its modification time and timestamp all its parents. * This is to keep its state from affecting that of its parent. */ gn->made = MADE; if (Make_Recheck(gn) == 0) pgn->flags.force = true; if (!(gn->type & OP_EXEC)) { pgn->flags.childMade = true; GNode_UpdateYoungestChild(pgn, gn); } } else if (opts.keepgoing) { pgn->flags.remake = false; } else { PrintOnError(gn, "\nStop.\n"); exit(1); } return true; } static void MakeOther(GNode *gn, GNode *pgn) { if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) { const char *target = GNode_VarTarget(gn); Var_Set(pgn, IMPSRC, target != NULL ? target : ""); } switch (gn->made) { case BEINGMADE: Error("Graph cycles through %s", gn->name); gn->made = ERROR; pgn->flags.remake = false; break; case MADE: if (!(gn->type & OP_EXEC)) { pgn->flags.childMade = true; GNode_UpdateYoungestChild(pgn, gn); } break; case UPTODATE: if (!(gn->type & OP_EXEC)) GNode_UpdateYoungestChild(pgn, gn); break; default: break; } } /* * Make a target. * * If an error is detected and not being ignored, the process exits. * * Input: * gn The node to make * pgn Parent to abort if necessary * * Output: * gn->made * UPTODATE gn was already up-to-date. * MADE gn was recreated successfully. * ERROR An error occurred while gn was being created, * either due to missing commands or in -k mode. * ABORTED gn was not remade because one of its * dependencies could not be made due to errors. */ void Compat_Make(GNode *gn, GNode *pgn) { if (shellName == NULL) /* we came here from jobs */ Shell_Init(); if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { if (!MakeUnmade(gn, pgn)) goto cohorts; /* XXX: Replace with GNode_IsError(gn) */ } else if (gn->made == ERROR) { /* * Already had an error when making this. * Tell the parent to abort. */ pgn->flags.remake = false; } else { MakeOther(gn, pgn); } cohorts: MakeNodes(&gn->cohorts, pgn); } static void MakeBeginNode(void) { GNode *gn = Targ_FindNode(".BEGIN"); if (gn == NULL) return; Compat_Make(gn, gn); if (GNode_IsError(gn)) { PrintOnError(gn, "\nStop.\n"); exit(1); } } static void InitSignals(void) { if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) bmake_signal(SIGINT, CompatInterrupt); if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) bmake_signal(SIGTERM, CompatInterrupt); if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) bmake_signal(SIGHUP, CompatInterrupt); if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) bmake_signal(SIGQUIT, CompatInterrupt); } void Compat_MakeAll(GNodeList *targs) { GNode *errorNode = NULL; if (shellName == NULL) Shell_Init(); InitSignals(); /* * Create the .END node now, to keep the (debug) output of the * counter.mk test the same as before 2020-09-23. This * implementation detail probably doesn't matter though. */ (void)Targ_GetEndNode(); if (!opts.query) MakeBeginNode(); /* * Expand .USE nodes right now, because they can modify the structure * of the tree. */ Make_ExpandUse(targs); while (!Lst_IsEmpty(targs)) { GNode *gn = Lst_Dequeue(targs); Compat_Make(gn, gn); if (gn->made == UPTODATE) { printf("`%s' is up to date.\n", gn->name); } else if (gn->made == ABORTED) { printf("`%s' not remade because of errors.\n", gn->name); } if (GNode_IsError(gn) && errorNode == NULL) errorNode = gn; } if (errorNode == NULL) { GNode *endNode = Targ_GetEndNode(); Compat_Make(endNode, endNode); if (GNode_IsError(endNode)) errorNode = endNode; } if (errorNode != NULL) { if (DEBUG(GRAPH2)) Targ_PrintGraph(2); else if (DEBUG(GRAPH3)) Targ_PrintGraph(3); PrintOnError(errorNode, "\nStop.\n"); exit(1); } } diff --git a/contrib/bmake/cond.c b/contrib/bmake/cond.c index 58c0069555e1..a724ba7ecf26 100644 --- a/contrib/bmake/cond.c +++ b/contrib/bmake/cond.c @@ -1,1246 +1,1245 @@ -/* $NetBSD: cond.c,v 1.366 2024/07/06 21:21:09 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.371 2025/01/11 21:21:33 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Handling of conditionals in a makefile. * * Interface: * Cond_EvalLine Evaluate the conditional directive, such as * '.if ', '.elifnmake ', '.else', '.endif'. * * Cond_EvalCondition * Evaluate the conditional, which is either the argument * of one of the .if directives or the condition in a * ':?then:else' variable modifier. * * Cond_EndFile At the end of reading a makefile, ensure that the * conditional directives are well-balanced. */ #include #include "make.h" #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.366 2024/07/06 21:21:09 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.371 2025/01/11 21:21:33 rillig Exp $"); /* * Conditional expressions conform to this grammar: * Or -> And ('||' And)* * And -> Term ('&&' Term)* * Term -> Function '(' Argument ')' * Term -> Leaf Operator Leaf * Term -> Leaf * Term -> '(' Or ')' * Term -> '!' Term * Leaf -> "string" * Leaf -> Number * Leaf -> VariableExpression * Leaf -> BareWord * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' * * BareWord is an unquoted string literal, its evaluation depends on the kind * of '.if' directive. * * The tokens are scanned by CondParser_Token, which returns: * TOK_AND for '&&' * TOK_OR for '||' * TOK_NOT for '!' * TOK_LPAREN for '(' * TOK_RPAREN for ')' * * Other terminal symbols are evaluated using either the default function or * the function given in the terminal, they return either TOK_TRUE, TOK_FALSE * or TOK_ERROR. */ typedef enum Token { TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR } Token; typedef enum ComparisonOp { LT, LE, GT, GE, EQ, NE } ComparisonOp; typedef struct CondParser { /* * The plain '.if ${VAR}' evaluates to true if the value of the * expression has length > 0 and is not numerically zero. The other * '.if' variants delegate to evalBare instead, for example '.ifdef * ${VAR}' is equivalent to '.if defined(${VAR})', checking whether * the variable named by the expression '${VAR}' is defined. */ bool plain; /* The function to apply on unquoted bare words. */ bool (*evalBare)(const char *); bool negateEvalBare; /* * Whether the left-hand side of a comparison may be an unquoted * string. This is allowed for expressions of the form * ${condition:?:}, see ApplyModifier_IfElse. Such a condition is * expanded before it is evaluated, due to ease of implementation. * This means that at the point where the condition is evaluated, * make cannot know anymore whether the left-hand side had originally * been an expression or a plain word. * * In conditional directives like '.if', the left-hand side must * either be an expression, a quoted string or a number. */ bool leftUnquotedOK; const char *p; /* The remaining condition to parse */ Token curr; /* Single push-back token used in parsing */ /* * Whether an error message has already been printed for this * condition. */ bool printedError; } CondParser; static CondResult CondParser_Or(CondParser *, bool); unsigned int cond_depth = 0; /* current .if nesting level */ /* Names for ComparisonOp. */ static const char opname[][3] = { "<", "<=", ">", ">=", "==", "!=" }; MAKE_INLINE bool skip_string(const char **pp, const char *str) { size_t len = strlen(str); bool ok = strncmp(*pp, str, len) == 0; if (ok) *pp += len; return ok; } static Token ToToken(bool cond) { return cond ? TOK_TRUE : TOK_FALSE; } static void CondParser_SkipWhitespace(CondParser *par) { cpp_skip_whitespace(&par->p); } /* * Parse a single word, taking into account balanced parentheses as well as * embedded expressions. Used for the argument of a built-in function as * well as for bare words, which are then passed to the default function. */ static char * ParseWord(const char **pp, bool doEval) { const char *p = *pp; Buffer word; int depth; Buf_Init(&word); depth = 0; for (;;) { char ch = *p; if (ch == '\0' || ch == ' ' || ch == '\t') break; if ((ch == '&' || ch == '|') && depth == 0) break; if (ch == '$') { - VarEvalMode emode = doEval - ? VARE_EVAL_DEFINED - : VARE_PARSE; - /* - * TODO: make Var_Parse complain about undefined - * variables. - */ + VarEvalMode emode = doEval ? VARE_EVAL : VARE_PARSE; FStr nestedVal = Var_Parse(&p, SCOPE_CMDLINE, emode); /* TODO: handle errors */ Buf_AddStr(&word, nestedVal.str); FStr_Done(&nestedVal); continue; } if (ch == '(') depth++; else if (ch == ')' && --depth < 0) break; Buf_AddByte(&word, ch); p++; } - cpp_skip_hspace(&p); *pp = p; return Buf_DoneData(&word); } /* Parse the function argument, including the surrounding parentheses. */ static char * ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) { - const char *p = *pp; + const char *p = *pp, *argStart, *argEnd; char *res; p++; /* skip the '(' */ cpp_skip_hspace(&p); + argStart = p; res = ParseWord(&p, doEval); + argEnd = p; cpp_skip_hspace(&p); if (*p++ != ')') { int len = 0; while (ch_isalpha(func[len])) len++; Parse_Error(PARSE_FATAL, - "Missing closing parenthesis for %.*s()", len, func); + "Missing ')' after argument '%.*s' for '%.*s'", + (int)(argEnd - argStart), argStart, len, func); par->printedError = true; free(res); return NULL; } *pp = p; return res; } /* See if the given variable is defined. */ static bool FuncDefined(const char *var) { return Var_Exists(SCOPE_CMDLINE, var); } /* See if a target matching targetPattern is requested to be made. */ static bool FuncMake(const char *targetPattern) { StringListNode *ln; bool warned = false; for (ln = opts.create.first; ln != NULL; ln = ln->next) { StrMatchResult res = Str_Match(ln->datum, targetPattern); if (res.error != NULL && !warned) { warned = true; Parse_Error(PARSE_WARNING, "%s in pattern argument '%s' to function 'make'", res.error, targetPattern); } if (res.matched) return true; } return false; } /* See if the given file exists. */ static bool FuncExists(const char *file) { bool result; char *path; path = Dir_FindFile(file, &dirSearchPath); DEBUG2(COND, "exists(%s) result is \"%s\"\n", file, path != NULL ? path : ""); result = path != NULL; free(path); return result; } /* See if the given node exists and is an actual target. */ static bool FuncTarget(const char *node) { GNode *gn = Targ_FindNode(node); return gn != NULL && GNode_IsTarget(gn); } /* * See if the given node exists and is an actual target with commands * associated with it. */ static bool FuncCommands(const char *node) { GNode *gn = Targ_FindNode(node); return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands); } /* * Convert the string to a floating point number. Accepted formats are * base-10 integer, base-16 integer and finite floating point numbers. */ static bool TryParseNumber(const char *str, double *out_value) { char *end; unsigned long ul_val; double dbl_val; if (str[0] == '\0') { /* XXX: why is an empty string a number? */ *out_value = 0.0; return true; } errno = 0; ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); if (*end == '\0' && errno != ERANGE) { *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; return true; } if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') return false; /* skip the expensive strtod call */ dbl_val = strtod(str, &end); if (*end != '\0') return false; *out_value = dbl_val; return true; } static bool is_separator(char ch) { return ch == '\0' || ch_isspace(ch) || ch == '!' || ch == '=' || ch == '>' || ch == '<' || ch == ')' /* but not '(' */; } /* * In a quoted or unquoted string literal or a number, parse an * expression and add its value to the buffer. * * Return whether to continue parsing the leaf. * * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} */ static bool CondParser_StringExpr(CondParser *par, const char *start, bool doEval, bool quoted, Buffer *buf, FStr *inout_str) { VarEvalMode emode; const char *p; bool atStart; /* true means an expression outside quotes */ emode = doEval && quoted ? VARE_EVAL - : doEval ? VARE_EVAL_DEFINED + : doEval ? VARE_EVAL_DEFINED_LOUD : VARE_PARSE; p = par->p; atStart = p == start; *inout_str = Var_Parse(&p, SCOPE_CMDLINE, emode); /* TODO: handle errors */ if (inout_str->str == var_Error) { FStr_Done(inout_str); *inout_str = FStr_InitRefer(NULL); return false; } par->p = p; if (atStart && is_separator(par->p[0])) return false; Buf_AddStr(buf, inout_str->str); FStr_Done(inout_str); *inout_str = FStr_InitRefer(NULL); /* not finished yet */ return true; } /* * Parse a string from an expression or an optionally quoted string, * on the left-hand and right-hand sides of comparisons. * * Return the string without any enclosing quotes, or NULL on error. * Sets out_quoted if the leaf was a quoted string literal. */ static FStr CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, bool *out_quoted) { Buffer buf; FStr str; bool quoted; const char *start; Buf_Init(&buf); str = FStr_InitRefer(NULL); *out_quoted = quoted = par->p[0] == '"'; start = par->p; if (quoted) par->p++; while (par->p[0] != '\0' && str.str == NULL) { switch (par->p[0]) { case '\\': par->p++; if (par->p[0] != '\0') { Buf_AddByte(&buf, par->p[0]); par->p++; } continue; case '"': par->p++; if (quoted) goto return_buf; /* skip the closing quote */ Buf_AddByte(&buf, '"'); continue; case ')': /* see is_separator */ case '!': case '=': case '>': case '<': case ' ': case '\t': if (!quoted) goto return_buf; Buf_AddByte(&buf, par->p[0]); par->p++; continue; case '$': if (!CondParser_StringExpr(par, start, doEval, quoted, &buf, &str)) goto return_str; continue; default: if (!unquotedOK && !quoted && *start != '$' && !ch_isdigit(*start)) { str = FStr_InitRefer(NULL); goto return_str; } Buf_AddByte(&buf, par->p[0]); par->p++; continue; } } return_buf: str = FStr_InitOwn(buf.data); buf.data = NULL; return_str: Buf_Done(&buf); return str; } /* * Evaluate a "comparison without operator", such as in ".if ${VAR}" or * ".if 0". */ static bool EvalTruthy(CondParser *par, const char *value, bool quoted) { double num; if (quoted) return value[0] != '\0'; if (TryParseNumber(value, &num)) return num != 0.0; if (par->plain) return value[0] != '\0'; return par->evalBare(value) != par->negateEvalBare; } /* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ static bool EvalCompareNum(double lhs, ComparisonOp op, double rhs) { DEBUG3(COND, "Comparing %f %s %f\n", lhs, opname[op], rhs); switch (op) { case LT: return lhs < rhs; case LE: return lhs <= rhs; case GT: return lhs > rhs; case GE: return lhs >= rhs; case EQ: return lhs == rhs; default: return lhs != rhs; } } static Token EvalCompareStr(CondParser *par, const char *lhs, ComparisonOp op, const char *rhs) { if (op != EQ && op != NE) { Parse_Error(PARSE_FATAL, "Comparison with '%s' requires both operands " "'%s' and '%s' to be numeric", opname[op], lhs, rhs); par->printedError = true; return TOK_ERROR; } DEBUG3(COND, "Comparing \"%s\" %s \"%s\"\n", lhs, opname[op], rhs); return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); } /* Evaluate a comparison, such as "${VAR} == 12345". */ static Token EvalCompare(CondParser *par, const char *lhs, bool lhsQuoted, ComparisonOp op, const char *rhs, bool rhsQuoted) { double left, right; if (!rhsQuoted && !lhsQuoted) if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) return ToToken(EvalCompareNum(left, op, right)); return EvalCompareStr(par, lhs, op, rhs); } static bool CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) { const char *p = par->p; if (p[0] == '<' && p[1] == '=') return par->p += 2, *out_op = LE, true; if (p[0] == '<') return par->p += 1, *out_op = LT, true; if (p[0] == '>' && p[1] == '=') return par->p += 2, *out_op = GE, true; if (p[0] == '>') return par->p += 1, *out_op = GT, true; if (p[0] == '=' && p[1] == '=') return par->p += 2, *out_op = EQ, true; if (p[0] == '!' && p[1] == '=') return par->p += 2, *out_op = NE, true; return false; } /* * Parse a comparison condition such as: * * 0 * ${VAR:Mpattern} * ${VAR} == value * ${VAR:U0} < 12345 */ static Token CondParser_Comparison(CondParser *par, bool doEval) { Token t = TOK_ERROR; FStr lhs, rhs; ComparisonOp op; bool lhsQuoted, rhsQuoted; lhs = CondParser_Leaf(par, doEval, par->leftUnquotedOK, &lhsQuoted); if (lhs.str == NULL) goto done_lhs; CondParser_SkipWhitespace(par); if (!CondParser_ComparisonOp(par, &op)) { t = ToToken(doEval && EvalTruthy(par, lhs.str, lhsQuoted)); goto done_lhs; } CondParser_SkipWhitespace(par); if (par->p[0] == '\0') { Parse_Error(PARSE_FATAL, "Missing right-hand side of operator '%s'", opname[op]); par->printedError = true; goto done_lhs; } rhs = CondParser_Leaf(par, doEval, true, &rhsQuoted); t = rhs.str == NULL ? TOK_ERROR : !doEval ? TOK_FALSE : EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); FStr_Done(&rhs); done_lhs: FStr_Done(&lhs); return t; } /* * The argument to empty() is a variable name, optionally followed by * variable modifiers. */ static bool CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) { const char *p = par->p; Token tok; FStr val; if (!skip_string(&p, "empty")) return false; cpp_skip_whitespace(&p); if (*p != '(') return false; p--; /* Make p[1] point to the '('. */ val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_EVAL : VARE_PARSE); /* TODO: handle errors */ if (val.str == var_Error) tok = TOK_ERROR; else { cpp_skip_whitespace(&val.str); tok = ToToken(doEval && val.str[0] == '\0'); } FStr_Done(&val); *out_token = tok; par->p = p; return true; } /* Parse a function call expression, such as 'exists(${file})'. */ static bool CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) { char *arg; const char *p = par->p; bool (*fn)(const char *); const char *fn_name = p; if (skip_string(&p, "defined")) fn = FuncDefined; else if (skip_string(&p, "make")) fn = FuncMake; else if (skip_string(&p, "exists")) fn = FuncExists; else if (skip_string(&p, "target")) fn = FuncTarget; else if (skip_string(&p, "commands")) fn = FuncCommands; else return false; cpp_skip_whitespace(&p); if (*p != '(') return false; arg = ParseFuncArg(par, &p, doEval, fn_name); *out_token = ToToken(doEval && arg != NULL && arg[0] != '\0' && fn(arg)); free(arg); par->p = p; return true; } /* * Parse a comparison that neither starts with '"' nor '$', such as the * unusual 'bare == right' or '3 == ${VAR}', or a simple leaf without * operator, which is a number, an expression or a string literal. * * TODO: Can this be merged into CondParser_Comparison? */ static Token CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) { Token t; char *arg; const char *p; p = par->p; if (ch_isdigit(p[0]) || p[0] == '-' || p[0] == '+') return CondParser_Comparison(par, doEval); /* * Most likely we have a bare word to apply the default function to. * However, ".if a == b" gets here when the "a" is unquoted and * doesn't start with a '$'. This surprises people. * If what follows the function argument is a '=' or '!' then the * syntax would be invalid if we did "defined(a)" - so instead treat * as an expression. */ /* * XXX: In edge cases, an expression may be evaluated twice, * see cond-token-plain.mk, keyword 'twice'. */ arg = ParseWord(&p, doEval); assert(arg[0] != '\0'); + cpp_skip_hspace(&p); if (*p == '=' || *p == '!' || *p == '<' || *p == '>') { free(arg); return CondParser_Comparison(par, doEval); } par->p = p; /* * Evaluate the argument using the default function. * This path always treats .if as .ifdef. To get here, the character * after .if must have been taken literally, so the argument cannot * be empty - even if it contained an expression. */ t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); free(arg); return t; } /* Return the next token or comparison result from the parser. */ static Token CondParser_Token(CondParser *par, bool doEval) { Token t; t = par->curr; if (t != TOK_NONE) { par->curr = TOK_NONE; return t; } cpp_skip_hspace(&par->p); switch (par->p[0]) { case '(': par->p++; return TOK_LPAREN; case ')': par->p++; return TOK_RPAREN; case '|': par->p++; if (par->p[0] == '|') par->p++; else { Parse_Error(PARSE_FATAL, "Unknown operator '|'"); par->printedError = true; return TOK_ERROR; } return TOK_OR; case '&': par->p++; if (par->p[0] == '&') par->p++; else { Parse_Error(PARSE_FATAL, "Unknown operator '&'"); par->printedError = true; return TOK_ERROR; } return TOK_AND; case '!': par->p++; return TOK_NOT; case '#': /* XXX: see unit-tests/cond-token-plain.mk */ case '\n': /* XXX: why should this end the condition? */ /* Probably obsolete now, from 1993-03-21. */ case '\0': return TOK_EOF; case '"': case '$': return CondParser_Comparison(par, doEval); default: if (CondParser_FuncCallEmpty(par, doEval, &t)) return t; if (CondParser_FuncCall(par, doEval, &t)) return t; return CondParser_ComparisonOrLeaf(par, doEval); } } /* Skip the next token if it equals t. */ static bool CondParser_Skip(CondParser *par, Token t) { Token actual; actual = CondParser_Token(par, false); if (actual == t) return true; assert(par->curr == TOK_NONE); assert(actual != TOK_NONE); par->curr = actual; return false; } /* * Term -> '(' Or ')' * Term -> '!' Term * Term -> Leaf Operator Leaf * Term -> Leaf */ static CondResult CondParser_Term(CondParser *par, bool doEval) { CondResult res; Token t; bool neg = false; while ((t = CondParser_Token(par, doEval)) == TOK_NOT) neg = !neg; if (t == TOK_TRUE || t == TOK_FALSE) return neg == (t == TOK_FALSE) ? CR_TRUE : CR_FALSE; if (t == TOK_LPAREN) { res = CondParser_Or(par, doEval); if (res == CR_ERROR) return CR_ERROR; if (CondParser_Token(par, doEval) != TOK_RPAREN) return CR_ERROR; return neg == (res == CR_FALSE) ? CR_TRUE : CR_FALSE; } return CR_ERROR; } /* * And -> Term ('&&' Term)* */ static CondResult CondParser_And(CondParser *par, bool doEval) { CondResult res, rhs; res = CR_TRUE; do { if ((rhs = CondParser_Term(par, doEval)) == CR_ERROR) return CR_ERROR; if (rhs == CR_FALSE) { res = CR_FALSE; doEval = false; } } while (CondParser_Skip(par, TOK_AND)); return res; } /* * Or -> And ('||' And)* */ static CondResult CondParser_Or(CondParser *par, bool doEval) { CondResult res, rhs; res = CR_FALSE; do { if ((rhs = CondParser_And(par, doEval)) == CR_ERROR) return CR_ERROR; if (rhs == CR_TRUE) { res = CR_TRUE; doEval = false; } } while (CondParser_Skip(par, TOK_OR)); return res; } /* * Evaluate the condition, including any side effects from the * expressions in the condition. The condition consists of &&, ||, !, * function(arg), comparisons and parenthetical groupings thereof. */ static CondResult CondEvalExpression(const char *cond, bool plain, bool (*evalBare)(const char *), bool negate, bool eprint, bool leftUnquotedOK) { CondParser par; CondResult rval; + int parseErrorsBefore = parseErrors; cpp_skip_hspace(&cond); par.plain = plain; par.evalBare = evalBare; par.negateEvalBare = negate; par.leftUnquotedOK = leftUnquotedOK; par.p = cond; par.curr = TOK_NONE; par.printedError = false; DEBUG1(COND, "CondParser_Eval: %s\n", par.p); rval = CondParser_Or(&par, true); if (par.curr != TOK_EOF) rval = CR_ERROR; - if (rval == CR_ERROR && eprint && !par.printedError) - Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); + if (rval == CR_ERROR && eprint && !par.printedError + && parseErrors == parseErrorsBefore) + Parse_Error(PARSE_FATAL, "Malformed conditional '%s'", cond); return rval; } /* * Evaluate a condition in a :? modifier, such as * ${"${VAR}" == value:?yes:no}. */ CondResult Cond_EvalCondition(const char *cond) { return CondEvalExpression(cond, true, FuncDefined, false, false, true); } static bool IsEndif(const char *p) { return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); } static bool DetermineKindOfConditional(const char **pp, bool *out_plain, bool (**out_evalBare)(const char *), bool *out_negate) { const char *p = *pp + 2; *out_plain = false; *out_evalBare = FuncDefined; *out_negate = skip_string(&p, "n"); if (skip_string(&p, "def")) { /* .ifdef and .ifndef */ } else if (skip_string(&p, "make")) /* .ifmake and .ifnmake */ *out_evalBare = FuncMake; else if (!*out_negate) /* plain .if */ *out_plain = true; else goto unknown_directive; if (ch_isalpha(*p)) goto unknown_directive; *pp = p; return true; unknown_directive: return false; } /* * Evaluate the conditional directive in the line, which is one of: * * .if * .ifmake * .ifnmake * .ifdef * .ifndef * .elif * .elifmake * .elifnmake * .elifdef * .elifndef * .else * .endif * * In these directives, consists of &&, ||, !, function(arg), * comparisons, expressions, bare words, numbers and strings, and * parenthetical groupings thereof. * * Results: * CR_TRUE to continue parsing the lines that follow the * conditional (when evaluates to true) * CR_FALSE to skip the lines after the conditional * (when evaluates to false, or when a previous * branch was already taken) * CR_ERROR if the conditional was not valid, either because of * a syntax error or because some variable was undefined * or because the condition could not be evaluated */ CondResult Cond_EvalLine(const char *line) { typedef enum IfState { /* None of the previous evaluated to true. */ IFS_INITIAL = 0, /* * The previous evaluated to true. The lines following * this condition are interpreted. */ IFS_ACTIVE = 1 << 0, /* The previous directive was an '.else'. */ IFS_SEEN_ELSE = 1 << 1, /* One of the previous evaluated to true. */ IFS_WAS_ACTIVE = 1 << 2 } IfState; static enum IfState *cond_states = NULL; static unsigned int cond_states_cap = 128; bool plain; bool (*evalBare)(const char *); bool negate; bool isElif; CondResult res; IfState state; const char *p = line; if (cond_states == NULL) { cond_states = bmake_malloc( cond_states_cap * sizeof *cond_states); cond_states[0] = IFS_ACTIVE; } p++; /* skip the leading '.' */ cpp_skip_hspace(&p); if (IsEndif(p)) { if (p[5] != '\0') { Parse_Error(PARSE_FATAL, "The .endif directive does not take arguments"); } if (cond_depth == CurFile_CondMinDepth()) { Parse_Error(PARSE_FATAL, "if-less endif"); return CR_TRUE; } /* Return state for previous conditional */ cond_depth--; Parse_GuardEndif(); return cond_states[cond_depth] & IFS_ACTIVE ? CR_TRUE : CR_FALSE; } /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ if (p[0] == 'e') { if (p[1] != 'l') return CR_ERROR; /* Quite likely this is 'else' or 'elif' */ p += 2; if (strncmp(p, "se", 2) == 0 && !ch_isalpha(p[2])) { if (p[2] != '\0') Parse_Error(PARSE_FATAL, "The .else directive " "does not take arguments"); if (cond_depth == CurFile_CondMinDepth()) { Parse_Error(PARSE_FATAL, "if-less else"); return CR_TRUE; } Parse_GuardElse(); state = cond_states[cond_depth]; if (state == IFS_INITIAL) { state = IFS_ACTIVE | IFS_SEEN_ELSE; } else { if (state & IFS_SEEN_ELSE) Parse_Error(PARSE_WARNING, "extra else"); state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; } cond_states[cond_depth] = state; return state & IFS_ACTIVE ? CR_TRUE : CR_FALSE; } /* Assume for now it is an elif */ isElif = true; } else isElif = false; if (p[0] != 'i' || p[1] != 'f') return CR_ERROR; if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) return CR_ERROR; if (isElif) { if (cond_depth == CurFile_CondMinDepth()) { Parse_Error(PARSE_FATAL, "if-less elif"); return CR_TRUE; } Parse_GuardElse(); state = cond_states[cond_depth]; if (state & IFS_SEEN_ELSE) { Parse_Error(PARSE_WARNING, "extra elif"); cond_states[cond_depth] = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; return CR_FALSE; } if (state != IFS_INITIAL) { cond_states[cond_depth] = IFS_WAS_ACTIVE; return CR_FALSE; } } else { /* Normal .if */ if (cond_depth + 1 >= cond_states_cap) { /* * This is rare, but not impossible. * In meta mode, dirdeps.mk (only runs at level 0) * can need more than the default. */ cond_states_cap += 32; cond_states = bmake_realloc(cond_states, cond_states_cap * sizeof *cond_states); } state = cond_states[cond_depth]; cond_depth++; if (!(state & IFS_ACTIVE)) { cond_states[cond_depth] = IFS_WAS_ACTIVE; return CR_FALSE; } } res = CondEvalExpression(p, plain, evalBare, negate, true, false); if (res == CR_ERROR) { /* Syntax error, error message already output. */ /* Skip everything to the matching '.endif'. */ /* An extra '.else' is not detected in this case. */ cond_states[cond_depth] = IFS_WAS_ACTIVE; return CR_FALSE; } cond_states[cond_depth] = res == CR_TRUE ? IFS_ACTIVE : IFS_INITIAL; return res; } static bool ParseVarnameGuard(const char **pp, const char **varname) { const char *p = *pp; if (ch_isalpha(*p) || *p == '_') { while (ch_isalnum(*p) || *p == '_') p++; *varname = *pp; *pp = p; return true; } return false; } /* Extracts the multiple-inclusion guard from a conditional, if any. */ Guard * Cond_ExtractGuard(const char *line) { const char *p, *varname; Substring dir; Guard *guard; p = line + 1; /* skip the '.' */ cpp_skip_hspace(&p); dir.start = p; while (ch_isalpha(*p)) p++; dir.end = p; cpp_skip_hspace(&p); if (Substring_Equals(dir, "if")) { if (skip_string(&p, "!defined(")) { if (ParseVarnameGuard(&p, &varname) && strcmp(p, ")") == 0) goto found_variable; } else if (skip_string(&p, "!target(")) { const char *arg_p = p; free(ParseWord(&p, false)); if (strcmp(p, ")") == 0) { guard = bmake_malloc(sizeof(*guard)); guard->kind = GK_TARGET; guard->name = ParseWord(&arg_p, true); return guard; } } } else if (Substring_Equals(dir, "ifndef")) { if (ParseVarnameGuard(&p, &varname) && *p == '\0') goto found_variable; } return NULL; found_variable: guard = bmake_malloc(sizeof(*guard)); guard->kind = GK_VARIABLE; guard->name = bmake_strsedup(varname, p); return guard; } void Cond_EndFile(void) { unsigned int open_conds = cond_depth - CurFile_CondMinDepth(); if (open_conds != 0) { Parse_Error(PARSE_FATAL, "%u open conditional%s", open_conds, open_conds == 1 ? "" : "s"); cond_depth = CurFile_CondMinDepth(); } } diff --git a/contrib/bmake/job.c b/contrib/bmake/job.c index 26a691323025..bd85e6d95fb8 100644 --- a/contrib/bmake/job.c +++ b/contrib/bmake/job.c @@ -1,3020 +1,2970 @@ -/* $NetBSD: job.c,v 1.480 2024/07/07 07:50:57 rillig Exp $ */ +/* $NetBSD: job.c,v 1.485 2025/01/19 10:57:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Create child processes and collect their output. * * Interface: * Job_Init Called to initialize this module. In addition, * the .BEGIN target is made, including all of its * dependencies before this function returns. * Hence, the makefiles must have been parsed * before this function is called. * * Job_End Clean up any memory used. * * Job_Make Start the creation of the given target. * * Job_CatchChildren * Check for and handle the termination of any * children. This must be called reasonably * frequently to keep the whole make going at * a decent clip, since job table entries aren't * removed until their process is caught this way. * * Job_CatchOutput * Print any output our children have produced. * Should also be called fairly frequently to * keep the user informed of what's going on. * If no output is waiting, it will block for * a time given by the SEL_* constants, below, * or until output is ready. * * Job_ParseShell Given a special dependency line with target '.SHELL', * define the shell that is used for the creation * commands in jobs mode. * * Job_Finish Perform any final processing which needs doing. * This includes the execution of any commands * which have been/were attached to the .END * target. It should only be called when the * job table is empty. * * Job_AbortAll Abort all currently running jobs. Do not handle * output or do anything for the jobs, just kill them. * Should only be called in an emergency. * * Job_CheckCommands * Verify that the commands for a target are * ok. Provide them if necessary and possible. * * Job_Touch Update a target without really updating it. * * Job_Wait Wait for all currently-running jobs to finish. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "wait.h" #include #if !defined(USE_SELECT) && defined(HAVE_POLL_H) #include #else #ifndef USE_SELECT /* no poll.h */ # define USE_SELECT #endif #if defined(HAVE_SYS_SELECT_H) # include #endif #endif #include #include #if defined(HAVE_SYS_SOCKET_H) # include #endif #include "make.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.480 2024/07/07 07:50:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.485 2025/01/19 10:57:10 rillig Exp $"); /* * A shell defines how the commands are run. All commands for a target are * written into a single file, which is then given to the shell to execute * the commands from it. The commands are written to the file using a few * templates for echo control and error control. * * The name of the shell is the basename for the predefined shells, such as * "sh", "csh", "bash". For custom shells, it is the full pathname, and its * basename is used to select the type of shell; the longest match wins. * So /usr/pkg/bin/bash has type sh, /usr/local/bin/tcsh has type csh. * * The echoing of command lines is controlled using hasEchoCtl, echoOff, * echoOn, noPrint and noPrintLen. When echoOff is executed by the shell, it * still outputs something, but this something is not interesting, therefore * it is filtered out using noPrint and noPrintLen. * * The error checking for individual commands is controlled using hasErrCtl, * errOn, errOff and runChkTmpl. * * In case a shell doesn't have error control, echoTmpl is a printf template * for echoing the command, should echoing be on; runIgnTmpl is another * printf template for executing the command while ignoring the return * status. Finally runChkTmpl is a printf template for running the command and * causing the shell to exit on error. If any of these strings are empty when * hasErrCtl is false, the command will be executed anyway as is, and if it * causes an error, so be it. Any templates set up to echo the command will * escape any '$ ` \ "' characters in the command string to avoid unwanted * shell code injection, the escaped command is safe to use in double quotes. * * The command-line flags "echo" and "exit" also control the behavior. The * "echo" flag causes the shell to start echoing commands right away. The * "exit" flag causes the shell to exit when an error is detected in one of * the commands. */ typedef struct Shell { /* * The name of the shell. For Bourne and C shells, this is used only * to find the shell description when used as the single source of a * .SHELL target. For user-defined shells, this is the full path of * the shell. */ const char *name; bool hasEchoCtl; /* whether both echoOff and echoOn are there */ const char *echoOff; /* command to turn echoing off */ const char *echoOn; /* command to turn echoing back on */ const char *noPrint; /* text to skip when printing output from the * shell. This is usually the same as echoOff */ size_t noPrintLen; /* length of noPrint command */ bool hasErrCtl; /* whether error checking can be controlled * for individual commands */ const char *errOn; /* command to turn on error checking */ const char *errOff; /* command to turn off error checking */ const char *echoTmpl; /* template to echo a command */ const char *runIgnTmpl; /* template to run a command without error * checking */ const char *runChkTmpl; /* template to run a command with error * checking */ /* * A string literal that results in a newline character when it * occurs outside of any 'quote' or "quote" characters. */ const char *newline; char commentChar; /* character used by shell for comment lines */ const char *echoFlag; /* shell flag to echo commands */ const char *errFlag; /* shell flag to exit on error */ } Shell; typedef struct CommandFlags { /* Whether to echo the command before or instead of running it. */ bool echo; /* Run the command even in -n or -N mode. */ bool always; /* * true if we turned error checking off before writing the command to * the commands file and need to turn it back on */ bool ignerr; } CommandFlags; /* * Write shell commands to a file. * * TODO: keep track of whether commands are echoed. * TODO: keep track of whether error checking is active. */ typedef struct ShellWriter { FILE *f; /* we've sent 'set -x' */ bool xtraced; } ShellWriter; /* * FreeBSD: traditionally .MAKE is not required to * pass jobs queue to sub-makes. * Use .MAKE.ALWAYS_PASS_JOB_QUEUE=no to disable. */ #define MAKE_ALWAYS_PASS_JOB_QUEUE "${.MAKE.ALWAYS_PASS_JOB_QUEUE:U}" static bool Always_pass_job_queue = true; /* * FreeBSD: aborting entire parallel make isn't always * desired. When doing tinderbox for example, failure of * one architecture should not stop all. * We still want to bail on interrupt though. */ #define MAKE_JOB_ERROR_TOKEN "${MAKE_JOB_ERROR_TOKEN:U}" static bool Job_error_token = true; /* error handling variables */ static int job_errors = 0; /* number of errors reported */ static enum { /* Why is the make aborting? */ ABORT_NONE, ABORT_ERROR, /* Aborted because of an error */ ABORT_INTERRUPT, /* Aborted because it was interrupted */ ABORT_WAIT /* Waiting for jobs to finish */ } aborting = ABORT_NONE; #define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ /* Tracks the number of tokens currently "out" to build jobs. */ int jobTokensRunning = 0; -typedef enum JobStartResult { - JOB_RUNNING, /* Job is running */ - JOB_ERROR, /* Error in starting the job */ - JOB_FINISHED /* The job is already finished */ -} JobStartResult; - /* * Descriptions for various shells. * * The build environment may set DEFSHELL_INDEX to one of * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to * select one of the predefined shells as the default shell. * * Alternatively, the build environment may set DEFSHELL_CUSTOM to the * name or the full path of a sh-compatible shell, which will be used as * the default shell. * * ".SHELL" lines in Makefiles can choose the default shell from the * set defined here, or add additional shells. */ #ifdef DEFSHELL_CUSTOM #define DEFSHELL_INDEX_CUSTOM 0 #define DEFSHELL_INDEX_SH 1 #define DEFSHELL_INDEX_KSH 2 #define DEFSHELL_INDEX_CSH 3 #else #define DEFSHELL_INDEX_SH 0 #define DEFSHELL_INDEX_KSH 1 #define DEFSHELL_INDEX_CSH 2 #endif #ifndef DEFSHELL_INDEX #define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */ #endif static Shell shells[] = { #ifdef DEFSHELL_CUSTOM /* * An sh-compatible shell with a non-standard name. * * Keep this in sync with the "sh" description below, but avoid * non-portable features that might not be supplied by all * sh-compatible shells. */ { DEFSHELL_CUSTOM, /* .name */ false, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "%s\n", /* .runIgnTmpl */ "{ %s \n} || exit $?\n", /* .runChkTmpl */ "'\n'", /* .newline */ '#', /* .commentChar */ "", /* .echoFlag */ "", /* .errFlag */ }, #endif /* DEFSHELL_CUSTOM */ /* * SH description. Echo control is also possible and, under * sun UNIX anyway, one can even control error checking. */ { "sh", /* .name */ false, /* .hasEchoCtl */ "", /* .echoOff */ "", /* .echoOn */ "", /* .noPrint */ 0, /* .noPrintLen */ false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "%s\n", /* .runIgnTmpl */ "{ %s \n} || exit $?\n", /* .runChkTmpl */ "'\n'", /* .newline */ '#', /* .commentChar*/ #if defined(MAKE_NATIVE) && defined(__NetBSD__) /* XXX: -q is not really echoFlag, it's more like noEchoInSysFlag. */ "q", /* .echoFlag */ #else "", /* .echoFlag */ #endif "", /* .errFlag */ }, /* * KSH description. */ { "ksh", /* .name */ true, /* .hasEchoCtl */ "set +v", /* .echoOff */ "set -v", /* .echoOn */ "set +v", /* .noPrint */ 6, /* .noPrintLen */ false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "%s\n", /* .runIgnTmpl */ "{ %s \n} || exit $?\n", /* .runChkTmpl */ "'\n'", /* .newline */ '#', /* .commentChar */ "v", /* .echoFlag */ "", /* .errFlag */ }, /* * CSH description. The csh can do echo control by playing * with the setting of the 'echo' shell variable. Sadly, * however, it is unable to do error control nicely. */ { "csh", /* .name */ true, /* .hasEchoCtl */ "unset verbose", /* .echoOff */ "set verbose", /* .echoOn */ "unset verbose", /* .noPrint */ 13, /* .noPrintLen */ false, /* .hasErrCtl */ "", /* .errOn */ "", /* .errOff */ "echo \"%s\"\n", /* .echoTmpl */ "csh -c \"%s || exit 0\"\n", /* .runIgnTmpl */ "", /* .runChkTmpl */ "'\\\n'", /* .newline */ '#', /* .commentChar */ "v", /* .echoFlag */ "e", /* .errFlag */ } }; /* * This is the shell to which we pass all commands in the Makefile. * It is set by the Job_ParseShell function. */ static Shell *shell = &shells[DEFSHELL_INDEX]; char *shellPath; /* full pathname of executable image */ const char *shellName = NULL; /* last component of shellPath */ char *shellErrFlag = NULL; static char *shell_freeIt = NULL; /* Allocated memory for custom .SHELL */ static Job *job_table; /* The structures that describe them */ static Job *job_table_end; /* job_table + maxJobs */ static unsigned int wantToken; static bool lurking_children = false; static bool make_suspended = false; /* Whether we've seen a SIGTSTP (etc) */ /* * Set of descriptors of pipes connected to * the output channels of children */ static struct pollfd *fds = NULL; static Job **jobByFdIndex = NULL; static nfds_t fdsLen = 0; static void watchfd(Job *); static void clearfd(Job *); static bool readyfd(Job *); static char *targPrefix = NULL; /* To identify a job change in the output. */ static Job tokenWaitJob; /* token wait pseudo-job */ static Job childExitJob; /* child exit pseudo-job */ #define CHILD_EXIT "." #define DO_JOB_RESUME "R" enum { npseudojobs = 2 /* number of pseudo-jobs */ }; static sigset_t caught_signals; /* Set of signals we handle */ static volatile sig_atomic_t caught_sigchld; static void CollectOutput(Job *, bool); static void JobInterrupt(bool, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); static void JobSigReset(void); static void SwitchOutputTo(GNode *gn) { /* The node for which output was most recently produced. */ static GNode *lastNode = NULL; if (gn == lastNode) return; lastNode = gn; if (opts.maxJobs != 1 && targPrefix != NULL && targPrefix[0] != '\0') (void)fprintf(stdout, "%s %s ---\n", targPrefix, gn->name); } static unsigned nfds_per_job(void) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) return 2; #endif return 1; } void Job_FlagsToString(const Job *job, char *buf, size_t bufsize) { snprintf(buf, bufsize, "%c%c%c", job->ignerr ? 'i' : '-', !job->echo ? 's' : '-', job->special ? 'S' : '-'); } static void DumpJobs(const char *where) { Job *job; char flags[4]; debug_printf("job table @ %s\n", where); for (job = job_table; job < job_table_end; job++) { Job_FlagsToString(job, flags, sizeof flags); debug_printf("job %d, status %d, flags %s, pid %d\n", (int)(job - job_table), job->status, flags, job->pid); } } /* * Delete the target of a failed, interrupted, or otherwise * unsuccessful job unless inhibited by .PRECIOUS. */ static void JobDeleteTarget(GNode *gn) { const char *file; if (gn->type & OP_JOIN) return; if (gn->type & OP_PHONY) return; if (GNode_IsPrecious(gn)) return; if (opts.noExecute) return; file = GNode_Path(gn); if (unlink_file(file) == 0) Error("*** %s removed", file); } /* * JobSigLock/JobSigUnlock * * Signal lock routines to get exclusive access. Currently used to * protect `jobs' and `stoppedJobs' list manipulations. */ static void JobSigLock(sigset_t *omaskp) { if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) { Punt("JobSigLock: sigprocmask: %s", strerror(errno)); sigemptyset(omaskp); } } static void JobSigUnlock(sigset_t *omaskp) { (void)sigprocmask(SIG_SETMASK, omaskp, NULL); } static void JobCreatePipe(Job *job, int minfd) { int i, fd, flags; int pipe_fds[2]; if (pipe(pipe_fds) == -1) Punt("Cannot create pipe: %s", strerror(errno)); for (i = 0; i < 2; i++) { /* Avoid using low-numbered fds */ fd = fcntl(pipe_fds[i], F_DUPFD, minfd); if (fd != -1) { close(pipe_fds[i]); pipe_fds[i] = fd; } } job->inPipe = pipe_fds[0]; job->outPipe = pipe_fds[1]; if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1) Punt("Cannot set close-on-exec: %s", strerror(errno)); if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1) Punt("Cannot set close-on-exec: %s", strerror(errno)); /* * We mark the input side of the pipe non-blocking; we poll(2) the * pipe when we're waiting for a job token, but we might lose the * race for the token when a new one becomes available, so the read * from the pipe should not block. */ flags = fcntl(job->inPipe, F_GETFL, 0); if (flags == -1) Punt("Cannot get flags: %s", strerror(errno)); flags |= O_NONBLOCK; if (fcntl(job->inPipe, F_SETFL, flags) == -1) Punt("Cannot set flags: %s", strerror(errno)); } /* Pass the signal to each running job. */ static void JobCondPassSig(int signo) { Job *job; DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); for (job = job_table; job < job_table_end; job++) { if (job->status != JOB_ST_RUNNING) continue; DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", signo, job->pid); KILLPG(job->pid, signo); } } /* * SIGCHLD handler. * * Sends a token on the child exit pipe to wake us up from select()/poll(). */ static void JobChildSig(int signo MAKE_ATTR_UNUSED) { caught_sigchld = 1; while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN) continue; } /* Resume all stopped jobs. */ static void JobContinueSig(int signo MAKE_ATTR_UNUSED) { /* * Defer sending SIGCONT to our stopped children until we return * from the signal handler. */ while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && errno == EAGAIN) continue; } /* * Pass a signal on to all jobs, then resend to ourselves. * We die by the same signal. */ MAKE_ATTR_DEAD static void JobPassSig_int(int signo) { /* Run .INTERRUPT target then exit */ JobInterrupt(true, signo); } /* * Pass a signal on to all jobs, then resend to ourselves. * We die by the same signal. */ MAKE_ATTR_DEAD static void JobPassSig_term(int signo) { /* Dont run .INTERRUPT target then exit */ JobInterrupt(false, signo); } static void JobPassSig_suspend(int signo) { sigset_t nmask, omask; struct sigaction act; /* Suppress job started/continued messages */ make_suspended = true; /* Pass the signal onto every job */ JobCondPassSig(signo); /* * Send ourselves the signal now we've given the message to everyone * else. Note we block everything else possible while we're getting * the signal. This ensures that all our jobs get continued when we * wake up before we take any other signal. */ sigfillset(&nmask); sigdelset(&nmask, signo); (void)sigprocmask(SIG_SETMASK, &nmask, &omask); act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); act.sa_flags = 0; (void)sigaction(signo, &act, NULL); DEBUG1(JOB, "JobPassSig passing signal %d to self.\n", signo); (void)kill(getpid(), signo); /* * We've been continued. * * A whole host of signals is going to happen! * SIGCHLD for any processes that actually suspended themselves. * SIGCHLD for any processes that exited while we were asleep. * The SIGCONT that actually caused us to wake up. * * Since we defer passing the SIGCONT on to our children until * the main processing loop, we can be sure that all the SIGCHLD * events will have happened by then - and that the waitpid() will * collect the child 'suspended' events. * For correct sequencing we just need to ensure we process the * waitpid() before passing on the SIGCONT. * * In any case nothing else is needed here. */ /* Restore handler and signal mask */ act.sa_handler = JobPassSig_suspend; (void)sigaction(signo, &act, NULL); (void)sigprocmask(SIG_SETMASK, &omask, NULL); } static Job * JobFindPid(int pid, JobStatus status, bool isJobs) { Job *job; for (job = job_table; job < job_table_end; job++) { if (job->status == status && job->pid == pid) return job; } if (DEBUG(JOB) && isJobs) DumpJobs("no pid"); return NULL; } /* Parse leading '@', '-' and '+', which control the exact execution mode. */ static void ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags) { char *p = *pp; out_cmdFlags->echo = true; out_cmdFlags->ignerr = false; out_cmdFlags->always = false; for (;;) { if (*p == '@') out_cmdFlags->echo = DEBUG(LOUD); else if (*p == '-') out_cmdFlags->ignerr = true; else if (*p == '+') out_cmdFlags->always = true; else if (!ch_isspace(*p)) /* Ignore whitespace for compatibility with GNU make */ break; p++; } pp_skip_whitespace(&p); *pp = p; } /* Escape a string for a double-quoted string literal in sh, csh and ksh. */ static char * EscapeShellDblQuot(const char *cmd) { size_t i, j; /* Worst that could happen is every char needs escaping. */ char *esc = bmake_malloc(strlen(cmd) * 2 + 1); for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) { if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || cmd[i] == '"') esc[j++] = '\\'; esc[j] = cmd[i]; } esc[j] = '\0'; return esc; } static void ShellWriter_WriteFmt(ShellWriter *wr, const char *fmt, const char *arg) { DEBUG1(JOB, fmt, arg); (void)fprintf(wr->f, fmt, arg); if (wr->f == stdout) (void)fflush(wr->f); } static void ShellWriter_WriteLine(ShellWriter *wr, const char *line) { ShellWriter_WriteFmt(wr, "%s\n", line); } static void ShellWriter_EchoOff(ShellWriter *wr) { if (shell->hasEchoCtl) ShellWriter_WriteLine(wr, shell->echoOff); } static void ShellWriter_EchoCmd(ShellWriter *wr, const char *escCmd) { ShellWriter_WriteFmt(wr, shell->echoTmpl, escCmd); } static void ShellWriter_EchoOn(ShellWriter *wr) { if (shell->hasEchoCtl) ShellWriter_WriteLine(wr, shell->echoOn); } static void ShellWriter_TraceOn(ShellWriter *wr) { if (!wr->xtraced) { ShellWriter_WriteLine(wr, "set -x"); wr->xtraced = true; } } static void ShellWriter_ErrOff(ShellWriter *wr, bool echo) { if (echo) ShellWriter_EchoOff(wr); ShellWriter_WriteLine(wr, shell->errOff); if (echo) ShellWriter_EchoOn(wr); } static void ShellWriter_ErrOn(ShellWriter *wr, bool echo) { if (echo) ShellWriter_EchoOff(wr); ShellWriter_WriteLine(wr, shell->errOn); if (echo) ShellWriter_EchoOn(wr); } /* * The shell has no built-in error control, so emulate error control by * enclosing each shell command in a template like "{ %s \n } || exit $?" * (configurable per shell). */ static void JobWriteSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, const char *escCmd, const char **inout_cmdTemplate) { /* XXX: Why is the whole job modified at this point? */ job->ignerr = true; if (job->echo && inout_cmdFlags->echo) { ShellWriter_EchoOff(wr); ShellWriter_EchoCmd(wr, escCmd); /* * Leave echoing off so the user doesn't see the commands * for toggling the error checking. */ inout_cmdFlags->echo = false; } *inout_cmdTemplate = shell->runIgnTmpl; /* * The template runIgnTmpl already takes care of ignoring errors, * so pretend error checking is still on. * XXX: What effects does this have, and why is it necessary? */ inout_cmdFlags->ignerr = false; } static void JobWriteSpecials(Job *job, ShellWriter *wr, const char *escCmd, bool run, CommandFlags *inout_cmdFlags, const char **inout_cmdTemplate) { if (!run) inout_cmdFlags->ignerr = false; else if (shell->hasErrCtl) ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo); else if (shell->runIgnTmpl != NULL && shell->runIgnTmpl[0] != '\0') { JobWriteSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, inout_cmdTemplate); } else inout_cmdFlags->ignerr = false; } /* * Write a shell command to the job's commands file, to be run later. * * If the command starts with '@' and neither the -s nor the -n flag was * given to make, stick a shell-specific echoOff command in the script. * * If the command starts with '-' and the shell has no error control (none * of the predefined shells has that), ignore errors for the entire job. * * XXX: Why ignore errors for the entire job? This is even documented in the * manual page, but without any rationale since there is no known rationale. * * XXX: The manual page says the '-' "affects the entire job", but that's not * accurate. The '-' does not affect the commands before the '-'. * * If the command is just "...", skip all further commands of this job. These * commands are attached to the .END node instead and will be run by * Job_Finish after all other targets have been made. */ static void JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) { bool run; CommandFlags cmdFlags; /* Template for writing a command to the shell file */ const char *cmdTemplate; char *xcmd; /* The expanded command */ char *xcmdStart; char *escCmd; /* xcmd escaped to be used in double quotes */ run = GNode_ShouldExecute(job->node); xcmd = Var_SubstInTarget(ucmd, job->node); /* TODO: handle errors */ xcmdStart = xcmd; cmdTemplate = "%s\n"; ParseCommandFlags(&xcmd, &cmdFlags); /* The '+' command flag overrides the -n or -N options. */ if (cmdFlags.always && !run) { /* * We're not actually executing anything... * but this one needs to be - use compat mode just for it. */ (void)Compat_RunCommand(ucmd, job->node, ln); free(xcmdStart); return; } /* * If the shell doesn't have error control, the alternate echoing * will be done (to avoid showing additional error checking code) * and this needs some characters escaped. */ escCmd = shell->hasErrCtl ? NULL : EscapeShellDblQuot(xcmd); if (!cmdFlags.echo) { if (job->echo && run && shell->hasEchoCtl) ShellWriter_EchoOff(wr); else if (shell->hasErrCtl) cmdFlags.echo = true; } if (cmdFlags.ignerr) { JobWriteSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); } else { /* * If errors are being checked and the shell doesn't have * error control but does supply an runChkTmpl template, then * set up commands to run through it. */ if (!shell->hasErrCtl && shell->runChkTmpl != NULL && shell->runChkTmpl[0] != '\0') { if (job->echo && cmdFlags.echo) { ShellWriter_EchoOff(wr); ShellWriter_EchoCmd(wr, escCmd); cmdFlags.echo = false; } /* * If it's a comment line or blank, avoid the possible * syntax error generated by "{\n} || exit $?". */ cmdTemplate = escCmd[0] == shell->commentChar || escCmd[0] == '\0' ? shell->runIgnTmpl : shell->runChkTmpl; cmdFlags.ignerr = false; } } if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0) ShellWriter_TraceOn(wr); ShellWriter_WriteFmt(wr, cmdTemplate, xcmd); free(xcmdStart); free(escCmd); if (cmdFlags.ignerr) ShellWriter_ErrOn(wr, cmdFlags.echo && job->echo); if (!cmdFlags.echo) ShellWriter_EchoOn(wr); } /* * Write all commands to the shell file that is later executed. * * The special command "..." stops writing and saves the remaining commands * to be executed later, when the target '.END' is made. * * Return whether at least one command was written to the shell file. */ static bool JobWriteCommands(Job *job) { StringListNode *ln; bool seen = false; ShellWriter wr; wr.f = job->cmdFILE; wr.xtraced = false; for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; if (strcmp(cmd, "...") == 0) { job->node->type |= OP_SAVE_CMDS; job->tailCmds = ln->next; break; } JobWriteCommand(job, &wr, ln, ln->datum); seen = true; } return seen; } /* * Save the delayed commands (those after '...'), to be executed later in * the '.END' node, when everything else is done. */ static void JobSaveCommands(Job *job) { StringListNode *ln; for (ln = job->tailCmds; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; char *expanded_cmd; /* * XXX: This Var_Subst is only intended to expand the dynamic * variables such as .TARGET, .IMPSRC. It is not intended to * expand the other variables as well; see deptgt-end.mk. */ expanded_cmd = Var_SubstInTarget(cmd, job->node); /* TODO: handle errors */ Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd); Parse_RegisterCommand(expanded_cmd); } } /* Called to close both input and output pipes when a job is finished. */ static void JobClosePipes(Job *job) { clearfd(job); (void)close(job->outPipe); job->outPipe = -1; CollectOutput(job, true); (void)close(job->inPipe); job->inPipe = -1; } static void DebugFailedJob(const Job *job) { const StringListNode *ln; if (!DEBUG(ERROR)) return; debug_printf("\n"); debug_printf("*** Failed target: %s\n", job->node->name); debug_printf("*** In directory: %s\n", curdir); debug_printf("*** Failed commands:\n"); for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; debug_printf("\t%s\n", cmd); if (strchr(cmd, '$') != NULL) { char *xcmd = Var_Subst(cmd, job->node, VARE_EVAL); debug_printf("\t=> %s\n", xcmd); free(xcmd); } } } static void JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) { SwitchOutputTo(job->node); #ifdef USE_META if (useMeta) { meta_job_error(job, job->node, job->ignerr, WEXITSTATUS(*inout_status)); } #endif if (!shouldDieQuietly(job->node, -1)) { DebugFailedJob(job); (void)printf("*** [%s] Error code %d%s\n", job->node->name, WEXITSTATUS(*inout_status), job->ignerr ? " (ignored)" : ""); } if (job->ignerr) WAIT_STATUS(*inout_status) = 0; else { if (deleteOnError) JobDeleteTarget(job->node); PrintOnError(job->node, "\n"); } } static void JobFinishDoneExited(Job *job, WAIT_T *inout_status) { DEBUG2(JOB, "Process %d [%s] exited.\n", job->pid, job->node->name); if (WEXITSTATUS(*inout_status) != 0) JobFinishDoneExitedError(job, inout_status); else if (DEBUG(JOB)) { SwitchOutputTo(job->node); (void)printf("*** [%s] Completed successfully\n", job->node->name); } } static void JobFinishDoneSignaled(Job *job, WAIT_T status) { SwitchOutputTo(job->node); DebugFailedJob(job); (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); if (deleteOnError) JobDeleteTarget(job->node); } static void JobFinishDone(Job *job, WAIT_T *inout_status) { if (WIFEXITED(*inout_status)) JobFinishDoneExited(job, inout_status); else JobFinishDoneSignaled(job, *inout_status); (void)fflush(stdout); } /* * Do final processing for the given job including updating parent nodes and * starting new jobs as available/necessary. * * Deferred commands for the job are placed on the .END node. * * If there was a serious error (job_errors != 0; not an ignored one), no more * jobs will be started. * * Input: * job job to finish * status sub-why job went away */ static void JobFinish (Job *job, WAIT_T status) { bool done, return_job_token; DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", job->pid, job->node->name, status); if ((WIFEXITED(status) && ((WEXITSTATUS(status) != 0 && !job->ignerr))) || WIFSIGNALED(status)) { /* Finished because of an error. */ JobClosePipes(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { if (fclose(job->cmdFILE) != 0) Punt("Cannot write shell script for '%s': %s", job->node->name, strerror(errno)); job->cmdFILE = NULL; } done = true; } else if (WIFEXITED(status)) { /* * Deal with ignored errors in -B mode. We need to print a * message telling of the ignored error as well as to run * the next command. */ done = WEXITSTATUS(status) != 0; JobClosePipes(job); } else { /* No need to close things down or anything. */ done = false; } if (done) JobFinishDone(job, &status); #ifdef USE_META if (useMeta) { int meta_status = meta_job_finish(job); if (meta_status != 0 && status == 0) status = meta_status; } #endif return_job_token = false; Trace_Log(JOBEND, job); if (!job->special) { if (WAIT_STATUS(status) != 0 || (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) return_job_token = true; } if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && (WAIT_STATUS(status) == 0)) { /* * As long as we aren't aborting and the job didn't return a * non-zero status that we shouldn't ignore, we call * Make_Update to update the parents. */ JobSaveCommands(job); job->node->made = MADE; if (!job->special) return_job_token = true; Make_Update(job->node); job->status = JOB_ST_FREE; } else if (status != 0) { job_errors++; job->status = JOB_ST_FREE; } if (job_errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) { /* Prevent more jobs from getting started. */ aborting = ABORT_ERROR; } if (return_job_token) Job_TokenReturn(); if (aborting == ABORT_ERROR && jobTokensRunning == 0) Finish(job_errors); } static void TouchRegular(GNode *gn) { const char *file = GNode_Path(gn); struct utimbuf times; int fd; char c; times.actime = now; times.modtime = now; if (utime(file, ×) >= 0) return; fd = open(file, O_RDWR | O_CREAT, 0666); if (fd < 0) { (void)fprintf(stderr, "*** couldn't touch %s: %s\n", file, strerror(errno)); (void)fflush(stderr); return; /* XXX: What about propagating the error? */ } /* * Last resort: update the file's time stamps in the traditional way. * XXX: This doesn't work for empty files, which are sometimes used * as marker files. */ if (read(fd, &c, 1) == 1) { (void)lseek(fd, 0, SEEK_SET); while (write(fd, &c, 1) == -1 && errno == EAGAIN) continue; } (void)close(fd); /* XXX: What about propagating the error? */ } /* - * Touch the given target. Called by JobStart when the -t flag was given. + * Touch the given target. Called by Job_Make when the -t flag was given. * * The modification date of the file is changed. * If the file did not exist, it is created. */ void Job_Touch(GNode *gn, bool echo) { if (gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_OPTIONAL | OP_SPECIAL | OP_PHONY)) { /* * These are "virtual" targets and should not really be * created. */ return; } if (echo || !GNode_ShouldExecute(gn)) { (void)fprintf(stdout, "touch %s\n", gn->name); (void)fflush(stdout); } if (!GNode_ShouldExecute(gn)) return; if (gn->type & OP_ARCHV) Arch_Touch(gn); else if (gn->type & OP_LIB) Arch_TouchLib(gn); else TouchRegular(gn); } /* * Make sure the given node has all the commands it needs. * * The node will have commands from the .DEFAULT rule added to it if it * needs them. * * Input: * gn The target whose commands need verifying * abortProc Function to abort with message * * Results: * true if the commands list is/was ok. */ bool Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { if (GNode_IsTarget(gn)) return true; if (!Lst_IsEmpty(&gn->commands)) return true; if ((gn->type & OP_LIB) && !Lst_IsEmpty(&gn->children)) return true; /* * No commands. Look for .DEFAULT rule from which we might infer * commands. */ if (defaultNode != NULL && !Lst_IsEmpty(&defaultNode->commands) && !(gn->type & OP_SPECIAL)) { /* * The traditional Make only looks for a .DEFAULT if the node * was never the target of an operator, so that's what we do * too. * * The .DEFAULT node acts like a transformation rule, in that * gn also inherits any attributes or sources attached to * .DEFAULT itself. */ Make_HandleUse(defaultNode, gn); Var_Set(gn, IMPSRC, GNode_VarTarget(gn)); return true; } Dir_UpdateMTime(gn, false); if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) return true; /* * The node wasn't the target of an operator. We have no .DEFAULT * rule to go on and the target doesn't already exist. There's * nothing more we can do for this branch. If the -k flag wasn't * given, we stop in our tracks, otherwise we just don't update * this node's parents so they never get examined. */ if (gn->flags.fromDepend) { if (!Job_RunTarget(".STALE", gn->fname)) fprintf(stdout, "%s: %s, %u: ignoring stale %s for %s\n", progname, gn->fname, gn->lineno, makeDependfile, gn->name); return true; } if (gn->type & OP_OPTIONAL) { (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", progname, gn->name, "ignored"); (void)fflush(stdout); return true; } if (opts.keepgoing) { (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", progname, gn->name, "continuing"); (void)fflush(stdout); return false; } abortProc("don't know how to make %s. Stop", gn->name); return false; } /* * Execute the shell for the given job. * * See Job_CatchOutput for handling the output of the shell. */ static void JobExec(Job *job, char **argv) { int cpid; /* ID of new child */ sigset_t mask; if (DEBUG(JOB)) { int i; debug_printf("Running %s\n", job->node->name); debug_printf("\tCommand: "); for (i = 0; argv[i] != NULL; i++) { debug_printf("%s ", argv[i]); } debug_printf("\n"); } /* * Some jobs produce no output, and it's disconcerting to have * no feedback of their running (since they produce no output, the * banner with their name in it never appears). This is an attempt to * provide that feedback, even if nothing follows it. */ if (job->echo) SwitchOutputTo(job->node); /* No interruptions until this job is on the `jobs' list */ JobSigLock(&mask); /* Pre-emptively mark job running, pid still zero though */ job->status = JOB_ST_RUNNING; Var_ReexportVars(job->node); - cpid = vfork(); + cpid = FORK_FUNCTION(); if (cpid == -1) - Punt("Cannot vfork: %s", strerror(errno)); + Punt("Cannot fork: %s", strerror(errno)); if (cpid == 0) { /* Child */ sigset_t tmask; #ifdef USE_META if (useMeta) meta_job_child(job); #endif /* * Reset all signal handlers; this is necessary because we * also need to unblock signals before we exec(2). */ JobSigReset(); /* Now unblock signals */ sigemptyset(&tmask); JobSigUnlock(&tmask); /* * Must duplicate the input stream down to the child's input * and reset it to the beginning (again). Since the stream * was marked close-on-exec, we must clear that bit in the * new input. */ if (dup2(fileno(job->cmdFILE), STDIN_FILENO) == -1) execDie("dup2", "job->cmdFILE"); if (fcntl(STDIN_FILENO, F_SETFD, 0) == -1) execDie("fcntl clear close-on-exec", "stdin"); if (lseek(STDIN_FILENO, 0, SEEK_SET) == -1) execDie("lseek to 0", "stdin"); if (Always_pass_job_queue || (job->node->type & (OP_MAKE | OP_SUBMAKE))) { /* Pass job token pipe to submakes. */ if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) execDie("clear close-on-exec", "tokenWaitJob.inPipe"); if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) execDie("clear close-on-exec", "tokenWaitJob.outPipe"); } /* * Set up the child's output to be routed through the pipe * we've created for it. */ if (dup2(job->outPipe, STDOUT_FILENO) == -1) execDie("dup2", "job->outPipe"); /* * The output channels are marked close on exec. This bit * was duplicated by dup2 (on some systems), so we have * to clear it before routing the shell's error output to * the same place as its standard output. */ if (fcntl(STDOUT_FILENO, F_SETFD, 0) == -1) execDie("clear close-on-exec", "stdout"); if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) execDie("dup2", "1, 2"); /* * We want to switch the child into a different process * family so we can kill it and all its descendants in * one fell swoop, by killing its process family, but not * commit suicide. */ #if defined(HAVE_SETPGID) (void)setpgid(0, getpid()); #else # if defined(HAVE_SETSID) /* XXX: dsl - I'm sure this should be setpgrp()... */ (void)setsid(); # else (void)setpgrp(0, getpid()); # endif #endif (void)execv(shellPath, argv); execDie("exec", shellPath); } /* Parent, continuing after the child exec */ job->pid = cpid; Trace_Log(JOBSTART, job); #ifdef USE_META if (useMeta) meta_job_parent(job, cpid); #endif /* * Set the current position in the buffer to the beginning * and mark another stream to watch in the outputs mask */ job->curPos = 0; watchfd(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { if (fclose(job->cmdFILE) != 0) Punt("Cannot write shell script for '%s': %s", job->node->name, strerror(errno)); job->cmdFILE = NULL; } /* Now that the job is actually running, add it to the table. */ if (DEBUG(JOB)) { debug_printf("JobExec(%s): pid %d added to jobs table\n", job->node->name, job->pid); DumpJobs("job started"); } JobSigUnlock(&mask); } /* Create the argv needed to execute the shell for a given job. */ static void JobMakeArgv(Job *job, char **argv) { int argc; static char args[10]; /* For merged arguments */ argv[0] = UNCONST(shellName); argc = 1; if ((shell->errFlag != NULL && shell->errFlag[0] != '-') || (shell->echoFlag != NULL && shell->echoFlag[0] != '-')) { /* * At least one of the flags doesn't have a minus before it, * so merge them together. Have to do this because the Bourne * shell thinks its second argument is a file to source. * Grrrr. Note the ten-character limitation on the combined * arguments. * * TODO: Research until when the above comments were * practically relevant. */ (void)snprintf(args, sizeof args, "-%s%s", (job->ignerr ? "" : (shell->errFlag != NULL ? shell->errFlag : "")), (!job->echo ? "" : (shell->echoFlag != NULL ? shell->echoFlag : ""))); if (args[1] != '\0') { argv[argc] = args; argc++; } } else { if (!job->ignerr && shell->errFlag != NULL) { argv[argc] = UNCONST(shell->errFlag); argc++; } if (job->echo && shell->echoFlag != NULL) { argv[argc] = UNCONST(shell->echoFlag); argc++; } } argv[argc] = NULL; } static void JobWriteShellCommands(Job *job, GNode *gn, bool *out_run) { /* * tfile is the name of a file into which all shell commands * are put. It is removed before the child shell is executed, * unless DEBUG(SCRIPT) is set. */ char tfile[MAXPATHLEN]; int tfd; /* File descriptor to the temp file */ tfd = Job_TempFile(TMPPAT, tfile, sizeof tfile); job->cmdFILE = fdopen(tfd, "w+"); if (job->cmdFILE == NULL) Punt("Could not fdopen %s", tfile); (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); #ifdef USE_META if (useMeta) { meta_job_start(job, gn); if (gn->type & OP_SILENT) /* might have changed */ job->echo = false; } #endif *out_run = JobWriteCommands(job); } -/* - * Start a target-creation process going for the target described by gn. - * - * Results: - * JOB_ERROR if there was an error in the commands, JOB_FINISHED - * if there isn't actually anything left to do for the job and - * JOB_RUNNING if the job has been started. - * - * Details: - * A new Job node is created and added to the list of running - * jobs. PMake is forked and a child shell created. - * - * NB: The return value is ignored by everyone. - */ -static JobStartResult -JobStart(GNode *gn, bool special) +void +Job_Make(GNode *gn) { Job *job; /* new job descriptor */ char *argv[10]; /* Argument vector to shell */ bool cmdsOK; /* true if the nodes commands were all right */ bool run; for (job = job_table; job < job_table_end; job++) { if (job->status == JOB_ST_FREE) break; } if (job >= job_table_end) - Punt("JobStart no job slots vacant"); + Punt("Job_Make no job slots vacant"); memset(job, 0, sizeof *job); job->node = gn; job->tailCmds = NULL; job->status = JOB_ST_SET_UP; - job->special = special || gn->type & OP_SPECIAL; + job->special = (gn->type & OP_SPECIAL) != OP_NONE; job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE; job->echo = !(opts.silent || gn->type & OP_SILENT); /* * Check the commands now so any attributes from .DEFAULT have a * chance to migrate to the node. */ cmdsOK = Job_CheckCommands(gn, Error); job->inPollfd = NULL; if (Lst_IsEmpty(&gn->commands)) { job->cmdFILE = stdout; run = false; /* * We're serious here, but if the commands were bogus, we're * also dead... */ if (!cmdsOK) { PrintOnError(gn, "\n"); /* provide some clue */ DieHorribly(); } } else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || (!opts.noExecute && !opts.touch)) { /* * The above condition looks very similar to * GNode_ShouldExecute but is subtly different. It prevents * that .MAKE targets are touched since these are usually * virtual targets. */ + int parseErrorsBefore; + /* * We're serious here, but if the commands were bogus, we're * also dead... */ if (!cmdsOK) { PrintOnError(gn, "\n"); /* provide some clue */ DieHorribly(); } + parseErrorsBefore = parseErrors; JobWriteShellCommands(job, gn, &run); + if (parseErrors != parseErrorsBefore) + run = false; (void)fflush(job->cmdFILE); } else if (!GNode_ShouldExecute(gn)) { /* * Just write all the commands to stdout in one fell swoop. * This still sets up job->tailCmds correctly. */ SwitchOutputTo(gn); job->cmdFILE = stdout; if (cmdsOK) JobWriteCommands(job); run = false; (void)fflush(job->cmdFILE); } else { Job_Touch(gn, job->echo); run = false; } /* If we're not supposed to execute a shell, don't. */ if (!run) { if (!job->special) Job_TokenReturn(); /* Unlink and close the command file if we opened one */ if (job->cmdFILE != NULL && job->cmdFILE != stdout) { (void)fclose(job->cmdFILE); job->cmdFILE = NULL; } /* * We only want to work our way up the graph if we aren't * here because the commands for the job were no good. */ if (cmdsOK && aborting == ABORT_NONE) { JobSaveCommands(job); job->node->made = MADE; Make_Update(job->node); } job->status = JOB_ST_FREE; - return cmdsOK ? JOB_FINISHED : JOB_ERROR; + return; } /* * Set up the control arguments to the shell. This is based on the * flags set earlier for this job. */ JobMakeArgv(job, argv); /* Create the pipe by which we'll get the shell's output. */ JobCreatePipe(job, 3); JobExec(job, argv); - return JOB_RUNNING; } /* * If the shell has an output filter (which only csh and ksh have by default), * print the output of the child process, skipping the noPrint text of the * shell. * * Return the part of the output that the calling function needs to output by * itself. */ static char * PrintFilteredOutput(char *p, const char *endp) /* XXX: p should be const */ { char *ep; /* XXX: should be const */ if (shell->noPrint == NULL || shell->noPrint[0] == '\0') return p; /* * XXX: What happens if shell->noPrint occurs on the boundary of * the buffer? To work correctly in all cases, this should rather * be a proper stream filter instead of doing string matching on * selected chunks of the output. */ while ((ep = strstr(p, shell->noPrint)) != NULL) { if (ep != p) { *ep = '\0'; /* XXX: avoid writing to the buffer */ /* * The only way there wouldn't be a newline after * this line is if it were the last in the buffer. * however, since the noPrint output comes after it, * there must be a newline, so we don't print one. */ /* XXX: What about null bytes in the output? */ (void)fprintf(stdout, "%s", p); (void)fflush(stdout); } p = ep + shell->noPrintLen; if (p == endp) break; p++; /* skip over the (XXX: assumed) newline */ pp_skip_whitespace(&p); } return p; } /* * This function is called whenever there is something to read on the pipe. * We collect more output from the given job and store it in the job's * outBuf. If this makes up a line, we print it tagged by the job's * identifier, as necessary. * * In the output of the shell, the 'noPrint' lines are removed. If the * command is not alone on the line (the character after it is not \0 or * \n), we do print whatever follows it. * * Input: * job the job whose output needs printing * finish true if this is the last time we'll be called * for this job */ static void CollectOutput(Job *job, bool finish) { bool gotNL; /* true if got a newline */ bool fbuf; /* true if our buffer filled up */ size_t nr; /* number of bytes read */ size_t i; /* auxiliary index into outBuf */ size_t max; /* limit for i (end of current data) */ ssize_t nRead; /* (Temporary) number of bytes read */ /* Read as many bytes as will fit in the buffer. */ again: gotNL = false; fbuf = false; nRead = read(job->inPipe, &job->outBuf[job->curPos], JOB_BUFSIZE - job->curPos); if (nRead < 0) { if (errno == EAGAIN) return; if (DEBUG(JOB)) perror("CollectOutput(piperead)"); nr = 0; } else nr = (size_t)nRead; if (nr == 0) finish = false; /* stop looping */ /* * If we hit the end-of-file (the job is dead), we must flush its * remaining output, so pretend we read a newline if there's any * output remaining in the buffer. */ if (nr == 0 && job->curPos != 0) { job->outBuf[job->curPos] = '\n'; nr = 1; } max = job->curPos + nr; for (i = job->curPos; i < max; i++) if (job->outBuf[i] == '\0') job->outBuf[i] = ' '; /* Look for the last newline in the bytes we just got. */ for (i = job->curPos + nr - 1; i >= job->curPos && i != (size_t)-1; i--) { if (job->outBuf[i] == '\n') { gotNL = true; break; } } if (!gotNL) { job->curPos += nr; if (job->curPos == JOB_BUFSIZE) { /* * If we've run out of buffer space, we have no choice * but to print the stuff. sigh. */ fbuf = true; i = job->curPos; } } if (gotNL || fbuf) { /* * Need to send the output to the screen. Null terminate it * first, overwriting the newline character if there was one. * So long as the line isn't one we should filter (according * to the shell description), we print the line, preceded * by a target banner if this target isn't the same as the * one for which we last printed something. * The rest of the data in the buffer are then shifted down * to the start of the buffer and curPos is set accordingly. */ job->outBuf[i] = '\0'; if (i >= job->curPos) { char *p; /* * FIXME: SwitchOutputTo should be here, according to * the comment above. But since PrintOutput does not * do anything in the default shell, this bug has gone * unnoticed until now. */ p = PrintFilteredOutput(job->outBuf, &job->outBuf[i]); /* * There's still more in the output buffer. This time, * though, we know there's no newline at the end, so * we add one of our own free will. */ if (*p != '\0') { if (!opts.silent) SwitchOutputTo(job->node); #ifdef USE_META if (useMeta) { meta_job_output(job, p, gotNL ? "\n" : ""); } #endif (void)fprintf(stdout, "%s%s", p, gotNL ? "\n" : ""); (void)fflush(stdout); } } /* * max is the last offset still in the buffer. Move any * remaining characters to the start of the buffer and * update the end marker curPos. */ if (i < max) { (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); job->curPos = max - (i + 1); } else { assert(i == max); job->curPos = 0; } } if (finish) { /* * If the finish flag is true, we must loop until we hit * end-of-file on the pipe. This is guaranteed to happen * eventually since the other end of the pipe is now closed * (we closed it explicitly and the child has exited). When * we do get an EOF, finish will be set false and we'll fall * through and out. */ goto again; } } static void JobRun(GNode *targ) { -#if 0 - /* - * Unfortunately it is too complicated to run .BEGIN, .END, and - * .INTERRUPT job in the parallel job module. As of 2020-09-25, - * unit-tests/deptgt-end-jobs.mk hangs in an endless loop. - * - * Running these jobs in compat mode also guarantees that these - * jobs do not overlap with other unrelated jobs. - */ - GNodeList lst = LST_INIT; - Lst_Append(&lst, targ); - (void)Make_Run(&lst); - Lst_Done(&lst); - JobStart(targ, true); - while (jobTokensRunning != 0) { - Job_CatchOutput(); - } -#else + /* Don't let these special jobs overlap with other unrelated jobs. */ Compat_Make(targ, targ); - /* XXX: Replace with GNode_IsError(gn) */ - if (targ->made == ERROR) { + if (GNode_IsError(targ)) { PrintOnError(targ, "\n\nStop.\n"); exit(1); } -#endif } /* * Handle the exit of a child. Called from Make_Make. * * The job descriptor is removed from the list of children. * * Notes: * We do waits, blocking or not, according to the wisdom of our * caller, until there are no more children to report. For each * job, call JobFinish to finish things off. */ void Job_CatchChildren(void) { int pid; /* pid of dead child */ WAIT_T status; /* Exit/termination status */ /* Don't even bother if we know there's no one around. */ if (jobTokensRunning == 0) return; /* Have we received SIGCHLD since last call? */ if (caught_sigchld == 0) return; caught_sigchld = 0; while ((pid = waitpid((pid_t)-1, &status, WNOHANG | WUNTRACED)) > 0) { DEBUG2(JOB, "Process %d exited/stopped status %x.\n", pid, WAIT_STATUS(status)); JobReapChild(pid, status, true); } } /* * It is possible that wait[pid]() was called from elsewhere, * this lets us reap jobs regardless. */ void JobReapChild(pid_t pid, WAIT_T status, bool isJobs) { Job *job; /* job descriptor for dead child */ /* Don't even bother if we know there's no one around. */ if (jobTokensRunning == 0) return; job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); if (job == NULL) { if (isJobs) { if (!lurking_children) Error("Child (%d) status %x not in table?", pid, status); } return; /* not ours */ } if (WIFSTOPPED(status)) { DEBUG2(JOB, "Process %d (%s) stopped.\n", job->pid, job->node->name); if (!make_suspended) { switch (WSTOPSIG(status)) { case SIGTSTP: (void)printf("*** [%s] Suspended\n", job->node->name); break; case SIGSTOP: (void)printf("*** [%s] Stopped\n", job->node->name); break; default: (void)printf("*** [%s] Stopped -- signal %d\n", job->node->name, WSTOPSIG(status)); } job->suspended = true; } (void)fflush(stdout); return; } job->status = JOB_ST_FINISHED; job->exit_status = WAIT_STATUS(status); if (WIFEXITED(status)) job->node->exit_status = WEXITSTATUS(status); JobFinish(job, status); } /* * Catch the output from our children, if we're using pipes do so. Otherwise * just block time until we get a signal(most likely a SIGCHLD) since there's * no point in just spinning when there's nothing to do and the reaping of a * child can wait for a while. */ void Job_CatchOutput(void) { int nready; Job *job; unsigned int i; (void)fflush(stdout); /* The first fd in the list is the job token pipe */ do { nready = poll(fds + 1 - wantToken, fdsLen - 1 + wantToken, POLL_MSEC); } while (nready < 0 && errno == EINTR); if (nready < 0) Punt("poll: %s", strerror(errno)); if (nready > 0 && readyfd(&childExitJob)) { char token = 0; ssize_t count; count = read(childExitJob.inPipe, &token, 1); if (count == 1) { if (token == DO_JOB_RESUME[0]) /* * Complete relay requested from our SIGCONT * handler */ JobRestartJobs(); } else if (count == 0) Punt("unexpected eof on token pipe"); else if (errno != EAGAIN) Punt("token pipe read: %s", strerror(errno)); nready--; } Job_CatchChildren(); if (nready == 0) return; for (i = npseudojobs * nfds_per_job(); i < fdsLen; i++) { if (fds[i].revents == 0) continue; job = jobByFdIndex[i]; if (job->status == JOB_ST_RUNNING) CollectOutput(job, false); #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) /* * With meta mode, we may have activity on the job's filemon * descriptor too, which at the moment is any pollfd other * than job->inPollfd. */ if (useMeta && job->inPollfd != &fds[i]) { if (meta_job_event(job) <= 0) fds[i].events = 0; /* never mind */ } #endif if (--nready == 0) return; } } -/* - * Start the creation of a target. Basically a front-end for JobStart used by - * the Make module. - */ -void -Job_Make(GNode *gn) -{ - (void)JobStart(gn, false); -} - static void InitShellNameAndPath(void) { shellName = shell->name; #ifdef DEFSHELL_CUSTOM if (shellName[0] == '/') { shellPath = bmake_strdup(shellName); shellName = str_basename(shellPath); return; } #endif #ifdef DEFSHELL_PATH shellPath = bmake_strdup(DEFSHELL_PATH); #else shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); #endif } void Shell_Init(void) { if (shellPath == NULL) InitShellNameAndPath(); Var_SetWithFlags(SCOPE_CMDLINE, ".SHELL", shellPath, VAR_SET_INTERNAL|VAR_SET_READONLY); if (shell->errFlag == NULL) shell->errFlag = ""; if (shell->echoFlag == NULL) shell->echoFlag = ""; if (shell->hasErrCtl && shell->errFlag[0] != '\0') { if (shellErrFlag != NULL && strcmp(shell->errFlag, &shellErrFlag[1]) != 0) { free(shellErrFlag); shellErrFlag = NULL; } if (shellErrFlag == NULL) shellErrFlag = str_concat2("-", shell->errFlag); } else if (shellErrFlag != NULL) { free(shellErrFlag); shellErrFlag = NULL; } } /* * Return the string literal that is used in the current command shell * to produce a newline character. */ const char * Shell_GetNewline(void) { return shell->newline; } void Job_SetPrefix(void) { if (targPrefix != NULL) free(targPrefix); else if (!Var_Exists(SCOPE_GLOBAL, ".MAKE.JOB.PREFIX")) Global_Set(".MAKE.JOB.PREFIX", "---"); targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ } static void AddSig(int sig, SignalProc handler) { if (bmake_signal(sig, SIG_IGN) != SIG_IGN) { sigaddset(&caught_signals, sig); (void)bmake_signal(sig, handler); } } /* Initialize the process module. */ void Job_Init(void) { Job_SetPrefix(); /* Allocate space for all the job info */ job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table); memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table); job_table_end = job_table + opts.maxJobs; wantToken = 0; caught_sigchld = 0; aborting = ABORT_NONE; job_errors = 0; Always_pass_job_queue = GetBooleanExpr(MAKE_ALWAYS_PASS_JOB_QUEUE, Always_pass_job_queue); Job_error_token = GetBooleanExpr(MAKE_JOB_ERROR_TOKEN, Job_error_token); /* * There is a non-zero chance that we already have children. * eg after 'make -f- < 0) continue; if (rval == 0) lurking_children = true; break; } Shell_Init(); JobCreatePipe(&childExitJob, 3); { /* Preallocate enough for the maximum number of jobs. */ size_t nfds = (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job(); fds = bmake_malloc(sizeof *fds * nfds); jobByFdIndex = bmake_malloc(sizeof *jobByFdIndex * nfds); } /* These are permanent entries and take slots 0 and 1 */ watchfd(&tokenWaitJob); watchfd(&childExitJob); sigemptyset(&caught_signals); /* Install a SIGCHLD handler. */ (void)bmake_signal(SIGCHLD, JobChildSig); sigaddset(&caught_signals, SIGCHLD); /* * Catch the four signals that POSIX specifies if they aren't ignored. * JobPassSig will take care of calling JobInterrupt if appropriate. */ AddSig(SIGINT, JobPassSig_int); AddSig(SIGHUP, JobPassSig_term); AddSig(SIGTERM, JobPassSig_term); AddSig(SIGQUIT, JobPassSig_term); /* * There are additional signals that need to be caught and passed if * either the export system wants to be told directly of signals or if * we're giving each job its own process group (since then it won't get * signals from the terminal driver as we own the terminal) */ AddSig(SIGTSTP, JobPassSig_suspend); AddSig(SIGTTOU, JobPassSig_suspend); AddSig(SIGTTIN, JobPassSig_suspend); AddSig(SIGWINCH, JobCondPassSig); AddSig(SIGCONT, JobContinueSig); (void)Job_RunTarget(".BEGIN", NULL); /* * Create the .END node now, even though no code in the unit tests * depends on it. See also Targ_GetEndNode in Compat_MakeAll. */ (void)Targ_GetEndNode(); } static void DelSig(int sig) { if (sigismember(&caught_signals, sig) != 0) (void)bmake_signal(sig, SIG_DFL); } static void JobSigReset(void) { DelSig(SIGINT); DelSig(SIGHUP); DelSig(SIGQUIT); DelSig(SIGTERM); DelSig(SIGTSTP); DelSig(SIGTTOU); DelSig(SIGTTIN); DelSig(SIGWINCH); DelSig(SIGCONT); (void)bmake_signal(SIGCHLD, SIG_DFL); } /* Find a shell in 'shells' given its name, or return NULL. */ static Shell * FindShellByName(const char *name) { Shell *sh = shells; const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0]; for (sh = shells; sh < shellsEnd; sh++) { if (strcmp(name, sh->name) == 0) return sh; } return NULL; } /* * Parse a shell specification and set up 'shell', shellPath and * shellName appropriately. * * Input: * line The shell spec * * Results: * false if the specification was incorrect. * * Side Effects: * 'shell' points to a Shell structure (either predefined or * created from the shell spec), shellPath is the full path of the * shell described by 'shell', while shellName is just the * final component of shellPath. * * Notes: * A shell specification consists of a .SHELL target, with dependency * operator, followed by a series of blank-separated words. Double * quotes can be used to use blanks in words. A backslash escapes * anything (most notably a double-quote and a space) and * provides the functionality it does in C. Each word consists of * keyword and value separated by an equal sign. There should be no * unnecessary spaces in the word. The keywords are as follows: * name Name of shell. * path Location of shell. * quiet Command to turn off echoing. * echo Command to turn echoing on * filter Result of turning off echoing that shouldn't be * printed. * echoFlag Flag to turn echoing on at the start * errFlag Flag to turn error checking on at the start * hasErrCtl True if shell has error checking control * newline String literal to represent a newline char * check Command to turn on error checking if hasErrCtl * is true or template of command to echo a command * for which error checking is off if hasErrCtl is * false. * ignore Command to turn off error checking if hasErrCtl * is true or template of command to execute a * command so as to ignore any errors it returns if * hasErrCtl is false. */ bool Job_ParseShell(char *line) { Words wordsList; char **words; char **argv; size_t argc; char *path; Shell newShell; bool fullSpec = false; Shell *sh; /* XXX: don't use line as an iterator variable */ pp_skip_whitespace(&line); free(shell_freeIt); memset(&newShell, 0, sizeof newShell); /* Parse the specification by keyword. */ wordsList = Str_Words(line, true); words = wordsList.words; argc = wordsList.len; path = wordsList.freeIt; if (words == NULL) { Error("Unterminated quoted string [%s]", line); return false; } shell_freeIt = path; for (path = NULL, argv = words; argc != 0; argc--, argv++) { char *arg = *argv; if (strncmp(arg, "path=", 5) == 0) { path = arg + 5; } else if (strncmp(arg, "name=", 5) == 0) { newShell.name = arg + 5; } else { if (strncmp(arg, "quiet=", 6) == 0) { newShell.echoOff = arg + 6; } else if (strncmp(arg, "echo=", 5) == 0) { newShell.echoOn = arg + 5; } else if (strncmp(arg, "filter=", 7) == 0) { newShell.noPrint = arg + 7; newShell.noPrintLen = strlen(newShell.noPrint); } else if (strncmp(arg, "echoFlag=", 9) == 0) { newShell.echoFlag = arg + 9; } else if (strncmp(arg, "errFlag=", 8) == 0) { newShell.errFlag = arg + 8; } else if (strncmp(arg, "hasErrCtl=", 10) == 0) { char c = arg[10]; newShell.hasErrCtl = c == 'Y' || c == 'y' || c == 'T' || c == 't'; } else if (strncmp(arg, "newline=", 8) == 0) { newShell.newline = arg + 8; } else if (strncmp(arg, "check=", 6) == 0) { /* * Before 2020-12-10, these two variables had * been a single variable. */ newShell.errOn = arg + 6; newShell.echoTmpl = arg + 6; } else if (strncmp(arg, "ignore=", 7) == 0) { /* * Before 2020-12-10, these two variables had * been a single variable. */ newShell.errOff = arg + 7; newShell.runIgnTmpl = arg + 7; } else if (strncmp(arg, "errout=", 7) == 0) { newShell.runChkTmpl = arg + 7; } else if (strncmp(arg, "comment=", 8) == 0) { newShell.commentChar = arg[8]; } else { Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", arg); free(words); return false; } fullSpec = true; } } if (path == NULL) { /* * If no path was given, the user wants one of the * pre-defined shells, yes? So we find the one s/he wants * with the help of FindShellByName and set things up the * right way. shellPath will be set up by Shell_Init. */ if (newShell.name == NULL) { Parse_Error(PARSE_FATAL, "Neither path nor name specified"); free(words); return false; } else { if ((sh = FindShellByName(newShell.name)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", newShell.name); free(words); return false; } shell = sh; shellName = newShell.name; if (shellPath != NULL) { /* * Shell_Init has already been called! * Do it again. */ free(shellPath); shellPath = NULL; Shell_Init(); } } } else { free(shellPath); shellPath = bmake_strdup(path); shellName = newShell.name != NULL ? newShell.name : str_basename(path); if (!fullSpec) { if ((sh = FindShellByName(shellName)) == NULL) { Parse_Error(PARSE_WARNING, "%s: No matching shell", shellName); free(words); return false; } shell = sh; } else { shell = bmake_malloc(sizeof *shell); *shell = newShell; } /* this will take care of shellErrFlag */ Shell_Init(); } if (shell->echoOn != NULL && shell->echoOff != NULL) shell->hasEchoCtl = true; if (!shell->hasErrCtl) { if (shell->echoTmpl == NULL) shell->echoTmpl = ""; if (shell->runIgnTmpl == NULL) shell->runIgnTmpl = "%s\n"; } /* * Do not free up the words themselves, since they might be in use * by the shell specification. */ free(words); return true; } /* * Handle the receipt of an interrupt. * * All children are killed. Another job will be started if the .INTERRUPT * target is defined. * * Input: * runINTERRUPT Non-zero if commands for the .INTERRUPT target * should be executed * signo signal received */ static void JobInterrupt(bool runINTERRUPT, int signo) { Job *job; /* job descriptor in that element */ GNode *interrupt; /* the node describing the .INTERRUPT target */ sigset_t mask; GNode *gn; aborting = ABORT_INTERRUPT; JobSigLock(&mask); for (job = job_table; job < job_table_end; job++) { if (job->status != JOB_ST_RUNNING) continue; gn = job->node; JobDeleteTarget(gn); if (job->pid != 0) { DEBUG2(JOB, "JobInterrupt passing signal %d to child %d.\n", signo, job->pid); KILLPG(job->pid, signo); } } JobSigUnlock(&mask); if (runINTERRUPT && !opts.touch) { interrupt = Targ_FindNode(".INTERRUPT"); if (interrupt != NULL) { opts.ignoreErrors = false; JobRun(interrupt); } } Trace_Log(MAKEINTR, NULL); exit(signo); /* XXX: why signo? */ } /* * Do the final processing, i.e. run the commands attached to the .END target. * * Return the number of errors reported. */ int Job_Finish(void) { GNode *endNode = Targ_GetEndNode(); if (!Lst_IsEmpty(&endNode->commands) || !Lst_IsEmpty(&endNode->children)) { if (job_errors != 0) Error("Errors reported so .END ignored"); else JobRun(endNode); } return job_errors; } #ifdef CLEANUP /* Clean up any memory used by the jobs module. */ void Job_End(void) { free(shell_freeIt); } #endif /* * Waits for all running jobs to finish and returns. * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. */ void Job_Wait(void) { aborting = ABORT_WAIT; while (jobTokensRunning != 0) { Job_CatchOutput(); } aborting = ABORT_NONE; } /* * Abort all currently running jobs without handling output or anything. * This function is to be called only in the event of a major error. * Most definitely NOT to be called from JobInterrupt. * * All children are killed, not just the firstborn. */ void Job_AbortAll(void) { Job *job; /* the job descriptor in that element */ WAIT_T foo; aborting = ABORT_ERROR; if (jobTokensRunning != 0) { for (job = job_table; job < job_table_end; job++) { if (job->status != JOB_ST_RUNNING) continue; /* * kill the child process with increasingly drastic * signals to make darn sure it's dead. */ KILLPG(job->pid, SIGINT); KILLPG(job->pid, SIGKILL); } } /* * Catch as many children as want to report in at first, then give up */ while (waitpid((pid_t)-1, &foo, WNOHANG) > 0) continue; } /* * Tries to restart stopped jobs if there are slots available. * Called in process context in response to a SIGCONT. */ static void JobRestartJobs(void) { Job *job; for (job = job_table; job < job_table_end; job++) { if (job->status == JOB_ST_RUNNING && (make_suspended || job->suspended)) { DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); if (job->suspended) { (void)printf("*** [%s] Continued\n", job->node->name); (void)fflush(stdout); } job->suspended = false; if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { debug_printf("Failed to send SIGCONT to %d\n", job->pid); } } if (job->status == JOB_ST_FINISHED) { /* * Job exit deferred after calling waitpid() in a * signal handler */ JobFinish(job, job->exit_status); } } make_suspended = false; } static void watchfd(Job *job) { if (job->inPollfd != NULL) Punt("Watching watched job"); fds[fdsLen].fd = job->inPipe; fds[fdsLen].events = POLLIN; jobByFdIndex[fdsLen] = job; job->inPollfd = &fds[fdsLen]; fdsLen++; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { fds[fdsLen].fd = meta_job_fd(job); fds[fdsLen].events = fds[fdsLen].fd == -1 ? 0 : POLLIN; jobByFdIndex[fdsLen] = job; fdsLen++; } #endif } static void clearfd(Job *job) { size_t i; if (job->inPollfd == NULL) Punt("Unwatching unwatched job"); i = (size_t)(job->inPollfd - fds); fdsLen--; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { /* * Sanity check: there should be two fds per job, so the job's * pollfd number should be even. */ assert(nfds_per_job() == 2); if (i % 2 != 0) Punt("odd-numbered fd with meta"); fdsLen--; } #endif /* Move last job in table into hole made by dead job. */ if (fdsLen != i) { fds[i] = fds[fdsLen]; jobByFdIndex[i] = jobByFdIndex[fdsLen]; jobByFdIndex[i]->inPollfd = &fds[i]; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) if (useMeta) { fds[i + 1] = fds[fdsLen + 1]; jobByFdIndex[i + 1] = jobByFdIndex[fdsLen + 1]; } #endif } job->inPollfd = NULL; } static bool readyfd(Job *job) { if (job->inPollfd == NULL) Punt("Polling unwatched job"); return (job->inPollfd->revents & POLLIN) != 0; } /* * Put a token (back) into the job pipe. * This allows a make process to start a build job. */ static void JobTokenAdd(void) { char tok = JOB_TOKENS[aborting], tok1; if (!Job_error_token && aborting == ABORT_ERROR) { if (jobTokensRunning == 0) return; tok = '+'; /* no error token */ } /* If we are depositing an error token flush everything else */ while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n", getpid(), aborting, tok); while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; } /* Get a temp file */ int Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz) { int fd; sigset_t mask; JobSigLock(&mask); fd = mkTempFile(pattern, tfile, tfile_sz); if (tfile != NULL && !DEBUG(SCRIPT)) unlink(tfile); JobSigUnlock(&mask); return fd; } /* Prep the job token pipe in the root make process. */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { int i; char jobarg[64]; if (jp_0 >= 0 && jp_1 >= 0) { /* Pipe passed in from parent */ tokenWaitJob.inPipe = jp_0; tokenWaitJob.outPipe = jp_1; (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC); (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC); return; } JobCreatePipe(&tokenWaitJob, 15); snprintf(jobarg, sizeof jobarg, "%d,%d", tokenWaitJob.inPipe, tokenWaitJob.outPipe); Global_Append(MAKEFLAGS, "-J"); Global_Append(MAKEFLAGS, jobarg); /* * Preload the job pipe with one token per job, save the one * "extra" token for the primary job. * * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is * larger than the write buffer size of the pipe, we will * deadlock here. */ for (i = 1; i < max_tokens; i++) JobTokenAdd(); } /* Return a withdrawn token to the pool. */ void Job_TokenReturn(void) { jobTokensRunning--; if (jobTokensRunning < 0) Punt("token botch"); if (jobTokensRunning != 0 || JOB_TOKENS[aborting] != '+') JobTokenAdd(); } /* * Attempt to withdraw a token from the pool. * * If pool is empty, set wantToken so that we wake up when a token is * released. * * Returns true if a token was withdrawn, and false if the pool is currently * empty. */ bool Job_TokenWithdraw(void) { char tok, tok1; ssize_t count; wantToken = 0; DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n", getpid(), aborting, jobTokensRunning); if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs)) return false; count = read(tokenWaitJob.inPipe, &tok, 1); if (count == 0) Fatal("eof on job pipe!"); if (count < 0 && jobTokensRunning != 0) { if (errno != EAGAIN) Fatal("job pipe read: %s", strerror(errno)); DEBUG1(JOB, "(%d) blocked for token\n", getpid()); wantToken = 1; return false; } if (count == 1 && tok != '+') { /* make being aborted - remove any other job tokens */ DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok); while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; /* And put the stopper back */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; if (shouldDieQuietly(NULL, 1)) exit(6); /* we aborted */ Fatal("A failure has been detected " "in another branch of the parallel make"); } if (count == 1 && jobTokensRunning == 0) /* We didn't want the token really */ while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; jobTokensRunning++; DEBUG1(JOB, "(%d) withdrew token\n", getpid()); return true; } /* * Run the named target if found. If a filename is specified, then set that * to the sources. * * Exits if the target fails. */ bool Job_RunTarget(const char *target, const char *fname) { GNode *gn = Targ_FindNode(target); if (gn == NULL) return false; if (fname != NULL) Var_Set(gn, ALLSRC, fname); JobRun(gn); - /* XXX: Replace with GNode_IsError(gn) */ - if (gn->made == ERROR) { - PrintOnError(gn, "\n\nStop.\n"); - exit(1); - } return true; } #ifdef USE_SELECT int emul_poll(struct pollfd *fd, int nfd, int timeout) { fd_set rfds, wfds; int i, maxfd, nselect, npoll; struct timeval tv, *tvp; long usecs; FD_ZERO(&rfds); FD_ZERO(&wfds); maxfd = -1; for (i = 0; i < nfd; i++) { fd[i].revents = 0; if (fd[i].events & POLLIN) FD_SET(fd[i].fd, &rfds); if (fd[i].events & POLLOUT) FD_SET(fd[i].fd, &wfds); if (fd[i].fd > maxfd) maxfd = fd[i].fd; } if (maxfd >= FD_SETSIZE) { Punt("Ran out of fd_set slots; " "recompile with a larger FD_SETSIZE."); } if (timeout < 0) { tvp = NULL; } else { usecs = timeout * 1000; tv.tv_sec = usecs / 1000000; tv.tv_usec = usecs % 1000000; tvp = &tv; } nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp); if (nselect <= 0) return nselect; npoll = 0; for (i = 0; i < nfd; i++) { if (FD_ISSET(fd[i].fd, &rfds)) fd[i].revents |= POLLIN; if (FD_ISSET(fd[i].fd, &wfds)) fd[i].revents |= POLLOUT; if (fd[i].revents) npoll++; } return npoll; } #endif /* USE_SELECT */ diff --git a/contrib/bmake/job.h b/contrib/bmake/job.h index 085aa04e8e44..4182dd6268d5 100644 --- a/contrib/bmake/job.h +++ b/contrib/bmake/job.h @@ -1,212 +1,212 @@ -/* $NetBSD: job.h,v 1.80 2024/07/07 07:50:57 rillig Exp $ */ +/* $NetBSD: job.h,v 1.81 2025/01/03 04:51:42 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)job.h 8.1 (Berkeley) 6/6/93 */ /* * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)job.h 8.1 (Berkeley) 6/6/93 */ /* Run jobs in parallel mode. */ #ifndef MAKE_JOB_H #define MAKE_JOB_H #define TMPPAT "makeXXXXXX" /* relative to tmpdir */ #ifdef USE_SELECT /* * Emulate poll() in terms of select(). This is not a complete * emulation but it is sufficient for make's purposes. */ #define poll emul_poll #define pollfd emul_pollfd struct emul_pollfd { int fd; short events; short revents; }; #define POLLIN 0x0001 #define POLLOUT 0x0004 int emul_poll(struct pollfd *, int, int); #endif /* * The POLL_MSEC constant determines the maximum number of milliseconds spent * in poll before coming out to see if a child has finished. */ #define POLL_MSEC 5000 struct pollfd; #ifdef USE_META # include "meta.h" #endif typedef enum JobStatus { JOB_ST_FREE = 0, /* Job is available */ JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */ /* XXX: What about the 2? */ JOB_ST_RUNNING = 3, /* Job is running, pid valid */ - JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */ + JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHLD) */ } JobStatus; /* * A Job manages the shell commands that are run to create a single target. * Each job is run in a separate subprocess by a shell. Several jobs can run * in parallel. * * The shell commands for the target are written to a temporary file, * then the shell is run with the temporary file as stdin, and the output * of that shell is captured via a pipe. * * When a job is finished, Make_Update updates all parents of the node * that was just remade, marking them as ready to be made next if all * other dependencies are finished as well. */ typedef struct Job { /* The process ID of the shell running the commands */ int pid; /* The target the child is making */ GNode *node; /* * If one of the shell commands is "...", all following commands are * delayed until the .END node is made. This list node points to the * first of these commands, if any. */ StringListNode *tailCmds; /* This is where the shell commands go. */ FILE *cmdFILE; int exit_status; /* from wait4() in signal handler */ JobStatus status; bool suspended; /* Ignore non-zero exits */ bool ignerr; /* Output the command before or instead of running it. */ bool echo; /* Target is a special one. */ bool special; int inPipe; /* Pipe for reading output from job */ int outPipe; /* Pipe for writing control commands */ struct pollfd *inPollfd; /* pollfd associated with inPipe */ #define JOB_BUFSIZE 1024 /* Buffer for storing the output of the job, line by line. */ char outBuf[JOB_BUFSIZE + 1]; size_t curPos; /* Current position in outBuf. */ #ifdef USE_META struct BuildMon bm; #endif } Job; extern char *shellPath; extern const char *shellName; extern char *shellErrFlag; extern int jobTokensRunning; /* tokens currently "out" */ void Shell_Init(void); const char *Shell_GetNewline(void) MAKE_ATTR_USE; void Job_Touch(GNode *, bool); bool Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)) MAKE_ATTR_USE; void Job_CatchChildren(void); void Job_CatchOutput(void); void Job_Make(GNode *); void Job_Init(void); bool Job_ParseShell(char *) MAKE_ATTR_USE; int Job_Finish(void); #ifdef CLEANUP void Job_End(void); #endif void Job_Wait(void); void Job_AbortAll(void); void Job_TokenReturn(void); bool Job_TokenWithdraw(void) MAKE_ATTR_USE; void Job_ServerStart(int, int, int); void Job_SetPrefix(void); bool Job_RunTarget(const char *, const char *); void Job_FlagsToString(const Job *, char *, size_t); int Job_TempFile(const char *, char *, size_t) MAKE_ATTR_USE; #endif diff --git a/contrib/bmake/main.c b/contrib/bmake/main.c index c1adf521296d..0b48b2d59389 100644 --- a/contrib/bmake/main.c +++ b/contrib/bmake/main.c @@ -1,2260 +1,2266 @@ -/* $NetBSD: main.c,v 1.632 2024/07/11 20:09:16 sjg Exp $ */ +/* $NetBSD: main.c,v 1.638 2025/01/19 12:59:39 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The main file for this entire program. Exit routines etc. reside here. * * Utility functions defined in this file: * * Main_ParseArgLine * Parse and process command line arguments from a * single string. Used to implement the special targets * .MFLAGS and .MAKEFLAGS. * * Error Print a tagged error message. * * Fatal Print an error message and exit. * * Punt Abort all jobs and exit with a message. * * Finish Finish things up by printing the number of errors * that occurred, and exit. */ #include #include #include #include #include #if defined(MAKE_NATIVE) && defined(HAVE_SYSCTL) #include #endif #include #include "wait.h" #include #include #include #include #include "make.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.632 2024/07/11 20:09:16 sjg Exp $"); +MAKE_RCSID("$NetBSD: main.c,v 1.638 2025/01/19 12:59:39 rillig Exp $"); #if defined(MAKE_NATIVE) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " "All rights reserved."); #endif #ifndef __arraycount # define __arraycount(__x) (sizeof(__x) / sizeof(__x[0])) #endif CmdOpts opts; time_t now; /* Time at start of make */ GNode *defaultNode; /* .DEFAULT node */ bool allPrecious; /* .PRECIOUS given on a line by itself */ bool deleteOnError; /* .DELETE_ON_ERROR: set */ static int maxJobTokens; /* -j argument */ static bool enterFlagObj; /* -w and objdir != srcdir */ static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ bool doing_depend; /* Set while reading .depend */ static bool jobsRunning; /* true if the jobs might be running */ static const char *tracefile; static bool ReadMakefile(const char *); static void purge_relative_cached_realpaths(void); static bool ignorePWD; /* if we use -C, PWD is meaningless */ static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ char curdir[MAXPATHLEN + 1]; /* Startup directory */ const char *progname; char *makeDependfile; pid_t myPid; int makelevel; bool forceJobs = false; static int main_errors = 0; static HashTable cached_realpaths; /* * For compatibility with the POSIX version of MAKEFLAGS that includes * all the options without '-', convert 'flags' to '-f -l -a -g -s '. */ static char * explode(const char *flags) { char *exploded, *ep; const char *p; if (flags == NULL) return NULL; for (p = flags; *p != '\0'; p++) if (!ch_isalpha(*p)) return bmake_strdup(flags); exploded = bmake_malloc((size_t)(p - flags) * 3 + 1); for (p = flags, ep = exploded; *p != '\0'; p++) { *ep++ = '-'; *ep++ = *p; *ep++ = ' '; } *ep = '\0'; return exploded; } MAKE_ATTR_DEAD static void usage(void) { size_t prognameLen = strcspn(progname, "["); (void)fprintf(stderr, "usage: %.*s [-BeikNnqrSstWwX]\n" " [-C directory] [-D variable] [-d flags] [-f makefile]\n" " [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n" " [-V variable] [-v variable] [variable=value] [target ...]\n", (int)prognameLen, progname); exit(2); } static void MainParseArgDebugFile(const char *arg) { const char *mode; size_t len; char *fname; if (opts.debug_file != stdout && opts.debug_file != stderr) fclose(opts.debug_file); if (*arg == '+') { arg++; mode = "a"; } else mode = "w"; if (strcmp(arg, "stdout") == 0) { opts.debug_file = stdout; return; } if (strcmp(arg, "stderr") == 0) { opts.debug_file = stderr; return; } len = strlen(arg); fname = bmake_malloc(len + 20); memcpy(fname, arg, len + 1); /* Replace the trailing '%d' after '.%d' with the pid. */ if (len >= 3 && memcmp(fname + len - 3, ".%d", 3) == 0) snprintf(fname + len - 2, 20, "%d", getpid()); opts.debug_file = fopen(fname, mode); if (opts.debug_file == NULL) { fprintf(stderr, "Cannot open debug file \"%s\"\n", fname); exit(2); } free(fname); } static void MainParseArgDebug(const char *argvalue) { const char *modules; DebugFlags debug = opts.debug; for (modules = argvalue; *modules != '\0'; modules++) { switch (*modules) { case '0': /* undocumented, only intended for tests */ memset(&debug, 0, sizeof(debug)); break; case 'A': memset(&debug, ~0, sizeof(debug)); break; case 'a': debug.DEBUG_ARCH = true; break; case 'C': debug.DEBUG_CWD = true; break; case 'c': debug.DEBUG_COND = true; break; case 'd': debug.DEBUG_DIR = true; break; case 'e': debug.DEBUG_ERROR = true; break; case 'f': debug.DEBUG_FOR = true; break; case 'g': if (modules[1] == '1') { debug.DEBUG_GRAPH1 = true; modules++; } else if (modules[1] == '2') { debug.DEBUG_GRAPH2 = true; modules++; } else if (modules[1] == '3') { debug.DEBUG_GRAPH3 = true; modules++; } break; case 'h': debug.DEBUG_HASH = true; break; case 'j': debug.DEBUG_JOB = true; break; case 'L': opts.strict = true; break; case 'l': debug.DEBUG_LOUD = true; break; case 'M': debug.DEBUG_META = true; break; case 'm': debug.DEBUG_MAKE = true; break; case 'n': debug.DEBUG_SCRIPT = true; break; case 'p': debug.DEBUG_PARSE = true; break; case 's': debug.DEBUG_SUFF = true; break; case 't': debug.DEBUG_TARG = true; break; case 'V': opts.debugVflag = true; break; case 'v': debug.DEBUG_VAR = true; break; case 'x': debug.DEBUG_SHELL = true; break; case 'F': MainParseArgDebugFile(modules + 1); goto finish; default: (void)fprintf(stderr, "%s: illegal argument to d option -- %c\n", progname, *modules); usage(); } } finish: opts.debug = debug; setvbuf(opts.debug_file, NULL, _IONBF, 0); if (opts.debug_file != stdout) setvbuf(stdout, NULL, _IOLBF, 0); } /* Is path relative or does it contain any relative component "." or ".."? */ static bool IsRelativePath(const char *path) { const char *p; if (path[0] != '/') return true; p = path; while ((p = strstr(p, "/.")) != NULL) { p += 2; if (*p == '.') p++; if (*p == '/' || *p == '\0') return true; } return false; } static void MainParseArgChdir(const char *argvalue) { struct stat sa, sb; if (chdir(argvalue) == -1) { (void)fprintf(stderr, "%s: chdir %s: %s\n", progname, argvalue, strerror(errno)); exit(2); /* Not 1 so -q can distinguish error */ } if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); exit(2); } if (!IsRelativePath(argvalue) && stat(argvalue, &sa) != -1 && stat(curdir, &sb) != -1 && sa.st_ino == sb.st_ino && sa.st_dev == sb.st_dev) snprintf(curdir, MAXPATHLEN, "%s", argvalue); ignorePWD = true; } static void MainParseArgJobsInternal(const char *argvalue) { char end; if (sscanf(argvalue, "%d,%d%c", &jp_0, &jp_1, &end) != 2) { (void)fprintf(stderr, "%s: internal error -- J option malformed (%s)\n", progname, argvalue); usage(); } if ((fcntl(jp_0, F_GETFD, 0) < 0) || (fcntl(jp_1, F_GETFD, 0) < 0)) { jp_0 = -1; jp_1 = -1; opts.compatMake = true; } else { Global_Append(MAKEFLAGS, "-J"); Global_Append(MAKEFLAGS, argvalue); } } static void MainParseArgJobs(const char *arg) { const char *p; char *end; char v[12]; forceJobs = true; opts.maxJobs = (int)strtol(arg, &end, 0); p = end; #ifdef _SC_NPROCESSORS_ONLN if (*p != '\0') { double d; if (*p == 'C') d = (opts.maxJobs > 0) ? opts.maxJobs : 1; else if (*p == '.') { d = strtod(arg, &end); p = end; } else d = 0.0; if (d > 0.0) { p = ""; opts.maxJobs = (int)sysconf(_SC_NPROCESSORS_ONLN); opts.maxJobs = (int)(d * (double)opts.maxJobs); } } #endif if (*p != '\0' || opts.maxJobs < 1) { (void)fprintf(stderr, "%s: argument '%s' to option '-j' " "must be a positive number\n", progname, arg); exit(2); /* Not 1 so -q can distinguish error */ } snprintf(v, sizeof(v), "%d", opts.maxJobs); Global_Append(MAKEFLAGS, "-j"); Global_Append(MAKEFLAGS, v); Global_Set(".MAKE.JOBS", v); maxJobTokens = opts.maxJobs; } static void MainParseArgSysInc(const char *argvalue) { if (strncmp(argvalue, ".../", 4) == 0) { char *found_path = Dir_FindHereOrAbove(curdir, argvalue + 4); if (found_path == NULL) return; (void)SearchPath_Add(sysIncPath, found_path); free(found_path); } else { (void)SearchPath_Add(sysIncPath, argvalue); } Global_Append(MAKEFLAGS, "-m"); Global_Append(MAKEFLAGS, argvalue); Dir_SetSYSPATH(); } static bool MainParseOption(char c, const char *argvalue) { switch (c) { case '\0': break; case 'B': opts.compatMake = true; Global_Append(MAKEFLAGS, "-B"); Global_Set(".MAKE.MODE", "compat"); break; case 'C': MainParseArgChdir(argvalue); break; case 'D': if (argvalue[0] == '\0') return false; Var_SetExpand(SCOPE_GLOBAL, argvalue, "1"); Global_Append(MAKEFLAGS, "-D"); Global_Append(MAKEFLAGS, argvalue); break; case 'I': SearchPath_Add(parseIncPath, argvalue); Global_Append(MAKEFLAGS, "-I"); Global_Append(MAKEFLAGS, argvalue); break; case 'J': MainParseArgJobsInternal(argvalue); break; case 'N': opts.noExecute = true; opts.noRecursiveExecute = true; Global_Append(MAKEFLAGS, "-N"); break; case 'S': opts.keepgoing = false; Global_Append(MAKEFLAGS, "-S"); break; case 'T': tracefile = bmake_strdup(argvalue); Global_Append(MAKEFLAGS, "-T"); Global_Append(MAKEFLAGS, argvalue); break; case 'V': case 'v': opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED; Lst_Append(&opts.variables, bmake_strdup(argvalue)); /* XXX: Why always -V? */ Global_Append(MAKEFLAGS, "-V"); Global_Append(MAKEFLAGS, argvalue); break; case 'W': opts.parseWarnFatal = true; /* XXX: why no Global_Append? */ break; case 'X': opts.varNoExportEnv = true; Global_Append(MAKEFLAGS, "-X"); break; case 'd': /* If '-d-opts' don't pass to children */ if (argvalue[0] == '-') argvalue++; else { Global_Append(MAKEFLAGS, "-d"); Global_Append(MAKEFLAGS, argvalue); } MainParseArgDebug(argvalue); break; case 'e': opts.checkEnvFirst = true; Global_Append(MAKEFLAGS, "-e"); break; case 'f': Lst_Append(&opts.makefiles, bmake_strdup(argvalue)); break; case 'i': opts.ignoreErrors = true; Global_Append(MAKEFLAGS, "-i"); break; case 'j': MainParseArgJobs(argvalue); break; case 'k': opts.keepgoing = true; Global_Append(MAKEFLAGS, "-k"); break; case 'm': MainParseArgSysInc(argvalue); /* XXX: why no Var_Append? */ break; case 'n': opts.noExecute = true; Global_Append(MAKEFLAGS, "-n"); break; case 'q': opts.query = true; /* Kind of nonsensical, wot? */ Global_Append(MAKEFLAGS, "-q"); break; case 'r': opts.noBuiltins = true; Global_Append(MAKEFLAGS, "-r"); break; case 's': opts.silent = true; Global_Append(MAKEFLAGS, "-s"); break; case 't': opts.touch = true; Global_Append(MAKEFLAGS, "-t"); break; case 'w': opts.enterFlag = true; Global_Append(MAKEFLAGS, "-w"); break; default: usage(); } return true; } /* * Parse the given arguments. Called from main() and from * Main_ParseArgLine() when the .MAKEFLAGS target is used. * * The arguments must be treated as read-only and will be freed after the * call. * * XXX: Deal with command line overriding .MAKEFLAGS in makefile */ static void MainParseArgs(int argc, char **argv) { char c; int arginc; char *argvalue; char *optscan; bool inOption, dashDash = false; const char *optspecs = "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w"; /* Can't actually use getopt(3) because rescanning is not portable */ rearg: inOption = false; optscan = NULL; while (argc > 1) { const char *optspec; if (!inOption) optscan = argv[1]; c = *optscan++; arginc = 0; if (inOption) { if (c == '\0') { argv++; argc--; inOption = false; continue; } } else { if (c != '-' || dashDash) break; inOption = true; c = *optscan++; } /* '-' found at some earlier point */ optspec = strchr(optspecs, c); if (c != '\0' && optspec != NULL && optspec[1] == ':') { /* * - found, and should have an * argument */ inOption = false; arginc = 1; argvalue = optscan; if (*argvalue == '\0') { if (argc < 3) goto noarg; argvalue = argv[2]; arginc = 2; } } else { argvalue = NULL; } switch (c) { case '\0': arginc = 1; inOption = false; break; case '-': dashDash = true; break; default: if (!MainParseOption(c, argvalue)) goto noarg; } argv += arginc; argc -= arginc; } /* * See if the rest of the arguments are variable assignments and * perform them if so. Else take them to be targets and stuff them * on the end of the "create" list. */ for (; argc > 1; argv++, argc--) { if (!Parse_VarAssign(argv[1], false, SCOPE_CMDLINE)) { if (argv[1][0] == '\0') Punt("illegal (null) argument."); if (argv[1][0] == '-' && !dashDash) goto rearg; Lst_Append(&opts.create, bmake_strdup(argv[1])); } } return; noarg: (void)fprintf(stderr, "%s: option requires an argument -- %c\n", progname, c); usage(); } /* * Break a line of arguments into words and parse them. * * Used when a .MFLAGS or .MAKEFLAGS target is encountered during parsing and * by main() when reading the MAKEFLAGS environment variable. */ void Main_ParseArgLine(const char *line) { Words words; char *buf; const char *p; if (line == NULL) return; for (p = line; *p == ' '; p++) continue; if (p[0] == '\0') return; { FStr argv0 = Var_Value(SCOPE_GLOBAL, ".MAKE"); buf = str_concat3(argv0.str, " ", p); FStr_Done(&argv0); } words = Str_Words(buf, true); if (words.words == NULL) { Error("Unterminated quoted string [%s]", buf); free(buf); return; } free(buf); MainParseArgs((int)words.len, words.words); Words_Free(words); } bool Main_SetObjdir(bool writable, const char *fmt, ...) { struct stat sb; char *path; char buf[MAXPATHLEN + 1]; char buf2[MAXPATHLEN + 1]; va_list ap; va_start(ap, fmt); vsnprintf(path = buf, MAXPATHLEN, fmt, ap); va_end(ap); if (path[0] != '/') { if (snprintf(buf2, MAXPATHLEN, "%s/%s", curdir, path) <= MAXPATHLEN) path = buf2; else return false; } /* look for the directory and try to chdir there */ if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) return false; if ((writable && access(path, W_OK) != 0) || chdir(path) != 0) { (void)fprintf(stderr, "%s: warning: %s: %s.\n", progname, path, strerror(errno)); /* Allow debugging how we got here - not always obvious */ if (GetBooleanExpr("${MAKE_DEBUG_OBJDIR_CHECK_WRITABLE}", false)) PrintOnError(NULL, ""); return false; } snprintf(objdir, sizeof objdir, "%s", path); Global_Set(".OBJDIR", objdir); setenv("PWD", objdir, 1); Dir_InitDot(); purge_relative_cached_realpaths(); if (opts.enterFlag && strcmp(objdir, curdir) != 0) enterFlagObj = true; return true; } static bool SetVarObjdir(bool writable, const char *var, const char *suffix) { FStr path = Var_Value(SCOPE_CMDLINE, var); if (path.str == NULL || path.str[0] == '\0') { FStr_Done(&path); return false; } Var_Expand(&path, SCOPE_GLOBAL, VARE_EVAL); (void)Main_SetObjdir(writable, "%s%s", path.str, suffix); FStr_Done(&path); return true; } /* * Splits str into words (in-place, modifying it), adding them to the list. * The string must be kept alive as long as the list. */ void AppendWords(StringList *lp, char *str) { char *p; const char *sep = " \t"; for (p = strtok(str, sep); p != NULL; p = strtok(NULL, sep)) Lst_Append(lp, p); } #ifdef SIGINFO static void siginfo(int signo MAKE_ATTR_UNUSED) { char dir[MAXPATHLEN]; char str[2 * MAXPATHLEN]; int len; if (getcwd(dir, sizeof dir) == NULL) return; len = snprintf(str, sizeof str, "%s: Working in: %s\n", progname, dir); if (len > 0) (void)write(STDERR_FILENO, str, (size_t)len); } #endif /* Allow makefiles some control over the mode we run in. */ static void MakeMode(void) { char *mode = Var_Subst("${.MAKE.MODE:tl}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ if (mode[0] != '\0') { if (strstr(mode, "compat") != NULL) { opts.compatMake = true; forceJobs = false; } #if USE_META if (strstr(mode, "meta") != NULL) meta_mode_init(mode); #endif if (strstr(mode, "randomize-targets") != NULL) opts.randomizeTargets = true; } free(mode); } static void PrintVar(const char *varname, bool expandVars) { if (strchr(varname, '$') != NULL) { char *evalue = Var_Subst(varname, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ printf("%s\n", evalue); free(evalue); } else if (expandVars) { char *expr = str_concat3("${", varname, "}"); char *evalue = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ free(expr); printf("%s\n", evalue); free(evalue); } else { FStr value = Var_Value(SCOPE_GLOBAL, varname); printf("%s\n", value.str != NULL ? value.str : ""); FStr_Done(&value); } } /* * Return a bool based on a variable. * * If the knob is not set, return the fallback. * If set, anything that looks or smells like "No", "False", "Off", "0", etc. * is false, otherwise true. */ bool GetBooleanExpr(const char *expr, bool fallback) { char *value; bool res; value = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ res = ParseBoolean(value, fallback); free(value); return res; } static void doPrintVars(void) { StringListNode *ln; bool expandVars; if (opts.printVars == PVM_EXPANDED) expandVars = true; else if (opts.debugVflag) expandVars = false; else expandVars = GetBooleanExpr("${.MAKE.EXPAND_VARIABLES}", false); for (ln = opts.variables.first; ln != NULL; ln = ln->next) { const char *varname = ln->datum; PrintVar(varname, expandVars); } } static bool runTargets(void) { GNodeList targs = LST_INIT; /* target nodes to create */ bool outOfDate; /* false if all targets up to date */ /* * Have now read the entire graph and need to make a list of * targets to create. If none was given on the command line, * we consult the parsing module to find the main target(s) * to create. */ if (Lst_IsEmpty(&opts.create)) Parse_MainName(&targs); else Targ_FindList(&targs, &opts.create); if (!opts.compatMake) { /* * Initialize job module before traversing the graph * now that any .BEGIN and .END targets have been read. * This is done only if the -q flag wasn't given * (to prevent the .BEGIN from being executed should * it exist). */ if (!opts.query) { Job_Init(); jobsRunning = true; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(&targs); } else { Compat_MakeAll(&targs); outOfDate = false; } Lst_Done(&targs); /* Don't free the targets themselves. */ return outOfDate; } /* * Set up the .TARGETS variable to contain the list of targets to be created. * If none specified, make the variable empty for now, the parser will fill * in the default or .MAIN target later. */ static void InitVarTargets(void) { StringListNode *ln; if (Lst_IsEmpty(&opts.create)) { Global_Set(".TARGETS", ""); return; } for (ln = opts.create.first; ln != NULL; ln = ln->next) { const char *name = ln->datum; Global_Append(".TARGETS", name); } } static void InitRandom(void) { struct timeval tv; gettimeofday(&tv, NULL); srandom((unsigned int)(tv.tv_sec + tv.tv_usec)); } static const char * InitVarMachine(const struct utsname *utsname MAKE_ATTR_UNUSED) { #ifdef FORCE_MACHINE return FORCE_MACHINE; #else const char *machine = getenv("MACHINE"); if (machine != NULL) return machine; #if defined(MAKE_NATIVE) return utsname->machine; #elif defined(MAKE_MACHINE) return MAKE_MACHINE; #else return "unknown"; #endif #endif } static const char * InitVarMachineArch(void) { #ifdef FORCE_MACHINE_ARCH return FORCE_MACHINE_ARCH; #else const char *env = getenv("MACHINE_ARCH"); if (env != NULL) return env; #if defined(MAKE_NATIVE) && defined(CTL_HW) { struct utsname utsname; static char machine_arch_buf[sizeof utsname.machine]; const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; size_t len = sizeof machine_arch_buf; if (sysctl(mib, (unsigned int)__arraycount(mib), machine_arch_buf, &len, NULL, 0) < 0) { (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, strerror(errno)); exit(2); } return machine_arch_buf; } #elif defined(MACHINE_ARCH) return MACHINE_ARCH; #elif defined(MAKE_MACHINE_ARCH) return MAKE_MACHINE_ARCH; #else return "unknown"; #endif #endif } #ifndef NO_PWD_OVERRIDE /* * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX * since the value of curdir can vary depending on how we got * here. That is, sitting at a shell prompt (shell that provides $PWD) * or via subdir.mk, in which case it's likely a shell which does * not provide it. * * So, to stop it breaking this case only, we ignore PWD if * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains an expression. */ static void HandlePWD(const struct stat *curdir_st) { char *pwd; FStr makeobjdir; struct stat pwd_st; if (ignorePWD || (pwd = getenv("PWD")) == NULL) return; if (Var_Exists(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX")) return; makeobjdir = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIR"); if (makeobjdir.str != NULL && strchr(makeobjdir.str, '$') != NULL) goto ignore_pwd; if (stat(pwd, &pwd_st) == 0 && curdir_st->st_ino == pwd_st.st_ino && curdir_st->st_dev == pwd_st.st_dev) snprintf(curdir, MAXPATHLEN, "%s", pwd); ignore_pwd: FStr_Done(&makeobjdir); } #endif /* * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, MAKEOBJDIR is set * in the environment, try only that value and fall back to .CURDIR if it * does not exist. * * Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE, * and finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none of these * paths exist, just use .CURDIR. */ static void InitObjdir(const char *machine, const char *machine_arch) { bool writable; Dir_InitCur(curdir); writable = GetBooleanExpr("${MAKE_OBJDIR_CHECK_WRITABLE}", true); (void)Main_SetObjdir(false, "%s", curdir); if (!SetVarObjdir(writable, "MAKEOBJDIRPREFIX", curdir) && !SetVarObjdir(writable, "MAKEOBJDIR", "") && !Main_SetObjdir(writable, "%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && !Main_SetObjdir(writable, "%s.%s", _PATH_OBJDIR, machine) && !Main_SetObjdir(writable, "%s", _PATH_OBJDIR)) (void)Main_SetObjdir(writable, "%s%s", _PATH_OBJDIRPREFIX, curdir); } /* get rid of resource limit on file descriptors */ static void UnlimitFiles(void) { #if defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE) struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && rl.rlim_cur != rl.rlim_max) { #ifdef BMAKE_NOFILE_MAX if (BMAKE_NOFILE_MAX < rl.rlim_max) rl.rlim_cur = BMAKE_NOFILE_MAX; else #endif rl.rlim_cur = rl.rlim_max; (void)setrlimit(RLIMIT_NOFILE, &rl); } #endif } static void CmdOpts_Init(void) { opts.compatMake = false; memset(&opts.debug, 0, sizeof(opts.debug)); /* opts.debug_file has already been initialized earlier */ opts.strict = false; opts.debugVflag = false; opts.checkEnvFirst = false; Lst_Init(&opts.makefiles); opts.ignoreErrors = false; /* Pay attention to non-zero returns */ opts.maxJobs = 1; opts.keepgoing = false; /* Stop on error */ opts.noRecursiveExecute = false; /* Execute all .MAKE targets */ opts.noExecute = false; /* Execute all commands */ opts.query = false; opts.noBuiltins = false; /* Read the built-in rules */ opts.silent = false; /* Print commands as executed */ opts.touch = false; opts.printVars = PVM_NONE; Lst_Init(&opts.variables); opts.parseWarnFatal = false; opts.enterFlag = false; opts.varNoExportEnv = false; Lst_Init(&opts.create); } /* * Initialize MAKE and .MAKE to the path of the executable, so that it can be * found by execvp(3) and the shells, even after a chdir. * * If it's a relative path and contains a '/', resolve it to an absolute path. * Otherwise keep it as is, assuming it will be found in the PATH. */ static void InitVarMake(const char *argv0) { const char *make = argv0; char pathbuf[MAXPATHLEN]; if (argv0[0] != '/' && strchr(argv0, '/') != NULL) { const char *abspath = cached_realpath(argv0, pathbuf); struct stat st; if (abspath != NULL && abspath[0] == '/' && stat(make, &st) == 0) make = abspath; } Global_Set("MAKE", make); Global_Set(".MAKE", make); } /* * Add the directories from the colon-separated syspath to defSysIncPath. * After returning, the contents of syspath is unspecified. */ static void InitDefSysIncPath(char *syspath) { static char defsyspath[] = _PATH_DEFSYSPATH; char *start, *p; /* * If no user-supplied system path was given (through the -m option) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ if (syspath == NULL || syspath[0] == '\0') syspath = defsyspath; else syspath = bmake_strdup(syspath); for (start = syspath; *start != '\0'; start = p) { for (p = start; *p != '\0' && *p != ':'; p++) continue; if (*p == ':') *p++ = '\0'; /* look for magic parent directory search string */ if (strncmp(start, ".../", 4) == 0) { char *dir = Dir_FindHereOrAbove(curdir, start + 4); if (dir != NULL) { (void)SearchPath_Add(defSysIncPath, dir); free(dir); } } else { (void)SearchPath_Add(defSysIncPath, start); } } if (syspath != defsyspath) free(syspath); } static void ReadBuiltinRules(void) { StringListNode *ln; StringList sysMkFiles = LST_INIT; SearchPath_Expand( Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath, _PATH_DEFSYSMK, &sysMkFiles); if (Lst_IsEmpty(&sysMkFiles)) Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); for (ln = sysMkFiles.first; ln != NULL; ln = ln->next) if (ReadMakefile(ln->datum)) break; if (ln == NULL) Fatal("%s: cannot open %s.", progname, (const char *)sysMkFiles.first->datum); Lst_DoneFree(&sysMkFiles); } static void InitMaxJobs(void) { char *value; int n; if (forceJobs || opts.compatMake || !Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS")) return; value = Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ n = (int)strtol(value, NULL, 0); if (n < 1) { (void)fprintf(stderr, "%s: illegal value for .MAKE.JOBS " "-- must be positive integer!\n", progname); exit(2); /* Not 1 so -q can distinguish error */ } if (n != opts.maxJobs) { Global_Append(MAKEFLAGS, "-j"); Global_Append(MAKEFLAGS, value); } opts.maxJobs = n; maxJobTokens = opts.maxJobs; forceJobs = true; free(value); } /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH environment * variable, i.e. ::... */ static void InitVpath(void) { char *vpath, savec, *path; if (!Var_Exists(SCOPE_CMDLINE, "VPATH")) return; vpath = Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ path = vpath; do { char *p; /* skip to end of directory */ for (p = path; *p != ':' && *p != '\0'; p++) continue; /* Save terminator character so know when to stop */ savec = *p; *p = '\0'; /* Add directory to search path */ (void)SearchPath_Add(&dirSearchPath, path); *p = savec; path = p + 1; } while (savec == ':'); free(vpath); } static void ReadAllMakefiles(const StringList *makefiles) { StringListNode *ln; for (ln = makefiles->first; ln != NULL; ln = ln->next) { const char *fname = ln->datum; if (!ReadMakefile(fname)) Fatal("%s: cannot open %s.", progname, fname); } } static void ReadFirstDefaultMakefile(void) { StringList makefiles = LST_INIT; StringListNode *ln; char *prefs = Var_Subst("${.MAKE.MAKEFILE_PREFERENCE}", SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ AppendWords(&makefiles, prefs); for (ln = makefiles.first; ln != NULL; ln = ln->next) if (ReadMakefile(ln->datum)) break; Lst_Done(&makefiles); free(prefs); } /* * Initialize variables such as MAKE, MACHINE, .MAKEFLAGS. * Initialize a few modules. * Parse the arguments from MAKEFLAGS and the command line. */ static void main_Init(int argc, char **argv) { struct stat sa; const char *machine; const char *machine_arch; char *syspath = getenv("MAKESYSPATH"); struct utsname utsname; /* default to writing debug to stderr */ opts.debug_file = stderr; Str_Intern_Init(); HashTable_Init(&cached_realpaths); #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif InitRandom(); progname = str_basename(argv[0]); UnlimitFiles(); if (uname(&utsname) == -1) { (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, strerror(errno)); exit(2); } machine = InitVarMachine(&utsname); machine_arch = InitVarMachineArch(); myPid = getpid(); /* remember this for vFork() */ /* Just in case MAKEOBJDIR wants us to do something tricky. */ Targ_Init(); #ifdef FORCE_MAKE_OS Global_Set_ReadOnly(".MAKE.OS", FORCE_MAKE_OS); #else Global_Set_ReadOnly(".MAKE.OS", utsname.sysname); #endif Global_Set("MACHINE", machine); Global_Set("MACHINE_ARCH", machine_arch); #ifdef MAKE_VERSION Global_Set("MAKE_VERSION", MAKE_VERSION); #endif Global_Set_ReadOnly(".newline", "\n"); #ifndef MAKEFILE_PREFERENCE_LIST /* This is the traditional preference for makefiles. */ # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif Global_Set(".MAKE.MAKEFILE_PREFERENCE", MAKEFILE_PREFERENCE_LIST); Global_Set(".MAKE.DEPENDFILE", ".depend"); /* Tell makefiles like jobs.mk whether we support -jC */ #ifdef _SC_NPROCESSORS_ONLN Global_Set_ReadOnly(".MAKE.JOBS.C", "yes"); #else Global_Set_ReadOnly(".MAKE.JOBS.C", "no"); #endif CmdOpts_Init(); allPrecious = false; /* Remove targets when interrupted */ deleteOnError = false; /* Historical default behavior */ jobsRunning = false; maxJobTokens = opts.maxJobs; ignorePWD = false; /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ /* * Initialize various variables. * MAKE also gets this name, for compatibility * .MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Parse_Init(); InitVarMake(argv[0]); Global_Set(MAKEFLAGS, ""); Global_Set(".MAKEOVERRIDES", ""); Global_Set("MFLAGS", ""); Global_Set(".ALLTARGETS", ""); Global_Set_ReadOnly(".MAKE.LEVEL.ENV", MAKE_LEVEL_ENV); /* Set some other useful variables. */ { - char buf[64], *ep = getenv(MAKE_LEVEL_ENV); + char buf[64]; + const char *ep = getenv(MAKE_LEVEL_ENV); makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0; if (makelevel < 0) makelevel = 0; snprintf(buf, sizeof buf, "%d", makelevel); Global_Set(".MAKE.LEVEL", buf); snprintf(buf, sizeof buf, "%u", myPid); Global_Set_ReadOnly(".MAKE.PID", buf); snprintf(buf, sizeof buf, "%u", getppid()); Global_Set_ReadOnly(".MAKE.PPID", buf); snprintf(buf, sizeof buf, "%u", getuid()); Global_Set_ReadOnly(".MAKE.UID", buf); snprintf(buf, sizeof buf, "%u", getgid()); Global_Set_ReadOnly(".MAKE.GID", buf); } if (makelevel > 0) { char pn[1024]; snprintf(pn, sizeof pn, "%s[%d]", progname, makelevel); progname = bmake_strdup(pn); } #ifdef USE_META meta_init(); #endif Dir_Init(); { char *makeflags = explode(getenv("MAKEFLAGS")); Main_ParseArgLine(makeflags); free(makeflags); } if (getcwd(curdir, MAXPATHLEN) == NULL) { (void)fprintf(stderr, "%s: getcwd: %s.\n", progname, strerror(errno)); exit(2); } MainParseArgs(argc, argv); if (opts.enterFlag) printf("%s: Entering directory `%s'\n", progname, curdir); if (stat(curdir, &sa) == -1) { (void)fprintf(stderr, "%s: %s: %s.\n", progname, curdir, strerror(errno)); exit(2); } #ifndef NO_PWD_OVERRIDE HandlePWD(&sa); #endif Global_Set(".CURDIR", curdir); InitObjdir(machine, machine_arch); Arch_Init(); Suff_Init(); Trace_Init(tracefile); defaultNode = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); InitVarTargets(); InitDefSysIncPath(syspath); } /* * Read the system makefile followed by either makefile, Makefile or the * files given by the -f option. Exit on parse errors. */ static void main_ReadFiles(void) { if (Lst_IsEmpty(&sysIncPath->dirs)) SearchPath_AddAll(sysIncPath, defSysIncPath); Dir_SetSYSPATH(); if (!opts.noBuiltins) ReadBuiltinRules(); posix_state = PS_MAYBE_NEXT_LINE; if (!Lst_IsEmpty(&opts.makefiles)) ReadAllMakefiles(&opts.makefiles); else ReadFirstDefaultMakefile(); } /* Compute the dependency graph. */ static void main_PrepareMaking(void) { /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ if (!opts.noBuiltins || opts.printVars == PVM_NONE) { makeDependfile = Var_Subst("${.MAKE.DEPENDFILE}", SCOPE_CMDLINE, VARE_EVAL); if (makeDependfile[0] != '\0') { /* TODO: handle errors */ doing_depend = true; (void)ReadMakefile(makeDependfile); doing_depend = false; } } if (enterFlagObj) printf("%s: Entering directory `%s'\n", progname, objdir); MakeMode(); { FStr makeflags = Var_Value(SCOPE_GLOBAL, MAKEFLAGS); Global_Append("MFLAGS", makeflags.str); FStr_Done(&makeflags); } InitMaxJobs(); if (!opts.compatMake && !forceJobs) opts.compatMake = true; if (!opts.compatMake) Job_ServerStart(maxJobTokens, jp_0, jp_1); DEBUG5(JOB, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); if (opts.printVars == PVM_NONE) Main_ExportMAKEFLAGS(true); /* initial export */ InitVpath(); /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_ExtendPaths(); /* * Propagate attributes through :: dependency lists. */ Targ_Propagate(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); } /* * Make the targets. * If the -v or -V options are given, print variables instead. * Return whether any of the targets is out-of-date. */ static bool main_Run(void) { if (opts.printVars != PVM_NONE) { /* print the values of any variables requested by the user */ doPrintVars(); return false; } else { return runTargets(); } } /* Clean up after making the targets. */ static void main_CleanUp(void) { #ifdef CLEANUP Lst_DoneFree(&opts.variables); Lst_DoneFree(&opts.makefiles); Lst_DoneFree(&opts.create); #endif if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Trace_Log(MAKEEND, NULL); if (enterFlagObj) printf("%s: Leaving directory `%s'\n", progname, objdir); if (opts.enterFlag) printf("%s: Leaving directory `%s'\n", progname, curdir); Var_Stats(); Targ_Stats(); #ifdef USE_META meta_finish(); #endif #ifdef CLEANUP Suff_End(); Targ_End(); Arch_End(); Parse_End(); Dir_End(); Job_End(); #endif Trace_End(); #ifdef CLEANUP Str_Intern_End(); #endif } /* Determine the exit code. */ static int main_Exit(bool outOfDate) { if ((opts.strict && main_errors > 0) || parseErrors > 0) return 2; /* Not 1 so -q can distinguish error */ return outOfDate ? 1 : 0; } int main(int argc, char **argv) { bool outOfDate; main_Init(argc, argv); main_ReadFiles(); main_PrepareMaking(); outOfDate = main_Run(); main_CleanUp(); return main_Exit(outOfDate); } /* * Open and parse the given makefile, with all its side effects. * Return false if the file could not be opened. */ static bool ReadMakefile(const char *fname) { int fd; char *name, *path = NULL; if (strcmp(fname, "-") == 0) { Parse_File("(stdin)", -1); Var_Set(SCOPE_INTERNAL, "MAKEFILE", ""); } else { + if (strncmp(fname, ".../", 4) == 0) { + name = Dir_FindHereOrAbove(curdir, fname + 4); + if (name != NULL) { + /* Dir_FindHereOrAbove returns dirname */ + path = str_concat3(name, "/", + str_basename(fname)); + free(name); + fd = open(path, O_RDONLY); + if (fd != -1) { + fname = path; + goto found; + } + } + } /* if we've chdir'd, rebuild the path name */ if (strcmp(curdir, objdir) != 0 && *fname != '/') { path = str_concat3(curdir, "/", fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; goto found; } free(path); /* If curdir failed, try objdir (ala .depend) */ path = str_concat3(objdir, "/", fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; goto found; } } else { fd = open(fname, O_RDONLY); if (fd != -1) goto found; } /* look in -I and system include directories. */ name = Dir_FindFile(fname, parseIncPath); if (name == NULL) { SearchPath *sysInc = Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath; name = Dir_FindFile(fname, sysInc); } if (name == NULL || (fd = open(name, O_RDONLY)) == -1) { free(name); free(path); return false; } fname = name; /* * set the MAKEFILE variable desired by System V fans -- the * placement of the setting here means it gets set to the last * makefile specified, as it is set by SysV make. */ found: if (!doing_depend) Var_Set(SCOPE_INTERNAL, "MAKEFILE", fname); Parse_File(fname, fd); } free(path); return true; } /* populate av for Cmd_Exec and Compat_RunCommand */ int Cmd_Argv(const char *cmd, size_t cmd_len, const char **av, size_t avsz, char *cmd_file, size_t cmd_filesz, bool eflag, bool xflag) { int ac = 0; int cmd_fd = -1; if (shellPath == NULL) Shell_Init(); if (cmd_file != NULL) { if (cmd_len == 0) cmd_len = strlen(cmd); if (cmd_len > MAKE_CMDLEN_LIMIT) { cmd_fd = mkTempFile(NULL, cmd_file, cmd_filesz); if (cmd_fd >= 0) { ssize_t n; n = write(cmd_fd, cmd, cmd_len); close(cmd_fd); if (n < (ssize_t)cmd_len) { unlink(cmd_file); cmd_fd = -1; } } } else cmd_file[0] = '\0'; } if (avsz < 4 || (eflag && avsz < 5)) return -1; /* The following works for any of the builtin shell specs. */ av[ac++] = shellPath; if (eflag) av[ac++] = shellErrFlag; if (cmd_fd >= 0) { if (xflag) av[ac++] = "-x"; av[ac++] = cmd_file; } else { av[ac++] = xflag ? "-xc" : "-c"; av[ac++] = cmd; } av[ac] = NULL; return ac; } /* * Execute the command in cmd, and return its output (only stdout, not * stderr, possibly empty). In the output, replace newlines with spaces. */ char * Cmd_Exec(const char *cmd, char **error) { const char *args[4]; /* Arguments for invoking the shell */ int pipefds[2]; int cpid; /* Child PID */ int pid; /* PID from wait() */ int status; /* command exit status */ Buffer buf; /* buffer to store the result */ ssize_t bytes_read; char *output; char *p; int saved_errno; char cmd_file[MAXPATHLEN]; DEBUG1(VAR, "Capturing the output of command \"%s\"\n", cmd); if (Cmd_Argv(cmd, 0, args, 4, cmd_file, sizeof(cmd_file), false, false) < 0 || pipe(pipefds) == -1) { *error = str_concat3( "Couldn't create pipe for \"", cmd, "\""); return bmake_strdup(""); } Var_ReexportVars(SCOPE_GLOBAL); - switch (cpid = vfork()) { + switch (cpid = FORK_FUNCTION()) { case 0: (void)close(pipefds[0]); (void)dup2(pipefds[1], STDOUT_FILENO); (void)close(pipefds[1]); (void)execv(shellPath, UNCONST(args)); _exit(1); /* NOTREACHED */ case -1: *error = str_concat3("Couldn't exec \"", cmd, "\""); return bmake_strdup(""); } (void)close(pipefds[1]); /* No need for the writing half */ saved_errno = 0; Buf_Init(&buf); do { char result[BUFSIZ]; bytes_read = read(pipefds[0], result, sizeof result); if (bytes_read > 0) Buf_AddBytes(&buf, result, (size_t)bytes_read); } while (bytes_read > 0 || (bytes_read == -1 && errno == EINTR)); if (bytes_read == -1) saved_errno = errno; (void)close(pipefds[0]); /* Close the input side of the pipe. */ while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) JobReapChild(pid, status, false); if (Buf_EndsWith(&buf, '\n')) buf.data[buf.len - 1] = '\0'; output = Buf_DoneData(&buf); for (p = output; *p != '\0'; p++) if (*p == '\n') *p = ' '; if (WIFSIGNALED(status)) *error = str_concat3("\"", cmd, "\" exited on a signal"); else if (WEXITSTATUS(status) != 0) { Buffer errBuf; Buf_Init(&errBuf); Buf_AddStr(&errBuf, "Command \""); Buf_AddStr(&errBuf, cmd); Buf_AddStr(&errBuf, "\" exited with status "); Buf_AddInt(&errBuf, WEXITSTATUS(status)); *error = Buf_DoneData(&errBuf); } else if (saved_errno != 0) *error = str_concat3( "Couldn't read shell's output for \"", cmd, "\""); else *error = NULL; if (cmd_file[0] != '\0') unlink(cmd_file); return output; } /* * Print a printf-style error message. * * In default mode, this error message has no consequences, for compatibility * reasons, in particular it does not affect the exit status. Only in lint * mode (-dL) it does. */ void Error(const char *fmt, ...) { va_list ap; FILE *f; f = opts.debug_file; if (f == stdout) f = stderr; (void)fflush(stdout); for (;;) { fprintf(f, "%s: ", progname); va_start(ap, fmt); (void)vfprintf(f, fmt, ap); va_end(ap); (void)fprintf(f, "\n"); (void)fflush(f); if (f == stderr) break; f = stderr; } main_errors++; } /* * Wait for any running jobs to finish, then produce an error message, * finally exit immediately. * * Exiting immediately differs from Parse_Error, which exits only after the * current top-level makefile has been parsed completely. */ void Fatal(const char *fmt, ...) { va_list ap; if (jobsRunning) Job_Wait(); (void)fflush(stdout); fprintf(stderr, "%s: ", progname); va_start(ap, fmt); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); (void)fflush(stderr); PrintStackTrace(true); PrintOnError(NULL, "\n"); if (DEBUG(GRAPH2) || DEBUG(GRAPH3)) Targ_PrintGraph(2); Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1 so -q can distinguish error */ } /* * Major exception once jobs are being created. * Kills all jobs, prints a message and exits. */ void Punt(const char *fmt, ...) { va_list ap; (void)fflush(stdout); (void)fprintf(stderr, "%s: ", progname); va_start(ap, fmt); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); (void)fflush(stderr); PrintOnError(NULL, "\n"); DieHorribly(); } /* Exit without giving a message. */ void DieHorribly(void) { if (jobsRunning) Job_AbortAll(); if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1 so -q can distinguish error */ } /* * Called when aborting due to errors in child shell to signal abnormal exit. * The program exits. * Errors is the number of errors encountered in Make_Make. */ void Finish(int errs) { if (shouldDieQuietly(NULL, -1)) exit(2); Fatal("%d error%s", errs, errs == 1 ? "" : "s"); } int unlink_file(const char *file) { struct stat st; if (lstat(file, &st) == -1) return -1; if (S_ISDIR(st.st_mode)) { /* * POSIX says for unlink: "The path argument shall not name * a directory unless [...]". */ errno = EISDIR; return -1; } return unlink(file); } static void write_all(int fd, const void *data, size_t n) { const char *mem = data; while (n > 0) { ssize_t written = write(fd, mem, n); /* XXX: Should this EAGAIN be EINTR? */ if (written == -1 && errno == EAGAIN) continue; if (written == -1) break; mem += written; n -= (size_t)written; } } /* Print why exec failed, avoiding stdio. */ void MAKE_ATTR_DEAD -execDie(const char *af, const char *av) +execDie(const char *func, const char *arg) { - Buffer buf; + char msg[1024]; + int len; - Buf_Init(&buf); - Buf_AddStr(&buf, progname); - Buf_AddStr(&buf, ": "); - Buf_AddStr(&buf, af); - Buf_AddStr(&buf, "("); - Buf_AddStr(&buf, av); - Buf_AddStr(&buf, ") failed ("); - Buf_AddStr(&buf, strerror(errno)); - Buf_AddStr(&buf, ")\n"); - - write_all(STDERR_FILENO, buf.data, buf.len); - - Buf_Done(&buf); + len = snprintf(msg, sizeof(msg), "%s: %s(%s) failed (%s)\n", + progname, func, arg, strerror(errno)); + write_all(STDERR_FILENO, msg, (size_t)len); _exit(1); } static void purge_relative_cached_realpaths(void) { HashIter hi; bool more; HashIter_Init(&hi, &cached_realpaths); more = HashIter_Next(&hi); while (more) { HashEntry *he = hi.entry; more = HashIter_Next(&hi); if (he->key[0] != '/') { DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); free(he->value); HashTable_DeleteEntry(&cached_realpaths, he); } } } const char * cached_realpath(const char *pathname, char *resolved) { const char *rp; if (pathname == NULL || pathname[0] == '\0') return NULL; rp = HashTable_FindValue(&cached_realpaths, pathname); if (rp != NULL) { snprintf(resolved, MAXPATHLEN, "%s", rp); return resolved; } rp = realpath(pathname, resolved); if (rp != NULL) { HashTable_Set(&cached_realpaths, pathname, bmake_strdup(rp)); DEBUG2(DIR, "cached_realpath: %s -> %s\n", pathname, rp); return resolved; } /* should we negative-cache? */ return NULL; } /* * Return true if we should die without noise. * For example our failing child was a sub-make or failure happened elsewhere. */ bool shouldDieQuietly(GNode *gn, int bf) { static int quietly = -1; if (quietly < 0) { if (DEBUG(JOB) || !GetBooleanExpr("${.MAKE.DIE_QUIETLY}", true)) quietly = 0; else if (bf >= 0) quietly = bf; else quietly = (gn != NULL && (gn->type & OP_MAKE)) ? 1 : 0; } return quietly != 0; } static void SetErrorVars(GNode *gn) { StringListNode *ln; char sts[16]; /* * We can print this even if there is no .ERROR target. */ snprintf(sts, sizeof(sts), "%d", gn->exit_status); Global_Set(".ERROR_EXIT", sts); Global_Set(".ERROR_TARGET", gn->name); Global_Delete(".ERROR_CMD"); for (ln = gn->commands.first; ln != NULL; ln = ln->next) { const char *cmd = ln->datum; if (cmd == NULL) break; Global_Append(".ERROR_CMD", cmd); } } /* * Print some helpful information in case of an error. * The caller should exit soon after calling this function. */ void PrintOnError(GNode *gn, const char *msg) { static GNode *errorNode = NULL; StringListNode *ln; if (DEBUG(HASH)) { Targ_Stats(); Var_Stats(); } if (errorNode != NULL) return; /* we've been here! */ printf("%s%s: stopped", msg, progname); ln = opts.create.first; if (ln != NULL || mainNode != NULL) { printf(" making \""); if (ln != NULL) { printf("%s", (const char *)ln->datum); for (ln = ln->next; ln != NULL; ln = ln->next) printf(" %s", (const char *)ln->datum); } else printf("%s", mainNode->name); printf("\""); } printf(" in %s\n", curdir); /* we generally want to keep quiet if a sub-make died */ if (shouldDieQuietly(gn, -1)) return; if (gn != NULL) SetErrorVars(gn); { char *errorVarsValues = Var_Subst( "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ printf("%s", errorVarsValues); free(errorVarsValues); } fflush(stdout); /* * Finally, see if there is a .ERROR target, and run it if so. */ errorNode = Targ_FindNode(".ERROR"); if (errorNode != NULL) { errorNode->type |= OP_SPECIAL; Compat_Make(errorNode, errorNode); } } void Main_ExportMAKEFLAGS(bool first) { static bool once = true; char *flags; if (once != first) return; once = false; flags = Var_Subst( "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ if (flags[0] != '\0') setenv("MAKEFLAGS", flags, 1); free(flags); } char * getTmpdir(void) { static char *tmpdir = NULL; struct stat st; if (tmpdir != NULL) return tmpdir; /* Honor $TMPDIR if it is valid, strip a trailing '/'. */ tmpdir = Var_Subst("${TMPDIR:tA:U" _PATH_TMP ":S,/$,,W}/", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { free(tmpdir); tmpdir = bmake_strdup(_PATH_TMP); } return tmpdir; } /* * Create and open a temp file using "pattern". * If tfile is provided, set it to a copy of the filename created. * Otherwise unlink the file once open. */ int mkTempFile(const char *pattern, char *tfile, size_t tfile_sz) { static char *tmpdir = NULL; char tbuf[MAXPATHLEN]; int fd; if (pattern == NULL) pattern = TMPPAT; if (tmpdir == NULL) tmpdir = getTmpdir(); if (tfile == NULL) { tfile = tbuf; tfile_sz = sizeof tbuf; } if (pattern[0] == '/') snprintf(tfile, tfile_sz, "%s", pattern); else snprintf(tfile, tfile_sz, "%s%s", tmpdir, pattern); if ((fd = mkstemp(tfile)) < 0) Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); if (tfile == tbuf) unlink(tfile); /* we just want the descriptor */ return fd; } /* * Convert a string representation of a boolean into a boolean value. * Anything that looks like "No", "False", "Off", "0" etc. is false, * the empty string is the fallback, everything else is true. */ bool ParseBoolean(const char *s, bool fallback) { char ch = ch_tolower(s[0]); if (ch == '\0') return fallback; if (ch == '0' || ch == 'f' || ch == 'n') return false; if (ch == 'o') return ch_tolower(s[1]) != 'f'; return true; } diff --git a/contrib/bmake/make.1 b/contrib/bmake/make.1 index 2f433275d41c..869dcea3db0c 100644 --- a/contrib/bmake/make.1 +++ b/contrib/bmake/make.1 @@ -1,2795 +1,2808 @@ -.\" $NetBSD: make.1,v 1.378 2024/07/01 21:02:26 sjg Exp $ +.\" $NetBSD: make.1,v 1.381 2024/11/14 19:30:13 sjg Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd July 1, 2024 +.Dd November 14, 2024 .Dt MAKE 1 .Os .Sh NAME .Nm make .Nd maintain program dependencies .Sh SYNOPSIS .Nm .Op Fl BeikNnqrSstWwX .Op Fl C Ar directory .Op Fl D Ar variable .Op Fl d Ar flags .Op Fl f Ar makefile .Op Fl I Ar directory .Op Fl J Ar private .Op Fl j Ar max_jobs .Op Fl m Ar directory .Op Fl T Ar file .Op Fl V Ar variable .Op Fl v Ar variable .Op Ar variable\| Ns Cm \&= Ns Ar value .Op Ar target No ... .Sh DESCRIPTION .Nm is a program designed to simplify the maintenance of other programs. Its input is a list of specifications as to the files upon which programs and other files depend. If no .Fl f Ar makefile option is given, .Nm -tries to open -.Sq Pa makefile -then -.Sq Pa Makefile +looks for the makefiles listed in +.Va .MAKE.MAKEFILE_PREFERENCE +(default +.Sq Pa makefile , +.Sq Pa Makefile ) in order to find the specifications. If the file .Sq Pa .depend exists, it is read, see .Xr mkdep 1 . .Pp This manual page is intended as a reference document only. For a more thorough description of .Nm and makefiles, please refer to .%T "PMake \- A Tutorial" (from 1993). .Pp .Nm prepends the contents of the .Ev MAKEFLAGS environment variable to the command line arguments before parsing them. .Pp The options are as follows: .Bl -tag -width Ds .It Fl B Try to be backwards compatible by executing a single shell per command and by making the sources of a dependency line in sequence. .It Fl C Ar directory Change to .Ar directory before reading the makefiles or doing anything else. If multiple .Fl C options are specified, each is interpreted relative to the previous one: .Fl C Pa / Fl C Pa etc is equivalent to .Fl C Pa /etc . .It Fl D Ar variable Define .Ar variable to be 1, in the global scope. .It Fl d Oo Cm \- Oc Ns Ar flags Turn on debugging, and specify which portions of .Nm are to print debugging information. Unless the flags are preceded by .Ql \- , they are added to the .Ev MAKEFLAGS environment variable and are passed on to any child make processes. By default, debugging information is printed to standard error, but this can be changed using the .Cm F debugging flag. The debugging output is always unbuffered; in addition, if debugging is enabled but debugging output is not directed to standard output, the standard output is line buffered. The available .Ar flags are: .Bl -tag -width Ds .It Cm A Print all possible debugging information; equivalent to specifying all of the debugging flags. .It Cm a Print debugging information about archive searching and caching. .It Cm C Print debugging information about the current working directory. .It Cm c Print debugging information about conditional evaluation. .It Cm d Print debugging information about directory searching and caching. .It Cm e Print debugging information about failed commands and targets. .It Cm F Ns Oo Cm \&+ Oc Ns Ar filename Specify where debugging output is written. This must be the last flag, because it consumes the remainder of the argument. If the character immediately after the .Cm F flag is .Ql \&+ , the file is opened in append mode; otherwise the file is overwritten. If the file name is .Ql stdout or .Ql stderr , debugging output is written to the standard output or standard error output respectively (and the .Ql \&+ option has no effect). Otherwise, the output is written to the named file. If the file name ends with .Ql .%d , the .Ql %d is replaced by the pid. .It Cm f Print debugging information about loop evaluation. .It Cm g1 Print the input graph before making anything. .It Cm g2 Print the input graph after making everything, or before exiting on error. .It Cm g3 Print the input graph before exiting on error. .It Cm h Print debugging information about hash table operations. .It Cm j Print debugging information about running multiple shells. .It Cm L Turn on lint checks. This throws errors for variable assignments that do not parse correctly, at the time of assignment, so the file and line number are available. .It Cm l Print commands in Makefiles regardless of whether or not they are prefixed by .Ql @ or other .Dq quiet flags. Also known as .Dq loud behavior. .It Cm M Print debugging information about .Dq meta mode decisions about targets. .It Cm m Print debugging information about making targets, including modification dates. .It Cm n Don't delete the temporary command scripts created when running commands. These temporary scripts are created in the directory referred to by the .Ev TMPDIR environment variable, or in .Pa /tmp if .Ev TMPDIR is unset or set to the empty string. The temporary scripts are created by .Xr mkstemp 3 , and have names of the form .Pa makeXXXXXX . .Em NOTE : This can create many files in .Ev TMPDIR or .Pa /tmp , so use with care. .It Cm p Print debugging information about makefile parsing. .It Cm s Print debugging information about suffix-transformation rules. .It Cm t Print debugging information about target list maintenance. .It Cm V Force the .Fl V option to print raw values of variables, overriding the default behavior set via .Va .MAKE.EXPAND_VARIABLES . .It Cm v Print debugging information about variable assignment and expansion. .It Cm x Run shell commands with .Fl x so the actual commands are printed as they are executed. .El .It Fl e Let environment variables override global variables within makefiles. .It Fl f Ar makefile -Specify a makefile to read instead of the default -.Pa makefile -or -.Pa Makefile . +Specify a makefile to read instead of one of the defaults listed in +.Va .MAKE.MAKEFILE_PREFERENCE . If .Ar makefile is .Ql \&- , standard input is read. +If +.Ar makefile +starts with the string +.Ql \&.../ , +.Nm +searches for the specified path in the rest of the argument +in the current directory and its parents. Multiple makefiles may be specified, and are read in the order specified. .It Fl I Ar directory Specify a directory in which to search for makefiles and included makefiles. The system makefile directory (or directories, see the .Fl m option) is automatically included as part of this list. .It Fl i Ignore non-zero exit of shell commands in the makefile. Equivalent to specifying .Ql \&- before each command line in the makefile. .It Fl J Ar private This option should .Em not be specified by the user. .Pp When the .Fl j option is in use in a recursive build, this option is passed by a make to child makes to allow all the make processes in the build to cooperate to avoid overloading the system. .It Fl j Ar max_jobs Specify the maximum number of jobs that .Nm may have running at any one time. If .Ar max_jobs is a floating point number, or ends with .Ql C , then the value is multiplied by the number of CPUs reported online by .Xr sysconf 3 . The value of .Ar max_jobs is saved in .Va .MAKE.JOBS . Turns compatibility mode off, unless the .Fl B option is also specified. When compatibility mode is off, all commands associated with a target are executed in a single shell invocation as opposed to the traditional one shell invocation per line. This can break traditional scripts which change directories on each command invocation and then expect to start with a fresh environment on the next line. It is more efficient to correct the scripts rather than turn backwards compatibility on. .Pp A job token pool with .Ar max_jobs tokens is used to control the total number of jobs running. Each instance of .Nm will wait for a token from the pool before running a new job. .It Fl k Continue processing after errors are encountered, but only on those targets that do not depend on the target whose creation caused the error. .It Fl m Ar directory Specify a directory in which to search for .Pa sys.mk and makefiles included via the .Li \&< Ns Ar file Ns Li \&> Ns -style include statement. The .Fl m option can be used multiple times to form a search path. This path overrides the default system include path .Pa /usr/share/mk . Furthermore, the system include path is appended to the search path used for .Li \*q Ns Ar file Ns Li \*q Ns -style include statements (see the .Fl I option). The system include path can be referenced via the read-only variable .Va .SYSPATH . .Pp If a directory name in the .Fl m argument (or the .Ev MAKESYSPATH environment variable) starts with the string .Ql \&.../ , .Nm searches for the specified file or directory named in the remaining part of the argument string. The search starts with the current directory and then works upward towards the root of the file system. If the search is successful, the resulting directory replaces the .Ql \&.../ specification in the .Fl m argument. This feature allows .Nm to easily search in the current source tree for customized .Pa sys.mk files (e.g., by using .Ql \&.../mk/sys.mk as an argument). .It Fl n Display the commands that would have been executed, but do not actually execute them unless the target depends on the .Va .MAKE special source (see below) or the command is prefixed with .Sq Cm + . .It Fl N Display the commands that would have been executed, but do not actually execute any of them; useful for debugging top-level makefiles without descending into subdirectories. .It Fl q Do not execute any commands, instead exit 0 if the specified targets are up to date, and 1 otherwise. .It Fl r Do not use the built-in rules specified in the system makefile. .It Fl S Stop processing if an error is encountered. This is the default behavior and the opposite of .Fl k . .It Fl s Do not echo any commands as they are executed. Equivalent to specifying .Sq Ic @ before each command line in the makefile. .It Fl T Ar tracefile When used with the .Fl j flag, append a trace record to .Ar tracefile for each job started and completed. .It Fl t Rather than re-building a target as specified in the makefile, create it or update its modification time to make it appear up-to-date. .It Fl V Ar variable Print the value of .Ar variable . Do not build any targets. Multiple instances of this option may be specified; the variables are printed one per line, with a blank line for each null or undefined variable. The value printed is extracted from the global scope after all makefiles have been read. .Pp By default, the raw variable contents (which may include additional unexpanded variable references) are shown. If .Ar variable contains a .Ql \&$ , it is not interpreted as a variable name but rather as an expression. Its value is expanded before printing. The value is also expanded before printing if .Va .MAKE.EXPAND_VARIABLES is set to true and the .Fl dV option has not been used to override it. .Pp Note that loop-local and target-local variables, as well as values taken temporarily by global variables during makefile processing, are not accessible via this option. The .Fl dv debug mode can be used to see these at the cost of generating substantial extraneous output. .It Fl v Ar variable Like .Fl V , but all printed variables are always expanded to their complete value. The last occurrence of .Fl V or .Fl v decides whether all variables are expanded or not. .It Fl W Treat any warnings during makefile parsing as errors. .It Fl w Print entering and leaving directory messages, pre and post processing. .It Fl X Don't export variables passed on the command line to the environment individually. Variables passed on the command line are still exported via the .Ev MAKEFLAGS environment variable. This option may be useful on systems which have a small limit on the size of command arguments. .It Ar variable\| Ns Cm \&= Ns Ar value Set the value of the variable .Ar variable to .Ar value . Normally, all values passed on the command line are also exported to sub-makes in the environment. The .Fl X flag disables this behavior. Variable assignments should follow options for POSIX compatibility but no ordering is enforced. .El .Pp There are several different types of lines in a makefile: dependency specifications, shell commands, variable assignments, include statements, conditional directives, for loops, other directives, and comments. .Pp Lines may be continued from one line to the next by ending them with a backslash .Pq Ql \e . The trailing newline character and initial whitespace on the following line are compressed into a single space. .Sh FILE DEPENDENCY SPECIFICATIONS Dependency lines consist of one or more targets, an operator, and zero or more sources. This creates a relationship where the targets .Dq depend on the sources and are customarily created from them. A target is considered out of date if it does not exist, or if its modification time is less than that of any of its sources. An out-of-date target is re-created, but not until all sources have been examined and themselves re-created as needed. Three operators may be used: .Bl -tag -width flag .It Ic \&: Many dependency lines may name this target but only one may have attached shell commands. All sources named in all dependency lines are considered together, and if needed the attached shell commands are run to create or re-create the target. If .Nm is interrupted, the target is removed. .It Ic \&! The same, but the target is always re-created whether or not it is out of date. .It Ic \&:: Any dependency line may have attached shell commands, but each one is handled independently: its sources are considered and the attached shell commands are run if the target is out of date with respect to (only) those sources. Thus, different groups of the attached shell commands may be run depending on the circumstances. Furthermore, unlike .Ic \&: , for dependency lines with no sources, the attached shell commands are always run. Also unlike .Ic \&: , the target is not removed if .Nm is interrupted. .El .Pp All dependency lines mentioning a particular target must use the same operator. .Pp Targets and sources may contain the shell wildcard values .Ql \&? , .Ql * , .Ql [] , and .Ql {} . The values .Ql \&? , .Ql * , and .Ql [] may only be used as part of the final component of the target or source, and only match existing files. The value .Ql {} need not necessarily be used to describe existing files. Expansion is in directory order, not alphabetically as done in the shell. .Sh SHELL COMMANDS Each target may have associated with it one or more lines of shell commands, normally used to create the target. Each of the lines in this script .Em must be preceded by a tab. (For historical reasons, spaces are not accepted.) While targets can occur in many dependency lines if desired, by default only one of these rules may be followed by a creation script. If the .Sq Ic \&:: operator is used, however, all rules may include scripts, and the respective scripts are executed in the order found. .Pp Each line is treated as a separate shell command, unless the end of line is escaped with a backslash .Ql \e , in which case that line and the next are combined. If the first characters of the command are any combination of .Sq Ic @ , .Sq Ic + , or .Sq Ic \- , the command is treated specially. .Bl -tag -offset indent -width indent .It Ic @ causes the command not to be echoed before it is executed. .It Ic + causes the command to be executed even when .Fl n is given. This is similar to the effect of the .Va .MAKE special source, except that the effect can be limited to a single line of a script. .It Ic \- in compatibility mode causes any non-zero exit status of the command line to be ignored. .El .Pp When .Nm is run in jobs mode with .Fl j Ar max_jobs , the entire script for the target is fed to a single instance of the shell. In compatibility (non-jobs) mode, each command is run in a separate process. If the command contains any shell meta characters .Pq Ql #=|^(){};&<>*?[]:$`\e\en , it is passed to the shell; otherwise .Nm attempts direct execution. If a line starts with .Sq Ic \- and the shell has ErrCtl enabled, failure of the command line is ignored as in compatibility mode. Otherwise .Sq Ic \- affects the entire job; the script stops at the first command line that fails, but the target is not deemed to have failed. .Pp Makefiles should be written so that the mode of .Nm operation does not change their behavior. For example, any command which uses .Dq cd or .Dq chdir without the intention of changing the directory for subsequent commands should be put in parentheses so it executes in a subshell. To force the use of a single shell, escape the line breaks so as to make the whole script one command. For example: .Bd -literal -offset indent avoid-chdir-side-effects: @echo "Building $@ in $$(pwd)" @(cd ${.CURDIR} && ${MAKE} $@) @echo "Back in $$(pwd)" ensure-one-shell-regardless-of-mode: @echo "Building $@ in $$(pwd)"; \e (cd ${.CURDIR} && ${MAKE} $@); \e echo "Back in $$(pwd)" .Ed .Pp Since .Nm changes the current working directory to .Sq Va .OBJDIR before executing any targets, each child process starts with that as its current working directory. .Sh VARIABLE ASSIGNMENTS Variables in make behave much like macros in the C preprocessor. .Pp Variable assignments have the form .Sq Ar NAME Ar op Ar value , where: .Bl -tag -offset Ds -width Ds .It Ar NAME is a single-word variable name, consisting, by tradition, of all upper-case letters, .It Ar op is one of the variable assignment operators described below, and .It Ar value is interpreted according to the variable assignment operator. .El .Pp Whitespace around .Ar NAME , .Ar op and .Ar value is discarded. .Ss Variable assignment operators The five operators that assign values to variables are: .Bl -tag -width Ds .It Ic \&= Assign the value to the variable. Any previous value is overwritten. .It Ic \&+= Append the value to the current value of the variable, separating them by a single space. .It Ic \&?= Assign the value to the variable if it is not already defined. .It Ic \&:= Expand the value, then assign it to the variable. .Pp .Em NOTE : References to undefined variables are .Em not expanded. This can cause problems when variable modifiers are used. .\" See var-op-expand.mk, the section with LATER and INDIRECT. .It Ic \&!= Expand the value and pass it to the shell for execution, then assign the output from the child's standard output to the variable. Any newlines in the result are replaced with spaces. .El .Ss Expansion of variables In most contexts where variables are expanded, .Ql \&$$ expands to a single dollar sign. In other contexts (most variable modifiers, string literals in conditions), .Ql \&\e$ expands to a single dollar sign. .Pp References to variables have the form .Cm \&${ Ns Ar name Ns Oo Ns Cm \&: Ns Ar modifiers Oc Ns Cm \&} or .Cm \&$( Ns Ar name Ns Oo Ns Cm \&: Ns Ar modifiers Oc Ns Cm \&) . If the variable name consists of only a single character and the expression contains no modifiers, the surrounding curly braces or parentheses are not required. This shorter form is not recommended. .Pp If the variable name contains a dollar, the name itself is expanded first. This allows almost arbitrary variable names, however names containing dollar, braces, parentheses or whitespace are really best avoided. .Pp If the result of expanding a nested variable expression contains a dollar sign .Pq Ql \&$ , the result is subject to further expansion. .Pp Variable substitution occurs at four distinct times, depending on where the variable is being used. .Bl -enum .It Variables in dependency lines are expanded as the line is read. .It Variables in conditionals are expanded individually, but only as far as necessary to determine the result of the conditional. .It Variables in shell commands are expanded when the shell command is executed. .It .Ic .for loop index variables are expanded on each loop iteration. Note that other variables are not expanded when composing the body of a loop, so the following example code: .Bd -literal -offset indent \&.for i in 1 2 3 a+= ${i} j= ${i} b+= ${j} \&.endfor all: @echo ${a} @echo ${b} .Ed .Pp prints: .Bd -literal -offset indent 1 2 3 3 3 3 .Ed .Pp After the loop is executed: .Bl -tag -offset indent -width indent .It Va a contains .Ql ${:U1} ${:U2} ${:U3} , which expands to .Ql 1 2 3 . .It Va j contains .Ql ${:U3} , which expands to .Ql 3 . .It Va b contains .Ql ${j} ${j} ${j} , which expands to .Ql ${:U3} ${:U3} ${:U3} and further to .Ql 3 3 3 . .El .El .Ss Variable classes The four different classes of variables (in order of increasing precedence) are: .Bl -tag -width Ds .It Environment variables Variables defined as part of .Nm Ns 's environment. .It Global variables Variables defined in the makefile or in included makefiles. .It Command line variables Variables defined as part of the command line. .It Local variables Variables that are defined specific to a certain target. .El .Pp Local variables can be set on a dependency line, unless .Va .MAKE.TARGET_LOCAL_VARIABLES is set to .Ql false . The rest of the line (which already has had global variables expanded) is the variable value. For example: .Bd -literal -offset indent COMPILER_WRAPPERS= ccache distcc icecc ${OBJS}: .MAKE.META.CMP_FILTER=${COMPILER_WRAPPERS:S,^,N,} .Ed .Pp Only the targets .Ql ${OBJS} are impacted by that filter (in .Dq meta mode) and simply enabling/disabling any of the compiler wrappers does not render all of those targets out-of-date. .Pp .Em NOTE : target-local variable assignments behave differently in that; .Bl -tag -width Ds -offset indent .It Ic \&+= Only appends to a previous local assignment for the same target and variable. .It Ic \&:= Is redundant with respect to global variables, which have already been expanded. .El .Pp The seven built-in local variables are: .Bl -tag -width ".Va .ARCHIVE" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as .Sq Va \&> . .It Va .ARCHIVE The name of the archive file; also known as .Sq Va \&! . .It Va .IMPSRC In suffix-transformation rules, the name/path of the source from which the target is to be transformed (the .Dq implied source); also known as .Sq Va \&< . It is not defined in explicit rules. .It Va .MEMBER The name of the archive member; also known as .Sq Va % . .It Va .OODATE The list of sources for this target that were deemed out-of-date; also known as .Sq Va \&? . .It Va .PREFIX The name of the target with suffix (if declared in .Ic .SUFFIXES ) removed; also known as .Sq Va * . .It Va .TARGET The name of the target; also known as .Sq Va @ . For compatibility with other makes this is an alias for .Va .ARCHIVE in archive member rules. .El .Pp The shorter forms .Po .Sq Va \&> , .Sq Va \&! , .Sq Va \&< , .Sq Va \&% , .Sq Va \&? , .Sq Va \&* , and .Sq Va \&@ .Pc are permitted for backward compatibility with historical makefiles and legacy POSIX make and are not recommended. .Pp Variants of these variables with the punctuation followed immediately by .Ql D or .Ql F , e.g.\& .Ql $(@D) , are legacy forms equivalent to using the .Ql :H and .Ql :T modifiers. These forms are accepted for compatibility with .At V makefiles and POSIX but are not recommended. .Pp Four of the local variables may be used in sources on dependency lines because they expand to the proper value for each target on the line. These variables are .Sq Va .TARGET , .Sq Va .PREFIX , .Sq Va .ARCHIVE , and .Sq Va .MEMBER . .Ss Additional built-in variables In addition, .Nm sets or knows about the following variables: .Bl -tag .\" NB: This list is sorted case-insensitive, ignoring punctuation. .\" NB: To find all built-in variables in make's source code, .\" NB: search for Var_*, Global_*, SetVarObjdir, GetBooleanExpr, .\" NB: and the implementation of Var_SetWithFlags. .\" NB: Last synced on 2023-01-01. .It Va .ALLTARGETS The list of all targets encountered in the makefiles. If evaluated during makefile parsing, lists only those targets encountered thus far. .It Va .CURDIR A path to the directory where .Nm was executed. Refer to the description of .Sq Va PWD for more details. .It Va .ERROR_CMD Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_CWD Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_EXIT Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_META_FILE Is used in error handling in .Dq meta mode, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .ERROR_TARGET Is used in error handling, see .Va MAKE_PRINT_VAR_ON_ERROR . .It Va .INCLUDEDFROMDIR The directory of the file this makefile was included from. .It Va .INCLUDEDFROMFILE The filename of the file this makefile was included from. .\" .INCLUDES is intentionally undocumented, as it is obsolete. .\" .LIBS is intentionally undocumented, as it is obsolete. .It Va MACHINE The machine hardware name, see .Xr uname 1 . .It Va MACHINE_ARCH The machine processor architecture name, see .Xr uname 1 . .It Va MAKE The name that .Nm was executed with .Pq Va argv[0] . .It Va .MAKE The same as .Va MAKE , for compatibility. The preferred variable to use is the environment variable .Ev MAKE because it is more compatible with other make variants and cannot be confused with the special target with the same name. .It Va .MAKE.ALWAYS_PASS_JOB_QUEUE Tells .Nm whether to pass the descriptors of the job token queue even if the target is not tagged with .Ic .MAKE The default is .Ql Pa yes for backwards compatability with .Fx 9.0 and earlier. .\" '.MAKE.cmd_filtered' is intentionally undocumented, .\" as it is an internal implementation detail. .It Va .MAKE.DEPENDFILE Names the makefile (default .Sq Pa .depend ) from which generated dependencies are read. .It Va .MAKE.DIE_QUIETLY If set to .Ql true , do not print error information at the end. .It Va .MAKE.EXPAND_VARIABLES A boolean that controls the default behavior of the .Fl V option. If true, variable values printed with .Fl V are fully expanded; if false, the raw variable contents (which may include additional unexpanded variable references) are shown. .It Va .MAKE.EXPORTED The list of variables exported by .Nm . .It Va MAKEFILE The top-level makefile that is currently read, as given in the command line. .It Va .MAKEFLAGS The environment variable .Sq Ev MAKEFLAGS may contain anything that may be specified on .Nm Ns 's command line. Anything specified on .Nm Ns 's command line is appended to the .Va .MAKEFLAGS variable, which is then added to the environment for all programs that .Nm executes. .It Va .MAKE.GID The numeric group ID of the user running .Nm . It is read-only. .It Va .MAKE.JOB.PREFIX If .Nm is run with .Fl j , the output for each target is prefixed with a token .Dl --- Ar target Li --- the first part of which can be controlled via .Va .MAKE.JOB.PREFIX . If .Va .MAKE.JOB.PREFIX is empty, no token is printed. For example, setting .Va .MAKE.JOB.PREFIX to .Ql ${.newline}---${.MAKE:T}[${.MAKE.PID}] would produce tokens like .Dl ---make[1234] Ar target Li --- making it easier to track the degree of parallelism being achieved. .It Va .MAKE.JOBS The argument to the .Fl j option. .It Va .MAKE.JOBS.C A read-only boolean that indicates whether the .Fl j option supports use of .Ql C . .It Va .MAKE.LEVEL The recursion depth of .Nm . The top-level instance of .Nm has level 0, and each child make has its parent level plus 1. This allows tests like: .Li .if ${.MAKE.LEVEL} == 0 to protect things which should only be evaluated in the top-level instance of .Nm . .It Va .MAKE.LEVEL.ENV The name of the environment variable that stores the level of nested calls to .Nm . .It Va .MAKE.MAKEFILE_PREFERENCE The ordered list of makefile names (default .Sq Pa makefile , .Sq Pa Makefile ) that .Nm looks for. .It Va .MAKE.MAKEFILES The list of makefiles read by .Nm , which is useful for tracking dependencies. Each makefile is recorded only once, regardless of the number of times read. .It Va .MAKE.META.BAILIWICK In .Dq meta mode, provides a list of prefixes which match the directories controlled by .Nm . If a file that was generated outside of .Va .OBJDIR but within said bailiwick is missing, the current target is considered out-of-date. .It Va .MAKE.META.CMP_FILTER In .Dq meta mode, it can (very rarely!) be useful to filter command lines before comparison. This variable can be set to a set of modifiers that are applied to each line of the old and new command that differ, if the filtered commands still differ, the target is considered out-of-date. .It Va .MAKE.META.CREATED In .Dq meta mode, this variable contains a list of all the meta files updated. If not empty, it can be used to trigger processing of .Va .MAKE.META.FILES . .It Va .MAKE.META.FILES In .Dq meta mode, this variable contains a list of all the meta files used (updated or not). This list can be used to process the meta files to extract dependency information. .It Va .MAKE.META.IGNORE_FILTER Provides a list of variable modifiers to apply to each pathname. Ignore if the expansion is an empty string. .It Va .MAKE.META.IGNORE_PATHS Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: .Sq Pa /dev /etc /proc /tmp /var/run /var/tmp .It Va .MAKE.META.IGNORE_PATTERNS Provides a list of patterns to match against pathnames. Ignore any that match. .It Va .MAKE.META.PREFIX Defines the message printed for each meta file updated in .Dq meta verbose mode. The default value is: .Dl Building ${.TARGET:H:tA}/${.TARGET:T} .It Va .MAKE.MODE Processed after reading all makefiles. Affects the mode that .Nm runs in. It can contain these keywords: .Bl -tag -width indent .It Cm compat Like .Fl B , puts .Nm into .Dq compat mode. .It Cm meta Puts .Nm into .Dq meta mode, where meta files are created for each target to capture the command run, the output generated, and if .Xr filemon 4 is available, the system calls which are of interest to .Nm . The captured output can be useful when diagnosing errors. .It Cm curdirOk= Ns Ar bf By default, .Nm does not create .Pa .meta files in .Sq Va .CURDIR . This can be overridden by setting .Ar bf to a value which represents true. .It Cm missing-meta= Ns Ar bf If .Ar bf is true, a missing .Pa .meta file makes the target out-of-date. .It Cm missing-filemon= Ns Ar bf If .Ar bf is true, missing filemon data makes the target out-of-date. .It Cm nofilemon Do not use .Xr filemon 4 . .It Cm env For debugging, it can be useful to include the environment in the .Pa .meta file. .It Cm verbose If in .Dq meta mode, print a clue about the target being built. This is useful if the build is otherwise running silently. The message printed is the expanded value of .Va .MAKE.META.PREFIX . .It Cm ignore-cmd Some makefiles have commands which are simply not stable. This keyword causes them to be ignored for determining whether a target is out of date in .Dq meta mode. See also .Ic .NOMETA_CMP . .It Cm silent= Ns Ar bf If .Ar bf is true, when a .meta file is created, mark the target .Ic .SILENT . .It Cm randomize-targets In both compat and parallel mode, do not make the targets in the usual order, but instead randomize their order. This mode can be used to detect undeclared dependencies between files. .El .It Va MAKEOBJDIR Used to create files in a separate directory, see .Va .OBJDIR . .It Va MAKE_OBJDIR_CHECK_WRITABLE When true, .Nm will check that .Va .OBJDIR is writable, and issue a warning if not. .It Va MAKE_DEBUG_OBJDIR_CHECK_WRITABLE When true and .Nm is warning about an unwritable .Va .OBJDIR , report the variables listed in .Va MAKE_PRINT_VAR_ON_ERROR to help debug. .It Va MAKEOBJDIRPREFIX Used to create files in a separate directory, see .Va .OBJDIR . +It should be an absolute path. .It Va .MAKE.OS The name of the operating system, see .Xr uname 1 . It is read-only. .It Va .MAKEOVERRIDES This variable is used to record the names of variables assigned to on the command line, so that they may be exported as part of .Sq Ev MAKEFLAGS . This behavior can be disabled by assigning an empty value to .Sq Va .MAKEOVERRIDES within a makefile. Extra variables can be exported from a makefile by appending their names to .Sq Va .MAKEOVERRIDES . .Sq Ev MAKEFLAGS is re-exported whenever .Sq Va .MAKEOVERRIDES is modified. .It Va .MAKE.PATH_FILEMON If .Nm was built with .Xr filemon 4 support, this is set to the path of the device node. This allows makefiles to test for this support. .It Va .MAKE.PID The process ID of .Nm . It is read-only. .It Va .MAKE.PPID The parent process ID of .Nm . It is read-only. .It Va MAKE_PRINT_VAR_ON_ERROR When .Nm stops due to an error, it sets .Sq Va .ERROR_TARGET to the name of the target that failed, .Sq Va .ERROR_EXIT to the exit status of the failed target, .Sq Va .ERROR_CMD to the commands of the failed target, and in .Dq meta mode, it also sets .Sq Va .ERROR_CWD to the .Xr getcwd 3 , and .Sq Va .ERROR_META_FILE to the path of the meta file (if any) describing the failed target. It then prints its name and the value of .Sq Va .CURDIR as well as the value of any variables named in .Sq Va MAKE_PRINT_VAR_ON_ERROR . .It Va .MAKE.SAVE_DOLLARS If true, .Ql $$ are preserved when doing .Ql := assignments. The default is false, for backwards compatibility. Set to true for compatability with other makes. If set to false, .Ql $$ becomes .Ql $ per normal evaluation rules. .It Va .MAKE.TARGET_LOCAL_VARIABLES If set to .Ql false , apparent variable assignments in dependency lines are treated as normal sources. .It Va .MAKE.UID The numeric ID of the user running .Nm . It is read-only. .\" 'MAKE_VERSION' is intentionally undocumented .\" since it is only defined in the bmake distribution, .\" but not in NetBSD's native make. .\" '.meta.%d.lcwd' is intentionally undocumented .\" since it is an internal implementation detail. .\" '.meta.%d.ldir' is intentionally undocumented .\" since it is an internal implementation detail. .\" 'MFLAGS' is intentionally undocumented .\" since it is obsolete. .It Va .newline This variable is simply assigned a newline character as its value. It is read-only. This allows expansions using the .Cm \&:@ modifier to put a newline between iterations of the loop rather than a space. For example, in case of an error, .Nm prints the variable names and their values using: .Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} .It Va .OBJDIR A path to the directory where the targets are built. Its value is determined by trying to .Xr chdir 2 to the following directories in order and using the first match: .Bl -enum .It .Cm ${MAKEOBJDIRPREFIX} Ns Cm ${.CURDIR} .Pp (Only if .Sq Ev MAKEOBJDIRPREFIX is set in the environment or on the command line.) .It .Cm ${MAKEOBJDIR} .Pp (Only if .Sq Ev MAKEOBJDIR is set in the environment or on the command line.) .It .Cm ${.CURDIR} Ns Pa /obj. Ns Cm ${MACHINE} .It .Cm ${.CURDIR} Ns Pa /obj .It .Pa /usr/obj/ Ns Cm ${.CURDIR} .It .Cm ${.CURDIR} .El .Pp Variable expansion is performed on the value before it is used, so expressions such as .Cm ${.CURDIR:S,^/usr/src,/var/obj,} may be used. This is especially useful with .Sq Ev MAKEOBJDIR . .Pp .Sq Va .OBJDIR may be modified in the makefile via the special target .Sq Ic .OBJDIR . In all cases, .Nm changes to the specified directory if it exists, and sets .Sq Va .OBJDIR and .Sq Va PWD to that directory before executing any targets. .Pp Except in the case of an explicit .Sq Ic .OBJDIR target, .Nm checks that the specified directory is writable and ignores it if not. This check can be skipped by setting the environment variable .Sq Ev MAKE_OBJDIR_CHECK_WRITABLE to .Dq no . .It Va .PARSEDIR The directory name of the current makefile being parsed. .It Va .PARSEFILE The basename of the current makefile being parsed. This variable and .Sq Va .PARSEDIR are both set only while the makefiles are being parsed. To retain their current values, assign them to a variable using assignment with expansion .Sq Cm \&:= . .It Va .PATH The space-separated list of directories that .Nm searches for files. To update this search list, use the special target .Sq Ic .PATH rather than modifying the variable directly. .It Va %POSIX Is set in POSIX mode, see the special .Ql Va .POSIX target. .\" XXX: There is no make variable named 'PWD', .\" XXX: make only reads and writes the environment variable 'PWD'. .It Va PWD Alternate path to the current directory. .Nm normally sets .Sq Va .CURDIR to the canonical path given by .Xr getcwd 3 . However, if the environment variable .Sq Ev PWD is set and gives a path to the current directory, .Nm sets .Sq Va .CURDIR to the value of .Sq Ev PWD instead. This behavior is disabled if .Sq Ev MAKEOBJDIRPREFIX is set or .Sq Ev MAKEOBJDIR contains a variable transform. .Sq Va PWD is set to the value of .Sq Va .OBJDIR for all programs which .Nm executes. .It Va .SHELL The pathname of the shell used to run target scripts. It is read-only. .It Va .SUFFIXES The list of known suffixes. It is read-only. .It Va .SYSPATH The space-separated list of directories that .Nm searches for makefiles, referred to as the system include path. To update this search list, use the special target .Sq Ic .SYSPATH rather than modifying the variable which is read-only. .It Va .TARGETS The list of targets explicitly specified on the command line, if any. .It Va VPATH The colon-separated .Pq Dq \&: list of directories that .Nm searches for files. This variable is supported for compatibility with old make programs only, use .Sq Va .PATH instead. .El .Ss Variable modifiers The general format of a variable expansion is: .Pp .Sm off .D1 Ic \&${ Ar variable\| Oo Ic \&: Ar modifier\| Oo Ic \&: No ... Oc Oc Ic \&} .Sm on .Pp Each modifier begins with a colon. To escape a colon, precede it with a backslash .Ql \e . .Pp A list of indirect modifiers can be specified via a variable, as follows: .Pp .Bd -literal -offset indent .Ar modifier_variable\^ Li \&= Ar modifier Ns Oo Ic \&: Ns No ... Oc .Sm off .Ic \&${ Ar variable Ic \&:${ Ar modifier_variable Ic \&} Oo Ic \&: No ... Oc Ic \&} .Sm on .Ed .Pp In this case, the first modifier in the .Ar modifier_variable does not start with a colon, since that colon already occurs in the referencing variable. If any of the modifiers in the .Ar modifier_variable contains a dollar sign .Pq Ql $ , these must be doubled to avoid early expansion. .Pp Some modifiers interpret the expression value as a single string, others treat the expression value as a whitespace-separated list of words. When splitting a string into words, whitespace can be escaped using double quotes, single quotes and backslashes, like in the shell. The quotes and backslashes are retained in the words. .Pp The supported modifiers are: .Bl -tag -width EEE .It Cm \&:E Replaces each word with its suffix. .It Cm \&:H Replaces each word with its dirname. .It Cm \&:M\| Ns Ar pattern Selects only those words that match .Ar pattern . The standard shell wildcard characters .Pf ( Ql * , .Ql \&? , and .Ql \&[] ) may be used. The wildcard characters may be escaped with a backslash .Pq Ql \e . As a consequence of the way values are split into words, matched, and then joined, the construct .Ql ${VAR:M*} removes all leading and trailing whitespace and normalizes the inter-word spacing to a single space. .It Cm \&:N\| Ns Ar pattern This is the opposite of .Sq Cm \&:M , selecting all words which do .Em not match .Ar pattern . .It Cm \&:O Orders the words lexicographically. .It Cm \&:On Orders the words numerically. A number followed by one of .Ql k , .Ql M or .Ql G is multiplied by the appropriate factor, which is 1024 for .Ql k , 1048576 for .Ql M , or 1073741824 for .Ql G . Both upper- and lower-case letters are accepted. .It Cm \&:Or Orders the words in reverse lexicographical order. .It Cm \&:Orn Orders the words in reverse numerical order. .It Cm \&:Ox Shuffles the words. The results are different each time you are referring to the modified variable; use the assignment with expansion .Sq Cm \&:= to prevent such behavior. For example, .Bd -literal -offset indent LIST= uno due tre quattro RANDOM_LIST= ${LIST:Ox} STATIC_RANDOM_LIST:= ${LIST:Ox} all: @echo "${RANDOM_LIST}" @echo "${RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" @echo "${STATIC_RANDOM_LIST}" .Ed may produce output similar to: .Bd -literal -offset indent quattro due tre uno tre due quattro uno due uno quattro tre due uno quattro tre .Ed .It Cm \&:Q Quotes every shell meta-character in the value, so that it can be passed safely to the shell. .It Cm \&:q Quotes every shell meta-character in the value, and also doubles .Sq $ characters so that it can be passed safely through recursive invocations of .Nm . This is equivalent to .Sq Cm \&:S/\e\&$/&&/g:Q . .It Cm \&:R Replaces each word with everything but its suffix. .It Cm \&:range Ns Oo Cm = Ns Ar count Oc The value is an integer sequence representing the words of the original value, or the supplied .Ar count . .It Cm \&:gmtime Ns Oo Cm = Ns Ar timestamp Oc The value is interpreted as a format string for .Xr strftime 3 , using .Xr gmtime 3 , producing the formatted timestamp. Note: the .Ql %s format should only be used with .Sq Cm \&:localtime . If a .Ar timestamp value is not provided or is 0, the current time is used. .It Cm \&:hash Computes a 32-bit hash of the value and encodes it as 8 hex digits. .It Cm \&:localtime Ns Oo Cm = Ns Ar timestamp Oc The value is interpreted as a format string for .Xr strftime 3 , using .Xr localtime 3 , producing the formatted timestamp. If a .Ar timestamp value is not provided or is 0, the current time is used. .It Cm \&:mtime Ns Oo Cm = Ns Ar timestamp Oc Call .Xr stat 2 with each word as pathname; use .Ql st_mtime as the new value. If .Xr stat 2 fails; use .Ar timestamp or current time. If .Ar timestamp is set to .Ql error , then .Xr stat 2 failure will cause an error. .It Cm \&:tA Attempts to convert the value to an absolute path using .Xr realpath 3 . If that fails, the value is unchanged. .It Cm \&:tl Converts the value to lower-case letters. .It Cm \&:ts Ns Ar c When joining the words after a modifier that treats the value as words, the words are normally separated by a space. This modifier changes the separator to the character .Ar c . If .Ar c is omitted, no separator is used. The common escapes (including octal numeric codes) work as expected. .It Cm \&:tt Converts the first character of each word to upper-case, and the rest to lower-case letters. .It Cm \&:tu Converts the value to upper-case letters. .It Cm \&:tW Causes subsequent modifiers to treat the value as a single word (possibly containing embedded whitespace). See also .Sq Cm \&:[*] . .It Cm \&:tw Causes the value to be treated as a list of words. See also .Sq Cm \&:[@] . .Sm off .It Cm \&:S\| No \&/ Ar old_string\| No \&/ Ar new_string\| No \&/ Op Cm 1gW .Sm on Modifies the first occurrence of .Ar old_string in each word of the value, replacing it with .Ar new_string . If a .Ql g is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a .Ql 1 is appended to the last delimiter of the pattern, only the first occurrence is affected. If a .Ql W is appended to the last delimiter of the pattern, the value is treated as a single word. If .Ar old_string begins with a caret .Pq Ql ^ , .Ar old_string is anchored at the beginning of each word. If .Ar old_string ends with a dollar sign .Pq Ql \&$ , it is anchored at the end of each word. Inside .Ar new_string , an ampersand .Pq Ql & is replaced by .Ar old_string (without the anchoring .Ql ^ or .Ql \&$ ) . Any character may be used as the delimiter for the parts of the modifier string. The anchoring, ampersand and delimiter characters can be escaped with a backslash .Pq Ql \e . .Pp Both .Ar old_string and .Ar new_string may contain nested expressions. To prevent a dollar sign from starting a nested expression, escape it with a backslash. .Sm off .It Cm \&:C\| No \&/ Ar pattern\| No \&/ Ar replacement\| No \&/ Op Cm 1gW .Sm on The .Cm \&:C modifier works like the .Cm \&:S modifier except that the old and new strings, instead of being simple strings, are an extended regular expression .Ar pattern (see .Xr regex 3 ) and an .Xr ed 1 Ns \-style .Ar replacement . Normally, the first occurrence of the pattern .Ar pattern in each word of the value is substituted with .Ar replacement . The .Ql 1 modifier causes the substitution to apply to at most one word; the .Ql g modifier causes the substitution to apply to as many instances of the search pattern .Ar pattern as occur in the word or words it is found in; the .Ql W modifier causes the value to be treated as a single word (possibly containing embedded whitespace). .Pp As for the .Cm \&:S modifier, the .Ar pattern and .Ar replacement are subjected to variable expansion before being parsed as regular expressions. .It Cm \&:T Replaces each word with its last path component (basename). .It Cm \&:u Removes adjacent duplicate words (like .Xr uniq 1 ) . .Sm off .It Cm \&:\&?\| Ar true_string\| Cm \&: Ar false_string .Sm on If the variable name (not its value), when parsed as a .Cm .if conditional expression, evaluates to true, return as its value the .Ar true_string , otherwise return the .Ar false_string . Since the variable name is used as the expression, \&:\&? must be the first modifier after the variable name .No itself Ns \^\(em\^ Ns which, of course, usually contains variable expansions. A common error is trying to use expressions like .Dl ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS). To determine if any words match .Dq 42 , you need to use something like: .Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} . .It Cm :\| Ns Ar old_string\| Ns Cm = Ns Ar new_string This is the .At V style substitution. It can only be the last modifier specified, as a .Ql \&: in either .Ar old_string or .Ar new_string is treated as a regular character, not as the end of the modifier. .Pp If .Ar old_string does not contain the pattern matching character .Ql % , and the word ends with .Ar old_string or equals it, that suffix is replaced with .Ar new_string . .Pp Otherwise, the first .Ql % in .Ar old_string matches a possibly empty substring of arbitrary characters, and if the whole pattern is found in the word, the matching part is replaced with .Ar new_string , and the first occurrence of .Ql % in .Ar new_string (if any) is replaced with the substring matched by the .Ql % . .Pp Both .Ar old_string and .Ar new_string may contain nested expressions. To prevent a dollar sign from starting a nested expression, escape it with a backslash. .Sm off .It Cm \&:@ Ar varname\| Cm @ Ar string\| Cm @ .Sm on This is the loop expansion mechanism from the OSF Development Environment (ODE) make. Unlike .Cm \&.for loops, expansion occurs at the time of reference. For each word in the value, assign the word to the variable named .Ar varname and evaluate .Ar string . The ODE convention is that .Ar varname should start and end with a period, for example: .Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@} .Pp However, a single-letter variable is often more readable: .Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} .It Cm \&:_ Ns Oo Cm = Ns Ar var Oc Saves the current variable value in .Ql $_ or the named .Ar var for later reference. Example usage: .Bd -literal -offset indent M_cmpv.units = 1 1000 1000000 M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\ \\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} .Ed Here .Ql $_ is used to save the result of the .Ql :S modifier which is later referenced using the index values from .Ql :range . .It Cm \&:U\| Ns Ar newval If the variable is undefined, the optional .Ar newval (which may be empty) is the value. If the variable is defined, the existing value is returned. This is another ODE make feature. It is handy for setting per-target CFLAGS for instance: .Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} If a value is only required if the variable is undefined, use: .Dl ${VAR:D:Unewval} .It Cm \&:D\| Ns Ar newval If the variable is defined, .Ar newval (which may be empty) is the value. .It Cm \&:L The name of the variable is the value. .It Cm \&:P The path of the node which has the same name as the variable is the value. If no such node exists or its path is null, the name of the variable is used. In order for this modifier to work, the name (node) must at least have appeared on the right-hand side of a dependency. .Sm off .It Cm \&:\&! Ar cmd\| Cm \&! .Sm on The output of running .Ar cmd is the value. .It Cm \&:sh The value is run as a command, and the output becomes the new value. .It Cm \&::= Ns Ar str The variable is assigned the value .Ar str after substitution. This modifier and its variations are useful in obscure situations such as wanting to set a variable at a point where a target's shell commands are being parsed. These assignment modifiers always expand to nothing. .Pp The .Sq Cm \&:: helps avoid false matches with the .At V style .Ql \&:= modifier and since substitution always occurs, the .Ql \&::= form is vaguely appropriate. .It Cm \&::?= Ns Ar str As for .Cm \&::= but only if the variable does not already have a value. .It Cm \&::+= Ns Ar str Append .Ar str to the variable. .It Cm \&::!= Ns Ar cmd Assign the output of .Ar cmd to the variable. .It Cm \&:\&[ Ns Ar range Ns Cm \&] Selects one or more words from the value, or performs other operations related to the way in which the value is split into words. .Pp An empty value, or a value that consists entirely of white-space, is treated as a single word. For the purposes of the .Sq Cm \&:[] modifier, the words are indexed both forwards using positive integers (where index 1 represents the first word), and backwards using negative integers (where index \-1 represents the last word). .Pp The .Ar range is subjected to variable expansion, and the expanded result is then interpreted as follows: .Bl -tag -width index .\" :[n] .It Ar index Selects a single word from the value. .\" :[start..end] .It Ar start Ns Cm \&.. Ns Ar end Selects all words from .Ar start to .Ar end , inclusive. For example, .Sq Cm \&:[2..-1] selects all words from the second word to the last word. If .Ar start is greater than .Ar end , the words are output in reverse order. For example, .Sq Cm \&:[-1..1] selects all the words from last to first. If the list is already ordered, this effectively reverses the list, but it is more efficient to use .Sq Cm \&:Or instead of .Sq Cm \&:O:[-1..1] . .\" :[*] .It Cm \&* Causes subsequent modifiers to treat the value as a single word (possibly containing embedded whitespace). Analogous to the effect of .Li \&$* in Bourne shell. .\" :[0] .It 0 Means the same as .Sq Cm \&:[*] . .\" :[*] .It Cm \&@ Causes subsequent modifiers to treat the value as a sequence of words delimited by whitespace. Analogous to the effect of .Li \&$@ in Bourne shell. .\" :[#] .It Cm \&# Returns the number of words in the value. .El \" :[range] .El .Sh DIRECTIVES .Nm offers directives for including makefiles, conditionals and for loops. All these directives are identified by a line beginning with a single dot .Pq Ql \&. character, followed by the keyword of the directive, such as .Cm include or .Cm if . .Ss File inclusion Files are included with either .Cm \&.include \&< Ns Ar file Ns Cm \&> or .Cm \&.include \&\*q Ns Ar file Ns Cm \&\*q . Variables between the angle brackets or double quotes are expanded to form the file name. If angle brackets are used, the included makefile is expected to be in the system makefile directory. If double quotes are used, the including makefile's directory and any directories specified using the .Fl I option are searched before the system makefile directory. .Pp For compatibility with other make variants, .Sq Cm include Ar file No ... (without leading dot) is also accepted. .Pp If the include statement is written as .Cm .-include or as .Cm .sinclude , errors locating and/or opening include files are ignored. .Pp If the include statement is written as .Cm .dinclude , not only are errors locating and/or opening include files ignored, but stale dependencies within the included file are ignored just like in .Va .MAKE.DEPENDFILE . .Ss Exporting variables The directives for exporting and unexporting variables are: .Bl -tag -width Ds .It Ic .export Ar variable No ... Export the specified global variable. .Pp For compatibility with other make programs, .Cm export Ar variable\| Ns Cm \&= Ns Ar value (without leading dot) is also accepted. .Pp Appending a variable name to .Va .MAKE.EXPORTED is equivalent to exporting a variable. .It Ic .export-all Export all globals except for internal variables (those that start with .Ql \&. ) . This is not affected by the .Fl X flag, so should be used with caution. .It Ic .export-env Ar variable No ... The same as .Ql .export , except that the variable is not appended to .Va .MAKE.EXPORTED . This allows exporting a value to the environment which is different from that used by .Nm internally. .It Ic .export-literal Ar variable No ... The same as .Ql .export-env , except that variables in the value are not expanded. .It Ic .unexport Ar variable No ... The opposite of .Ql .export . The specified global .Ar variable is removed from .Va .MAKE.EXPORTED . If no variable list is provided, all globals are unexported, and .Va .MAKE.EXPORTED deleted. .It Ic .unexport-env Unexport all globals previously exported and clear the environment inherited from the parent. This operation causes a memory leak of the original environment, so should be used sparingly. Testing for .Va .MAKE.LEVEL being 0 would make sense. Also note that any variables which originated in the parent environment should be explicitly preserved if desired. For example: .Bd -literal -offset indent .Li .if ${.MAKE.LEVEL} == 0 PATH := ${PATH} .Li .unexport-env .Li .export PATH .Li .endif .Pp .Ed Would result in an environment containing only .Sq Ev PATH , which is the minimal useful environment. .\" TODO: Check the below sentence, environment variables don't start with '.'. Actually .Sq Va .MAKE.LEVEL is also pushed into the new environment. .El .Ss Messages The directives for printing messages to the output are: .Bl -tag -width Ds .It Ic .info Ar message The message is printed along with the name of the makefile and line number. .It Ic .warning Ar message The message prefixed by .Sq Li warning: is printed along with the name of the makefile and line number. .It Ic .error Ar message The message is printed along with the name of the makefile and line number, .Nm exits immediately. .El .Ss Conditionals The directives for conditionals are: .ds maybenot Oo Ic \&! Oc Ns .Bl -tag .It Ic .if \*[maybenot] Ar expression Op Ar operator expression No ... Test the value of an expression. .It Ic .ifdef \*[maybenot] Ar variable Op Ar operator variable No ... Test whether a variable is defined. .It Ic .ifndef \*[maybenot] Ar variable Op Ar operator variable No ... Test whether a variable is not defined. .It Ic .ifmake \*[maybenot] Ar target Op Ar operator target No ... Test the target being requested. .It Ic .ifnmake \*[maybenot] Ar target Op Ar operator target No ... Test the target being requested. .It Ic .else Reverse the sense of the last conditional. .It Ic .elif \*[maybenot] Ar expression Op Ar operator expression No ... A combination of .Sq Ic .else followed by .Sq Ic .if . .It Ic .elifdef \*[maybenot] Ar variable Op Ar operator variable No ... A combination of .Sq Ic .else followed by .Sq Ic .ifdef . .It Ic .elifndef \*[maybenot] Ar variable Op Ar operator variable No ... A combination of .Sq Ic .else followed by .Sq Ic .ifndef . .It Ic .elifmake \*[maybenot] Ar target Op Ar operator target No ... A combination of .Sq Ic .else followed by .Sq Ic .ifmake . .It Ic .elifnmake \*[maybenot] Ar target Op Ar operator target No ... A combination of .Sq Ic .else followed by .Sq Ic .ifnmake . .It Ic .endif End the body of the conditional. .El .Pp The .Ar operator may be any one of the following: .Bl -tag .It Ic \&|\&| Logical OR. .It Ic \&&& Logical AND; of higher precedence than .Sq Ic \&|\&| . .El .Pp .Nm only evaluates a conditional as far as is necessary to determine its value. Parentheses can be used to override the operator precedence. The boolean operator .Sq Ic \&! may be used to logically negate an expression, typically a function call. It is of higher precedence than .Sq Ic \&&& . .Pp The value of .Ar expression may be any of the following function call expressions: .Bl -tag .Sm off .It Ic defined Li \&( Ar varname Li \&) .Sm on Evaluates to true if the variable .Ar varname has been defined. .Sm off .It Ic make Li \&( Ar target Li \&) .Sm on Evaluates to true if the target was specified as part of .Nm Ns 's command line or was declared the default target (either implicitly or explicitly, see .Va .MAIN ) before the line containing the conditional. .Sm off .It Ic empty Li \&( Ar varname Oo Li : Ar modifiers Oc Li \&) .Sm on Evaluates to true if the expansion of the variable, after applying the modifiers, results in an empty string. .Sm off .It Ic exists Li \&( Ar pathname Li \&) .Sm on Evaluates to true if the given pathname exists. If relative, the pathname is searched for on the system search path (see .Va .PATH ) . .Sm off .It Ic target Li \&( Ar target Li \&) .Sm on Evaluates to true if the target has been defined. .Sm off .It Ic commands Li \&( Ar target Li \&) .Sm on Evaluates to true if the target has been defined and has commands associated with it. .El .Pp .Ar Expression may also be an arithmetic or string comparison. Variable expansion is performed on both sides of the comparison. If both sides are numeric and neither is enclosed in quotes, the comparison is done numerically, otherwise lexicographically. A string is interpreted as a hexadecimal integer if it is preceded by .Li 0x , otherwise it is interpreted as a decimal floating-point number; octal numbers are not supported. .Pp All comparisons may use the operators .Sq Ic \&== and .Sq Ic \&!= . Numeric comparisons may also use the operators .Sq Ic \&< , .Sq Ic \&<= , .Sq Ic \&> and .Sq Ic \&>= . .Pp If the comparison has neither a comparison operator nor a right side, the expression evaluates to true if it is nonempty and its numeric value (if any) is not zero. .Pp When .Nm is evaluating one of these conditional expressions, and it encounters a (whitespace-separated) word it doesn't recognize, either the .Dq make or .Dq defined function is applied to it, depending on the form of the conditional. If the form is .Sq Ic .ifdef , .Sq Ic .ifndef or .Sq Ic .if , the .Dq defined function is applied. Similarly, if the form is .Sq Ic .ifmake or .Sq Ic .ifnmake , the .Dq make function is applied. .Pp If the conditional evaluates to true, parsing of the makefile continues as before. If it evaluates to false, the following lines until the corresponding .Sq Ic .elif variant, .Sq Ic .else or .Sq Ic .endif are skipped. .Ss For loops For loops are typically used to apply a set of rules to a list of files. The syntax of a for loop is: .Pp .Bl -tag -compact -width Ds .It Ic \&.for Ar variable Oo Ar variable No ... Oc Ic in Ar expression .It Aq Ar make-lines .It Ic \&.endfor .El .Pp The .Ar expression is expanded and then split into words. On each iteration of the loop, one word is taken and assigned to each .Ar variable , in order, and these .Ar variables are substituted into the .Ar make-lines inside the body of the for loop. The number of words must come out even; that is, if there are three iteration variables, the number of words provided must be a multiple of three. .Pp If .Sq Ic .break is encountered within a .Cm \&.for loop, it causes early termination of the loop, otherwise a parse error. .\" TODO: Describe limitations with defined/empty. .Ss Other directives .Bl -tag -width Ds .It Ic .undef Ar variable No ... Un-define the specified global variables. Only global variables can be un-defined. .El .Sh COMMENTS Comments begin with a hash .Pq Ql \&# character, anywhere but in a shell command line, and continue to the end of an unescaped new line. .Sh SPECIAL SOURCES (ATTRIBUTES) .Bl -tag -width .IGNOREx .It Ic .EXEC Target is never out of date, but always execute commands anyway. .It Ic .IGNORE Ignore any errors from the commands associated with this target, exactly as if they all were preceded by a dash .Pq Ql \- . .\" .It Ic .INVISIBLE .\" XXX .\" .It Ic .JOIN .\" XXX .It Ic .MADE Mark all sources of this target as being up to date. .It Ic .MAKE Execute the commands associated with this target even if the .Fl n or .Fl t options were specified. Normally used to mark recursive .Nm Ns s . .It Ic .META Create a meta file for the target, even if it is flagged as .Ic .PHONY , .Ic .MAKE , or .Ic .SPECIAL . Usage in conjunction with .Ic .MAKE is the most likely case. In .Dq meta mode, the target is out-of-date if the meta file is missing. .It Ic .NOMETA Do not create a meta file for the target. Meta files are also not created for .Ic .PHONY , .Ic .MAKE , or .Ic .SPECIAL targets. .It Ic .NOMETA_CMP Ignore differences in commands when deciding if target is out of date. This is useful if the command contains a value which always changes. If the number of commands change, though, the target is still considered out of date. The same effect applies to any command line that uses the variable .Va .OODATE , which can be used for that purpose even when not otherwise needed or desired: .Bd -literal -offset indent skip-compare-for-some: @echo this is compared @echo this is not ${.OODATE:M.NOMETA_CMP} @echo this is also compared .Ed The .Cm \&:M pattern suppresses any expansion of the unwanted variable. .It Ic .NOPATH Do not search for the target in the directories specified by .Va .PATH . .It Ic .NOTMAIN Normally .Nm selects the first target it encounters as the default target to be built if no target was specified. This source prevents this target from being selected. .It Ic .OPTIONAL If a target is marked with this attribute and .Nm can't figure out how to create it, it ignores this fact and assumes the file isn't needed or already exists. .It Ic .PHONY The target does not correspond to an actual file; it is always considered to be out of date, and is not created with the .Fl t option. Suffix-transformation rules are not applied to .Ic .PHONY targets. .It Ic .PRECIOUS When .Nm is interrupted, it normally removes any partially made targets. This source prevents the target from being removed. .It Ic .RECURSIVE Synonym for .Ic .MAKE . .It Ic .SILENT Do not echo any of the commands associated with this target, exactly as if they all were preceded by an at sign .Pq Ql @ . .It Ic .USE Turn the target into .Nm Ns 's version of a macro. When the target is used as a source for another target, the other target acquires the commands, sources, and attributes (except for .Ic .USE ) of the source. If the target already has commands, the .Ic .USE target's commands are appended to them. .It Ic .USEBEFORE Like .Ic .USE , but instead of appending, prepend the .Ic .USEBEFORE target commands to the target. .It Ic .WAIT If .Ic .WAIT appears in a dependency line, the sources that precede it are made before the sources that succeed it in the line. Since the dependents of files are not made until the file itself could be made, this also stops the dependents being built unless they are needed for another branch of the dependency tree. So given: .Bd -literal x: a .WAIT b echo x a: echo a b: b1 echo b b1: echo b1 .Ed the output is always .Ql a , .Ql b1 , .Ql b , .Ql x . .Pp The ordering imposed by .Ic .WAIT is only relevant for parallel makes. .El .Sh SPECIAL TARGETS Special targets may not be included with other targets, i.e. they must be the only target specified. .Bl -tag -width .BEGINx .It Ic .BEGIN Any command lines attached to this target are executed before anything else is done. .It Ic .DEFAULT This is sort of a .Ic .USE rule for any target (that was used only as a source) that .Nm can't figure out any other way to create. Only the shell script is used. The .Va .IMPSRC variable of a target that inherits .Ic .DEFAULT Ns 's commands is set to the target's own name. .It Ic .DELETE_ON_ERROR If this target is present in the makefile, it globally causes make to delete targets whose commands fail. (By default, only targets whose commands are interrupted during execution are deleted. This is the historical behavior.) This setting can be used to help prevent half-finished or malformed targets from being left around and corrupting future rebuilds. .It Ic .END Any command lines attached to this target are executed after everything else is done successfully. .It Ic .ERROR Any command lines attached to this target are executed when another target fails. See .Va MAKE_PRINT_VAR_ON_ERROR for the variables that will be set. .It Ic .IGNORE Mark each of the sources with the .Ic .IGNORE attribute. If no sources are specified, this is the equivalent of specifying the .Fl i option. .It Ic .INTERRUPT If .Nm is interrupted, the commands for this target are executed. .It Ic .MAIN If no target is specified when .Nm is invoked, this target is built. .It Ic .MAKEFLAGS This target provides a way to specify flags for .Nm at the time when the makefiles are read. The flags are as if typed to the shell, though the .Fl f option has no effect. .\" XXX: NOT YET!!!! .\" .It Ic .NOTPARALLEL .\" The named targets are executed in non parallel mode. .\" If no targets are .\" specified, all targets are executed in non parallel mode. .It Ic .NOPATH Apply the .Ic .NOPATH attribute to any specified sources. .It Ic .NOTPARALLEL Disable parallel mode. .It Ic .NO_PARALLEL Synonym for .Ic .NOTPARALLEL , for compatibility with other pmake variants. .It Ic .NOREADONLY clear the read-only attribute from the global variables specified as sources. .It Ic .OBJDIR The source is a new value for .Sq Va .OBJDIR . If it exists, .Nm changes the current working directory to it and updates the value of .Sq Va .OBJDIR . .It Ic .ORDER In parallel mode, the named targets are made in sequence. This ordering does not add targets to the list of targets to be made. .Pp Since the dependents of a target do not get built until the target itself could be built, unless .Ql a is built by another part of the dependency graph, the following is a dependency loop: .Bd -literal \&.ORDER: b a b: a .Ed .Pp .\" XXX: NOT YET!!!! .\" .It Ic .PARALLEL .\" The named targets are executed in parallel mode. .\" If no targets are .\" specified, all targets are executed in parallel mode. .It Ic .PATH The sources are directories which are to be searched for files not found in the current directory. If no sources are specified, any previously specified directories are removed from the search path. If the source is the special .Ic .DOTLAST target, the current working directory is searched last. .It Ic .PATH. Ns Ar suffix Like .Ic .PATH but applies only to files with a particular suffix. The suffix must have been previously declared with .Ic .SUFFIXES . .It Ic .PHONY Apply the .Ic .PHONY attribute to any specified sources. .It Ic .POSIX If this is the first non-comment line in the main makefile, the variable .Va %POSIX is set to the value .Ql 1003.2 and the makefile .Ql is included if it exists, to provide POSIX-compatible default rules. If .Nm is run with the .Fl r flag, only .Ql posix.mk contributes to the default rules. .It Ic .PRECIOUS Apply the .Ic .PRECIOUS attribute to any specified sources. If no sources are specified, the .Ic .PRECIOUS attribute is applied to every target in the file. .It Ic .READONLY set the read-only attribute on the global variables specified as sources. .It Ic .SHELL Sets the shell that .Nm uses to execute commands. The sources are a set of .Ar field\| Ns Cm \&= Ns Ar value pairs. .Bl -tag -width ".Li hasErrCtls" .It Li name This is the minimal specification, used to select one of the built-in shell specs; .Li sh , .Li ksh , and .Li csh . .It Li path Specifies the absolute path to the shell. .It Li hasErrCtl Indicates whether the shell supports exit on error. .It Li check The command to turn on error checking. .It Li ignore The command to disable error checking. .It Li echo The command to turn on echoing of commands executed. .It Li quiet The command to turn off echoing of commands executed. .It Li filter The output to filter after issuing the .Li quiet command. It is typically identical to .Li quiet . .It Li errFlag The flag to pass the shell to enable error checking. .It Li echoFlag The flag to pass the shell to enable command echoing. .It Li newline The string literal to pass the shell that results in a single newline character when used outside of any quoting characters. .El Example: .Bd -literal \&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e check="set \-e" ignore="set +e" \e echo="set \-v" quiet="set +v" filter="set +v" \e echoFlag=v errFlag=e newline="'\en'" .Ed .It Ic .SILENT Apply the .Ic .SILENT attribute to any specified sources. If no sources are specified, the .Ic .SILENT attribute is applied to every command in the file. .It Ic .STALE This target gets run when a dependency file contains stale entries, having .Va .ALLSRC set to the name of that dependency file. .It Ic .SUFFIXES Each source specifies a suffix to .Nm . If no sources are specified, any previously specified suffixes are deleted. It allows the creation of suffix-transformation rules. .Pp Example: .Bd -literal \&.SUFFIXES: .c .o \&.c.o: cc \-o ${.TARGET} \-c ${.IMPSRC} .Ed .It Ic .SYSPATH The sources are directories which are to be added to the system include path which .Nm searches for makefiles. If no sources are specified, any previously specified directories are removed from the system include path. .El .Sh ENVIRONMENT .Nm uses the following environment variables, if they exist: .Ev MACHINE , .Ev MACHINE_ARCH , .Ev MAKE , .Ev MAKEFLAGS , .Ev MAKEOBJDIR , .Ev MAKEOBJDIRPREFIX , .Ev MAKESYSPATH , .Ev PWD , and .Ev TMPDIR . .Pp .Ev MAKEOBJDIRPREFIX and .Ev MAKEOBJDIR -may only be set in the environment or on the command line to +should be set in the environment or on the command line to .Nm and not as makefile variables; see the description of .Sq Va .OBJDIR for more details. +It is possible to set these via makefile variables but unless done +very early and the +.Sq Ic .OBJDIR +target is used to reset +.Sq Va .OBJDIR , +there may be unexpected side effects. .Sh FILES .Bl -tag -width /usr/share/mk -compact .It .depend list of dependencies .It makefile first default makefile if no makefile is specified on the command line .It Makefile second default makefile if no makefile is specified on the command line .It sys.mk system makefile .It /usr/share/mk system makefile directory .El .Sh COMPATIBILITY The basic make syntax is compatible between different make variants; however the special variables, variable modifiers and conditionals are not. .Ss Older versions An incomplete list of changes in older versions of .Nm : .Pp The way that .for loop variables are substituted changed after .Nx 5.0 so that they still appear to be variable expansions. In particular this stops them being treated as syntax, and removes some obscure problems using them in .if statements. .Pp The way that parallel makes are scheduled changed in .Nx 4.0 so that .ORDER and .WAIT apply recursively to the dependent nodes. The algorithms used may change again in the future. .Ss Other make dialects Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not support most of the features of .Nm as described in this manual. Most notably: .Bl -bullet -offset indent .It The .Ic .WAIT and .Ic .ORDER declarations and most functionality pertaining to parallelization. (GNU make supports parallelization but lacks the features needed to control it effectively.) .It Directives, including for loops and conditionals and most of the forms of include files. (GNU make has its own incompatible and less powerful syntax for conditionals.) .\" The "less powerful" above means that GNU make does not have the .\" make(target), target(target) and commands(target) functions. .It All built-in variables that begin with a dot. .It Most of the special sources and targets that begin with a dot, with the notable exception of .Ic .PHONY , .Ic .PRECIOUS , and .Ic .SUFFIXES . .It Variable modifiers, except for the .Ql :old=new string substitution, which does not portably support globbing with .Ql % and historically only works on declared suffixes. .It The .Ic $> variable even in its short form; most makes support this functionality but its name varies. .El .Pp Some features are somewhat more portable, such as assignment with .Ic += , .Ic ?= , and .Ic != . The .Va .PATH functionality is based on an older feature .Ic VPATH found in GNU make and many versions of SVR4 make; however, historically its behavior is too ill-defined (and too buggy) to rely upon. .Pp The .Ic $@ and .Ic $< variables are more or less universally portable, as is the .Ic $(MAKE) variable. Basic use of suffix rules (for files only in the current directory, not trying to chain transformations together, etc.) is also reasonably portable. .Sh SEE ALSO .Xr mkdep 1 , .Xr style.Makefile 5 .Sh HISTORY A .Nm command appeared in .At v7 . This .Nm implementation is based on Adam de Boor's pmake program, which was written for Sprite at Berkeley. It was designed to be a parallel distributed make running jobs on different machines using a daemon called .Dq customs . .Pp Historically the target/dependency .Ic FRC has been used to FoRCe rebuilding (since the target/dependency does not exist ... unless someone creates an .Pa FRC file). .Sh BUGS The .Nm syntax is difficult to parse. For instance, finding the end of a variable's use should involve scanning each of the modifiers, using the correct terminator for each field. In many places .Nm just counts {} and () in order to find the end of a variable expansion. .Pp There is no way of escaping a space character in a filename. .Pp In jobs mode, when a target fails; .Nm will put an error token into the job token pool. This will cause all other instances of .Nm using that token pool to abort the build and exit with error code 6. Sometimes the attempt to suppress a cascade of unnecessary errors, can result in a seemingly unexplained .Ql *** Error code 6 diff --git a/contrib/bmake/make.h b/contrib/bmake/make.h index 0023da6b2167..05714ddfd151 100644 --- a/contrib/bmake/make.h +++ b/contrib/bmake/make.h @@ -1,1269 +1,1284 @@ -/* $NetBSD: make.h,v 1.344 2024/07/11 20:09:16 sjg Exp $ */ +/* $NetBSD: make.h,v 1.349 2025/01/19 10:57:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)make.h 8.3 (Berkeley) 6/13/95 */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: @(#)make.h 8.3 (Berkeley) 6/13/95 */ /* * make.h -- * The global definitions for make */ #ifndef MAKE_MAKE_H #define MAKE_MAKE_H #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_STRING_H #include #else #include #endif #include #include #ifndef FD_CLOEXEC #define FD_CLOEXEC 1 #endif #if defined(__GNUC__) #define MAKE_GNUC_PREREQ(x, y) \ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \ (__GNUC__ > (x))) #else #define MAKE_GNUC_PREREQ(x, y) 0 #endif #if MAKE_GNUC_PREREQ(2, 7) || lint #define MAKE_ATTR_UNUSED __attribute__((__unused__)) #else #define MAKE_ATTR_UNUSED /* delete */ #endif #if MAKE_GNUC_PREREQ(2, 5) #define MAKE_ATTR_DEAD __attribute__((__noreturn__)) #elif defined(__GNUC__) #define MAKE_ATTR_DEAD __volatile #else #define MAKE_ATTR_DEAD /* delete */ #endif #if MAKE_GNUC_PREREQ(2, 7) #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) \ __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #else #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ #endif #if MAKE_GNUC_PREREQ(4, 0) #define MAKE_ATTR_USE __attribute__((__warn_unused_result__)) #else #define MAKE_ATTR_USE /* delete */ #endif #if MAKE_GNUC_PREREQ(8, 0) #define MAKE_ATTR_NOINLINE __attribute__((__noinline__)) #else #define MAKE_ATTR_NOINLINE /* delete */ #endif #if __STDC_VERSION__ >= 199901L || defined(lint) #define MAKE_INLINE static inline MAKE_ATTR_UNUSED #else #define MAKE_INLINE static MAKE_ATTR_UNUSED #endif /* MAKE_STATIC marks a function that may or may not be inlined. */ #if defined(lint) /* As of 2021-07-31, NetBSD lint ignores __attribute__((unused)). */ #define MAKE_STATIC MAKE_INLINE #else #define MAKE_STATIC static MAKE_ATTR_UNUSED #endif #if __STDC_VERSION__ >= 199901L || defined(lint) || defined(USE_C99_BOOLEAN) #include #elif defined(__bool_true_false_are_defined) /* * All files of make must be compiled with the same definition of bool. * Since one of the files includes , that means the header is * available on this platform. Recompile everything with -DUSE_C99_BOOLEAN. */ #error " is included in pre-C99 mode" #elif defined(bool) || defined(true) || defined(false) /* * In pre-C99 mode, make does not expect that bool is already defined. * You need to ensure that all translation units use the same definition for * bool. */ #error "bool/true/false is defined in pre-C99 mode" #else typedef unsigned char bool; #define true 1 #define false 0 #endif +/* + * In code coverage mode with gcc>=12, calling vfork/exec does not mark any + * further code from the parent process as covered. gcc-10.5.0 is fine, as + * are fork/exec calls, as well as posix_spawn. + */ +#ifndef FORK_FUNCTION +#define FORK_FUNCTION vfork +#endif + #include "lst.h" #include "make_malloc.h" #include "str.h" #include "hash.h" #include "make-conf.h" #include "buf.h" /* * some vendors don't have this --sjg */ #if defined(S_IFDIR) && !defined(S_ISDIR) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #if defined(sun) && (defined(__svr4__) || defined(__SVR4)) # define POSIX_SIGNALS #endif /* * IRIX defines OP_NONE in sys/fcntl.h */ #if defined(OP_NONE) # undef OP_NONE #endif /* * The typical flow of states is: * * The direct successful path: * UNMADE -> BEINGMADE -> MADE. * * The direct error path: * UNMADE -> BEINGMADE -> ERROR. * * The successful path when dependencies need to be made first: * UNMADE -> DEFERRED -> REQUESTED -> BEINGMADE -> MADE. * * A node that has dependencies, and one of the dependencies cannot be made: * UNMADE -> DEFERRED -> ABORTED. * * A node that turns out to be up-to-date: * UNMADE -> BEINGMADE -> UPTODATE. */ typedef enum GNodeMade { /* Not examined yet. */ UNMADE, /* * The node has been examined but is not yet ready since its * dependencies have to be made first. */ DEFERRED, /* The node is on the toBeMade list. */ REQUESTED, /* * The node is already being made. Trying to build a node in this * state indicates a cycle in the graph. */ BEINGMADE, /* Was out-of-date and has been made. */ MADE, /* Was already up-to-date, does not need to be made. */ UPTODATE, /* * An error occurred while it was being made. Used only in compat * mode. */ ERROR, /* * The target was aborted due to an error making a dependency. Used * only in compat mode. */ ABORTED } GNodeMade; /* * The OP_ constants are used when parsing a dependency line as a way of * communicating to other parts of the program the way in which a target * should be made. * * Some of the OP_ constants can be combined, others cannot. * * See the tests depsrc-*.mk and deptgt-*.mk. */ typedef enum GNodeType { OP_NONE = 0, /* * The dependency operator ':' is the most common one. The commands * of this node are executed if any child is out-of-date. */ OP_DEPENDS = 1 << 0, /* * The dependency operator '!' always executes its commands, even if * its children are up-to-date. */ OP_FORCE = 1 << 1, /* * The dependency operator '::' behaves like ':', except that it * allows multiple dependency groups to be defined. Each of these * groups is executed on its own, independently from the others. Each * individual dependency group is called a cohort. */ OP_DOUBLEDEP = 1 << 2, /* Matches the dependency operators ':', '!' and '::'. */ OP_OPMASK = OP_DEPENDS | OP_FORCE | OP_DOUBLEDEP, /* Don't care if the target doesn't exist and can't be created. */ OP_OPTIONAL = 1 << 3, /* Use associated commands for parents. */ OP_USE = 1 << 4, /* * Target is never out of date, but always execute commands anyway. * Its time doesn't matter, so it has none...sort of. */ OP_EXEC = 1 << 5, /* * Ignore non-zero exit status from shell commands when creating the * node. */ OP_IGNORE = 1 << 6, /* Don't remove the target when interrupted. */ OP_PRECIOUS = 1 << 7, /* Don't echo commands when executed. */ OP_SILENT = 1 << 8, /* * Target is a recursive make so its commands should always be * executed when it is out of date, regardless of the state of the -n * or -t flags. */ OP_MAKE = 1 << 9, /* * Target is out-of-date only if any of its children was out-of-date. */ OP_JOIN = 1 << 10, /* Assume the children of the node have been already made. */ OP_MADE = 1 << 11, /* Special .BEGIN, .END or .INTERRUPT. */ OP_SPECIAL = 1 << 12, /* Like .USE, only prepend commands. */ OP_USEBEFORE = 1 << 13, /* * The node is invisible to its parents. I.e. it doesn't show up in * the parents' local variables (.IMPSRC, .ALLSRC). */ OP_INVISIBLE = 1 << 14, /* * The node does not become the main target, even if it is the first * target in the first makefile. */ OP_NOTMAIN = 1 << 15, /* Not a file target; run always. */ OP_PHONY = 1 << 16, /* Don't search for the file in the path. */ OP_NOPATH = 1 << 17, /* * In a dependency line "target: source1 .WAIT source2", source1 is * made first, including its children. Once that is finished, * source2 is made, including its children. The .WAIT keyword may * appear more than once in a single dependency declaration. */ OP_WAIT = 1 << 18, /* .NOMETA do not create a .meta file */ OP_NOMETA = 1 << 19, /* .META we _do_ want a .meta file */ OP_META = 1 << 20, /* Do not compare commands in .meta file */ OP_NOMETA_CMP = 1 << 21, /* Possibly a submake node */ OP_SUBMAKE = 1 << 22, /* Attributes applied by PMake */ /* The node is a transformation rule, such as ".c.o". */ OP_TRANSFORM = 1 << 30, /* Target is a member of an archive */ /* XXX: How does this differ from OP_ARCHV? */ OP_MEMBER = 1 << 29, /* * The node is a library, its name has the form "-l". */ OP_LIB = 1 << 28, /* * The node is an archive member, its name has the form * "archive(member)". */ /* XXX: How does this differ from OP_MEMBER? */ OP_ARCHV = 1 << 27, /* * Target has all the commands it should. Used when parsing to catch * multiple command groups for a target. Only applies to the * dependency operators ':' and '!', but not to '::'. */ OP_HAS_COMMANDS = 1 << 26, /* * The special command "..." has been seen. All further commands from * this node will be saved on the .END node instead, to be executed * at the very end. */ OP_SAVE_CMDS = 1 << 25, /* * Already processed by Suff_FindDeps, to find dependencies from * suffix transformation rules. */ OP_DEPS_FOUND = 1 << 24, /* Node found while expanding .ALLSRC */ OP_MARK = 1 << 23 } GNodeType; typedef struct GNodeFlags { /* this target needs to be (re)made */ bool remake:1; /* children of this target were made */ bool childMade:1; /* children don't exist, and we pretend made */ bool force:1; /* Set by Make_ProcessWait() */ bool doneWait:1; /* Build requested by .ORDER processing */ bool doneOrder:1; /* Node created from .depend */ bool fromDepend:1; /* We do it once only */ bool doneAllsrc:1; /* Used by MakePrintStatus */ bool cycle:1; /* Used by MakePrintStatus */ bool doneCycle:1; } GNodeFlags; typedef struct List StringList; typedef struct ListNode StringListNode; typedef struct List GNodeList; typedef struct ListNode GNodeListNode; typedef struct SearchPath { List /* of CachedDir */ dirs; } SearchPath; /* * A graph node represents a target that can possibly be made, including its * relation to other targets. */ typedef struct GNode { /* The target's name, such as "clean" or "make.c" */ char *name; /* The unexpanded name of a .USE node */ char *uname; /* * The full pathname of the file belonging to the target. * * XXX: What about .PHONY targets? These don't have an associated * path. */ char *path; /* * The type of operator used to define the sources (see the OP flags * below). * * XXX: This looks like a wild mixture of type and flags. */ GNodeType type; GNodeFlags flags; /* The state of processing on this node */ GNodeMade made; /* The number of unmade children */ int unmade; /* * The modification time; 0 means the node does not have a * corresponding file; see GNode_IsOODate. */ time_t mtime; struct GNode *youngestChild; /* * The GNodes for which this node is an implied source. May be empty. * For example, when there is an inference rule for .c.o, the node * for file.c has the node for file.o in this list. */ GNodeList implicitParents; /* * The nodes that depend on this one, or in other words, the nodes * for which this is a source. */ GNodeList parents; /* The nodes on which this one depends. */ GNodeList children; /* * .ORDER nodes we need made. The nodes that must be made (if they're * made) before this node can be made, but that do not enter into the * datedness of this node. */ GNodeList order_pred; /* * .ORDER nodes who need us. The nodes that must be made (if they're * made at all) after this node is made, but that do not depend on * this node, in the normal sense. */ GNodeList order_succ; /* * Other nodes of the same name, for targets that were defined using * the '::' dependency operator (OP_DOUBLEDEP). */ GNodeList cohorts; /* The "#n" suffix for this cohort, or "" for other nodes */ char cohort_num[8]; /* The number of unmade instances on the cohorts list */ int unmade_cohorts; /* * Pointer to the first instance of a '::' node; only set when on a * cohorts list */ struct GNode *centurion; /* Last time (sequence number) we tried to make this node */ unsigned int checked_seqno; /* * The "local" variables that are specific to this target and this * target only, such as $@, $<, $?. * * Also used for the global variable scopes SCOPE_GLOBAL, * SCOPE_CMDLINE, SCOPE_INTERNAL, which contain variables with * arbitrary names. */ HashTable /* of Var pointer */ vars; /* The commands to be given to a shell to create this target. */ StringList commands; /* * Suffix for the node (determined by Suff_FindDeps and opaque to * everyone but the Suff module) */ struct Suffix *suffix; /* Filename where the GNode got defined, unlimited lifetime */ const char *fname; /* Line number where the GNode got defined, 1-based */ unsigned lineno; int exit_status; } GNode; /* * Keep track of whether to include when parsing the line * '.POSIX:'. */ extern enum PosixState { PS_NOT_YET, PS_MAYBE_NEXT_LINE, PS_NOW_OR_NEVER, PS_TOO_LATE } posix_state; /* Error levels for diagnostics during parsing. */ typedef enum ParseErrorLevel { /* * Exit when the current top-level makefile has been parsed * completely. */ PARSE_FATAL = 1, /* Print "warning"; may be upgraded to fatal by the -w option. */ PARSE_WARNING, /* Informational, mainly used during development of makefiles. */ PARSE_INFO } ParseErrorLevel; /* * Values returned by Cond_EvalLine and Cond_EvalCondition. */ typedef enum CondResult { CR_TRUE, /* Parse the next lines */ CR_FALSE, /* Skip the next lines */ CR_ERROR /* Unknown directive or parse error */ } CondResult; typedef struct { enum GuardKind { GK_VARIABLE, GK_TARGET } kind; char *name; } Guard; /* Names of the variables that are "local" to a specific target. */ #define TARGET "@" /* Target of dependency */ #define OODATE "?" /* All out-of-date sources */ #define ALLSRC ">" /* All sources */ #define IMPSRC "<" /* Source implied by transformation */ #define PREFIX "*" /* Common prefix */ #define ARCHIVE "!" /* Archive in "archive(member)" syntax */ #define MEMBER "%" /* Member in "archive(member)" syntax */ /* * Global Variables */ /* True if every target is precious */ extern bool allPrecious; /* True if failed targets should be deleted */ extern bool deleteOnError; /* true while processing .depend */ extern bool doing_depend; /* .DEFAULT rule */ extern GNode *defaultNode; /* * Variables defined internally by make which should not override those set * by makefiles. */ extern GNode *SCOPE_INTERNAL; /* Variables defined in a global scope, e.g in the makefile itself. */ extern GNode *SCOPE_GLOBAL; /* Variables defined on the command line. */ extern GNode *SCOPE_CMDLINE; /* * Value returned by Var_Parse when an error is encountered. It points to an * empty string, so naive callers needn't worry about it. */ extern char var_Error[]; /* The time at the start of this whole process */ extern time_t now; /* * The list of directories to search when looking for targets (set by the * special target .PATH). */ extern SearchPath dirSearchPath; /* Used for .include "...". */ extern SearchPath *parseIncPath; /* * Used for .include <...>, for the built-in sys.mk and for makefiles from * the command line arguments. */ extern SearchPath *sysIncPath; /* The default for sysIncPath. */ extern SearchPath *defSysIncPath; /* Startup directory */ extern char curdir[]; /* The basename of the program name, suffixed with [n] for sub-makes. */ extern const char *progname; extern int makelevel; /* Name of the .depend makefile */ extern char *makeDependfile; /* If we replaced environ, this will be non-NULL. */ extern char **savedEnv; extern GNode *mainNode; extern pid_t myPid; #define MAKEFLAGS ".MAKEFLAGS" #ifndef MAKE_LEVEL_ENV # define MAKE_LEVEL_ENV "MAKELEVEL" #endif typedef struct DebugFlags { bool DEBUG_ARCH:1; bool DEBUG_COND:1; bool DEBUG_CWD:1; bool DEBUG_DIR:1; bool DEBUG_ERROR:1; bool DEBUG_FOR:1; bool DEBUG_GRAPH1:1; bool DEBUG_GRAPH2:1; bool DEBUG_GRAPH3:1; bool DEBUG_HASH:1; bool DEBUG_JOB:1; bool DEBUG_LOUD:1; bool DEBUG_MAKE:1; bool DEBUG_META:1; bool DEBUG_PARSE:1; bool DEBUG_SCRIPT:1; bool DEBUG_SHELL:1; bool DEBUG_SUFF:1; bool DEBUG_TARG:1; bool DEBUG_VAR:1; } DebugFlags; #define CONCAT(a, b) a##b #define DEBUG(module) (opts.debug.CONCAT(DEBUG_, module)) void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); #define DEBUG_IMPL(module, args) \ do { \ if (DEBUG(module)) \ debug_printf args; \ } while (false) #define DEBUG0(module, fmt) \ DEBUG_IMPL(module, (fmt)) #define DEBUG1(module, fmt, arg1) \ DEBUG_IMPL(module, (fmt, arg1)) #define DEBUG2(module, fmt, arg1, arg2) \ DEBUG_IMPL(module, (fmt, arg1, arg2)) #define DEBUG3(module, fmt, arg1, arg2, arg3) \ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3)) #define DEBUG4(module, fmt, arg1, arg2, arg3, arg4) \ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4)) #define DEBUG5(module, fmt, arg1, arg2, arg3, arg4, arg5) \ DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4, arg5)) typedef enum PrintVarsMode { PVM_NONE, PVM_UNEXPANDED, PVM_EXPANDED } PrintVarsMode; /* Command line options */ typedef struct CmdOpts { /* -B: whether to be compatible to traditional make */ bool compatMake; /* * -d: debug control: There is one flag per module. It is up to the * module what debug information to print. */ DebugFlags debug; - /* -df: debug output is written here - default stderr */ + /* -dF: debug output is written here - default stderr */ FILE *debug_file; /* * -dL: lint mode * * Runs make in strict mode, with additional checks and better error * handling. */ bool strict; /* -dV: for the -V option, print unexpanded variable values */ bool debugVflag; /* -e: check environment variables before global variables */ bool checkEnvFirst; /* -f: the makefiles to read */ StringList makefiles; /* -i: if true, ignore all errors from shell commands */ bool ignoreErrors; /* * -j: the maximum number of jobs that can run in parallel; this is * coordinated with the submakes */ int maxJobs; /* * -k: if true and an error occurs while making a node, continue * making nodes that do not depend on the erroneous node */ bool keepgoing; /* -N: execute no commands from the targets */ bool noRecursiveExecute; /* -n: execute almost no commands from the targets */ bool noExecute; /* * -q: if true, do not really make anything, just see if the targets * are out-of-date */ bool query; /* -r: raw mode, do not load the builtin rules. */ bool noBuiltins; /* -s: don't echo the shell commands before executing them */ bool silent; /* * -t: touch the targets if they are out-of-date, but don't actually * make them */ bool touch; /* -[Vv]: print expanded or unexpanded selected variables */ PrintVarsMode printVars; /* -[Vv]: the variables to print */ StringList variables; /* -W: if true, makefile parsing warnings are treated as errors */ bool parseWarnFatal; /* -w: print 'Entering' and 'Leaving' for submakes */ bool enterFlag; /* * -X: if true, do not export variables set on the command line to * the environment. */ bool varNoExportEnv; /* * The target names specified on the command line. Used to resolve * .if make(...) statements. */ StringList create; /* * Randomize the order in which the targets from toBeMade are made, * to catch undeclared dependencies. */ bool randomizeTargets; } CmdOpts; extern CmdOpts opts; extern bool forceJobs; extern char **environ; /* arch.c */ void Arch_Init(void); #ifdef CLEANUP void Arch_End(void); #endif bool Arch_ParseArchive(char **, GNodeList *, GNode *); void Arch_Touch(GNode *); void Arch_TouchLib(GNode *); void Arch_UpdateMTime(GNode *); void Arch_UpdateMemberMTime(GNode *); void Arch_FindLib(GNode *, SearchPath *); bool Arch_LibOODate(GNode *) MAKE_ATTR_USE; bool Arch_IsLib(GNode *) MAKE_ATTR_USE; /* compat.c */ bool Compat_RunCommand(const char *, GNode *, StringListNode *); void Compat_MakeAll(GNodeList *); void Compat_Make(GNode *, GNode *); /* cond.c */ extern unsigned int cond_depth; CondResult Cond_EvalCondition(const char *) MAKE_ATTR_USE; CondResult Cond_EvalLine(const char *) MAKE_ATTR_USE; Guard *Cond_ExtractGuard(const char *) MAKE_ATTR_USE; void Cond_EndFile(void); /* dir.c; see also dir.h */ MAKE_INLINE const char * MAKE_ATTR_USE str_basename(const char *pathname) { const char *lastSlash = strrchr(pathname, '/'); return lastSlash != NULL ? lastSlash + 1 : pathname; } MAKE_INLINE SearchPath * MAKE_ATTR_USE SearchPath_New(void) { SearchPath *path = bmake_malloc(sizeof *path); Lst_Init(&path->dirs); return path; } void SearchPath_Free(SearchPath *); /* for.c */ struct ForLoop; int For_Eval(const char *) MAKE_ATTR_USE; bool For_Accum(const char *, int *) MAKE_ATTR_USE; void For_Run(unsigned, unsigned); bool For_NextIteration(struct ForLoop *, Buffer *); char *ForLoop_Details(const struct ForLoop *); void ForLoop_Free(struct ForLoop *); void For_Break(struct ForLoop *); /* job.c */ void JobReapChild(pid_t, int, bool); /* longer than this we use a temp file */ #ifndef MAKE_CMDLEN_LIMIT # define MAKE_CMDLEN_LIMIT 1000 #endif /* main.c */ void Main_ParseArgLine(const char *); int Cmd_Argv(const char *, size_t, const char **, size_t, char *, size_t, bool, bool); char *Cmd_Exec(const char *, char **) MAKE_ATTR_USE; void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void DieHorribly(void) MAKE_ATTR_DEAD; void Finish(int) MAKE_ATTR_DEAD; int unlink_file(const char *) MAKE_ATTR_USE; void execDie(const char *, const char *); char *getTmpdir(void) MAKE_ATTR_USE; bool ParseBoolean(const char *, bool) MAKE_ATTR_USE; const char *cached_realpath(const char *, char *); bool GetBooleanExpr(const char *, bool); /* parse.c */ extern int parseErrors; void Parse_Init(void); #ifdef CLEANUP void Parse_End(void); #endif void PrintLocation(FILE *, bool, const GNode *); void PrintStackTrace(bool); void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); bool Parse_VarAssign(const char *, bool, GNode *) MAKE_ATTR_USE; void Parse_File(const char *, int); void Parse_PushInput(const char *, unsigned, unsigned, Buffer, struct ForLoop *); void Parse_MainName(GNodeList *); unsigned int CurFile_CondMinDepth(void) MAKE_ATTR_USE; void Parse_GuardElse(void); void Parse_GuardEndif(void); /* suff.c */ void Suff_Init(void); #ifdef CLEANUP void Suff_End(void); #endif void Suff_ClearSuffixes(void); bool Suff_IsTransform(const char *) MAKE_ATTR_USE; GNode *Suff_AddTransform(const char *); void Suff_EndTransform(GNode *); void Suff_AddSuffix(const char *); SearchPath *Suff_GetPath(const char *) MAKE_ATTR_USE; void Suff_ExtendPaths(void); void Suff_AddInclude(const char *); void Suff_AddLib(const char *); void Suff_FindDeps(GNode *); SearchPath *Suff_FindPath(GNode *) MAKE_ATTR_USE; void Suff_SetNull(const char *); void Suff_PrintAll(void); char *Suff_NamesStr(void) MAKE_ATTR_USE; /* targ.c */ void Targ_Init(void); void Targ_End(void); void Targ_Stats(void); GNodeList *Targ_List(void) MAKE_ATTR_USE; GNode *GNode_New(const char *) MAKE_ATTR_USE; GNode *Targ_FindNode(const char *) MAKE_ATTR_USE; GNode *Targ_GetNode(const char *) MAKE_ATTR_USE; GNode *Targ_NewInternalNode(const char *) MAKE_ATTR_USE; GNode *Targ_GetEndNode(void); void Targ_FindList(GNodeList *, StringList *); void Targ_PrintCmds(GNode *); void Targ_PrintNode(GNode *, int); void Targ_PrintNodes(GNodeList *, int); const char *Targ_FmtTime(time_t) MAKE_ATTR_USE; void Targ_PrintType(GNodeType); void Targ_PrintGraph(int); void Targ_Propagate(void); const char *GNodeMade_Name(GNodeMade) MAKE_ATTR_USE; #ifdef CLEANUP void Parse_RegisterCommand(char *); #else MAKE_INLINE void Parse_RegisterCommand(char *cmd MAKE_ATTR_UNUSED) { } #endif /* var.c */ typedef enum VarEvalMode { /* * Only parse the expression but don't evaluate any part of it. * * TODO: Document what Var_Parse and Var_Subst return in this mode. * As of 2021-03-15, they return unspecified, inconsistent results. */ VARE_PARSE, /* * Parse text in which '${...}' and '$(...)' are not parsed as * subexpressions (with all their individual escaping rules) but * instead simply as text with balanced '${}' or '$()'. Other '$' * are copied verbatim. */ VARE_PARSE_BALANCED, /* Parse and evaluate the expression. */ VARE_EVAL, /* * Parse and evaluate the expression. It is an error if a * subexpression evaluates to undefined. */ + VARE_EVAL_DEFINED_LOUD, + + /* + * Parse and evaluate the expression. It is a silent error if a + * subexpression evaluates to undefined. + */ VARE_EVAL_DEFINED, /* * Parse and evaluate the expression. Keep undefined variables as-is * instead of expanding them to an empty string. * * Example for a ':=' assignment: * CFLAGS = $(.INCLUDES) * CFLAGS := -I.. $(CFLAGS) * # If .INCLUDES (an undocumented special variable, by the * # way) is still undefined, the updated CFLAGS becomes * # "-I.. $(.INCLUDES)". */ VARE_EVAL_KEEP_UNDEFINED, /* * Parse and evaluate the expression. Keep '$$' as '$$' and preserve * undefined subexpressions. */ VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED } VarEvalMode; typedef enum VarSetFlags { VAR_SET_NONE = 0, /* do not export */ VAR_SET_NO_EXPORT = 1 << 0, /* * Make the variable read-only. No further modification is possible, * except for another call to Var_Set with the same flag. See the * special targets '.NOREADONLY' and '.READONLY'. */ VAR_SET_READONLY = 1 << 1, VAR_SET_INTERNAL = 1 << 2 } VarSetFlags; typedef enum VarExportMode { /* .export-all */ VEM_ALL, /* .export-env */ VEM_ENV, /* .export: Initial export or update an already exported variable. */ VEM_PLAIN, /* .export-literal: Do not expand the variable value. */ VEM_LITERAL } VarExportMode; void Var_Delete(GNode *, const char *); #ifdef CLEANUP void Var_DeleteAll(GNode *scope); #endif void Var_Undef(const char *); void Var_Set(GNode *, const char *, const char *); void Var_SetExpand(GNode *, const char *, const char *); void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags); void Var_Append(GNode *, const char *, const char *); void Var_AppendExpand(GNode *, const char *, const char *); bool Var_Exists(GNode *, const char *) MAKE_ATTR_USE; bool Var_ExistsExpand(GNode *, const char *) MAKE_ATTR_USE; FStr Var_Value(GNode *, const char *) MAKE_ATTR_USE; const char *GNode_ValueDirect(GNode *, const char *) MAKE_ATTR_USE; FStr Var_Parse(const char **, GNode *, VarEvalMode); char *Var_Subst(const char *, GNode *, VarEvalMode); char *Var_SubstInTarget(const char *, GNode *); void Var_Expand(FStr *, GNode *, VarEvalMode); void Var_Stats(void); void Var_Dump(GNode *); void Var_ReexportVars(GNode *); void Var_Export(VarExportMode, const char *); void Var_ExportVars(const char *); void Var_UnExport(bool, const char *); void Var_ReadOnly(const char *, bool); void Global_Set(const char *, const char *); void Global_Append(const char *, const char *); void Global_Delete(const char *); void Global_Set_ReadOnly(const char *, const char *); -const char *EvalStack_Details(void); +void EvalStack_PrintDetails(void); /* util.c */ typedef void (*SignalProc)(int); SignalProc bmake_signal(int, SignalProc); /* make.c */ void GNode_UpdateYoungestChild(GNode *, GNode *); bool GNode_IsOODate(GNode *) MAKE_ATTR_USE; void Make_ExpandUse(GNodeList *); time_t Make_Recheck(GNode *) MAKE_ATTR_USE; void Make_HandleUse(GNode *, GNode *); void Make_Update(GNode *); void GNode_SetLocalVars(GNode *); bool Make_Run(GNodeList *); bool shouldDieQuietly(GNode *, int) MAKE_ATTR_USE; void PrintOnError(GNode *, const char *); void Main_ExportMAKEFLAGS(bool); bool Main_SetObjdir(bool, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); int mkTempFile(const char *, char *, size_t) MAKE_ATTR_USE; void AppendWords(StringList *, char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); bool GNode_ShouldExecute(GNode *gn) MAKE_ATTR_USE; #ifndef HAVE_STRLCPY size_t strlcpy(char *, const char *, size_t); #endif /* See if the node was seen on the left-hand side of a dependency operator. */ MAKE_INLINE bool MAKE_ATTR_USE GNode_IsTarget(const GNode *gn) { return (gn->type & OP_OPMASK) != OP_NONE; } MAKE_INLINE const char * MAKE_ATTR_USE GNode_Path(const GNode *gn) { return gn->path != NULL ? gn->path : gn->name; } MAKE_INLINE bool MAKE_ATTR_USE GNode_IsWaitingFor(const GNode *gn) { return gn->flags.remake && gn->made <= REQUESTED; } MAKE_INLINE bool MAKE_ATTR_USE GNode_IsReady(const GNode *gn) { return gn->made > DEFERRED; } MAKE_INLINE bool MAKE_ATTR_USE GNode_IsDone(const GNode *gn) { return gn->made >= MADE; } MAKE_INLINE bool MAKE_ATTR_USE GNode_IsError(const GNode *gn) { return gn->made == ERROR || gn->made == ABORTED; } MAKE_INLINE bool MAKE_ATTR_USE GNode_IsMainCandidate(const GNode *gn) { return (gn->type & (OP_NOTMAIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_TRANSFORM)) == 0; } /* Return whether the target file should be preserved on interrupt. */ MAKE_INLINE bool MAKE_ATTR_USE GNode_IsPrecious(const GNode *gn) { /* XXX: Why are '::' targets precious? */ return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarTarget(GNode *gn) { return GNode_ValueDirect(gn, TARGET); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarOodate(GNode *gn) { return GNode_ValueDirect(gn, OODATE); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarAllsrc(GNode *gn) { return GNode_ValueDirect(gn, ALLSRC); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarImpsrc(GNode *gn) { return GNode_ValueDirect(gn, IMPSRC); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarPrefix(GNode *gn) { return GNode_ValueDirect(gn, PREFIX); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); } MAKE_INLINE const char * MAKE_ATTR_USE GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); } MAKE_INLINE void * MAKE_ATTR_USE UNCONST(const void *ptr) { void *ret; memcpy(&ret, &ptr, sizeof(ret)); return ret; } /* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */ #ifdef HAVE_LIMITS_H #include #endif #ifndef MAXPATHLEN #define MAXPATHLEN BMAKE_PATH_MAX #endif #ifndef PATH_MAX #define PATH_MAX MAXPATHLEN #endif #if defined(SYSV) #define KILLPG(pid, sig) kill(-(pid), (sig)) #else #define KILLPG(pid, sig) killpg((pid), (sig)) #endif MAKE_INLINE bool MAKE_ATTR_USE ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } MAKE_INLINE bool MAKE_ATTR_USE ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } MAKE_INLINE bool MAKE_ATTR_USE ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } MAKE_INLINE bool MAKE_ATTR_USE ch_islower(char ch) { return islower((unsigned char)ch) != 0; } MAKE_INLINE bool MAKE_ATTR_USE ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } MAKE_INLINE bool MAKE_ATTR_USE ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; } MAKE_INLINE char MAKE_ATTR_USE ch_tolower(char ch) { return (char)tolower((unsigned char)ch); } MAKE_INLINE char MAKE_ATTR_USE ch_toupper(char ch) { return (char)toupper((unsigned char)ch); } MAKE_INLINE void cpp_skip_whitespace(const char **pp) { while (ch_isspace(**pp)) (*pp)++; } MAKE_INLINE void cpp_skip_hspace(const char **pp) { while (**pp == ' ' || **pp == '\t') (*pp)++; } MAKE_INLINE bool cpp_skip_string(const char **pp, const char *s) { const char *p = *pp; while (*p == *s && *s != '\0') p++, s++; if (*s == '\0') *pp = p; return *s == '\0'; } MAKE_INLINE void pp_skip_whitespace(char **pp) { while (ch_isspace(**pp)) (*pp)++; } MAKE_INLINE void pp_skip_hspace(char **pp) { while (**pp == ' ' || **pp == '\t') (*pp)++; } #if defined(lint) void do_not_define_rcsid(void); /* for lint */ # define MAKE_RCSID(id) void do_not_define_rcsid(void) #elif defined(MAKE_NATIVE) # include # ifndef __IDSTRING # define __IDSTRING(name,string) \ static const char name[] MAKE_ATTR_UNUSED = string # endif # ifndef __RCSID # define __RCSID(s) __IDSTRING(rcsid,s) # endif # ifndef __COPYRIGHT # define __COPYRIGHT(s) __IDSTRING(copyright,s) # endif # define MAKE_RCSID(id) __RCSID(id) #elif defined(MAKE_ALL_IN_ONE) && defined(__COUNTER__) # define MAKE_RCSID_CONCAT(x, y) CONCAT(x, y) # define MAKE_RCSID(id) static volatile char \ MAKE_RCSID_CONCAT(rcsid_, __COUNTER__)[] = id #elif defined(MAKE_ALL_IN_ONE) # define MAKE_RCSID(id) void do_not_define_rcsid(void) #else # define MAKE_RCSID(id) static volatile char rcsid[] = id #endif #endif diff --git a/contrib/bmake/mk/ChangeLog b/contrib/bmake/mk/ChangeLog index 0e521d9ecce6..4274d9d3483b 100644 --- a/contrib/bmake/mk/ChangeLog +++ b/contrib/bmake/mk/ChangeLog @@ -1,2418 +1,2535 @@ +2025-01-10 Simon J Gerraty + + * rust.mk: use RUST_LIBS and RUST_PROGS + +2025-01-01 Simon J Gerraty + + * install-mk (MK_VERSION): 20250101 + + * use W flag to :S and :C rather than :tW + +2024-12-16 Simon J Gerraty + + * rust.mk: add RUSTFLAGS if needed + +2024-12-12 Simon J Gerraty + + * init.mk (OBJS_SRCS_FILTER): apply this as + ${OBJS_SRCS_FILTER:ts:} as we do in FreeBSD. + +2024-12-03 Simon J Gerraty + + * install-mk (MK_VERSION): 20241202 + + * setopts.sh: needed by newlog.sh + +2024-11-22 Simon J Gerraty + + * meta.sys.mk: add META_MODE_XTRAS to META_MODE to make it + easier to add things like 'env' when debugging. + + * install-mk (MK_VERSION): 20241122 + + * rust.mk: rename CARGO* to RUST_CARGO* so I don't feel + like this makefile should be renamed to cargo.mk + +2024-11-11 Simon J Gerraty + + * dirdeps.mk: add DIRDEPS_CACHED_ENV to dirdeps-cached and + DIRDEP_USE_EPILOGUE to _DIRDEP_USE + +2024-10-30 Simon J Gerraty + + * meta.sys.mk: if MK_META_ERROR_TARGET is yes and NEWLOG_SH is + set, and ERROR_LOGDIR already exists, hook _rotateErrorLog to + .BEGIN target. + +2024-10-27 Simon J Gerraty + + * options.mk: add support for DEBUG_OPTIONS (similar to + DEBUG_DIRDEPS) to allow us to see where options get set. + Eg. DEBUG_OPTIONS="STAGING*" gives: + + bmake[1]: "mk/options.mk" line 89: sys.mk: MK_STAGING=yes (MK_DIRDEPS_BUILD=no) + bmake[1]: "mk/options.mk" line 66: local.init.mk: MK_STAGING=yes + bmake[1]: "mk/options.mk" line 66: local.init.mk: MK_STAGING_PROG=no + bmake[1]: "mk/options.mk" line 89: own.mk: MK_STAGING_MAN=yes (MK_STAGING=yes) + bmake[1]: "mk/options.mk" line 89: own.mk: MK_STAGING_PROG=no (MK_STAGING=yes) + bmake[1]: "mk/options.mk" line 89: own.mk: MK_STAGING_RUST=no (MK_STAGING_PROG=no) + + * own.mk: fix setting of STAGE_OBJTOP (normally set by sys.dirdeps.mk) + +2024-10-26 Simon J Gerraty + + * rust.mk: add some documentation and support for staging + +2024-10-25 Simon J Gerraty + + * rust.mk: a means of integrating Rust projects into a larger build. + +2024-10-18 Simon J Gerraty + + * dirdeps-targets.mk: if DEBUG_DIRDEPS_TARGETS and we found + STATIC_DIRDEPS_CACHE, report its relative path. + +2024-09-30 Simon J Gerraty + + * dirdeps.mk: tweak the debug message for "Loading" a + Makefile.depend file, always report what the actual makefile is + with the DIRDEP it is for. + Remove the redundant "Looking" message. + +2024-09-26 Simon J Gerraty + + * meta2deps.py: when raising AssertionError include meta file name + with $SB trimmed if possible. + +2024-09-23 Simon J Gerraty + + * meta2deps.py: replace assert() with raise AssertionError when we + detect missing eXits, to ensure a meaningful message gets into + log. + +2024-09-21 Simon J Gerraty + + * install-mk (MK_VERSION): 20240921 + + * FILES: add genfiles.mk + +2024-09-20 Simon J Gerraty + + * install-mk (MK_VERSION): 20240920 + + * cython.mk: Get PYTHON_VERSION from PYTHON + +2024-08-31 Simon J Gerraty + + * subdir.mk: add ${SUBDIR.yes} - allows for SUBDIR.${MK_*} + and handle subdir with '-' in its name. + +2024-08-23 Simon J Gerraty + + * install-mk (MK_VERSION): 20240820 + + * links.mk: Allow a filter to be applied to SYMLINKS etc. + It is up to [BUILD_][SYM]LINKS_FILTER to do something sane. + Also only claim we are making a symlink if the value changed. + 2024-06-22 Simon J Gerraty * install-mk (MK_VERSION): 20240616 * dirdeps.mk: apply DEP_DIRDEPS_BUILD_DIR_FILTER after we have computed build dirs, since some filters cannot be easily expressed via DEP_DIRDEPS_FILTER. 2024-05-31 Simon J Gerraty * dirdeps.mk: move reset of DIRDEPS_EXPORT_VARS until after we a finished with it if building a cache. 2024-05-04 Simon J Gerraty * install-mk (MK_VERSION): 20240504 * dirdeps.mk: allow BUILD_DIRDEPS_OVERRIDES to pass overrides to sub-make building DIRDEPS_CACHE. 2024-04-24 Simon J Gerraty * meta.autodep.mk: do not override start_utc 2024-04-18 Simon J Gerraty * sys.dirdeps.mk: set defaults for DEP_* at level 0 too. These help when first include of Makefile.depend happens in a leaf dir. * install-mk (MK_VERSION): 20240414 2024-04-09 Simon J Gerraty * install-mk (MK_VERSION): 20240408 * init.mk: allow for _ as well as . to join V and Q from QUALIFIED_VAR_LIST and VAR_QUALIFIER_LIST. * progs.mk: avoid overlap between PROG_VARS and init.mk's QUALIFIED_VAR_LIST since PROG would also match its VAR_QUALIFIER_LIST, libs.mk does not have the same issue. * subdir.mk: _SUBDIRUSE for realinstall should run install remove include of ${.CURDIR}/Makefile.inc that can be done via local.subdir.mk where needed * own.mk: do not conflict with man.mk 2024-03-19 Simon J Gerraty * install-mk (MK_VERSION): 20240314 * add sys/Cygwin.mk from Christian Franke 2024-03-09 Simon J Gerraty * install-mk (MK_VERSION): 20240309 * meta.sys.mk: _metaError: if .ERROR_EXIT == 6, we do not want to save the .ERROR_META_FILE 2024-02-20 Simon J Gerraty * install-mk (MK_VERSION): 20240220 * sys.dirdeps.mk, dirdeps-targets.mk, init.mk: do not set .MAIN: dirdeps in sys.dirdeps.mk dirdeps-targets.mk will do that for top-level builds and init.mk will do it for others. This allows a Makefile which has no need of 'dirdeps' to set .MAIN for itself and "just work". 2024-02-18 Simon J Gerraty * bsd.*.mk: for makefiles that get a bsd. symlink, use _this in multiple inclusion tags since .PARSEFILE will not DTRT when such a makefile is included directly by Makefile and automatically (without bsd. prefix). Since we cannot guarantee that our sys.mk will be used, we provide a default _this in each makefile that gets a bsd. prefix such that the value is the same regardless of bsd. prefix. * subdir.mk: drop the !target guard on $SUBDIR_TARGETS 2024-02-12 Simon J Gerraty * install-mk (MK_VERSION): 20240212 * SPDX-License-Identifier: BSD-2-Clause Add SPDX-License-Identifier to inidicate that I consider my copyright on any of these makefiles equivalent to BSD-2-Clause * autoconf.mk: allow for configure.ac as currently recommended * subdir.mk: support @auto which is replaced with each subdir that has a [Mm]akefile. * subdir.mk: include local.subdir.mk if it exists. * subdir.mk: rework to handle .WAIT 2024-02-11 Simon J Gerraty * subdir.mk: _SUBDIRUSE report the target we are entering subdirs for. 2024-02-10 Simon J Gerraty * prog.mk: treat empty SRCS the same as undefined 2024-02-02 Simon J Gerraty * Avoid undefined errors in lint (-dL) mode * man.mk (CMT2DOC_FLAGS): note that -mm does mdoc(7) 2024-01-28 Simon J Gerraty * install-mk (MK_VERSION): 20240128 * FILES: add ccm.dep.mk for C++ modules add suffixes.mk for common location for generic SUFFIX rules. * auto.dep.mk autodep.mk meta.autodep.mk: include ccm.dep.mk replace OBJ_EXTENSIONS with OBJ_SUFFIXES * autodep.mk: leverage CXX_SUFFIXES for __depsrcs and update style (spaces around = etc) * init.mk: add OBJS_SRCS_FILTER to filter SRCS when setting OBJS * meta2deps.py: handle multiple ./ embedded in path better. 2024-01-05 Simon J Gerraty * install-mk (MK_VERSION): 20240105 * dirdeps.mk: for MAKE_VERSION 20240105 we do not have the same limits on command line length, so skip export of lists to env. 2023-12-24 Simon J Gerraty * man.mk: add logic for staging man pages 2023-11-28 Simon J Gerraty * jobs.mk: avoid C suffix in JOB_MAX_C if factor is floating point. This keeps JOB_MAX numeric incase another makefile does comparisons. 2023-11-04 Simon J Gerraty * dpadd.mk: add support for DPLIBS_QUALIFIER_LIST * gendirdeps.mk: if META_XTRAS is passed to us, add to META_FILES 2023-10-03 Simon J Gerraty * compiler.mk (COMPILER_VERSION): clang at least is into double digit major versions. 2023-10-02 Simon J Gerraty * install-mk (MK_VERSION): 20231001 * set _CCLINK in init.mk so lib.mk can use it for default SHLIB_LD * lib.mk (cleanlib): use LD_solink so we remove all the right files. Use -Wl for -soname since we now default to linking with CC We should not need SHLIB_LDSTARTFILE or SHLIB_LDENDFILE when linking with CC. 2023-09-24 Simon J Gerraty * init.mk (QUALIFIED_VAR_LIST): Add SRCS 2023-09-09 Simon J Gerraty * jobs.mk (JOB_MAX): use -jC if we can we actually use JOB_MAX_C which defaults to 1.33C 2023-08-18 Simon J Gerraty * now_utc: %s only works with :localtime 2023-07-14 Simon J Gerraty * install-sh: ignore -c as claimed and only insist on a directory for destination when more than one file to copy. * sys.mk: when looking for SYS_OS_MK try ${.MAKE.OS} and ${.MAKE.OS:S,64,,} early (so we find sys/IRIX.mk for IRIX64) 2023-07-13 Simon J Gerraty * install-mk (MK_VERSION): 20230711 * sys.mk: set SYS_MK and INSTALL_SH for systems with incompatible install(1) * sys/IRIX.mk: when setting ROOT_GROUP only match the first :0: set INSTALL to install-sh rather than pathname that may not exist (yet). 2023-07-07 Simon J Gerraty * dirdeps.mk: pass DIRDEP_TARGETS to DIRDEP_MAKE normally this is empty - for the default target, but there are use-cases where we might set it to something else. 2023-07-04 Simon J Gerraty * install-mk (MK_VERSION): 20230704 * dirdeps.mk: apply DEBUG_DIRDEPS_LIST_FILTER to lists we output when DEBUG_DIRDEPS is in effect. Eg. DEBUG_DIRDEPS_LIST_FILTER=ts\n can greatly improve readability. 2023-05-25 Simon J Gerraty * meta.autodep.mk (beforegendirdeps): allow tasks to be done at END but before gendirdeps 2023-05-22 Simon J Gerraty * install-mk (MK_VERSION): 20230522 * host-target.mk: deal with garbage from uname -m on Darwin ppc, also NetBSD appears to use x86_64 for MACHINE_ARCH these days so just leave it be. For Darwin arm and i386 use _HOST_MACHINE for _HOST_ARCH so we get arm64 and x86_64 in HOST_TARGET. 2023-05-15 Simon J Gerraty * sys.vars.mk: M_mtime use :mtime or 'stat -f %m' for older versions of bmake. * dirdeps.mk (TARGET_SPEC_VARS.host): While *most* projects need only DEP_MACHINE for host, there is always an exception. So we allow for TARGET_SPEC_VARS.host to be a subset of TARGET_SPEC_VARS. The default will *just work* for most projects. We set DEP_TARGET_SPEC_VARS and hence DEP_TARGET_SPEC based on DEP_MACHINE. Allow for M_dep_qual_fixes.host to be different too and take care to apply the right set. 2023-05-14 Simon J Gerraty * sys.dirdeps.mk: we *do* want to override OBJTOP and if MAKEOBJDIR was not in env as we want it; put it there - carefully. Ensure OBJROOT ends in / or - (/ preferred) Add more comments to explain what/why. 2023-05-13 Simon J Gerraty * install-mk (MK_VERSION): 20230512 * dirdeps.mk: take care not to qualify "host" dirdeps * sys.dirdeps.mk (OBJTOP): must use ?= 2023-05-10 Simon J Gerraty * meta.autodep.mk: if LOCAL_DEPENDS_GUARD is "no" suppress processing of .depend 2023-05-09 Simon J Gerraty * dirdeps.mk: do not add _CURDIR to DIRDEPS for SRCTOP * meta.sys.mk sys.dirdeps.mk: originally DIRDEPS_BUILD and META_MODE were the same thing, but META_MODE is useful by itself. Move things from meta.sys.mk which actually pertain to DIRDEPS_BUILD to sys.dirdeps.mk 2023-05-04 Simon J Gerraty * install-mk (MK_VERSION): 20230504 May the Forth be with you * dirdeps.mk: as with meta.sys.mk we treat "host" as special. DEP_TARGET_SPEC is just ${DEP_MACHINE} * meta.sys.mk: ensure DEP_* for TARGET_SPEC_VARS are set at level > 0 since these are often refered to in Makefile.depend* 2023-04-26 Simon J Gerraty * jobs.mk: report ${.TARGET} ${JOB_ARGS} ${JOB_LOG} and anything in ${JOB_LOG_START} * jobs.mk: look for newlog.sh in ${.SYSPATH:U${.PARSEDIR}} or a scripts subdir before searching $PATH. * FILES: include newlog.sh for jobs.mk 2023-04-20 Simon J Gerraty * install-mk (MK_VERSION): 20230420 * lib.mk: include LDFLAGS and LDADD when linking shared libs * gendirdeps.mk: document setting GENDIRDEPS_FILTER_VARS etc via local.meta.sys.mk rather than local.gendirdeps.mk so DEP_* variables can be set at level 1+ to avoid syntax errors when used in conditionals in manually maintained Makefile.depend files. * dirdeps.mk: ensure M_dep_qual_fixes is applied to all _machines 2023-04-18 Simon J Gerraty * dirdeps.mk: check we were not included by Makefile.depend.options as the result is bad. 2023-04-14 Simon J Gerraty * install-mk (MK_VERSION): 20230414 * meta.sys.mk: since we have :range we can put the logic for processing TARGET_SPEC from env here. * dirdeps.mk: reset DIRDEPS and DEP_RELDIR before including local.dirdeps-missing.mk, also improve debug output. * dirdeps.mk: to allow make -f dirdeps.mk include.$TARGET_SPEC we need to use :M*[/.]* same as for when actually setting DIRDEPS from the targets on command line. 2023-04-12 Simon J Gerraty * Add jobs.mk 2023-03-21 Simon J Gerraty * install-mk (MK_VERSION): 20230321 * meta.stage.mk: allow STAGE_SHLIB_LINKS_FILTER to filter STAGE_LIBS for SHLIB_LINKS. * autoconf.mk: add .WAIT after config.status 2023-02-17 Simon J Gerraty * sys.vars.mk: add M_Index to report the index of a word in a list. 2023-02-15 Simon J Gerraty * install-mk (MK_VERSION): 20230215 * warnings.mk: allow better control of -Werror allow -Wno-error or similar to be added if WARNINGS_SET < WERROR_SET account for COMPILER_TYPE 2023-01-29 Simon J Gerraty * autoconf.mk: hook config.status to beforebuild. * whats.mk: what*.c is NOTMAIN 2023-01-27 Simon J Gerraty * install-mk (MK_VERSION): 20230127 control umask so directories are created with suitable mode. 2023-01-20 Simon J Gerraty * install-mk (MK_VERSION): 20230120 * sys.vars.mk: add M_On and M_Onr also cleanup to be more consistent wrt testing MAKE_VERSION 2023-01-12 Simon J Gerraty * install-mk (MK_VERSION): 20230112 * meta2deps.{py,sh}: assert if filemon data is truncated we should see the '# Bye bye' record - assert if we do not. 2022-09-09 Simon J Gerraty * install-mk (MK_VERSION): 20220909 * sys/Linux.mk set EGREP to grep -E to avoid deprecation warnings 2022-09-06 Simon J Gerraty * dirdeps-options.mk: explain the need to use ${DEP_${TARGET_SPEC_VAR}:U${TARGET_SPEC_VAR}} when refering to ${TARGET_SPEC_VAR} 2022-09-03 Simon J Gerraty * install-mk (MK_VERSION): 20220903 * M_cmpv handle more than 3 dots and clear leading 0's 2022-07-20 Simon J Gerraty * install-mk (MK_VERSION): 20220720 * prog.mk: handle PROG_CXX for more than just NetBSD 2022-06-20 Simon J Gerraty * install-mk (MK_VERSION): 20220620 * yacc.mk: when we have *.y in SRCS used explicit rules and .ORDER rather than just suffix rules 2022-04-23 Simon J Gerraty * install-mk (MK_VERSION): 20220422 * gendirdeps.mk: If LOCAL_DEPENDS_GUARD is set to "no" do not capture any local depends in Makefile.depend 2022-03-25 Simon J Gerraty * install-mk (MK_VERSION): 20220323 * posix.mk: default rules for .POSIX: 2022-03-17 Simon J Gerraty * sys/*.mk: remove l from ARFLAGS 2022-03-14 Simon J Gerraty * install-mk (MK_VERSION): 20220314 * dirdeps-options.mk: allow options to be per RELDIR try DIRDEPS_OPTIONS_QUALIFIER_LIST first prefixed with ${DEP_RELDIR}. 2022-02-14 Simon J Gerraty * install-mk (MK_VERSION): 20220214 * cc-wrap.mk: fix :@ modifier 2022-02-06 Simon J Gerraty * install-mk (MK_VERSION): 20220206 * cc-wrap.mk: docuement how CCACHE etc might be set for maximum flexibility 2022-02-05 Simon J Gerraty * sys.vars.mk: use JOT_CMD (jot or seq) if available for M_JOT 2022-02-04 Simon J Gerraty * install-mk (MK_VERSION): 20220204 * host-target.mk: use .MAKE.OS if available 2022-02-02 Simon J Gerraty * install-mk (MK_VERSION): 20220202 * cc-wrap.mk: allow other entries in CC_WRAP_FILTER We add our filter on extensions last, so prior filters can apply to the whole value of .IMPSRC 2022-02-01 Simon J Gerraty * cc-wrap.mk: take advantage of target local variables to wrap compilers like CC CXX with wrappers like ccache distcc etc 2022-01-28 Simon J Gerraty * meta2deps: we do not expect any trace data for setid apps 2022-01-26 Simon J Gerraty * dirdeps.mk: ensure TARGET_SPEC and TARGET_SPEC_VARS are passed to sub-make using DIRDEPS_CACHE 2022-01-07 Simon J Gerraty * dirdeps.mk: use _cache_script to minimize the number of shells forked when generating dirdeps.cache 2022-01-02 Simon J Gerraty * install-mk (MK_VERSION): 20220101 * dirdeps.mk: initialize DEP_* and _debug_reldir earlier. If initial DIRDEPS are from command line, create the target _dirdeps_cmdline as an indication. 2022-01-01 Simon J Gerraty * init.mk (_SKIP_BUILD): when doing DIRDEPS_BUILD at top-level only some targets are allowed at level 0, for leaf makefiles only the default (all) target is restricted 2021-12-28 Simon J Gerraty * install-mk (MK_VERSION): 20211228 * meta2deps.py: filemon on Linux is not as reliable as we might like, we do not want to update DIRDEPS if filemon output is incomplete. Track pids that we 'E'xec and make sure we see an e'X'it for each one. Throw an error if we are missing any 'X' records. 2021-12-12 Simon J Gerraty * sys.mk: simplify; include meta.sys.mk if MK_META_MODE is yes. * meta.sys.mk: do not check for /dev/filemon if .MAKE.PATH_FILEMON is something else. * meta.autodep.mk: we can now reference ${.SUFFIXES} * meta2deps.py: derive a list of dirdep extensions from TARGET_SPEC to trim from dirdeps. * dirdeps.mk: flip the computation of qualified vs unqualified dirdeps - it is much simpler to check for unqualified first. 2021-12-11 Simon J Gerraty * install-mk (MK_VERSION): 20211212 * auto.dep.mk: rearrange so that the trivial implementation for recent bmake is more obvious. 2021-12-07 Simon J Gerraty * install-mk (MK_VERSION): 20211207 * Ensure guard targets are .NOTMAIN * meta.sys.mk: check for nofilemon support when we skip level 0 * auto.dep.mk: make this usable in meta mode for platforms that cannot use meta.autodep.mk * meta2deps.py: avoid confusion if MACHINE and another TARGET_SPEC_VAR have same value. 2021-11-27 Simon J Gerraty * dirdeps.mk: when building dirdeps.cache, minimize the amount of data put into env, by stripping ${SRCTOP}/ from each entry. A long sandbox name can double the amount of memory consumed and in extreme cases cause failure. While we are at it, strip ${SRCTOP}/ from a lot of the debug output. 2021-11-11 Simon J Gerraty * install-mk (MK_VERSION): 20211111 * meta.stage.mk (LN_CP_SCRIPT): if staging to NFS cp -p can fail so fallback to cp if necessary. 2021-10-30 Simon J Gerraty * man.mk (CMT2DOC): use cmt2doc.py rather than the 30 year old cmt2doc.pl 2021-10-24 Simon J Gerraty * meta.stage.mk: stage_as_and_symlink use ${STAGE_LINK_AS_$f:U$f} as the symlink (rare) 2021-10-16 Simon J Gerraty * autoconf.mk: if AUTOCONF_GENERATED_MAKEFILE is set and has not been read, throw an error after running configure telling user to restart. 2021-10-13 Simon J Gerraty * install-mk (MK_VERSION): 20211011 * Add support for SCO_SV 2021-10-01 Simon J Gerraty * install-mk (MK_VERSION): 20211001 * man.mk: use MAN_SUFFIXES and CMT2DOC_SUFFIXES for more flexibility 2021-09-13 Simon J Gerraty * options.mk (describe-options): print options and their values and optional description 2021-09-11 Simon J Gerraty * install-mk (MK_VERSION): 20210911 * options.mk (show-options): print options and their values 2021-09-08 Simon J Gerraty * install-mk (MK_VERSION): 20210909 * lib.mk: apply patch from to fix shared libs on Linux 2021-08-08 Simon J Gerraty * install-mk (MK_VERSION): 20210808 * options.mk: issue warning for WITH_*=no 2021-06-16 Simon J Gerraty * install-mk (MK_VERSION): 20210616 * dirdeps.mk: when using .MAKE.DEPENDFILE_PREFERENCE to find depend files to read, anchor MACHINE at , or end of string to avoid prefix match. 2021-05-04 Simon J Gerraty * install-mk (MK_VERSION): 20210504 * dirdeps.mk: re-implement ALL_MACHINES support to better cater for local complexities, when ONLY_TARGET_SPEC_LIST is not set. local.dirdeps.mk can set DIRDEPS_ALL_MACHINES_FILTER and/or DIRDEPS_ALL_MACHINES_FILTER_XTRAS to filter the results we get from listing all existing Makefile.depend.* 2021-04-20 Simon J Gerraty * install-mk (MK_VERSION): 20210420 * dirdeps.mk: revert previous - not always safe. 2021-03-20 Simon J Gerraty * install-mk (MK_VERSION): 20210321 * dirdeps.mk: when generating dirdeps.cache we only need to hook the initial DIRDEPS to the dirdeps target. That and any _build_xtra_dirs (like tests which should not be hooked directly to the dependency graph - to avoid cycles) 2021-01-30 Simon J Gerraty * install-mk (MK_VERSION): 20210130 * dirdeps.mk: expr 2 - 1 - 1 exits with a bad status we need to guard against this in DIRDEP_LOADAVG_REPORT. * dirdeps.mk: restore respect for TARGET_MACHINE 2021-01-06 Simon J Gerraty * install-mk (MK_VERSION): 20210101 * dirdeps.mk: first time we are read, just use TARGET_SPEC for _DEP_TARGET_SPEC 2020-12-22 Simon J Gerraty * sys.mk (MAKE_SHELL): use ${.SHELL:Ush} and use := when setting SHELL 2020-12-21 Simon J Gerraty * install-mk (MK_VERSION): 20201221 * dirdeps-options.mk: latest bmake allows only one arg to .undef 2020-12-11 Simon J Gerraty * dirdeps-targets.mk: allow for "." in DIRDEPS_TARGETS_DIRS so that any directory can be treated as a target. 2020-11-26 Simon J Gerraty * install-mk (MK_VERSION): 20201126 * own.mk: use .MAKE.{UID,GID} if available. * init.mk: suppress _SKIP_BUILD warning if doing -V 2020-11-20 Simon J Gerraty * install-mk (MK_VERSION): 20201120 * init.mk: rename LEVEL0_TARGETS to DIRDEPS_BUILD_LEVEL0_TARGETS * dirdeps-targets.mk: fix typo in comment 2020-11-06 Simon J Gerraty * install-mk (MK_VERSION): 20201106 * meta.autodep.mk: use OBJ_EXTENSIONS rather than hardcode sed args to tweak extensions for local deps. 2020-11-01 Simon J Gerraty * install-mk (MK_VERSION): 20201101 * dirdeps.mk: most leaf makefiles are not suitable for building dirdeps.cache so if RELDIR is not "." use dirdeps.mk 2020-10-28 Simon J Gerraty * install-mk (MK_VERSION): 20201028 * dirdeps.mk: if we don't have :range use equivalent of M_RANGE when building dirdeps.cache for leaf directory use -f dirdeps.mk * sys.vars.mk: add M_JOT and M_RANGE 2020-10-01 Simon J Gerraty * install-mk (MK_VERSION): 20201001 * meta2deps.{py,sh}: throw an error if we don't see filemon version 2020-09-09 Simon J Gerraty * install-mk (MK_VERSION): 20200909 * dirdeps-cache-update.mk: use cache_update_dirdep as guard target 2020-08-26 Simon J Gerraty * dirdeps.mk: ensure we cannot confuse a static cache for dynamic (even more rare that use of static cache is playing clever tricks with it) 2020-08-16 Simon J Gerraty * dirdeps-cache-update.mk: allow MK_STATIC_DIRDEPS_CACHE_UPDATE_IMMEDIATE to control when we actually update STATIC_DIRDEPS_CACHE. * stage-install.sh: create dest directory if needed before running install(1) 2020-08-10 Simon J Gerraty * dirdeps-targets.mk: include Makefile.dirdeps.options * dirdeps.mk: use _TARGETS if defined for DIRDEPS_CACHE 2020-08-09 Simon J Gerraty * dirdeps.mk: default BUILD_DIRDEPS_MAKEFILE to empty * dirdeps-cache-update.mk: building parallel cache update under the context of dirdeps-cached would be ideal, but is problematic, so it runs as a sibling. Use cache-built target to ensure we wait for it to complete if necessary. 2020-08-06 Simon J Gerraty * install-mk (MK_VERSION): 20200806 * dirdeps-options: allow TARGET_SPEC to affect option values. Use DIRDEPS_OPTIONS_QUALIFIER_LIST before using bare MK_* * dirdeps-targets.mk: check for MK_STATIC_DIRDEPS_CACHE defined before looking for STATIC_DIRDEPS_CACHE 2020-08-05 Simon J Gerraty * host-target.mk: Darwin use MACHINE for HOST_ARCH too * dirdeps-options.mk: improve debug output 2020-07-22 Simon J Gerraty * dirdeps.mk: set and export DYNAMIC_DIRDEPS_CACHE for use by dirdeps-cache-update.mk * dirdeps-targets.mk: set and export STATIC_DIRDEPS_CACHE for use by dirdeps-cache-update.mk even if we don't use it. * dirdeps-cache-update.mk: we only need worry about the background update case, with the above, the update from DIRDEPS_CACHE is simple. * meta2deps.py: R 1234 . is not interesting 2020-07-20 Simon J Gerraty * sys.mk: default MK_STATIC_DIRDEPS_CACHE from MK_DIRDEPS_CACHE * dirdeps-options.mk: do not :tu DIRDEPS_OPTIONS allows use of lower case for pseudo options. * dirdeps-cache-update.mk: magic to deal with STATIC_DIRDEPS_CACHE 2020-07-18 Simon J Gerraty * dirdeps-targets.mk: Look for Makefile.dirdeps.cache which allows us to have a static cache for expensive targets. Use -DWITHOUT_STATIC_DIRDEPS_CACHE -DWITH_DIRDEPS_CACHE to regenerate the dirdeps.cache it is a copy of. 2020-07-17 Simon J Gerraty * Get rid of BUILD_AT_LEVEL0, MK_DIRDEPS_BUILD makes more sense. 2020-07-16 Simon J Gerraty * dirdeps.mk (DIRDEP_LOADAVG_REPORT): make it easy to record load averages at intervals during build. 2020-07-15 Simon J Gerraty * install-mk (MK_VERSION): 20200715 * dirdeps.mk: tweak Checking line to make matching Finished lines for post-build analysis easier. * meta.autodep.mk: use !defined(WITHOUT_META_STATS) * progs.mk: avoid prog.mk outputting multiple Finished lines 2020-07-11 Simon J Gerraty * dirdeps.mk: further optimize dirdeps.cache generate a DIRDEPS.${.TARGET} list for other purposes and improve the layout. 2020-07-10 Simon J Gerraty * dirdeps.mk: optimize content of dirdeps.cache 2020-06-28 Simon J Gerraty * sys/*.mk: make it easier for local*sys.mk to customize by using ?= 2020-06-22 Simon J Gerraty * gendirdeps.mk (LOCAL_DEPENDS_GUARD): if we don't build at level 0 it is much safer to guard local depends with a simple check for .MAKE.LEVEL > 0 2020-06-10 Simon J Gerraty * install-mk (MK_VERSION): 20200610 * mkopt.sh: this needs posix shell so #!/bin/sh should be ok 2020-06-06 Simon J Gerraty * install-mk (MK_VERSION): 20200606 * dirdeps-targets.mk: allow for filtering of .TARGETS * meta2deps.py: fix bug in processing 'L'ink and 'M'ove entries - and we don't care about 'W'rite entries. Also ignore absolute paths that do not exist. 2020-05-25 Simon J Gerraty * install-mk (MK_VERSION): 20200525 * init.mk: expand and simplify handling of qualified vars like CPPFLAGS.${.TARGET:T} 2020-05-15 Simon J Gerraty * install-mk (MK_VERSION): 20200515 * dirdeps.mk: set _debug_* earlier and allow passing -d* flags to submake when building DIRDEPS_CACHE 2020-05-09 Simon J Gerraty * whats.mk: more easily extensible 2020-05-02 Simon J Gerraty * whats.mk: greatly simplify by adding what.c to SRCS 2020-05-01 Simon J Gerraty * whats.mk: for libs take care how we add to *OBJS * lib.mk: : works better with whats.mk 2020-04-25 Simon J Gerraty * install-mk (MK_VERSION): 20200420 * meta.stage.mk: it is not a STAGE_CONFLICT if some-target.dirdep contains the same ${RELDIR} and a prefix match for our ${TARGET_SPEC} 2020-04-16 Simon J Gerraty * install-mk (MK_VERSION): 20200416 * sys/*.mk: set MAKE_SHELL rather than SHELL so as not to interfere with user env. * sys.mk: default MAKE_SHELL to sh and SHELL to MAKE_SHELL * autodep.mk: use MAKE_SHELL. 2019-11-21 Simon J Gerraty * gendirdeps.mk: clear .SUFFIXES to avoid a lot of wasted effort, and unexport _meta_files when no longer needed as it consumes space we need for command line. 2019-11-11 Simon J Gerraty * dirdeps.mk _DIRDEP_USE: use DIRDEP_DIR and add DIRDEP_USE_PRELUDE at start - facilitates job distribution 2019-10-04 Simon J Gerraty * dirdeps-targets.mk: Use TARGET_SPEC_LAST_LIST defaults to ${${TARGET_SPEC_VARS:[-1]}_LIST} to match valid TARGET_SPEC qualified depend files. 2019-10-02 Simon J Gerraty * dirdeps-targets.mk: encapsulate logic for finding top-level targets to set initial DIRDEPS for DIRDEPS_BUILD 2019-09-27 Simon J Gerraty * install-mk (MK_VERSION): 20190911 * compiler.mk: set COMPILER_TYPE 2019-07-17 Simon J Gerraty * install-mk (MK_VERSION): 20190704 * sys/Darwin.mk: support for Objective-C and clang 2019-05-30 Simon J Gerraty * dirdeps.mk: avoid insanely long command line when generating cache 2019-05-23 Simon J Gerraty * install-mk (MK_VERSION): 20190505 * whats.mk: handle corner case SHLIB defined but not LIB 2018-09-19 Simon J Gerraty * install-mk (MK_VERSION): 20180919 * dirdeps-options.mk: .undef cannot handle var that expands to more than one var. 2018-07-08 Simon J Gerraty * meta.stage.mk: allow wildcards in STAGE_FILES.* etc. 2018-06-01 Simon J Gerraty * meta.autodep.mk: export META_FILES to avoid command line limit * gendirdeps.mk: if we have lots of .meta files put them in an @list 2018-05-28 Simon J Gerraty * dirdeps-options.mk: use local.dirdeps-options.mk not local.dirdeps-option.mk 2018-04-20 Simon J Gerraty * install-mk (MK_VERSION): 20180420 * dirdeps.mk: include local.dirdeps-build.mk when .MAKE.LEVEL > 0 ie. we are building something. 2018-04-14 Simon J Gerraty * FILES: add dirdeps-options.mk to deal with optional DIRDEPS. 2018-04-05 Simon J Gerraty * install-mk (MK_VERSION): 20180405 * ldorder.mk: describe how to use LDORDER_EXTERN_BARRIER if needed. 2018-01-18 Simon J Gerraty * install-mk (MK_VERSION): 20180118 * ldorder.mk: let make compute correct link order 2017-12-12 Simon J Gerraty * install-mk (MK_VERSION): 20171212 * gendirdeps.mk: guard against bogus entries in GENDIRDEPS_FILTER 2017-11-14 Simon J. Gerraty * install-mk (MK_VERSION): 20171111 * lib.mk: ensure META_NOECHO is set 2017-10-25 Simon J. Gerraty * Allow for host32 on rare occasions. 2017-10-18 Simon J. Gerraty * install-mk (MK_VERSION): 20171018 * whats.mk: include what_thing in what_uuid to avoid problem when building multiple apps in the same directory. 2017-08-12 Simon J. Gerraty * install-mk (MK_VERSION): 20170812 * autoconf.mk: Use CONFIGURE_DEPS so Makefile can add dependencies for config.recheck and config.gen 2017-06-30 Simon J. Gerraty * install-mk (MK_VERSION): 20170630 * meta.stage.mk: avoid triggering stage_* targets with nothing to do. 2017-05-23 Simon J. Gerraty * meta2deps.py: take special care of '..' 2017-05-15 Simon J. Gerraty * install-mk (MK_VERSION): 20170515 * dirdeps.mk (DEP_EXPORT_VARS): on rare occasions it is useful/necessary for a Makefile.depend file to export some knobs. This is complicated when we are doing DIRDEPS_CACHE, so we will handle export of any variables listed in DEP_EXPORT_VARS. 2017-05-08 Simon J. Gerraty * install-mk (MK_VERSION): 20170505 * meta2deps.py: fix botched indenation. 2017-05-05 Simon J. Gerraty * sys/*.mk: Remove setting of MAKE it is unnecessary and in many cases wrong (basname rather than full path) * scripts.mk (SCRIPTSGROUPS): make this more like files.mk and inc.mk * init.mk: define realbuild to simplify logic in {lib,prog}.mk etc 2017-05-01 Simon J. Gerraty * install-mk (MK_VERSION): 20170501 * doc.mk: fix typo in DOC_INSTALL_OWN * inc.mk: handle INCGROUPS similar to freebsd * files.mk: add something for files too * add staging logic to lib.mk prog.mk etc. 2017-04-24 Simon J. Gerraty * install-mk (MK_VERSION): 20170424 * dirdeps.mk: set NO_DIRDEPS when bootstrapping. also target of bootstrap-this when sed is needed should be ${_want:T} 2017-04-18 Simon J. Gerraty * install-mk (MK_VERSION): 20170418 * auto.obj.mk: if using MAKEOBJDIRPREFIX check if it is a prefix match for .CURDIR - in which case .CURDIR *is* __objdir. 2017-04-01 Simon J. Gerraty * install-mk (MK_VERSION): 20170401 * meta2deps.py: add is_src so we can check if obj dependency is also a src dependency. 2017-03-26 Simon J. Gerraty * install-mk (MK_VERSION): 20170326 * meta.stage.mk: do nothing if NO_STAGING is defined. 2017-03-24 Simon J. Gerraty * auto.obj.mk: handle the case of __objdir=obj or obj.${MACHINE} etc. 2017-03-18 Simon J. Gerraty * mkopt.sh: treat WITH_*=NO like no; ie. WITHOUT_* 2017-03-01 Simon J. Gerraty * install-mk (MK_VERSION): 20170301 * dirdeps.mk (_build_all_dirs): update this outside test for empty DIRDEPS. * meta.stage.mk: allow multiple inclusion to the extent it makes sense. 2017-02-14 Simon J. Gerraty * prog.mk (install_links): depends on realinstall 2017-02-12 Simon J. Gerraty * install-mk (MK_VERSION): 20170212 * dpadd.mk: avoid applying :T:R twice to DPLIBS entries 2017-01-30 Simon J. Gerraty * install-mk (MK_VERSION): 20170130 * dirdeps.mk: use :range if we can. * sys.vars.mk: provide M_cmpv if MAKE_VERSION >= 20170130 * meta2deps.py: clean paths without using realpath() where possible. fix sort_unique. 2016-12-12 Simon J. Gerraty * install-mk (MK_VERSION): 20161212 * meta2deps.py: set pid_cwd[pid] when we process 'C'hdir, rather than when we detect pid change. 2016-12-07 Simon J. Gerraty * install-mk (MK_VERSION): 20161207 * meta.stage.mk: add stage_as_and_symlink for staging packages. We build foo.tgz stage_as foo-${VERSION}.tgz but want to be able to use foo.tgz to reference the latest staged version - so we make foo.tgz a symlink to it. Using a target to do both operations ensures we stay in sync. 2016-11-26 Simon J. Gerraty * install-mk (MK_VERSION): 20161126 * dirdeps.mk: set DIRDEPS_CACHE before we include local.dirdeps.mk so it can add dependencies. 2016-10-10 Simon J. Gerraty * dirdeps.mk: set DEP_* before we expand .MAKE.DEPENDFILE_PREFERENCE do that they can influence the result correctly. * dirdeps.mk (${DIRDEPS_CACHE}): make sure we pass on TARGET_SPEC * dirdeps.mk: Add ONLY_TARGET_SPEC_LIST and NOT_TARGET_SPEC_LIST similar to ONLY_MACHINE_LIST and NOT_MACHINE_LIST 2016-10-05 Simon J. Gerraty * dirdeps.mk: remove dependence on jot (normal situations anyway). Before we read another Makefile.depend* set DEP_* vars from _DEP_TARGET_SPEC in case it uses any of them with := When bootstrapping, trim any ,* from extention of chosen _src Makefile.depend* to get the machine value we subst for. 2016-09-30 Simon J. Gerraty * dirdeps.mk: use TARGET_SPEC_VARS to qualify components added to DEP_SKIP_DIR and DEP_DIRDEPS_FILTER * sys.mk: extract some bits to sys.{debug,vars}.mk for easier re-use by others. 2016-09-23 Simon Gerraty * lib.mk: Use ${PICO} for extension for PIC objects. default to .pico (like NetBSD) safe on case insensitive filesystem. 2016-08-19 Simon J. Gerraty * meta.sys.mk (META_COOKIE_TOUCH): use ${.OBJDIR}/${.TARGET:T} as default 2016-08-15 Simon J. Gerraty * install-mk (MK_VERSION): 20160815 * dirdeps.mk (.MAKE.META.IGNORE_FILTER): set filter to only consider Makefile.depend* when checking if DIRDEPS_CACHE is up-to-date. 2016-08-13 Simon J. Gerraty * meta.sys.mk (.MAKE.META.IGNORE_PATHS): in meta mode we can ignore the mtime of makefiles 2016-08-02 Simon J. Gerraty * install-mk (MK_VERSION): 20160802 * lib.mk (libinstall): depends on beforinstall * prog.mk (proginstall): depends on beforinstall patch from Lauri Tirkkonen * dirdeps.mk (bootstrap): When bootstrapping; creat .MAKE.DEPENDFILE_DEFAULT and allow additional filtering via .MAKE.DEPENDFILE_BOOTSTRAP_SED * dirdeps.mk: move some comments to where they make sense. 2016-07-27 Simon J. Gerraty * dirdeps.mk (DIRDEPS_CACHE): no dirname. 2016-06-02 Simon J. Gerraty * install-mk (MK_VERSION): 20160602 * meta.autodep.mk: when passing META_FILES to gendirdeps.mk do not apply :T to META_XTRAS patch from Bryan Drewery at FreeBSD.org. 2016-05-30 Simon J. Gerraty * install-mk (MK_VERSION): 20160530 * meta.stage.mk: we assume ${CLEANFILES} gets .NOPATH make it so. 2016-05-12 Simon J. Gerraty * install-mk (MK_VERSION): 20160512 * dpadd.mk: always include local.dpadd.mk if it exists remove some things that better belong in local.dpadd.mk skip INCLUDES_* for staged libs unless SRC_* defined. * own.mk: add INCLUDEDIR 2016-04-18 Simon J. Gerraty * dirdeps.mk: when doing -f dirdeps.mk if target suppies no TARGET_MACHINE - :E will be empty or match part of path, use ${MACHINE} 2016-04-07 Simon J. Gerraty * meta.autodep.mk: issue a warning if UPDATE_DEPENDFILE=NO due to NO_FILEMON_COOKIE * dirdeps.mk: move the logic that allows for make -f dirdeps.mk some/dir.${TARGET_SPEC} inside the check for !target(_DIRDEP_USE) 2016-04-04 Simon J. Gerraty * Use <> when including local*.mk and others which may exist elsewhere so that user can better control what they get. * meta.autodep.mk (NO_FILEMON_COOKIE): create a cookie if we ever build dir with nofilemon so that UPDATE_DEPENDFILE will be forced to NO until cleaned. 2016-04-01 Simon J. Gerraty * install-mk (MK_VERSION): 20160401 * meta2deps.py: fix old print statement when debugging. * gendirdeps.mk: META2DEPS_CMD append M2D_EXCLUDES with -X patch from Bryan Drewery 2016-03-22 Simon J. Gerraty * install-mk (MK_VERSION): 20160317 (St. Pats) * warnings.mk: g++ does not like -Wimplicit * sys.mk sys/*.mk lib.mk prog.mk: use CXX_SUFFIXES to handle the pelthora of common suffixes for C++ * lib.mk: use .So for shared objects 2016-03-15 Simon J. Gerraty * install-mk (MK_VERSION): 20160315 * meta.stage.mk (LN_CP_SCRIPT): do not ln(1) if we have to chmod(1) normally only applies to scripts. * dirdeps.mk: NO_DIRDEPS_BELOW to supress DIRDEPS below RELDIR as well as outside it. 2016-03-10 Simon J. Gerraty * install-mk (MK_VERSION): 20160310 * dirdeps.mk: use targets rather than a list to track DIRDEPS that we have processed; the list gets very inefficient as number of DIRDEPS gets large. * sys.dependfile.mk: fix comment wrt MACHINE * meta.autodep.mk: ignore staged DPADDs when bootstrapping. patch from Bryan Drewery 2016-03-02 Simon J. Gerraty * meta2deps.sh: don't ignore subdirs. patch from Bryan Drewery 2016-02-26 Simon J. Gerraty * install-mk (MK_VERSION): 20160226 * gendirdeps.mk: mark _DEPENDFILE .NOMETA 2016-02-20 Simon J. Gerraty * dirdeps.mk: we shouldn't normally include .depend but if we do use .dinclude if we can. 2016-02-18 Simon J. Gerraty * install-mk (MK_VERSION): 20160218 * sys.clean-env.mk: with recent change to Var_Subst() we cannot use the '$$' trick, but .export-literal does the job we need. * auto.dep.mk: make use .dinclude if we can. 2016-02-05 Simon J. Gerraty * dirdeps.mk: Add _build_all_dirs such that local.dirdeps.mk can add fully qualified dirs to it. These will be built normally but the current DEP_RELDIR will not depend on then (to avoid cycles). This makes it easy to hook things like unit-tests into build. 2016-01-21 Simon J. Gerraty * dirdeps.mk: add bootstrap-empty 2015-12-12 Simon J. Gerraty * install-mk (MK_VERSION): 20151212 * auto.obj.mk: do not require MAKEOBJDIRPREFIX to exist. only apply :tA to __objdir when comparing to .OBJDIR 2015-11-14 Simon J. Gerraty * install-mk (MK_VERSION): 20151111 * meta.sys.mk: include sys.dependfile.mk * sys.mk (OPTIONS_DEFAULT_NO): use options.mk to set MK_AUTO_OBJ and MK_DIRDEPS_BUILD include local.sys.env.mk early include local.sys.mk later * own.mk (OPTIONS_DEFAULT_NO): AUTO_OBJ etc moved to sys.mk 2015-11-13 Simon J. Gerraty * meta.sys.mk (META_COOKIE_TOUCH): add ${META_COOKIE_TOUCH} to the end of scripts to touch cookie * meta.stage.mk: stage_libs should ignore SYMLINKS. 2015-10-23 Simon J. Gerraty * install-mk (MK_VERSION): 20151022 * sys.mk: BSD/OS does not have 'type' as a shell builtin. 2015-10-20 Simon J. Gerraty * install-mk (MK_VERSION): 20151020 * dirdeps.mk: Add logic for make -f dirdeps.mk some/dir.${TARGET_SPEC} 2015-10-14 Simon J. Gerraty * install-mk (MK_VERSION): 20151010 2015-10-02 Simon J. Gerraty * meta.stage.mk: use staging: ${STAGE_TARGETS:... to have stage_lins run last in non-jobs mode. Use .ORDER only for jobs mode. 2015-09-02 Simon J. Gerraty * rst2htm.mk: allow for per target flags etc. 2015-09-01 Simon J. Gerraty * install-mk (MK_VERSION): 20150901 * doc.mk: create dir if needed use DOC_INSTALL_OWN 2015-06-15 Simon J. Gerraty * install-mk (MK_VERSION): 20150615 * auto.obj.mk: allow use of MAKEOBJDIRPREFIX too. Follow make's normal precedence rules. * gendirdeps.mk: allow customization of the header. eg. for FreeBSD: GENDIRDEPS_HEADER= echo '\# ${FreeBSD:L:@v@$$$v$$ @:M*F*}'; * meta.autodep.mk: ignore dirdeps.cache* * meta.stage.mk: when bootstrapping options it can be handy to throw warnings rather than errors for staging conflicts. * meta.sys.mk: include local.meta.sys.mk for customization 2015-06-06 Simon J. Gerraty * install-mk (MK_VERSION): 20150606 * dirdeps.mk: don't rely on manually maintained Makefile.depend to set DEP_RELDIR and reset DIRDEPS. By setting DEP_RELDIR ourselves we can skip :tA * gendirdeps.mk: skip setting DEP_RELDIR. 2015-05-24 Simon J. Gerraty * dirdeps.mk: avoid wildcards like make(bootstrap*) 2015-05-20 Simon J. Gerraty * install-mk (MK_VERSION): 20150520 * dirdeps.mk: when we are building dirdeps cache file we *want* meta_oodate to look at all the Makefile.depend files, so set .MAKE.DEPENDFILE to something that won't match. * meta.stage.mk: for STAGE_AS_* basename of file may not be unique so first use absolute path as key. Also skip staging at level 0. 2015-04-30 Simon J. Gerraty * install-mk (MK_VERSION): 20150430 * dirdeps.mk: fix _count_dirdeps for non-cache case. 2015-04-16 Simon J. Gerraty * install-mk (MK_VERSION): 20150411 bump version * own.mk: put AUTO_OBJ in OPTIONS_DEFAULT_NO rather than YES. it is here mainly for documentation purposes, since if using auto.obj.mk it is better done via sys.mk 2015-04-01 Simon J. Gerraty * install-mk (MK_VERSION): 20150401 * meta2deps.sh: support @list * meta2deps.py: updates from Juniper o add EXCLUDES o skip bogus input files. o treat 'M' and 'L' as both an 'R' and a 'W' 2015-03-03 Simon J. Gerraty * install-mk (MK_VERSION): 20150303 * dirdeps.mk: if MK_DIRDEPS_CACHE is yes, use dirdeps-cache which is built via sub-make so we have a .meta file to tell if it is out-of-date. The dirdeps-cache contains the same dependency rules that we normaly construct on the fly. This adds a few seconds overhead when the cache is out of date, but for a large target, the savings can be significant (10-20min). 2014-11-18 Simon J. Gerraty * install-mk (MK_VERSION): 20141118 * meta.stage.mk: add stale_staged * dirdeps.mk (_DIRDEP_USE_LEVEL): allow this to be tweaked only useful under very rare conditions such as FreeBSD's make universe. * auto.obj.mk: Allow MK_AUTO_OBJ to set MKOBJDIRS=auto 2014-11-11 Simon J. Gerraty * install-mk (MK_VERSION): 20141111 * mkopt.sh: use consistent semantics for _mk_opt and _mk_opts 2014-11-09 Simon J. Gerraty * FILES: include mkopt.sh which allows handling options in shell scripts in a manner compatible with options.mk 2014-10-12 Simon J. Gerraty * meta.stage.mk: ensure only _STAGED_DIRS under objroot are used for GENDIRDEPS_FILTER to avoid surprises. 2014-10-10 Simon J. Gerraty * dirdeps.mk (NSkipHostDir): this needs SRCTOP prepended since by the time it is applied to __depdirs they have. * dirdeps.mk fix filtering of _machines since M_dep_qual_fixes expects patterns like *.${MACHINE} * cython.mk (pyprefix?): use pyprefix to find python bits since prefix might be something else (where we install our stuff) 2014-09-11 Simon J. Gerraty * install-mk (MK_VERSION): 20140911 * dirdeps.mk: add bootstrap target to simplify adding support for new MACHINE. 2014-09-01 Simon J. Gerraty * gendirdeps.mk: Add handling of GENDIRDEPS_FILTER_DIR_VARS and GENDIRDEPS_FILTER_VARS to make it easier to produce sharable Makefile.depend files. 2014-08-28 Simon J. Gerraty * install-mk (MK_VERSION): 20140828 * cython.mk: capture logic for building python extension modules with Cython. 2014-08-08 Simon J. Gerraty * meta.stage.mk (_STAGE_AS_BASENAME_USE): Add StageAs variant 2014-08-02 Simon J. Gerraty * install-mk (MK_VERSION): 20140801 * dep.mk: use explicit MKDEP_MK rather than overload MKDEP to identify the autodep.mk variant. * sys.dependfile.mk: delete .MAKE.DEPENDFILE if its initial value does not match .MAKE.DEPENDFILE_PREFIX * meta.autodep.mk: if _bootstrap_dirdeps add RELDIR to DIRDEPS 2014-05-22 Simon J. Gerraty * install-mk (MK_VERSION): 20140522 * lib.mk: use CC to link shlib for linux too patch from Brendan MacDonell 2014-05-05 Simon J. Gerraty * meta.autodep.mk: add _reldir_{finish,failed} for gathering stats if WITH_META_STATS is defined. 2014-05-02 Simon J. Gerraty * dirdeps.mk: accept -DWITHOUT_DIRDEPS (same a as -DNO_DIRDEPS) to supress dirdeps outside of .CURDIR. 2014-04-05 Simon J. Gerraty * Fix spelling errors - patch from Pedro Giffuni 2014-03-14 Simon J. Gerraty * install-mk (MK_VERSION): 20140314 * dirdeps.mk (beforedirdeps): a handy hook * dirdeps.mk (DIRDEP_MAKE): allow the actual command we run to visit leaf dirs to be intercepted (eg. for distributed build). * dirdeps.mk (__depdirs): ensure // don't sneak in * gendirdeps.mk (DIRDEPS): ensure // don't sneak in 2014-02-21 Simon J. Gerraty * rst2htm.mk (RST2PDF): add support for rst2pdf 2014-02-14 Simon J. Gerraty * install-mk (MK_VERSION): bump version * dirdeps.mk (_last_dependfile): use .INCLUDEDFROMFILE if available. 2014-02-10 Simon J. Gerraty * options.mk: avoid :U so this isn't bmake dependent 2014-02-09 Simon J. Gerraty * options.mk: cleanup and simplify semanitcs NO_* dominates all, if both WITH_* and WITHOUT_* are defined then result is DOMINATE_* which defaults to "no". Ie. WITHOUT_ normally wins. 2013-12-12 Simon J. Gerraty * install-mk (MK_VERSION): bump version * meta2deps.py: convert to print function for python3 compat. we also need to open files with mode 'r' rather than 'rb' otherwise we get bytes instead of strings. 2013-10-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version * dirdeps.mk: when TARGET_SPEC_VARS is more than just MACHINE apply the same filtering (M_dep_qual_fixes) when setting _machines as _build_dirs. Also fix the filtering of Makefile.depend files - for reporting what we are looking for (M_dep_qual_fixes can get confused by Makefile.depend) Add some more debug info. 2013-09-04 Simon J. Gerraty * gendirdeps.mk (_objtops): fix typo also while processing M2D_OBJROOTS to gather qualdir_list qualify $ql with loop iterator to ensure correct results. 2013-08-01 Simon J. Gerraty * install-mk (MK_VERSION): 20130801 * libs.mk: update to match progs.mk 2013-07-26 Simon J. Gerraty * install-mk (MK_VERSION): 20130726 some updates from Juniper and FreeBSD o meta2deps.py: indicate file and line number when we hit parse errors also allow @file to provide huge list of .meta files. * meta2deps.py: add try_parse() to cleanup the above. 2013-07-16 Simon J. Gerraty * install-mk (MK_VERSION): 20130716 * own.mk: add GPROG as an option * prog.mk: honor MK_GPROF==yes 2013-05-10 Simon J. Gerraty * install-mk (MK_VERSION): 20130505 * gendirdeps.mk, meta2deps.py, meta2deps.sh: handle $TARGET_SPEC for when $MACHINE isn't enough for objdir distinction. Bring meta2deps.sh closer to par with meta2deps.py. 2013-04-18 Simon J. Gerraty * meta.stage.mk: set INSTALL to STAGE_INSTALL when making 'all' also if the target 'beforeinstall' exists, make it depend on .dirdep (incase it uses STAGE_INSTALL). 2013-04-17 Simon J. Gerraty * install-mk (MK_VERSION): 20130401 ;-) * meta.stage.mk (STAGE_INSTALL_SH): add stage-install.sh as wrapper around install(1). * options.mk (OPTION_PREFIX): Allow a prefix other than MK_ 2013-03-30 Simon J. Gerraty * meta2deps.py (MetaFile.__init__): ensure self.cwd is initialized. * install-mk (MK_VERSION): bump version 2013-03-21 Simon J. Gerraty * install-mk (MK_VERSION): bump version * gendirdeps.mk: do not apply :tA to DPADD entries, since we lose any trailing /., rather apply :tA only when needed. * gendirdeps.mk: better mimic meta2deps handling of .dirdep files. * meta.stage.mk (LN_CP_SCRIPT): Add LnCp to do the ln||cp dance consistently. * dirdeps.mk: better describe the dance in sys.mk for TARGET_SPEC. 2013-03-18 Simon J. Gerraty * gendirdeps.mk: revert the dance around .MAKE.DEPENDFILE_DEFAULT it is simpler to just not update when say building for "host" (where we know we apply filters to DIRDEPS), and using a non-machine qualified dependfile. 2013-03-16 Simon J. Gerraty * dirdeps.mk: improve DIRDEPS filtering by allowing DEP_SKIP_DIR and DEP_DIRDEPS_FILTER to vary by DEP_MACHINE and DEP_TARGET_SPEC * gendirdeps.mk: ensure _objroot has trailing / if it needs it. * meta2deps.py: if machine is "host", then also trim self.host_target from any OBJROOTS. 2013-03-11 Simon J. Gerraty * gendirdeps.mk: if .MAKE.DEPENDFILE_DEFAULT is not machine qualified but _DEPENDFILE is, and .MAKE.DEPENDFILE_DEFAULT exists but _DEPENDFILE does not, compare the new _DEPENDFILE against .MAKE.DEPENDFILE_DEFAULT and discard if the same. 2013-03-08 Simon J. Gerraty * meta.stage.mk: use STAGE_TARGETS to control .ORDER and hook to all: via staging: 2013-03-07 Simon J. Gerraty * sys.dependfile.mk (.MAKE.DEPENDFILE_DEFAULT): use a separate variable for the default .MAKE.DEPENDFILE value so that it can be controlled independently of .MAKE.DEPENDFILE_PREFERENCE * meta.stage.mk: throw error if cp fails etc. Stage*() return early if passed no args. .ORDER stage_* 2013-03-03 Simon J. Gerraty * install-mk (MK_VERSION): bump version * gendirdeps.mk: handle multiple M2D_OBJROOTS better. 2013-02-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20130210 * import latest dirdeps.mk, gendirdeps.mk and meta2deps.py from Juniper. o dirdeps.mk now fully supports TARGET_SPEC consisting of more than just MACHINE. o no longer use DEP_MACHINE from Makefile.depend* so remove it. 2013-01-23 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20130123 * meta.stage.mk: add stage_links (hard links). if doing hard links, we add dest to link as well. Default the stage dir for [sym]links to STAGE_OBJTOP since these are typically specified as absolute paths. Add -m "mode" flag to StageFiles and StageAs. 2012-11-11 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20121111 * autoconf.mk: avoid meta mode seeing changed commands for config.status * meta.autodep.mk: pass resolved MAKESYSPATH to gendirdeps in case we were found via .../mk * sys.clean-env.mk: move it from examples, we and others use it "as is". * FILES: add srctop.mk and options.mk * own.mk: convert to using options.mk which is modeled after FreeBSD's handling of MK_* but more flexible. This allows MK_* for boolean knobs to not be confused with MK* which can be commands. * examples/sys.clean-env.mk: add WITH[OUT]_ to MAKE_ENV_SAVE_PREFIX_LIST. Mention that HOME=/var/empty might be a good idea. 2012-11-08 Simon J. Gerraty * sys.dependfile.mk: if not depend file exists, $MACHINE specific ones are supported but not the default, check if any exist and follow suit. 2012-11-06 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20121106 2012-11-05 Simon J. Gerraty * import latest dirdeps.mk and meta2deps.py from Juniper. * progs.mk: add MAN and CXXFLAGS to PROG_VARS also add PROGS_TARGETS and pass on PROG_CXX if it seems appropriate. 2012-11-04 Simon J. Gerraty * meta.stage.mk: update CLEANFILES remove redundant cp of .dirdep from STAGE_AS_SCRIPT. * progs.mk: Add LDADD to PROG_VARS 2012-10-12 Simon J. Gerraty * meta.stage.mk (STAGE_DIR_FILTER): track dirs we stage to in _STAGED_DIRS so that these can be turned into filters for GENDIRDEPS_FILTER. 2012-10-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20121010 * meta.stage.mk (STAGE_DIRDEP_SCRIPT): check that an existing target.dirdep matches .dirdep 2012-08-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120808 * import latest meta2deps.py from Juniper. 2012-07-11 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120711 * dep.mk: add explicit dependencies on SRCS after applying SRCS_DEP_FILTER * meta.autodep.mk: add explicit dependencies on SRCS after applying SRCS_DEP_FILTER * meta.autodep.mk: ensure GENDIRDEPS_FILTER is exported if needed. 2012-06-26 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120626 * meta.sys.mk: ignore PYTHON if it does not exist compare ${.MAKE.DEPENDFILE:E} against ${MACHINE} is more reliable. * meta.stage.mk: examine .MAKE.DEPENDFILE_PREFERENCE for any entries ending in .${MACHINE} to decide if qualified _dirdep is needed. * gendirdeps.mk: only produce unqualified deps if no .MAKE.DEPENDFILE_PREFERENCE ends in .${MACHINE} * meta.subdir.mk: apply SUBDIRDEPS_FILTER 2012-04-20 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120420 * add sys.dependfile.mk so we can experiment with .MAKE.DEPENDFILE_PREFERENCE * meta.autodep.mk: _DEPENDFILE is precious! 2012-03-15 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120315 * install-new.mk: avoid being interrupted 2012-02-26 Simon J. Gerraty * man.mk: MAN might have multiple values so be careful with exists(). 2012-01-19 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20120112 * fix examples/sys.clean-env.mk so that MAKEOBJDIR is handled as: MAKEOBJDIR='${.CURDIR:S,${SRCTOP},${OBJTOP},}' 2011-12-03 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111201 * import dirdeps.mk from Juniper sjg@ o more consistent handling of DEP_MACHINE, especially when dealing with an odd Makefile.depend, when normally using Makefile.depend.${MACHINE} 2011-11-22 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111122 * meta.autodep.mk: add some debug output, be more crisp about updating. Use ${.ALLTARGETS:M*.o} as a clue for .depend 2011-11-13 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111111 it's too cool to miss * import meta* updates from Juniper sjg@ o dirdeps.mk set DEP_MACHINE for Makefile.depend (when we are normally using Makefile.depend.${MACHINE}), handy for read-only manually maintained dependencies. o meta2deps.py add a clear 'ERROR:' token if an exception is raised. o gendirdeps.mk if ERROR: from meta2deps.py do not update anything. 2011-10-30 Simon J. Gerraty * install-new.mk separate the cmp and copy logic to its own function. 2011-10-28 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111028 * sys.mk: include auto.obj.mk if MKOBJDIRS is set to auto * subdir.mk: ensure _SUBDIRUSE is provided * meta.autodep.mk: remove dependency of gendirdeps.mk on auto.obj.mk * meta.subdir.mk: always allow for Makefile.depend 2011-10-10 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111010 o minor tweak to *dirdeps.mk from Juniper sjg@ 2011-10-01 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20111001 o add meta2deps.py from Juniper sjg@ o tweak gendirdeps.mk to work with meta2deps.py when not cross-building * autoconf.mk: add autoconf-input as a hook for regenerating AUTOCONF_INPUTS (configure). 2011-08-24 Simon J. Gerraty * meta.autodep.mk: if we do not have OBJS, .depend isn't a useful trigger for updating Makefile.depend* 2011-08-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110808 * obj.mk: minor cleanup * auto.obj.mk: improve description of Mkdirs and honor NO_OBJ too. 2011-08-01 Simon J. Gerraty * auto.obj.mk (.OBJDIR): throw an error if we cannot use the specified dir. 2011-06-28 Simon J. Gerraty * meta.autodep.mk: if XMAKE_META_FILE is set the makefile uses a foreign make, and so dependencies can only be gathered from a clean tree build. 2011-06-24 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110622 * meta.autodep.mk: improve bootstraping 2011-06-10 Simon J. Gerraty * yacc.mk: handle the corner case of .c being removed while .h remains. 2011-06-08 Simon J. Gerraty * yacc.mk: do .y.h and .y.c separately 2011-06-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110606 * don't store SRC_DIRDEPS in Makefile.depend* by default not everyone needs it. 2011-05-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110505 first release including meta mode makefiles 2011-05-02 Simon J. Gerraty * meta.stage.mk: add STAGE_AS_SETS and stage_as for things that need to be staged with different names. 2011-05-01 Simon J. Gerraty * meta.stage.mk: add notion of STAGE_SETS so a makefile can stage to multiple dirs 2011-04-03 Simon J. Gerraty * rst2htm.mk: convert rst to s5 (slides) or plain html depending on target name. 2011-03-30 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110330 2011-03-29 Simon J. Gerraty * sys.mk (_DEBUG_MAKE_FLAGS): use indirection so that DEBUG_MAKE_FLAGS0 can be used to debug level 0 only and DEBUG_MAKE_FLAGS for the rest. * sys.mk: re-define M_whence in terms of M_type. M_type is useful for checking if something is a builtin. 2011-03-16 Simon J. Gerraty * meta.stage.mk: add stage_symlinks and leverage StageLinks for stage_libs 2011-03-10 Simon J. Gerraty * dirdeps.mk: correct value for _depdir_files depends on .MAKE.DEPENDFILE Add our copyright - just to make it clear we have frobbed this quite a bit. DEP_MACHINE needs to be set to MACHINE each time, if using only Makefile.depend (cf. Makefile.depend.${MACHINE}) * meta.stage.mk: meta mode version of staging * init.mk, final.mk: include local.*.mk to simplify customization 2011-03-03 Simon J. Gerraty * auto.obj.mk: just because we are doing mk destroy, we should still set .OBJDIR correctly if it exists. * install-mk (mksrc): do not exclude meta.sys.mk 2011-03-01 Simon J. Gerraty * host-target.mk: set/export _HOST_ARCH etc separately, catch junk resulting from uname -p, so we can find sys/Linux.mk correctly. 2011-02-18 Simon J. Gerraty * meta.sys.mk: throw an error if /dev/filemon is missing and we expected to be updating Makefile.depend* 2011-02-14 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20110214 * meta.subdir.mk: add support for -DBOOTSTRAP_DEPENDFILES 2010-09-25 Simon J. Gerraty * meta.sys.mk: not valid for older bmake 2010-09-24 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100919 include dirdeps.mk et al from Juniper Networks, for meta mode - requires filemon(9). * sys.mk, subdir.mk: Add hooks for meta mode. we do this as meta.sys.mk, meta.autodep.mk and meta.subdir.mk to make turning it on/off simple. 2010-06-16 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100616 * fix typo in sys.mk 2010-06-12 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100612 * lib.mk: remove duplicate addition to SOBJS 2010-06-10 Simon J. Gerraty * sys.mk: Add a means of selectively turning on debug flags. Eg. DEBUG_MAKE_FLAGS=-dv DEBUG_MAKE_DIRS="*lib/sjg" will act as if we did make -dv if .CURDIR ends in lib/sjg DEBUG_MAKE_SYS_DIRS does the same thing, but we set the flags at the start of sys.mk rather than the end. This only makes sense for leaf dirs, so we check that .MAKE.LEVEL > 0 2010-06-09 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100608 * sys.mk: include sys.env.mk later so it can use M_ListToSkip et al. * examples/sys.clean-env.mk: require MAKE_VERIONS >= 20100606 also make it easier for folk to tweak 2010-06-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100606 do not install examples/* * FILES: add examples/sys.clean-env.mk * examples/sys.clean-env.mk: use .export-env to handle MAKEOBJDIR this requires bmake-20100606 or later to work. 2010-05-13 Simon J. Gerraty * sys.mk (M_tA): better simulate the result of :tA if not available. 2010-05-04 Simon J. Gerraty * sys.mk: canonicalize MAKE_VERSION old versions reported bmake- build- whereas we only care about 2010-04-25 Simon J. Gerraty * install-mk: just warn about FORCE_{BSD,SYS}_MK being ignored * lib.mk: we only build the shared lib if SHLIB_FULLVERSION is !empty 2010-04-22 Simon J. Gerraty * dpadd.mk: use LDADD_* if defined. 2010-04-21 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100420 * sys/NetBSD.mk: add MACHINE_CPU to keep netbsd makefiles happy * autoconf.mk allow AUTO_AUTOCONF 2010-04-19 Simon J. Gerraty * obj.mk: add objwarn to keep freebsd makefiles happy * auto.obj.mk: ensure Mkdirs is available. * FILES: add auto.dep.mk - a simpler version of autodep.mk * dep.mk: auto.dep.mk does not do 'make depend' so ignore it if asked to do that. fix/simplify the tests for when to run mkdep. * auto.dep.mk: add some explanation of how/what we do. * autodep.mk: skip the .OPTIONAL frobbing of .depend bmake's FROM_DEPEND flag makes it redundant. 2010-04-13 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100404 * subdir.mk: protect from multiple inclusion using _SUBDIRUSE. * obj.mk: protect from multiple inclusion even as bsd.obj.mk Also create a target _SUBDIRUSE so that we can be used without subdir.mk 2010-04-12 Simon J. Gerraty * dep.mk: use <> when .including so can override. 2010-01-11 Simon J. Gerraty * lib.mk (SHLIB_LINKS): ensure a string comparison. 2010-01-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20100102 * own.mk: ensure PRINTOBJDIR works * autoconf.mk: pass on CONFIGURE_ARGS * init.mk: handle COPTS.${.IMPSRC:T} etc. * lib.mk: allow sys.mk to control SHLIB_FULLVERSION fix handling of symlinks for darwin * libnames.mk: add DSHLIBEXT for libs which only exist as shared. * man.mk: suppress chown when not root. * rst2htm.mk: allow srcs from multiple locations. * sys.mk: M_whence, stop after 1st line of output. * sys/Darwin.mk: Use .dylib for DSHLIBEXT and HOST_LIBEXT * sys/SunOS.mk: we need to export PATH 2009-12-23 Simon J. Gerraty * install-mk (MK_VERSION): bump version include rst2htm.mk 2009-12-17 Simon J. Gerraty * sys.mk,libnames.mk add .-include this allows local customization without the need to edit the distributed files. 2009-12-14 Simon J. Gerraty * dpadd.mk (__dpadd_libdirs): order -L's to avoid picking up older versions already installed. 2009-12-13 Simon J. Gerraty * stage.mk (.stage-install): generalize lib.mk's .libinstall * rules.mk rules for generic Makefile. * inc.mk install for includes. 2009-12-11 Simon J. Gerraty * sys/NetBSD.mk (MAKE_VERSION): some of our *.mk want to check this, so provide it if using native make. 2009-12-10 Simon J. Gerraty * FILES: move all the platform *.sys.mk files to sys/*.mk * Rename Generic.sys.mk to sys.mk - we always want it. 2009-11-17 Simon J. Gerraty * install-mk (MK_VERSION): bump version * host-target.mk: only export the expensive stuff * Generic.sys.mk (sys_mk): for SunOS we need to look for ${HOST_OS}.${HOST_OSMAJOR} too! 2009-11-07 Simon J. Gerraty * install-mk (MK_VERSION): bump version * lib.mk: if sys.mk doesn't give us an lorder, don't use it. based on patch from Greg Olszewski. * Generic.sys.mk: if we have nothing to work with set LORDER etc only if we can find it. 2009-09-08 Simon J. Gerraty * install-mk (MK_VERSION): bump version * man.mk: cleanman: remove CLEANMAN if defined. 2009-09-04 Simon J. Gerraty * SunOS.5.sys.mk (CC): Use ?= like the other *sys.mk 2009-07-17 Simon J. Gerraty * install-mk (MK_VERSION): bump version include auto.obj.mk 2009-03-26 Simon J. Gerraty * prog.mk,lib.mk: ensure test of USE_DPADD_MK doesn't fail. 2008-11-11 Simon J. Gerraty * install-mk (MK_VERSION): bump version man.mk: ensure we generate *.cat1 etc in . 2008-07-16 Simon J. Gerraty * install-mk (MK_VERSION): bump version add prlist.mk 2007-11-25 Simon J. Gerraty * Generic.sys.mk: Allow os specific sys.mk to be in a subdir of ${.PARSEDIR} 2007-11-22 Simon J. Gerraty * install-mk (MK_VERSION): bump version * general cleanup * dpadd.mk introduce DPMAGIC_LIBS_* 2007-04-30 Simon J. Gerraty * install-mk (MK_VERSION): bump version * libs.mk, progs.mk, autodep.mk: allow for per lib/prog depend files and ensure clean is called for each lib/prog. 2007-03-27 Simon J. Gerraty * autodep.mk (.depend): delete lines that do not start with space and do not contain ':' 2007-02-16 Simon J. Gerraty * autodep.mk (.depend): gcc may wrap lines if pathnames are long so make sure the transform for .OPTIONAL copes. 2007-02-03 Simon J. Gerraty * install-mk (MK_VERSION): bump version * own.mk: make sure RM and LN are defined. * obj.mk: fix a typo, and objlink target. 2006-12-30 Simon J. Gerraty * install-mk (MK_VERSION): bump version * added libs.mk - analogous to progs.mk make both of them always inlcude {lib,prog}.mk 2006-12-28 Simon J. Gerraty * progs.mk: add a means of building multiple apps in one dir. 2006-11-26 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20061126 * warnings.mk: detect invalid WARNINGS_SET * warnings.mk: use ${.TARGET:T:R}.o when looking for target specific warnings. * For .cc sources, turn off warnings that g++ vomits on. 2006-11-08 Simon J. Gerraty * own.mk: if __initialized__ target doesn't exist and we are FreeBSD we got here directly from sys.mk 2006-11-06 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20061106 add scripts.mk 2006-03-18 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20060318 * autodep.mk: avoid := when modifying OBJS into __dependsrcs 2006-03-02 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20060302 * autodep.mk: use -MF et al to help gcc+ccache DTRT. 2006-03-01 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20060301 * autodep.mk (.depend): if MAKE_VERSION is newer than 20050530 we can make .END depend on .depend and make .depend depend on __depsrcs that exist. * dpadd.mk: add SRC_PATHADD 2005-11-04 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20051104 * prog.mk: remove all the LIBC?= junk, use .-include libnames.mk instead (none by default). also if USE_DPADD_MK is set, include that. 2005-10-09 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20051001 Add UnixWare.sys.mk from Klaus Heinz. 2005-04-05 Simon J. Gerraty * install-mk: always install *.sys.mk and if need be symlink one to sys.mk 2005-03-22 Simon J. Gerraty * subdir.mk, own.mk: use .MAKE rather than MAKE 2004-02-15 Simon J. Gerraty * own.mk: don't use NetBSD's _SRC_TOP_ it can cause confusion. Also don't take just 'mk' as a srctop indicator. 2004-02-14 Simon J. Gerraty * warnings.mk: overhauled, now very powerful. 2004-02-03 Simon J. Gerraty * Generic.sys.mk: need to use ${.PARSEDIR} with exists(). 2004-02-01 Simon J. Gerraty * install-mk (MK_VERSION): bump version to 20040201 * extract HOST_TARGET stuff to host-target.mk so own.mk and Generic.sys.mk can share. * fix typo in autodep.mk _SUBDIRUSE not _SUBDIR. 2003-09-30 Simon J. Gerraty * install-mk (MK_VERSION): 20030930 * rename generic.sys.mk to Generic.sys.mk so that it does not get installed (unless being used as sys.mk) * set OS and ROOT_GROUP for those that we know the value. for others (eg. Generic.sys.mk) wrap the != in an .ifndef so we don't do it again for each sub-make. 2003-09-28 Simon J. Gerraty * install-mk (MK_VERSION): 20030928 Add some extra *.sys.mk from bootstrap-pkgsrc some of these likely still need work. Make everything default to root:wheel ownership, sys.mk can set ROOT_GROUP accordingly. 2003-08-07 Simon J. Gerraty * install-mk: if FORCE_BSD_MK={cp,ln} use the ones in SYS_MK_DIR not the portable ones. 2003-07-31 Simon J. Gerraty * install-mk: add ability to use cp -f when updating destination .mk files. Also now possible to play games with FORCE_SYS_MK=ln etc on *BSD machines to link /usr/share/mk/sys.mk into dest - not recommended unless you seriously want to. 2003-07-28 Simon J. Gerraty * own.mk (IMPFLAGS): add support for COPTS.${IMPSRC:T} etc for semi-compatability with NetBSD. 2003-07-23 Simon J. Gerraty * install-mk: add a version indicator 2003-07-22 Simon J. Gerraty * prog.mk: don't try and use ${LIBCRT0} if its /dev/null * install-mk: Allow FORCE_SYS_MK to come from env diff --git a/contrib/bmake/mk/FILES b/contrib/bmake/mk/FILES index 39ac101d36f4..85d8b15dbd44 100644 --- a/contrib/bmake/mk/FILES +++ b/contrib/bmake/mk/FILES @@ -1,84 +1,87 @@ ChangeLog FILES LICENSE README auto.obj.mk autoconf.mk autodep.mk auto.dep.mk cc-wrap.mk ccm.dep.mk compiler.mk cython.mk dep.mk doc.mk dpadd.mk files.mk final.mk +genfiles.mk host-target.mk host.libnames.mk inc.mk init.mk install-mk install-sh java.mk jobs.mk ldorder.mk lib.mk libnames.mk libs.mk links.mk man.mk manifest.mk mk-files.txt mkopt.sh newlog.sh nls.mk obj.mk options.mk own.mk posix.mk prlist.mk prog.mk progs.mk rst2htm.mk +rust.mk scripts.mk +setopts.sh srctop.mk stage-install.sh subdir.mk suffixes.mk sys.mk sys.clean-env.mk sys.debug.mk sys.dependfile.mk sys.dirdeps.mk sys.vars.mk sys/AIX.mk sys/Cygwin.mk sys/Darwin.mk sys/Generic.mk sys/HP-UX.mk sys/IRIX.mk sys/Linux.mk sys/NetBSD.mk sys/OSF1.mk sys/OpenBSD.mk sys/SCO_SV.mk sys/SunOS.mk sys/UnixWare.mk target-flags.mk warnings.mk whats.mk yacc.mk dirdeps.mk dirdeps-cache-update.mk dirdeps-options.mk dirdeps-targets.mk gendirdeps.mk install-new.mk meta2deps.py meta2deps.sh meta.sys.mk meta.autodep.mk meta.stage.mk meta.subdir.mk diff --git a/contrib/bmake/mk/README b/contrib/bmake/mk/README index 161426cfcd0c..7ffc3653c09d 100644 --- a/contrib/bmake/mk/README +++ b/contrib/bmake/mk/README @@ -1,401 +1,401 @@ -# $Id: README,v 1.2 2020/08/19 17:51:53 sjg Exp $ +# $Id: README,v 1.3 2024/12/10 22:15:02 sjg Exp $ -This directory contains some macro's derrived from the NetBSD bsd.*.mk -macros. They have the same names but without the bsd., separate macro -files are needed to ensure we can make them do what we want for -builing things outside of /usr/src. Nearly all the comments below +This directory contains some makefiles derrived from the NetBSD bsd.*.mk +ones. They have the same names but without the bsd., separate +makefiles are needed to ensure we can make them do what we want for +building things outside of /usr/src. Nearly all the comments below apply. # $NetBSD: bsd.README,v 1.18 1997/01/13 00:54:23 mark Exp $ # @(#)bsd.README 5.1 (Berkeley) 5/11/90 This is the README file for the new make "include" files for the BSD source tree. The files are installed in /usr/share/mk, and are, by convention, named with the suffix ".mk". Note, this file is not intended to replace reading through the .mk files for anything tricky. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= RANDOM THINGS WORTH KNOWING: The files are simply C-style #include files, and pretty much behave like you'd expect. The syntax is slightly different in that a single '.' is used instead of the hash mark, i.e. ".include ". One difference that will save you lots of debugging time is that inclusion of the file is normally done at the *end* of the Makefile. The reason for this is because .mk files often modify variables and behavior based on the values of variables set in the Makefile. To make this work, remember that the FIRST target found is the target that is used, i.e. if the Makefile has: a: echo a a: echo a number two the command "make a" will echo "a". To make things confusing, the SECOND variable assignment is the overriding one, i.e. if the Makefile has: a= foo a= bar b: echo ${a} the command "make b" will echo "bar". This is for compatibility with the way the V7 make behaved. It's fairly difficult to make the BSD .mk files work when you're building multiple programs in a single directory. It's a lot easier split up the programs than to deal with the problem. Most of the agony comes from making the "obj" directory stuff work right, not because we switch to a new version of make. So, don't get mad at us, figure out a better way to handle multiple architectures so we can quit using the symbolic link stuff. (Imake doesn't count.) The file .depend in the source directory is expected to contain dependencies for the source files. This file is read automatically by make after reading the Makefile. The variable DESTDIR works as before. It's not set anywhere but will change the tree where the file gets installed. The profiled libraries are no longer built in a different directory than the regular libraries. A new suffix, ".po", is used to denote a profiled object. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file has the default rules for all makes, in the BSD environment or otherwise. You probably don't want to touch this file. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file handles installing manual pages and their links. It has a single target: maninstall: Install the manual pages and their links. It sets/uses the following variables: MANDIR Base path for manual installation. MANGRP Manual group. MANOWN Manual owner. MANMODE Manual mode. MANSUBDIR Subdirectory under the manual page section, i.e. "/vax" or "/tahoe" for machine specific manual pages. MAN The manual pages to be installed (use a .1 - .9 suffix). MLINKS List of manual page links (using a .1 - .9 suffix). The linked-to file must come first, the linked file second, and there may be multiple pairs. The files are soft-linked. The include file includes a file named "../Makefile.inc" if it exists. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file contains source tree configuration parameters, such as the owners, groups, etc. for both manual pages and binaries, and a few global "feature configuration" parameters. It has no targets. To get system-specific configuration parameters, bsd.own.mk will try to include the file specified by the "MAKECONF" variable. If MAKECONF is not set, or no such file exists, the system make configuration file, /etc/mk.conf is included. These files may define any of the variables described below. bsd.own.mk sets the following variables, if they are not already defined (defaults are in brackets): BSDSRCDIR The real path to the system sources, so that 'make obj' will work correctly. [/usr/src] BSDOBJDIR The real path to the system 'obj' tree, so that 'make obj' will work correctly. [/usr/obj] BINGRP Binary group. [bin] BINOWN Binary owner. [bin] BINMODE Binary mode. [555] NONBINMODE Mode for non-executable files. [444] MANDIR Base path for manual installation. [/usr/share/man/cat] MANGRP Manual group. [bin] MANOWN Manual owner. [bin] MANMODE Manual mode. [${NONBINMODE}] LIBDIR Base path for library installation. [/usr/lib] LINTLIBDIR Base path for lint(1) library installation. [/usr/libdata/lint] LIBGRP Library group. [${BINGRP}] LIBOWN Library owner. [${BINOWN}] LIBMODE Library mode. [${NONBINMODE}] DOCDIR Base path for system documentation (e.g. PSD, USD, etc.) installation. [/usr/share/doc] DOCGRP Documentation group. [bin] DOCOWN Documentation owner. [bin] DOCMODE Documentation mode. [${NONBINMODE}] NLSDIR Base path for National Language Support files installation. [/usr/share/nls] NLSGRP National Language Support files group. [bin] NLSOWN National Language Support files owner. [bin] NLSMODE National Language Support files mode. [${NONBINMODE}] STRIP The flag passed to the install program to cause the binary to be stripped. This is to be used when building your own install script so that the entire system can be made stripped/not-stripped using a single knob. [-s] COPY The flag passed to the install program to cause the binary to be copied rather than moved. This is to be used when building our own install script so that the entire system can either be installed with copies, or with moves using a single knob. [-c] Additionally, the following variables may be set by bsd.own.mk or in a make configuration file to modify the behaviour of the system build process (default values are in brackets along with comments, if set by bsd.own.mk): EXPORTABLE_SYSTEM Do not build /usr/src/domestic, even if it is present. SKEY Compile in support for S/key authentication. [yes, set unconditionally] KERBEROS Compile in support for Kerberos 4 authentication. KERBEROS5 Compile in support for Kerberos 5 authentication. MANZ Compress manual pages at installation time. SYS_INCLUDE Copy or symlink kernel include files into /usr/include. Possible values are "symlinks" or "copies" (which is the same as the variable being unset). NOPROFILE Do not build profiled versions of system libraries NOPIC Do not build PIC versions of system libraries, and do not build shared libraries. [set if ${MACHINE_ARCH} is "mips", "vax", "alpha" or "arm32", unset otherwise.] NOLINT Do not build lint libraries. [set, set unconditionally] bsd.own.mk is generally useful when building your own Makefiles so that they use the same default owners etc. as the rest of the tree. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file handles building programs from one or more source files, along with their manual pages. It has a limited number of suffixes, consistent with the current needs of the BSD tree. It has eight targets: all: build the program and its manual page clean: remove the program, any object files and the files a.out, Errs, errs, mklog, and core. cleandir: remove all of the files removed by the target clean, as well as .depend, tags, and any manual pages. depend: make the dependencies for the source files, and store them in the file .depend. includes: install any header files. install: install the program and its manual pages; if the Makefile does not itself define the target install, the targets beforeinstall and afterinstall may also be used to cause actions immediately before and after the install target is executed. lint: run lint on the source files tags: create a tags file for the source files. It sets/uses the following variables: BINGRP Binary group. BINOWN Binary owner. BINMODE Binary mode. CLEANFILES Additional files to remove for the clean and cleandir targets. COPTS Additional flags to the compiler when creating C objects. HIDEGAME If HIDEGAME is defined, the binary is installed in /usr/games/hide, and a symbolic link is created to /usr/games/dm. LDADD Additional loader objects. Usually used for libraries. For example, to load with the compatibility and utility libraries, use: LDADD+=-lutil -lcompat LDFLAGS Additional loader flags. LINKS The list of binary links; should be full pathnames, the linked-to file coming first, followed by the linked file. The files are hard-linked. For example, to link /bin/test and /bin/[, use: LINKS= ${DESTDIR}/bin/test ${DESTDIR}/bin/[ MAN Manual pages (should end in .1 - .9). If no MAN variable is defined, "MAN=${PROG}.1" is assumed. PROG The name of the program to build. If not supplied, nothing is built. SRCS List of source files to build the program. If PROG is not defined, it's assumed to be ${PROG}.c. DPADD Additional dependencies for the program. Usually used for libraries. For example, to depend on the compatibility and utility libraries use: DPADD+=${LIBCOMPAT} ${LIBUTIL} The following libraries are predefined for DPADD: LIBC /lib/libc.a LIBCOMPAT /usr/lib/libcompat.a LIBCRYPT /usr/lib/libcrypt.a LIBCURSES /usr/lib/libcurses.a LIBDBM /usr/lib/libdbm.a LIBDES /usr/lib/libdes.a LIBL /usr/lib/libl.a LIBKDB /usr/lib/libkdb.a LIBKRB /usr/lib/libkrb.a LIBKVM /usr/lib/libkvm.a LIBM /usr/lib/libm.a LIBMP /usr/lib/libmp.a LIBPC /usr/lib/libpc.a LIBPLOT /usr/lib/libplot.a LIBRPC /usr/lib/sunrpc.a LIBTERM /usr/lib/libterm.a LIBUTIL /usr/lib/libutil.a SHAREDSTRINGS If defined, a new .c.o rule is used that results in shared strings, using xstr(1). Note that this will not work with parallel makes. STRIP The flag passed to the install program to cause the binary to be stripped. SUBDIR A list of subdirectories that should be built as well. Each of the targets will execute the same target in the subdirectories. The include file includes the file named "../Makefile.inc" if it exists, as well as the include file . Some simple examples: To build foo from foo.c with a manual page foo.1, use: PROG= foo .include To build foo from foo.c with a manual page foo.2, add the line: MAN= foo.2 If foo does not have a manual page at all, add the line: NOMAN= noman If foo has multiple source files, add the line: SRCS= a.c b.c c.c d.c =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file contains the default targets for building subdirectories. It has the same eight targets as : all, clean, cleandir, depend, includes, install, lint, and tags. For all of the directories listed in the variable SUBDIRS, the specified directory will be visited and the target made. There is also a default target which allows the command "make subdir" where subdir is any directory listed in the variable SUBDIRS. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file is used by and . It contains overrides that are used when building the NetBSD source tree. For instance, if "PARALLEL" is defined by the program/library Makefile, it includes a set of rules for lex and yacc that allow multiple lex and yacc targets to be built in parallel. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= The include file has support for building libraries. It has the same eight targets as : all, clean, cleandir, depend, includes, install, lint, and tags. It has a limited number of suffixes, consistent with the current needs of the BSD tree. It sets/uses the following variables: LIB The name of the library to build. LIBDIR Target directory for libraries. LINTLIBDIR Target directory for lint libraries. LIBGRP Library group. LIBOWN Library owner. LIBMODE Library mode. LDADD Additional loader objects. MAN The manual pages to be installed (use a .1 - .9 suffix). SRCS List of source files to build the library. Suffix types .s, .c, and .f are supported. Note, .s files are preferred to .c files of the same name. (This is not the default for versions of make.) The include file includes the file named "../Makefile.inc" if it exists, as well as the include file . It has rules for building profiled objects; profiled libraries are built by default. Libraries are ranlib'd when made. diff --git a/contrib/bmake/mk/cython.mk b/contrib/bmake/mk/cython.mk index 3c28eb0a4525..8eda318e187c 100644 --- a/contrib/bmake/mk/cython.mk +++ b/contrib/bmake/mk/cython.mk @@ -1,103 +1,108 @@ # SPDX-License-Identifier: BSD-2-Clause # # RCSid: -# $Id: cython.mk,v 1.9 2024/02/17 17:26:57 sjg Exp $ +# $Id: cython.mk,v 1.10 2024/09/20 06:16:41 sjg Exp $ # -# @(#) Copyright (c) 2014, Simon J. Gerraty +# @(#) Copyright (c) 2014-2024, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # pyprefix is where python bits are # which may not be where we want to put ours (prefix) .if exists(/usr/pkg/include) -pyprefix?= /usr/pkg +pyprefix ?= /usr/pkg .endif -pyprefix?= /usr/local +pyprefix ?= /usr/local -PYTHON_VERSION?= 2.7 -PYTHON_H?= ${pyprefix}/include/python${PYTHON_VERSION}/Python.h -PYVERSION:= ${PYTHON_VERSION:C,\..*,,} +PYTHON ?= python3 +.if empty(PYTHON_VERSION) +PYTHON_VERSION != ${PYTHON} --version | sed 's,Python \([2-9]\.[0-9][0-9]*\).*,\1,' +.export PYTHON_VERSION +.endif +PYTHON_H ?= ${pyprefix}/include/python${PYTHON_VERSION}/Python.h +PYVERSION := ${PYTHON_VERSION:C,\..*,,} CFLAGS+= -I${PYTHON_H:H} # conf.host_target() is limited to uname -m rather than uname -p -_HOST_MACHINE!= uname -m +# so we cannot assume it will always match HOST_TARGET +_HOST_MACHINE != uname -m .if ${HOST_TARGET:M*${_HOST_MACHINE}} == "" -PY_HOST_TARGET:= ${HOST_TARGET:S,${_HOST_ARCH:U${uname -p:L:sh}}$,${_HOST_MACHINE},} +PY_HOST_TARGET := ${HOST_TARGET:S,${_HOST_ARCH:U${uname -p:L:sh}}$,${_HOST_MACHINE},} .endif -COMPILE.c?= ${CC} -c ${CFLAGS} -PICO?= .pico +COMPILE.c ?= ${CC} -c ${CFLAGS} +PICO ?= .pico .SUFFIXES: ${PICO} .c .c${PICO}: ${COMPILE.c} ${PICFLAG} ${CC_PIC} ${.IMPSRC} -o ${.TARGET} # this is what we build .if !empty(CYTHON_MODULE_NAME) CYTHON_MODULE = ${CYTHON_MODULE_NAME}${CYTHON_PYVERSION}.so -CYTHON_SRCS?= ${CYTHON_MODULE_NAME}.pyx +CYTHON_SRCS ?= ${CYTHON_MODULE_NAME}.pyx # this is where we save generated src -CYTHON_SAVEGENDIR?= ${.CURDIR}/gen +CYTHON_SAVEGENDIR ?= ${.CURDIR}/gen # set this empty if you don't want to handle multiple versions .if !defined(CYTHON_PYVERSION) -CYTHON_PYVERSION:= ${PYVERSION} +CYTHON_PYVERSION := ${PYVERSION} .endif -CYTHON_GENSRCS= ${CYTHON_SRCS:R:S,$,${CYTHON_PYVERSION}.c,} -SRCS+= ${CYTHON_GENSRCS} +CYTHON_GENSRCS = ${CYTHON_SRCS:R:S,$,${CYTHON_PYVERSION}.c,} +SRCS += ${CYTHON_GENSRCS} .SUFFIXES: .pyx .c .So -CYTHON?= ${pyprefix}/bin/cython +CYTHON ?= ${pyprefix}/bin/cython # if we don't have cython we can use pre-generated srcs -.if ${type ${CYTHON} 2> /dev/null || echo:L:sh:M/*} == "" +.if empty(CYTHON) || !exists(${CYTHON}) .PATH: ${CYTHON_SAVEGENDIR} .else .if !empty(CYTHON_PYVERSION) .for c in ${CYTHON_SRCS} ${c:R}${CYTHON_PYVERSION}.${c:E}: $c ln -sf ${.ALLSRC:M*pyx} ${.TARGET} .endfor .endif .pyx.c: ${CYTHON} ${CYTHON_FLAGS} -${PYVERSION} -o ${.TARGET} ${.IMPSRC} save-gen: ${CYTHON_GENSRCS} mkdir -p ${CYTHON_SAVEGENDIR} cp -p ${.ALLSRC} ${CYTHON_SAVEGENDIR} .endif ${CYTHON_MODULE}: ${SRCS:S,.c,${PICO},} ${CC} ${CC_SHARED:U-shared} -o ${.TARGET} ${.ALLSRC:M*${PICO}} ${LDADD} MODULE_BINDIR?= ${.CURDIR:H}/${PY_HOST_TARGET:U${HOST_TARGET}} build-cython-module: ${CYTHON_MODULE} install-cython-module: ${CYTHON_MODULE} test -d ${DESTDIR}${MODULE_BINDIR} || \ ${INSTALL} -d ${DESTDIR}${MODULE_BINDIR} ${INSTALL} -m 755 ${.ALLSRC} ${DESTDIR}${MODULE_BINDIR} -CLEANFILES+= *${PICO} ${CYTHON_MODULE} +CLEANFILES += *${PICO} ${CYTHON_MODULE} .endif diff --git a/contrib/bmake/mk/dirdeps-targets.mk b/contrib/bmake/mk/dirdeps-targets.mk index 9e3fb814fada..a8a547c0ce9b 100644 --- a/contrib/bmake/mk/dirdeps-targets.mk +++ b/contrib/bmake/mk/dirdeps-targets.mk @@ -1,178 +1,181 @@ # SPDX-License-Identifier: BSD-2-Clause # # RCSid: -# $Id: dirdeps-targets.mk,v 1.27 2024/02/25 19:12:13 sjg Exp $ +# $Id: dirdeps-targets.mk,v 1.28 2024/10/19 00:47:38 sjg Exp $ # # @(#) Copyright (c) 2019-2020 Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # ## # This makefile is used to set initial DIRDEPS for top-level build # targets. # # The basic idea is that we have a list of directories in # DIRDEPS_TARGETS_DIRS which are relative to SRCTOP. # When asked to make 'foo' we look for any directory named 'foo' # under DIRDEPS_TARGETS_DIRS. # We then search those dirs for any Makefile.depend* # Finally we select any that match conditions like REQUESTED_MACHINE # or TARGET_SPEC and initialize DIRDEPS accordingly. # # We will check each of the initial DIRDEPS for Makefile.dirdeps.options # and include any found. # This makes it feasible to tweak options like MK_DIRDEPS_CACHE # for a specific target. # # If MK_STATIC_DIRDEPS_CACHE is defined we will check if the # initial DIRDEPS has a static cache (Makefile.dirdeps.cache). # This only makes sense for seriously expensive targets. # .if ${.MAKE.LEVEL} == 0 # pickup customizations .-include # this is what we are here for .MAIN: dirdeps # for DIRDEPS_BUILD this is how we prime the pump # include . to allow any directory to work as a target DIRDEPS_TARGETS_DIRS ?= targets targets/pseudo # these prefixes can modify how we behave # they need to be stripped when looking for target dirs DIRDEPS_TARGETS_PREFIX_LIST ?= pkg- build- # some .TARGETS need filtering DIRDEPS_TARGETS_FILTER += Nall # matching target dirs if any tdirs := ${.TARGETS:${DIRDEPS_TARGETS_FILTER:ts:}:${DIRDEPS_TARGETS_PREFIX_LIST:@p@S,^$p,,@:ts:}:@t@${DIRDEPS_TARGETS_DIRS:@d@$d/$t@}@:@d@${exists(${SRCTOP}/$d):?$d:}@} .if !empty(DEBUG_DIRDEPS_TARGETS) .info tdirs=${tdirs} .endif .if !empty(tdirs) # some things we know we want to ignore DIRDEPS_TARGETS_SKIP_LIST += \ *~ \ *.bak \ *.inc \ *.old \ *.options \ *.orig \ *.rej \ # the list of MACHINEs we consider DIRDEPS_TARGETS_MACHINE_LIST += \ ${ALL_MACHINE_LIST:U} \ ${PSEUDO_MACHINE_LIST:Ucommon host host32} \ ${TARGET_MACHINE_LIST} DIRDEPS_TARGETS_MACHINE_LIST := ${DIRDEPS_TARGETS_MACHINE_LIST:O:u} # raw Makefile.depend* list tdeps != 'cd' ${SRCTOP} && 'ls' -1 ${tdirs:O:u:@d@$d/${.MAKE.DEPENDFILE_PREFIX}*@:S,^./,,} 2> /dev/null; echo .if ${DEBUG_DIRDEPS_TARGETS:U:Mdep*} != "" .info tdeps=${tdeps} .endif # remove things we know we don't want tdeps := ${tdeps:${DIRDEPS_TARGETS_SKIP_LIST:${M_ListToSkip}}} .if ${DEBUG_DIRDEPS_TARGETS:U:Mdep*} != "" .info tdeps=${tdeps} .endif # plain entries (no qualifiers) these apply to any TARGET_SPEC ptdeps := ${tdeps:M*${.MAKE.DEPENDFILE_PREFIX}:S,/${.MAKE.DEPENDFILE_PREFIX},,} # MACHINE qualified entries mqtdeps := ${DIRDEPS_TARGETS_MACHINE_LIST:@m@${tdeps:M*.$m}@:S,/${.MAKE.DEPENDFILE_PREFIX},,} tqtdeps = .if ${TARGET_SPEC_VARS:[#]} > 1 # TARGET_SPEC qualified entries .if !empty(TARGET_SPEC_LIST) # we have a list of valid TARGET_SPECS; use it tqtdeps := ${TARGET_SPEC_LIST:U:O:u:@t@${tdeps:M*.$t}@:S,/${.MAKE.DEPENDFILE_PREFIX},,} .else # do we have a list of valid tuple members for at least # the last tupple element? if so match on that TARGET_SPEC_LAST_LIST ?= ${${TARGET_SPEC_VARS:[-1]}_LIST} .if !empty(TARGET_SPEC_LAST_LIST) tqtdeps := ${TARGET_SPEC_LAST_LIST:U:O:u:@t@${tdeps:M*,$t}@:S,/${.MAKE.DEPENDFILE_PREFIX},,} .else # this is sub-optimal match MACHINE, tqtdeps := ${DIRDEPS_TARGETS_MACHINE_LIST:@m@${tdeps:M*.$m,*}@:S,/${.MAKE.DEPENDFILE_PREFIX},,} .endif .endif .endif # now work out what we want in DIRDEPS DIRDEPS = ${ptdeps} .if empty(REQUESTED_MACHINE) # we want them all just as found DIRDEPS += ${mqtdeps} ${tqtdeps} .else # we only want those that match REQUESTED_MACHINE/REQUESTED_TARGET_SPEC # or REQUESTED_TARGET_SPEC (TARGET_SPEC) DIRDEPS += \ ${mqtdeps:M*.${REQUESTED_MACHINE}} \ ${tqtdeps:M*.${REQUESTED_TARGET_SPEC:U${TARGET_SPEC}}} \ .endif # clean up DIRDEPS := ${DIRDEPS:O:u} .if !empty(DEBUG_DIRDEPS_TARGETS) .for x in tdeps ptdeps mqtdeps tqtdeps DIRDEPS .info $x=${$x} .endfor .endif .endif # if we got DIRDEPS get to work .if !empty(DIRDEPS) DIRDEPS.dirs := ${DIRDEPS:S,^,${SRCTOP}/,:@d@${exists($d):?$d:${d:R}}@} # some targets want to tweak options we might want to process now .for m in ${DIRDEPS.dirs:S,$,/Makefile.dirdeps.options,} .-include <$m> .endfor .if defined(MK_STATIC_DIRDEPS_CACHE) # some targets are very expensive to compute dirdeps for # so we may have a static cache .for c in ${DIRDEPS.dirs:S,$,/Makefile.dirdeps.cache,} .if exists($c) STATIC_DIRDEPS_CACHE ?= $c .if ${MK_STATIC_DIRDEPS_CACHE} == "yes" DIRDEPS_CACHE ?= $c MK_DIRDEPS_CACHE = yes .endif .endif .endfor .if defined(STATIC_DIRDEPS_CACHE) .export STATIC_DIRDEPS_CACHE +.if !empty(DEBUG_DIRDEPS_TARGETS) +.info STATIC_DIRDEPS_CACHE=${STATIC_DIRDEPS_CACHE:S,${SRCTOP}/,,} +.endif .endif .endif # allow a top-level makefile to do other stuff # before including dirdeps.mk .if ${MK_DIRDEPS_TARGETS_INCLUDE_DIRDEPS:Uyes} == "yes" .include .endif DIRDEPS_TARGETS_SKIP += all clean* destroy* .for t in ${.TARGETS:${DIRDEPS_TARGETS_SKIP:${M_ListToSkip}}} $t: dirdeps .endfor .endif .endif diff --git a/contrib/bmake/mk/dirdeps.mk b/contrib/bmake/mk/dirdeps.mk index 2a9d2ac92102..b281d15ec346 100644 --- a/contrib/bmake/mk/dirdeps.mk +++ b/contrib/bmake/mk/dirdeps.mk @@ -1,1014 +1,1012 @@ -# $Id: dirdeps.mk,v 1.170 2024/06/24 02:21:00 sjg Exp $ +# $Id: dirdeps.mk,v 1.175 2025/01/05 01:16:19 sjg Exp $ # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2010-2023, Simon J. Gerraty # Copyright (c) 2010-2018, Juniper Networks, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Much of the complexity here is for supporting cross-building. # If a tree does not support that, simply using plain Makefile.depend # should provide sufficient clue. # Otherwise the recommendation is to use Makefile.depend.${MACHINE} # as expected below. # Note: this file gets multiply included. # This is what we do with DIRDEPS # DIRDEPS: # This is a list of directories - relative to SRCTOP, it is # normally only of interest to .MAKE.LEVEL 0. # In some cases the entry may be qualified with a . # or . suffix (see TARGET_SPEC_VARS below), # for example to force building something for the pseudo # machines "host" or "common" regardless of current ${MACHINE}. # # All unqualified entries end up being qualified with .${TARGET_SPEC} # and partially qualified (if TARGET_SPEC_VARS has multiple # entries) are also expanded to a full .. # The _DIRDEP_USE target uses the suffix to set TARGET_SPEC # correctly when visiting each entry. # # The fully qualified directory entries are used to construct a # dependency graph that will drive the build later. # # Also, for each fully qualified directory target, we will search # using ${.MAKE.DEPENDFILE_PREFERENCE} to find additional # dependencies. We use Makefile.depend (default value for # .MAKE.DEPENDFILE_PREFIX) to refer to these makefiles to # distinguish them from others. # # Before each Makefile.depend file is read, we set # DEP_RELDIR to be the RELDIR (path relative to SRCTOP) for # its directory, and DEP_MACHINE etc according to the . # represented by the suffix of the corresponding target. # # Since each Makefile.depend file includes dirdeps.mk, this # processing is recursive and results in .MAKE.LEVEL 0 learning the # dependencies of the tree wrt the initial directory (_DEP_RELDIR). # # NOTE: given the extent of processing that DIRDEPS undergoes it # is important that any variables in entries use :U to guard # against surprises when undefined. # # TARGET_SPEC_VARS # The default value is just MACHINE, and for most environments # this is sufficient. The _DIRDEP_USE target actually sets # both MACHINE and TARGET_SPEC to the suffix of the current # target so that in the general case TARGET_SPEC can be ignored. # # If more than MACHINE is needed then sys.mk needs to decompose # TARGET_SPEC and set the relevant variables accordingly. # It is important that MACHINE be included in and actually be # the first member of TARGET_SPEC_VARS. This allows other # variables to be considered optional, and some of the treatment # below relies on MACHINE being the first entry. # Note: TARGET_SPEC cannot contain any '.'s so the target # triple used by compiler folk won't work (directly anyway). # # For example: # # # Always list MACHINE first, # # other variables might be optional. # TARGET_SPEC_VARS = MACHINE TARGET_OS # .if ${TARGET_SPEC:Uno:M*,*} != "" # _tspec := ${TARGET_SPEC:S/,/ /g} # MACHINE := ${_tspec:[1]} # TARGET_OS := ${_tspec:[2]} # # etc. # # We need to stop that TARGET_SPEC affecting any submakes # # and deal with MACHINE=${TARGET_SPEC} in the environment. # TARGET_SPEC = # # export but do not track # .export-env TARGET_SPEC # .export ${TARGET_SPEC_VARS} # .for v in ${TARGET_SPEC_VARS:O:u} # .if empty($v) # .undef $v # .endif # .endfor # .endif # # make sure we know what TARGET_SPEC is # # as we may need it to find Makefile.depend* # TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,} # # The following variables can influence the initial DIRDEPS # computation with regard to the TARGET_SPECs that will be # built. # Most should also be considered by init.mk # # ONLY_TARGET_SPEC_LIST # Defines a list of TARGET_SPECs for which the current # directory can be built. # If ALL_MACHINES is defined, we build for all the # TARGET_SPECs listed. # # ONLY_MACHINE_LIST # As for ONLY_TARGET_SPEC_LIST but only specifies # MACHINEs. # # NOT_TARGET_SPEC_LIST # A list of TARGET_SPECs for which the current # directory should not be built. # # NOT_MACHINE_LIST # A list of MACHINEs the current directory should not be # built for. # # DIRDEPS_EXPORT_VARS (DEP_EXPORT_VARS) # It is discouraged, but sometimes necessary for a # Makefile.depend file to influence the environment. # Doing this correctly (especially if using DIRDEPS_CACHE) is # tricky so a Makefile.depend file can set DIRDEPS_EXPORT_VARS # and dirdeps.mk will do the deed: # # MK_UEFI = yes # DIRDEPS_EXPORT_VARS = MK_UEFI # # _build_xtra_dirs # local.dirdeps.mk can add targets to this variable. # They will be hooked into the build, but independent of # any other DIRDEP. # # This allows for adding TESTS to the build, such that the build -# if any test fails, but without the risk of introducing +# will fail if any test fails, but without the risk of introducing # circular dependencies. now_utc ?= ${%s:L:localtime} .if !defined(start_utc) start_utc := ${now_utc} .endif .if !target(bootstrap) && (make(bootstrap) || \ make(bootstrap-this) || \ make(bootstrap-recurse) || \ make(bootstrap-empty)) # disable most of below .MAKE.LEVEL = 1 .endif # touch this at your peril _DIRDEP_USE_LEVEL?= 0 .if ${.MAKE.LEVEL} == ${_DIRDEP_USE_LEVEL} # only the first instance is interested in all this # the first time we are included the _DIRDEP_USE target will not be defined # we can use this as a clue to do initialization and other one time things. .if !target(_DIRDEP_USE) # do some setup we only need once _CURDIR ?= ${.CURDIR} _OBJDIR ?= ${.OBJDIR} .if ${MAKEFILE:T} == ${.PARSEFILE} && empty(DIRDEPS) && ${.TARGETS:Uall:M*[/.]*} != "" # This little trick let's us do # # mk -f dirdeps.mk some/dir.${TARGET_SPEC} # all: ${.TARGETS:Nall}: all DIRDEPS := ${.TARGETS:M*[/.]*} # so that -DNO_DIRDEPS works DEP_RELDIR := ${DIRDEPS:[1]:R} # this will become DEP_MACHINE below TARGET_MACHINE := ${DIRDEPS:[1]:E:C/,.*//} .if ${TARGET_MACHINE:N*/*} == "" TARGET_MACHINE := ${MACHINE} .endif # disable DIRDEPS_CACHE as it does not like this trick MK_DIRDEPS_CACHE = no # incase anyone needs to know _dirdeps_cmdline: .endif # make sure we get the behavior we expect .MAKE.SAVE_DOLLARS = no # make sure these are empty to start with _DEP_TARGET_SPEC = # If TARGET_SPEC_VARS is other than just MACHINE # it should be set by sys.mk or similar by now. # TARGET_SPEC must not contain any '.'s. TARGET_SPEC_VARS ?= MACHINE # we allow for this to be a subset TARGET_SPEC_VARS.host ?= MACHINE TARGET_SPEC_VARS.host32 = ${TARGET_SPEC_VARS.host} # this is what we started with TARGET_SPEC = ${TARGET_SPEC_VARS:@v@${$v:U}@:ts,} # this is what we mostly use below DEP_TARGET_SPEC_VARS = ${TARGET_SPEC_VARS.${DEP_MACHINE}:U${TARGET_SPEC_VARS}} DEP_TARGET_SPEC = ${DEP_TARGET_SPEC_VARS:S,^,DEP_,:@v@${$v:U}@:ts,} # make sure we have defaults .for v in ${DEP_TARGET_SPEC_VARS} DEP_$v ?= ${$v} .endfor .if ${TARGET_SPEC_VARS:[#]} > 1 # Ok, this gets more complex (putting it mildly). # In order to stay sane, we need to ensure that all the build_dirs # we compute below are fully qualified wrt DEP_TARGET_SPEC. # The makefiles may only partially specify (eg. MACHINE only), # so we need to construct a set of modifiers to fill in the gaps. _tspec_x := ${TARGET_SPEC_VARS:${M_RANGE:Urange}} # this handles unqualified entries M_dep_qual_fixes = C;(/[^/.,]+)$$;\1.$${DEP_TARGET_SPEC}; # there needs to be at least one item missing for these to make sense .for i in ${_tspec_x:[2..-1]} _tspec_m$i := ${TARGET_SPEC_VARS:[2..$i]:@w@[^,]+@:ts,} _tspec_a$i := ,${TARGET_SPEC_VARS:[$i..-1]:@v@$$$${DEP_$v}@:ts,} M_dep_qual_fixes += C;(\.${_tspec_m$i})$$;\1${_tspec_a$i}; .endfor TARGET_SPEC_VARSr := ${TARGET_SPEC_VARS:[-1..1]} .if ${TARGET_SPEC_VARS.host} == ${TARGET_SPEC_VARS} M_dep_qual_fixes.host = ${M_dep_qual_fixes} .elif ${TARGET_SPEC_VARS.host:[#]} > 1 _htspec_x := ${TARGET_SPEC_VARS.host:${M_RANGE:Urange}} # this handles unqualified entries M_dep_qual_fixes.host = C;(/[^/.,]+)$$;\1.$${DEP_TARGET_SPEC}; # there needs to be at least one item missing for these to make sense .for i in ${_htspec_x:[2..-1]} _htspec_m$i := ${TARGET_SPEC_VARS.host:[2..$i]:@w@[^,]+@:ts,} _htspec_a$i := ,${TARGET_SPEC_VARS.host:[$i..-1]:@v@$$$${DEP_$v}@:ts,} M_dep_qual_fixes.host += C;(\.${_htspec_m$i})$$;\1${_htspec_a$i}; .endfor .else M_dep_qual_fixes.host = U .endif .else # A harmless? default. M_dep_qual_fixes = U .endif M_dep_qual_fixes.host ?= ${M_dep_qual_fixes} M_dep_qual_fixes.host32 = ${M_dep_qual_fixes.host} .if !defined(.MAKE.DEPENDFILE_PREFERENCE) # .MAKE.DEPENDFILE_PREFERENCE makes the logic below neater? # you really want this set by sys.mk or similar .MAKE.DEPENDFILE_PREFERENCE = ${_CURDIR}/${.MAKE.DEPENDFILE:T} .if ${.MAKE.DEPENDFILE:E} == "${TARGET_SPEC}" .if ${TARGET_SPEC} != ${MACHINE} .MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R}.$${MACHINE} .endif .MAKE.DEPENDFILE_PREFERENCE += ${_CURDIR}/${.MAKE.DEPENDFILE:T:R} .endif .endif _default_dependfile := ${.MAKE.DEPENDFILE_PREFERENCE:[1]:T} _machine_dependfiles := ${.MAKE.DEPENDFILE_PREFERENCE:T:M*${MACHINE}*} # for machine specific dependfiles we require ${MACHINE} to be at the end # also for the sake of sanity we require a common prefix .if !defined(.MAKE.DEPENDFILE_PREFIX) # knowing .MAKE.DEPENDFILE_PREFIX helps .if !empty(_machine_dependfiles) .MAKE.DEPENDFILE_PREFIX := ${_machine_dependfiles:[1]:T:R} .else .MAKE.DEPENDFILE_PREFIX := ${_default_dependfile:T} .endif .endif # turn a list into a set of :N modifiers # NskipFoo = ${Foo:${M_ListToSkip}} M_ListToSkip ?= O:u:S,^,N,:ts: # this is how we identify non-machine specific dependfiles N_notmachine := ${.MAKE.DEPENDFILE_PREFERENCE:E:N*${MACHINE}*:${M_ListToSkip}} # this gets reset for each dirdep we check DEP_RELDIR ?= ${RELDIR} # remember the initial value of DEP_RELDIR - we test for it below. _DEP_RELDIR := ${DEP_RELDIR} # this can cause lots of output! # set to a set of glob expressions that might match RELDIR DEBUG_DIRDEPS ?= no # make sure this target exists dirdeps: beforedirdeps .WAIT beforedirdeps: .endif # !target(_DIRDEP_USE) .if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.${DEP_MACHINE}:L:M$x}@} != "" _debug_reldir = 1 .else _debug_reldir = 0 .endif .if ${DEBUG_DIRDEPS:@x@${DEP_RELDIR:M$x}${${DEP_RELDIR}.depend depend:L:M$x}@} != "" _debug_search = 1 .else _debug_search = 0 .endif # First off, we want to know what ${MACHINE} to build for. # This can be complicated if we are using a mixture of ${MACHINE} specific # and non-specific Makefile.depend* # if we were included recursively _DEP_TARGET_SPEC should be valid. .if empty(_DEP_TARGET_SPEC) DEP_MACHINE = ${TARGET_MACHINE:U${MACHINE}} _DEP_TARGET_SPEC := ${DEP_TARGET_SPEC} .if ${.INCLUDEDFROMFILE:U:M${.MAKE.DEPENDFILE_PREFIX}*} != "" # record that we've read dependfile for this _dirdeps_checked.${_CURDIR}.${TARGET_SPEC}: .endif .endif # by now _DEP_TARGET_SPEC should be set, parse it. .if ${TARGET_SPEC_VARS:[#]} > 1 # we need to parse DEP_MACHINE may or may not contain more info _tspec := ${_DEP_TARGET_SPEC:S/,/ /g} .for i in ${_tspec_x} DEP_${TARGET_SPEC_VARS:[$i]} := ${_tspec:[$i]} .endfor .for v in ${DEP_TARGET_SPEC_VARS:O:u} .if empty(DEP_$v) .undef DEP_$v .endif .endfor .else DEP_MACHINE := ${_DEP_TARGET_SPEC} .endif # reset each time through _build_all_dirs = _build_xtra_dirs = # DIRDEPS_CACHE can be very handy for debugging. # Also if repeatedly building the same target, # we can avoid the overhead of re-computing the tree dependencies. MK_DIRDEPS_CACHE ?= no BUILD_DIRDEPS_CACHE ?= no BUILD_DIRDEPS ?= yes .if ${MK_DIRDEPS_CACHE} == "yes" # this is where we will cache all our work DIRDEPS_CACHE ?= ${_OBJDIR:tA}/dirdeps.cache${_TARGETS:U${.TARGETS}:Nall:O:u:ts-:S,/,_,g:S,^,.,:N.} .endif # sanity check: Makefile.depend.options should *not* include us .if ${.INCLUDEDFROMFILE:U:M${.MAKE.DEPENDFILE_PREFIX}.options} != "" .error ${DEP_RELDIR}/${.MAKE.DEPENDFILE_PREFIX}.options: should include dirdeps-options.mk .endif # pickup customizations # as below you can use !target(_DIRDEP_USE) to protect things # which should only be done once. .-include .if !target(_DIRDEP_USE) # things we skip for host tools SKIP_HOSTDIR ?= NSkipHostDir = ${SKIP_HOSTDIR:N*.host*:S,$,.host*,:N.host*:S,^,${SRCTOP}/,:${M_ListToSkip}} # things we always skip # SKIP_DIRDEPS allows for adding entries on command line. SKIP_DIR += .host *.WAIT ${SKIP_DIRDEPS} SKIP_DIR.host += ${SKIP_HOSTDIR} DEP_SKIP_DIR = ${SKIP_DIR} \ ${SKIP_DIR.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${SKIP_DIR.${DEP_$v}:U}@} \ ${SKIP_DIRDEPS.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${SKIP_DIRDEPS.${DEP_$v}:U}@} NSkipDir = ${DEP_SKIP_DIR:${M_ListToSkip}} .if defined(NODIRDEPS) || defined(WITHOUT_DIRDEPS) NO_DIRDEPS = .elif defined(WITHOUT_DIRDEPS_BELOW) NO_DIRDEPS_BELOW = .endif .if defined(NO_DIRDEPS) # confine ourselves to the original dir and below. DIRDEPS_FILTER += M${_DEP_RELDIR}* .elif defined(NO_DIRDEPS_BELOW) DIRDEPS_FILTER += M${_DEP_RELDIR} .endif # this is what we run below DIRDEP_MAKE ?= ${.MAKE} DIRDEP_DIR ?= ${.TARGET:R} # we normally want the default target DIRDEP_TARGETS ?= # if you want us to report load averages during build # DIRDEP_USE_PRELUDE += ${DIRDEP_LOADAVG_REPORT}; DIRDEP_LOADAVG_CMD ?= ${UPTIME:Uuptime} | sed 's,.*\(load\),\1,' DIRDEP_LOADAVG_LAST = 0 # yes the expression here is a bit complicated, # the trick is to only eval ${DIRDEP_LOADAVG_LAST::=${now_utc}} # when we want to report. # Note: expr(1) will exit 1 if the expression evaluates to 0 # hence the || true DIRDEP_LOADAVG_REPORT = \ test -z "${"${expr ${now_utc} - ${DIRDEP_LOADAVG_INTERVAL:U60} - ${DIRDEP_LOADAVG_LAST} || true:L:sh:N-*}":?yes${DIRDEP_LOADAVG_LAST::=${now_utc}}:}" || \ echo "${TRACER}`${DIRDEP_LOADAVG_CMD}`" # we suppress SUBDIR when visiting the leaves # we assume sys.mk will set MACHINE_ARCH # you can add extras to DIRDEP_USE_ENV # if there is no makefile in the target directory, we skip it. _DIRDEP_USE: .USE .MAKE @for m in ${.MAKE.MAKEFILE_PREFERENCE}; do \ test -s ${.TARGET:R}/$$m || continue; \ echo "${TRACER}Checking ${.TARGET:S,^${SRCTOP}/,,} for ${.TARGET:E} ..."; \ ${DIRDEP_USE_PRELUDE} \ MACHINE_ARCH= NO_SUBDIR=1 ${DIRDEP_USE_ENV} \ TARGET_SPEC=${.TARGET:E} \ MACHINE=${.TARGET:E} \ ${DIRDEP_MAKE} -C ${DIRDEP_DIR} ${DIRDEP_TARGETS} || exit 1; \ + ${DIRDEP_USE_EPILOGUE} \ break; \ done .ifdef ALL_MACHINES .if empty(ONLY_TARGET_SPEC_LIST) && empty(ONLY_MACHINE_LIST) # we start with everything _machine_list != echo; 'ls' -1 ${_CURDIR}/${.MAKE.DEPENDFILE_PREFIX}* 2> /dev/null # some things we know we want to ignore DIRDEPS_TARGETS_SKIP_LIST += \ *~ \ *.bak \ *.inc \ *.old \ *.options \ *.orig \ *.rej \ # first trim things we know we want to skip # and provide canonical form _machine_list := ${_machine_list:${DIRDEPS_TARGETS_SKIP_LIST:${M_ListToSkip}}:T:E} # cater for local complexities # local.dirdeps.mk can set # DIRDEPS_ALL_MACHINES_FILTER and # DIRDEPS_ALL_MACHINES_FILTER_XTRAS for final tweaks .if !empty(ALL_TARGET_SPEC_LIST) .if ${_debug_reldir} .info ALL_TARGET_SPEC_LIST=${ALL_TARGET_SPEC_LIST} .endif DIRDEPS_ALL_MACHINES_FILTER += \ @x@$${ALL_TARGET_SPEC_LIST:@s@$${x:M$$s}@}@ .elif !empty(ALL_MACHINE_LIST) .if ${_debug_reldir} .info ALL_MACHINE_LIST=${ALL_MACHINE_LIST} .endif .if ${TARGET_SPEC_VARS:[#]} > 1 # the space below can result in extraneous ':' DIRDEPS_ALL_MACHINES_FILTER += \ @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m,*} $${x:M$$m}@}@ .else DIRDEPS_ALL_MACHINES_FILTER += \ @x@$${ALL_MACHINE_LIST:@m@$${x:M$$m}@}@ .endif .endif # add local XTRAS - default to something benign DIRDEPS_ALL_MACHINES_FILTER += \ ${DIRDEPS_ALL_MACHINES_FILTER_XTRAS:UNbak} .if ${_debug_reldir} .info _machine_list=${_machine_list} .info DIRDEPS_ALL_MACHINES_FILTER=${DIRDEPS_ALL_MACHINES_FILTER} .endif _only_machines := ${_machine_list:${DIRDEPS_ALL_MACHINES_FILTER:ts:}:S,:, ,g} .else _only_machines := ${ONLY_TARGET_SPEC_LIST:U} ${ONLY_MACHINE_LIST:U} .endif .if empty(_only_machines) # we must be boot-strapping _only_machines := ${TARGET_MACHINE:U${ALL_TARGET_SPEC_LIST:U${ALL_MACHINE_LIST:U${DEP_MACHINE}}}} .endif # cleanup the result _only_machines := ${_only_machines:O:u} .if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: ALL_MACHINES _only_machines=${_only_machines} .endif .else # ! ALL_MACHINES # if ONLY_TARGET_SPEC_LIST or ONLY_MACHINE_LIST is set, we are limited to that. # Note that ONLY_TARGET_SPEC_LIST should be fully qualified. # if TARGET_MACHINE is set - it is really the same as ONLY_MACHINE_LIST # otherwise DEP_MACHINE is it - so DEP_MACHINE will match. _only_machines := ${ONLY_TARGET_SPEC_LIST:U:M${DEP_MACHINE},*} .if empty(_only_machines) _only_machines := ${ONLY_MACHINE_LIST:U${TARGET_MACHINE:U${DEP_MACHINE}}:M${DEP_MACHINE}} .endif .endif .if !empty(NOT_MACHINE_LIST) _only_machines := ${_only_machines:${NOT_MACHINE_LIST:${M_ListToSkip}}} .endif .if !empty(NOT_TARGET_SPEC_LIST) # we must first qualify _dm := ${DEP_MACHINE} _only_machines := ${_only_machines:M*,*} ${_only_machines:N*,*:@DEP_MACHINE@${DEP_TARGET_SPEC}@:S,^,.,:${M_dep_qual_fixes:ts:}:O:u:S,^.,,} DEP_MACHINE := ${_dm} _only_machines := ${_only_machines:${NOT_TARGET_SPEC_LIST:${M_ListToSkip}}} .endif # clean up _only_machines := ${_only_machines:O:u} .if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _only_machines=${_only_machines} .endif # make sure we have a starting place? DIRDEPS ?= ${RELDIR} .endif # target .if !defined(NO_DIRDEPS) && !defined(NO_DIRDEPS_BELOW) .if ${MK_DIRDEPS_CACHE} == "yes" # just ensure this exists build-dirdeps: M_oneperline = @x@\\${.newline} $$x@ .if ${BUILD_DIRDEPS_CACHE} == "no" .if !target(dirdeps-cached) # we do this via sub-make BUILD_DIRDEPS = no # ignore anything but these .MAKE.META.IGNORE_FILTER = M*/${.MAKE.DEPENDFILE_PREFIX}* dirdeps: dirdeps-cached dirdeps-cached: ${DIRDEPS_CACHE} .MAKE @echo "${TRACER}Using ${DIRDEPS_CACHE}" - @MAKELEVEL=${.MAKE.LEVEL} \ + @${DIRDEPS_CACHED_ENV} MAKELEVEL=${.MAKE.LEVEL} \ TARGET_SPEC=${TARGET_SPEC} \ ${TARGET_SPEC_VARS:@v@$v=${$v}@} \ ${.MAKE} -C ${_CURDIR} -f ${DIRDEPS_CACHE} \ dirdeps MK_DIRDEPS_CACHE=no BUILD_DIRDEPS=no # leaf makefiles rarely work for building DIRDEPS_CACHE .if ${RELDIR} != "." BUILD_DIRDEPS_MAKEFILE ?= -f dirdeps.mk .endif # these should generally do BUILD_DIRDEPS_MAKEFILE ?= BUILD_DIRDEPS_OVERRIDES ?= BUILD_DIRDEPS_TARGETS ?= ${.TARGETS} .if ${DIRDEPS_CACHE} != ${STATIC_DIRDEPS_CACHE:Uno} && ${DIRDEPS_CACHE:M${SRCTOP}/*} == "" # export this for dirdeps-cache-update.mk DYNAMIC_DIRDEPS_CACHE := ${DIRDEPS_CACHE} .export DYNAMIC_DIRDEPS_CACHE # we need the .meta file to ensure we update if # any of the Makefile.depend* changed. # We do not want to compare the command line though. ${DIRDEPS_CACHE}: .META .NOMETA_CMP +@{ echo '# Autogenerated - do NOT edit!'; echo; \ echo 'BUILD_DIRDEPS=no'; echo; \ echo '.include '; echo; \ } > ${.TARGET}.new +@MAKELEVEL=${.MAKE.LEVEL} DIRDEPS_CACHE=${DIRDEPS_CACHE} \ DIRDEPS="${DIRDEPS}" \ TARGET_SPEC=${TARGET_SPEC} \ MAKEFLAGS= ${DIRDEP_CACHE_MAKE:U${.MAKE}} -C ${_CURDIR} \ ${BUILD_DIRDEPS_MAKEFILE} \ ${BUILD_DIRDEPS_TARGETS} \ ${BUILD_DIRDEPS_OVERRIDES} \ BUILD_DIRDEPS_CACHE=yes \ .MAKE.DEPENDFILE=.none \ ${"${DEBUG_DIRDEPS:Nno}":?DEBUG_DIRDEPS='${DEBUG_DIRDEPS}':} \ - ${.MAKEFLAGS:tW:S,-D ,-D,g:tw:M*WITH*} \ - ${.MAKEFLAGS:tW:S,-d ,-d,g:tw:M-d*} \ + ${.MAKEFLAGS:S,-D ,-D,gW:M*WITH*} \ + ${.MAKEFLAGS:S,-d ,-d,gW:M-d*} \ 3>&1 1>&2 | sed 's,${SRCTOP},_{SRCTOP},g;s,_{SRCTOP}/_{SRCTOP},_{SRCTOP},g;s,_{,$${,g' >> ${.TARGET}.new && \ - mv ${.TARGET}.new ${.TARGET} + { ${BUILD_DIRDEPS_EPILOGUE} mv ${.TARGET}.new ${.TARGET}; } .endif .endif .elif !target(_count_dirdeps) # we want to capture the dirdeps count in the cache .END: _count_dirdeps _count_dirdeps: .NOMETA @{ echo; echo '.info $${.newline}$${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]} ${DIRDEP_INFO_XTRAS}'; } >&3 .endif .elif !make(dirdeps) && !target(_count_dirdeps) beforedirdeps: _count_dirdeps _count_dirdeps: .NOMETA @echo "${TRACER}Makefiles read: total=${.MAKE.MAKEFILES:[#]} depend=${.MAKE.MAKEFILES:M*depend*:[#]} dirdeps=${.ALLTARGETS:M${SRCTOP}*:O:u:[#]} ${DIRDEP_INFO_XTRAS} seconds=`expr ${now_utc} - ${start_utc}`" .endif .endif .if ${BUILD_DIRDEPS} == "yes" # the rest is done repeatedly for every Makefile.depend we read. # if we are anything but the original dir we care only about the # machine type we were included for.. .if ${DEP_RELDIR} == "." _this_dir := ${SRCTOP} .else _this_dir := ${SRCTOP}/${DEP_RELDIR} .endif # on rare occasions, there can be a need for extra help _dep_hack := ${_this_dir}/${.MAKE.DEPENDFILE_PREFIX}.inc .-include <${_dep_hack}> .-include <${_dep_hack:R}.options> .if ${DEP_RELDIR} != ${_DEP_RELDIR} || ${DEP_TARGET_SPEC} != ${TARGET_SPEC} # this should be all _machines := ${DEP_MACHINE} .else # this is the machine list we actually use below _machines := ${_only_machines} .if defined(HOSTPROG) || ${DEP_MACHINE:Nhost*} == "" # we need to build this guy's dependencies for host as well. .if ${DEP_MACHINE:Nhost*} == "" _machines += ${DEP_MACHINE} .else _machines += host .endif .endif _machines := ${_machines:O:u} .endif .if ${DEP_TARGET_SPEC_VARS:[#]} > 1 # we need to tweak _machines _dm := ${DEP_MACHINE} # apply the same filtering that we do when qualifying DIRDEPS. # M_dep_qual_fixes expects .${MACHINE}* so add (and remove) '.' # Again we expect that any already qualified machines are fully qualified. _machines := ${_machines:M*,*} ${_machines:N*,*:@DEP_MACHINE@${DEP_TARGET_SPEC}@:S,^,.,:S,^.,,} DEP_MACHINE := ${_dm} _machines := ${_machines:${M_dep_qual_fixes.${DEP_MACHINE}:U${M_dep_qual_fixes}:ts:}:O:u} .endif # reset each time through _build_dirs = .if ${DEP_RELDIR} == ${_DEP_RELDIR} && ${_CURDIR} != ${SRCTOP} # pickup other machines for this dir if necessary _build_dirs += ${_machines:@m@${_CURDIR}.$m@} .endif .if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: nDIRDEPS=${DIRDEPS:[#]} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: DIRDEPS=${DIRDEPS:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: _machines='${_machines}' .endif .if !empty(DIRDEPS) # these we reset each time through as they can depend on DEP_MACHINE DEP_DIRDEPS_FILTER = \ ${DIRDEPS_FILTER.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${DIRDEPS_FILTER.${DEP_$v}:U}@} \ ${DIRDEPS_FILTER:U} .if empty(DEP_DIRDEPS_FILTER) # something harmless DEP_DIRDEPS_FILTER = u .endif # this is applied after we have computed build dirs # so everything is fully qualified and starts with ${SRCTOP}/ DEP_DIRDEPS_BUILD_DIR_FILTER = \ ${DIRDEPS_BUILD_DIR_FILTER.${DEP_TARGET_SPEC}:U} \ ${TARGET_SPEC_VARS:@v@${DIRDEPS_BUILD_DIR_FILTER.${DEP_$v}:U}@} \ ${DIRDEPS_BUILD_DIR_FILTER:U} .if empty(DEP_DIRDEPS_BUILD_DIR_FILTER) # something harmless DEP_DIRDEPS_BUILD_DIR_FILTER = u .endif # this is what we start with __depdirs := ${DIRDEPS:${NSkipDir}:${DEP_DIRDEPS_FILTER:ts:}:C,//+,/,g:O:u:@d@${SRCTOP}/$d@} # some entries may be qualified with . or . # we can tell the unqualified ones easily - because they exist __unqual_depdirs := ${__depdirs:@d@${exists($d):?$d:}@} __qual_depdirs := ${__depdirs:${__unqual_depdirs:Uno:${M_ListToSkip}}} .if ${DEP_RELDIR} == ${_DEP_RELDIR} # if it was called out - we likely need it. __hostdpadd := ${DPADD:U.:M${HOST_OBJTOP}/*:S,${HOST_OBJTOP}/,,:H:${NSkipDir}:${DIRDEPS_FILTER:ts:}:S,$,.host,:N.*:@d@${SRCTOP}/$d@} \ ${DPADD:U.:M${HOST_OBJTOP32:Uno}/*:S,${HOST_OBJTOP32:Uno}/,,:H:${NSkipDir}:${DIRDEPS_FILTER:ts:}:S,$,.host32,:N.*:@d@${SRCTOP}/$d@} __qual_depdirs += ${__hostdpadd} .endif .if ${_debug_reldir} .info DEP_DIRDEPS_FILTER=${DEP_DIRDEPS_FILTER:ts:} .info DEP_DIRDEPS_BUILD_DIR_FILTER=${DEP_DIRDEPS_BUILD_DIR_FILTER:ts:} .info depdirs=${__depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .info qualified=${__qual_depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .info unqualified=${__unqual_depdirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .endif # _build_dirs is what we will feed to _DIRDEP_USE _build_dirs += \ ${__qual_depdirs:M*.host:${NSkipHostDir}:N.host} \ ${__qual_depdirs:N*.host} \ ${_machines:Mhost*:@m@${__unqual_depdirs:@d@$d.$m@}@:${NSkipHostDir}:N.host} \ ${_machines:Nhost*:@m@${__unqual_depdirs:@d@$d.$m@}@} # qualify everything now .if ${_debug_reldir} .info _build_dirs=${_build_dirs:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .endif # make sure we do not mess with qualifying "host" entries _build_dirs := ${_build_dirs:M*.host*:${M_dep_qual_fixes.host:ts:}} \ ${_build_dirs:N*.host*:${M_dep_qual_fixes:ts:}} # some filters can only be applied now _build_dirs := ${_build_dirs:${DEP_DIRDEPS_BUILD_DIR_FILTER:ts:}:O:u} .if ${_debug_reldir} .info _build_dirs=${_build_dirs:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .endif .endif # empty DIRDEPS _build_all_dirs += ${_build_dirs} ${_build_xtra_dirs} _build_all_dirs := ${_build_all_dirs:O:u} # we prefer DIRDEPS_EXPORT_VARS .if empty(DIRDEPS_EXPORT_VARS) && !empty(DEP_EXPORT_VARS) DIRDEPS_EXPORT_VARS = ${DEP_EXPORT_VARS} .endif # Normally if doing make -V something, # we do not want to waste time chasing DIRDEPS # but if we want to count the number of Makefile.depend* read, we do. .if ${.MAKEFLAGS:M-V${_V_READ_DIRDEPS:U}} == "" .if !empty(_build_all_dirs) .if ${BUILD_DIRDEPS_CACHE} == "yes" # we use _cache_script to minimize the number of times we fork the shell _cache_script = echo '\# ${DEP_RELDIR}.${DEP_TARGET_SPEC}'; # guard against _new_dirdeps being too big for a single command line _new_dirdeps := ${_build_all_dirs:@x@${target($x):?:$x}@:S,^${SRCTOP}/,,} _cache_xtra_deps := ${_build_xtra_dirs:S,^${SRCTOP}/,,} .if !empty(DIRDEPS_EXPORT_VARS) # Discouraged, but there are always exceptions. # Handle it here rather than explain how. _cache_xvars := echo; ${DIRDEPS_EXPORT_VARS:@v@echo '$v = ${$v}';@} echo '.export ${DIRDEPS_EXPORT_VARS}'; echo; _cache_script += ${_cache_xvars} .endif .else # this makes it all happen dirdeps: ${_build_all_dirs} .endif ${_build_all_dirs}: _DIRDEP_USE .if ${_debug_reldir} .info ${DEP_RELDIR}.${DEP_TARGET_SPEC}: needs: ${_build_dirs:S,^${SRCTOP}/,,:${DEBUG_DIRDEPS_LIST_FILTER:U:N/:ts:}} .endif # this builds the dependency graph .for m in ${_machines} .if ${BUILD_DIRDEPS_CACHE} == "yes" && !empty(_build_dirs) _cache_deps = _cache_script += echo; echo 'DIRDEPS.${_this_dir}.$m = \'; .endif # it would be nice to do :N${.TARGET} .if !empty(__qual_depdirs) .for q in ${__qual_depdirs:M*.host*:${M_dep_qual_fixes.host:ts:}:E:O:u:N$m} \ ${__qual_depdirs:N*.host*:${M_dep_qual_fixes:ts:}:E:O:u:N$m} .if ${_debug_reldir} || ${DEBUG_DIRDEPS:@x@${${DEP_RELDIR}.$m:L:M$x}${${DEP_RELDIR}.$q:L:M$x}@} != "" .info ${DEP_RELDIR}.$m: q=$q graph: ${_build_dirs:M*.$q:S,^${SRCTOP}/,,} .endif .if ${BUILD_DIRDEPS_CACHE} == "yes" _cache_deps += ${_build_dirs:M*.$q:S,^${SRCTOP}/,,} .else ${_this_dir}.$m: ${_build_dirs:M*.$q} .endif .endfor .endif .if ${_debug_reldir} .info ${DEP_RELDIR}.$m: graph: ${_build_dirs:M*.$m:N${_this_dir}.$m:S,^${SRCTOP}/,,} .endif .if ${BUILD_DIRDEPS_CACHE} == "yes" .if !empty(_build_dirs) _cache_deps += ${_build_dirs:M*.$m:N${_this_dir}.$m:S,^${SRCTOP}/,,} # anything in _{build,env}_xtra_dirs is hooked to dirdeps: only .if ${MAKE_VERSION} < 20240105 .if !empty(_cache_deps) .export _cache_deps _cache_script += for x in $$_cache_deps; do echo " _{SRCTOP}/$$x \\"; done; .endif .export _cache_xtra_deps _new_dirdeps x!= echo; { echo; ${_cache_script} echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \ echo; echo 'dirdeps: ${_this_dir}.$m \'; \ for x in $$_cache_xtra_deps; do echo " _{SRCTOP}/$$x \\"; done; \ echo; for x in $$_new_dirdeps; do echo "_{SRCTOP}/$$x: _DIRDEP_USE"; done; } >&3 .else # we do not have the same limits on command lines .if !empty(_cache_deps) _cache_script += for x in ${_cache_deps}; do echo " _{SRCTOP}/$$x \\"; done; .endif x!= echo; { echo; ${_cache_script} echo; echo '${_this_dir}.$m: $${DIRDEPS.${_this_dir}.$m}'; \ echo; echo 'dirdeps: ${_this_dir}.$m \'; \ for x in ${_cache_xtra_deps}; do echo " _{SRCTOP}/$$x \\"; done; \ echo; for x in ${_new_dirdeps}; do echo "_{SRCTOP}/$$x: _DIRDEP_USE"; done; } >&3 .endif .endif .else ${_this_dir}.$m: ${_build_dirs:M*.$m:N${_this_dir}.$m} .endif .endfor .endif .if !empty(DIRDEPS_EXPORT_VARS) .if ${BUILD_DIRDEPS_CACHE} == "no" .export ${DIRDEPS_EXPORT_VARS} .endif # Reset these, we are done with them for this iteration. DIRDEPS_EXPORT_VARS = DEP_EXPORT_VARS = .endif # Now find more dependencies - and recurse. .for d in ${_build_all_dirs} .if !target(_dirdeps_checked.$d) # once only _dirdeps_checked.$d: _dr := ${d:S,^${SRCTOP}/,,} .if ${_debug_search} .info checking ${_dr} .endif # Note: _build_all_dirs is fully qualifed so d:R is always the directory .if exists(${d:R}) # we pass _DEP_TARGET_SPEC to tell the next step what we want _DEP_TARGET_SPEC := ${d:E} # some makefiles may still look at this _DEP_MACHINE := ${d:E:C/,.*//} DEP_MACHINE := ${_DEP_MACHINE} # set these too in case Makefile.depend* uses them .if ${DEP_TARGET_SPEC_VARS:[#]} > 1 _dtspec := ${_DEP_TARGET_SPEC:S/,/ /g} .for i in ${_tspec_x} DEP_${DEP_TARGET_SPEC_VARS:[$i]} := ${_dtspec:[$i]} .endfor .endif # Warning: there is an assumption here that MACHINE is always # the first entry in TARGET_SPEC_VARS. # If TARGET_SPEC and MACHINE are insufficient, you have a problem. _m := ${.MAKE.DEPENDFILE_PREFERENCE:T:S;${TARGET_SPEC}$;${d:E};:C;${MACHINE}((,.+)?)$;${d:E:C/,.*//}\1;:@m@${exists(${d:R}/$m):?${d:R}/$m:}@:[1]} .if !empty(_m) # M_dep_qual_fixes isn't geared to Makefile.depend _qm := ${_m:C;(\.depend)$;\1.${d:E};:${M_dep_qual_fixes.${d:E}:U${M_dep_qual_fixes}:ts:}} -.if ${_debug_search} -.info Looking for ${_qm} -.endif # set this "just in case" # we can skip :tA since we computed the path above DEP_RELDIR := ${_m:H:S,^${SRCTOP}/,,} # and reset this DIRDEPS = -.if ${_debug_reldir} && ${_qm} != ${_m} -.info loading ${_m:S,${SRCTOP}/,,} for ${_dr} +.if ${_debug_search} || ${_debug_reldir} +.info Loading ${_m:S,${SRCTOP}/,,} for ${_dr} .endif .include <${_m}> .else # set these as if we found Makefile.depend* DEP_RELDIR := ${_dr:R} DIRDEPS = .if ${_debug_reldir} .info loading local.dirdeps-missing.mk for ${_dr} .endif .-include .endif .endif .endif .endfor .endif # -V .endif # BUILD_DIRDEPS .elif ${.MAKE.LEVEL} > 42 .error You should have stopped recursing by now. .else # we are building something DEP_RELDIR := ${RELDIR} _DEP_RELDIR := ${RELDIR} # Since we are/should be included by .MAKE.DEPENDFILE # This is a final opportunity to add/hook global rules. .-include # skip _reldir_{finish,failed} if not included from Makefile.depend* # or not in meta mode .if !defined(WITHOUT_META_STATS) && ${.INCLUDEDFROMFILE:U:M${.MAKE.DEPENDFILE_PREFIX}*} != "" && ${.MAKE.MODE:Mmeta} != "" meta_stats= meta=${empty(.MAKE.META.FILES):?0:${.MAKE.META.FILES:[#]}} \ created=${empty(.MAKE.META.CREATED):?0:${.MAKE.META.CREATED:[#]}} .if !target(_reldir_finish) .END: _reldir_finish _reldir_finish: .NOMETA @echo "${TRACER}Finished ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" .endif .if !target(_reldir_failed) .ERROR: _reldir_failed _reldir_failed: .NOMETA @echo "${TRACER}Failed ${RELDIR}.${TARGET_SPEC} seconds=$$(( ${now_utc} - ${start_utc} )) ${meta_stats}" .endif .endif # pickup local dependencies .if ${MAKE_VERSION} < 20160220 .-include <.depend> .else .dinclude <.depend> .endif .endif # bootstrapping new dependencies made easy? .if !target(bootstrap-empty) .if !target(bootstrap) && (make(bootstrap) || \ make(bootstrap-this) || \ make(bootstrap-recurse) || \ make(bootstrap-empty)) # if we are bootstrapping create the default _want = ${.CURDIR}/${.MAKE.DEPENDFILE_DEFAULT:T} .if exists(${_want}) # stop here ${.TARGETS:Mboot*}: .elif !make(bootstrap-empty) # find a Makefile.depend to use as _src _src != cd ${.CURDIR} && for m in ${.MAKE.DEPENDFILE_PREFERENCE:T:S,${MACHINE},*,}; do test -s $$m || continue; echo $$m; break; done; echo .if empty(_src) .error cannot find any of ${.MAKE.DEPENDFILE_PREFERENCE:T}${.newline}Use: bootstrap-empty .endif _src?= ${.MAKE.DEPENDFILE} .MAKE.DEPENDFILE_BOOTSTRAP_SED+= -e 's/${_src:E:C/,.*//}/${MACHINE}/g' # just create Makefile.depend* for this dir bootstrap-this: .NOTMAIN @echo Bootstrapping ${RELDIR}/${_want:T} from ${_src:T}; \ echo You need to build ${RELDIR} to correctly populate it. .if ${_src:T} != ${.MAKE.DEPENDFILE_PREFIX:T} (cd ${.CURDIR} && sed ${.MAKE.DEPENDFILE_BOOTSTRAP_SED} ${_src} > ${_want:T}) .else cp ${.CURDIR}/${_src:T} ${_want} .endif # create Makefile.depend* for this dir and its dependencies bootstrap: bootstrap-recurse bootstrap-recurse: bootstrap-this _mf := ${.PARSEFILE} bootstrap-recurse: .NOTMAIN .MAKE @cd ${SRCTOP} && \ for d in `cd ${RELDIR} && ${.MAKE} -B -f ${"${.MAKEFLAGS:M-n}":?${_src}:${.MAKE.DEPENDFILE:T}} -V DIRDEPS`; do \ test -d $$d || d=$${d%.*}; \ test -d $$d || continue; \ echo "Checking $$d for bootstrap ..."; \ (cd $$d && ${.MAKE} -f ${_mf} bootstrap-recurse); \ done .endif # create an empty Makefile.depend* to get the ball rolling. bootstrap-empty: .NOTMAIN .NOMETA @echo Creating empty ${RELDIR}/${_want:T}; \ echo You need to build ${RELDIR} to correctly populate it. @{ echo DIRDEPS=; echo ".include "; } > ${_want} .endif .endif diff --git a/contrib/bmake/mk/gendirdeps.mk b/contrib/bmake/mk/gendirdeps.mk index 5d54aa232152..53e736da3391 100644 --- a/contrib/bmake/mk/gendirdeps.mk +++ b/contrib/bmake/mk/gendirdeps.mk @@ -1,425 +1,425 @@ -# $Id: gendirdeps.mk,v 1.50 2023/11/04 16:47:34 sjg Exp $ +# $Id: gendirdeps.mk,v 1.51 2025/01/05 01:16:19 sjg Exp $ # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2011-2020, Simon J. Gerraty # Copyright (c) 2010-2018, Juniper Networks, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # This makefile [re]generates ${.MAKE.DEPENDFILE} # .include # Assumptions: # RELDIR is the relative path from ${SRCTOP} to ${_CURDIR} # (SRCTOP is ${SB}/src) # _CURDIR is the absolute version of ${.CURDIR} # _OBJDIR is the absolute version of ${.OBJDIR} # _objroot is realpath of ${_OBJTOP} without ${MACHINE} # this may be different from _OBJROOT if $SB/obj is a # symlink to another filesystem. # _objroot must be a prefix match for _objtop # If any of GENDIRDEPS_FILTER, GENDIRDEPS_FILTER_DIR_VARS # or GENDIRDEPS_FILTER_VARS are set, we use them to filter the # output from filemon(4). # Any references to variables that dirdeps.mk will set # such as DEP_MACHINE, DEP_RELDIR etc, should use that form. # Thus we want ${DEP_MACHINE} not ${MACHINE} used in DIRDEPS. # # If any manually maintained Makefile.depend files will use any # DEP_* variables in conditionals, precautions are needed to avoid # errors when Makefile.depend is read at level 1+ (ie not via # dirdeps.mk) # Using MACHINE as an example; such makefiles can do: # # DEP_MACHINE ?= ${MACHINE} # .if ${DEP_MACHINE} == "xyz" # # or: # # .if ${DEP_MACHINE:U${MACHINE}} == "xyz" # # but it might be safer to set GENDIRDEPS_FILTER_DIR_VARS and # GENDIRDEPS_FILTER_VARS via local.meta.sys.mk rather than # local.gendirdeps.mk and then: # # .if ${.MAKE.LEVEL} > 0 # .for V in ${GENDIRDEPS_FILTER_DIR_VARS:MDEP_*} \ # ${GENDIRDEPS_FILTER_VARS:MDEP_*} # $V ?= ${${V:S,DEP_,,}} # .endfor # .endif # .MAIN: all # keep this simple .MAKE.MODE = compat all: _CURDIR ?= ${.CURDIR} _OBJDIR ?= ${.OBJDIR} _OBJTOP ?= ${OBJTOP} _OBJROOT ?= ${OBJROOT:U${_OBJTOP:H}} .if ${_OBJROOT:M*/} _slash=/ .else _slash= .endif _objroot ?= ${_OBJROOT:tA}${_slash} _this = ${.PARSEDIR}/${.PARSEFILE} # remember what to make _DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T} # We do _not_ want to read our own output! .MAKE.DEPENDFILE = /dev/null # caller should have set this META_FILES ?= ${.MAKE.META.FILES} # this sometimes needs to be passed separately .if !empty(META_XTRAS) META_FILES += ${META_XTRAS:N\*.meta} .endif .if !empty(META_FILES) .if ${.MAKE.LEVEL} > 0 && !empty(GENDIRDEPS_FILTER) # so we can compare below .-include <${_DEPENDFILE}> # yes, I mean :U with no value _DIRDEPS := ${DIRDEPS:U:O:u} .endif META_FILES := ${META_FILES:T:O:u} # pickup customizations .-include # these are actually prefixes that we'll skip # they should all be absolute paths SKIP_GENDIRDEPS ?= .if !empty(SKIP_GENDIRDEPS) _skip_gendirdeps = ${EGREP:Uegrep} -v '^(${SKIP_GENDIRDEPS:O:u:ts|})' | .else _skip_gendirdeps = .endif # Below we will turn _{VAR} into ${VAR} which keeps this simple # GENDIRDEPS_FILTER_DIR_VARS is a list of dirs to be substiuted for. # GENDIRDEPS_FILTER_VARS is more general. # In each case order matters. .if !empty(GENDIRDEPS_FILTER_DIR_VARS) GENDIRDEPS_FILTER += ${GENDIRDEPS_FILTER_DIR_VARS:@v@S,${$v},_{${v}},@} .endif .if !empty(GENDIRDEPS_FILTER_VARS) GENDIRDEPS_FILTER += ${GENDIRDEPS_FILTER_VARS:@v@S,/${$v}/,/_{${v}}/,@:NS,//,*:u} .endif # this (*should* be set in meta.sys.mk) # is the script that extracts what we want. META2DEPS ?= ${.PARSEDIR}/meta2deps.sh META2DEPS := ${META2DEPS} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" && ${DEBUG_GENDIRDEPS:Uno:Mmeta2d*} != "" _time = time _sh_x = sh -x _py_d = -ddd .else _time = _sh_x = _py_d = .endif .if ${META2DEPS:E} == "py" # we can afford to do this all the time. DPDEPS ?= no META2DEPS_CMD = ${_time} ${PYTHON} ${META2DEPS} ${_py_d} .if ${DPDEPS:tl} != "no" META2DEPS_CMD += -D ${DPDEPS} .endif META2DEPS_FILTER = sed 's,^src:,${SRCTOP}/,;s,^\([^/]\),${OBJTOP}/\1,' | .elif ${META2DEPS:E} == "sh" META2DEPS_CMD = ${_time} ${_sh_x} ${META2DEPS} OBJTOP=${_OBJTOP} .else META2DEPS_CMD ?= ${META2DEPS} .endif .if ${TARGET_OBJ_SPEC:U${MACHINE}} != ${MACHINE} META2DEPS_CMD += -T ${TARGET_OBJ_SPEC} .endif META2DEPS_CMD += \ -R ${RELDIR} -H ${HOST_TARGET} \ ${M2D_OBJROOTS:O:u:@o@-O $o@} \ ${M2D_EXCLUDES:O:u:@o@-X $o@} \ M2D_OBJROOTS += ${OBJTOP} ${_OBJROOT} ${_objroot} .if defined(SB_OBJROOT) M2D_OBJROOTS += ${SB_OBJROOT} .endif .if defined(STAGE_ROOT) M2D_OBJROOTS += ${STAGE_ROOT} .endif .if ${.MAKE.DEPENDFILE_PREFERENCE:U${.MAKE.DEPENDFILE}:M*.${MACHINE}} == "" # meta2deps.py only groks objroot # so we need to give it what it expects # and tell it not to add machine qualifiers META2DEPS_ARGS += MACHINE=none .endif .if defined(SB_BACKING_SB) META2DEPS_CMD += -S ${SB_BACKING_SB}/src M2D_OBJROOTS += ${SB_BACKING_SB}/${SB_OBJPREFIX} .endif GENDIRDEPS_SEDCMDS += \ -e 's,//*$$,,;s,\.${HOST_TARGET:Uhost}$$,.host,' \ -e 's,\.${HOST_TARGET32:Uhost32}$$,.host32,' \ -e 's,\.${MACHINE}$$,,' \ -e 's:\.${TARGET_SPEC:U${MACHINE}}$$::' # we are only interested in the dirs # specifically those we read something from. # we canonicalize them to keep things simple # if we are using a split-fs sandbox, it gets a little messier. _objtop := ${_OBJTOP:tA} # some people put *.meta in META_XTRAS to make sure we get here _meta_files := ${META_FILES:N\*.meta:O:u} # assume a big list _meta_files_arg= @meta.list .if empty(_meta_files) && ${META_FILES:M\*.meta} != "" # XXX this should be considered a bad idea, # since we cannot ignore stale .meta x != cd ${_OBJDIR} && find . -name '*.meta' -print -o \( -type d ! -name . -prune \) | sed 's,^./,,' > meta.list; echo .elif ${_meta_files:[#]} > 500 .export _meta_files x != echo; for m in $$_meta_files; do echo $$m; done > meta.list # _meta_files is consuming a lot of env space # that can impact command line length, # and we do not need it any more .undef _meta_files .unexport _meta_files .else _meta_files_arg:= ${_meta_files} .endif dir_list != cd ${_OBJDIR} && \ ${META2DEPS_CMD} MACHINE=${MACHINE} \ SRCTOP=${SRCTOP} RELDIR=${RELDIR} CURDIR=${_CURDIR} \ ${META2DEPS_ARGS} \ ${_meta_files_arg} | ${META2DEPS_FILTER} ${_skip_gendirdeps} \ sed ${GENDIRDEPS_SEDCMDS} .if ${dir_list:M*ERROR\:*} != "" -.warning ${dir_list:tW:C,.*(ERROR),\1,} +.warning ${dir_list:C,.*(ERROR),\1,W} .warning Skipping ${_DEPENDFILE:S,${SRCTOP}/,,} # we are not going to update anything .else dpadd_dir_list= .if !empty(DPADD) _nonlibs := ${DPADD:T:Nlib*:N*include} .if !empty(_nonlibs) ddep_list = .for f in ${_nonlibs:@x@${DPADD:M*/$x}@} .if exists($f.dirdep) ddep_list += $f.dirdep .elif exists(${f:H}.dirdep) ddep_list += ${f:H}.dirdep .else dir_list += ${f:H:tA} dpadd_dir_list += ${f:H:tA} .endif .endfor .if !empty(ddep_list) ddeps != cat ${ddep_list:O:u} | ${META2DEPS_FILTER} ${_skip_gendirdeps} \ sed ${GENDIRDEPS_SEDCMDS} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" .info ${RELDIR}: raw_dir_list='${dir_list}' .info ${RELDIR}: ddeps='${ddeps}' .endif dir_list += ${ddeps} .endif .endif .endif # DIRDEPS represent things that had to have been built first # so they should all be undir OBJTOP. # Note that ${_OBJTOP}/bsd/include/machine will get reported # to us as $SRCTOP/bsd/sys/$MACHINE_ARCH/include meaning we # will want to visit bsd/include # so we add # ${"${dir_list:M*bsd/sys/${MACHINE_ARCH}/include}":?bsd/include:} # to GENDIRDEPS_DIR_LIST_XTRAS _objtops = ${OBJTOP} ${_OBJTOP} ${_objtop} _objtops := ${_objtops:O:u} dirdep_list = \ ${_objtops:@o@${dir_list:M$o*/*:C,$o[^/]*/,,}@} \ ${GENDIRDEPS_DIR_LIST_XTRAS} # sort longest first M2D_OBJROOTS := ${M2D_OBJROOTS:O:u:[-1..1]} # anything we use from an object dir other than ours # needs to be qualified with its . suffix # (we used the pseudo machine "host" for the HOST_TARGET). skip_ql= ${SRCTOP}* ${_objtops:@o@$o*@} .for o in ${M2D_OBJROOTS:${skip_ql:${M_ListToSkip}}} # we need := so only skip_ql to this point applies ql.$o := ${dir_list:${skip_ql:${M_ListToSkip}}:M$o*/*/*:C,$o([^/]+)/(.*),\2.\1,:S,.${HOST_TARGET},.host,} qualdir_list += ${ql.$o} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" .info ${RELDIR}: o=$o ${ql.$o qualdir_list:L:@v@$v=${$v}@} .endif skip_ql+= $o* .endfor dirdep_list := ${dirdep_list:O:u} qualdir_list := ${qualdir_list:N*.${MACHINE}:O:u} DIRDEPS = \ ${dirdep_list:N${RELDIR}:N${RELDIR}/*} \ ${qualdir_list:N${RELDIR}.*:N${RELDIR}/*} # We only consider things below $RELDIR/ if they have a makefile. # This is the same test that _DIRDEP_USE applies. # We have do a double test with dirdep_list as it _may_ contain # qualified dirs - if we got anything from a stage dir. # qualdir_list we know are all qualified. # It would be nice do peform this check for all of DIRDEPS, # but we cannot assume that all of the tree is present, # in fact we can only assume that RELDIR is. DIRDEPS += \ ${dirdep_list:M${RELDIR}/*:@d@${.MAKE.MAKEFILE_PREFERENCE:@m@${exists(${SRCTOP}/$d/$m):?$d:${exists(${SRCTOP}/${d:R}/$m):?$d:}}@}@} \ ${qualdir_list:M${RELDIR}/*:@d@${.MAKE.MAKEFILE_PREFERENCE:@m@${exists(${SRCTOP}/${d:R}/$m):?$d:}@}@} # what modifiers do we allow in GENDIRDEPS_FILTER GENDIRDEPS_FILTER_MASK += @CMNS DIRDEPS := ${DIRDEPS:${GENDIRDEPS_FILTER:UNno:M[${GENDIRDEPS_FILTER_MASK:O:u:ts}]*:ts:}:C,//+,/,g:O:u} .if ${DEBUG_GENDIRDEPS:Uno:@x@${RELDIR:M$x}@} != "" .info ${RELDIR}: M2D_OBJROOTS=${M2D_OBJROOTS} .info ${RELDIR}: M2D_EXCLUDES=${M2D_EXCLUDES} .info ${RELDIR}: dir_list='${dir_list}' .info ${RELDIR}: dpadd_dir_list='${dpadd_dir_list}' .info ${RELDIR}: dirdep_list='${dirdep_list}' .info ${RELDIR}: qualdir_list='${qualdir_list}' .info ${RELDIR}: SKIP_GENDIRDEPS='${SKIP_GENDIRDEPS}' .info ${RELDIR}: GENDIRDEPS_FILTER='${GENDIRDEPS_FILTER}' .info ${RELDIR}: FORCE_DPADD='${DPADD}' .info ${RELDIR}: DIRDEPS='${DIRDEPS}' .endif # SRC_DIRDEPS is for checkout logic src_dirdep_list = \ ${dir_list:M${SRCTOP}/*:S,${SRCTOP}/,,} SRC_DIRDEPS = \ ${src_dirdep_list:N${RELDIR}:N${RELDIR}/*:C,(/h)/.*,,} SRC_DIRDEPS := ${SRC_DIRDEPS:${GENDIRDEPS_SRC_FILTER:UN/*:ts:}:C,//+,/,g:O:u} # if you want to capture SRC_DIRDEPS in .MAKE.DEPENDFILE put # SRC_DIRDEPS_FILE = ${_DEPENDFILE} # in local.gendirdeps.mk .if ${SRC_DIRDEPS_FILE:Uno:tl} != "no" ECHO_SRC_DIRDEPS = echo 'SRC_DIRDEPS = \'; echo '${SRC_DIRDEPS:@d@ $d \\${.newline}@}'; echo; .if ${SRC_DIRDEPS_FILE:T} == ${_DEPENDFILE:T} _include_src_dirdeps = ${ECHO_SRC_DIRDEPS} .else all: ${SRC_DIRDEPS_FILE} .if !target(${SRC_DIRDEPS_FILE}) ${SRC_DIRDEPS_FILE}: ${META_FILES} ${_this} ${META2DEPS} @(${ECHO_SRC_DIRDEPS}) > $@ .endif .endif .endif _include_src_dirdeps ?= all: ${_DEPENDFILE} # if this is going to exist it would be there by now .if !exists(.depend) CAT_DEPEND = /dev/null .endif CAT_DEPEND ?= .depend .if !empty(_DIRDEPS) && ${DIRDEPS} != ${_DIRDEPS} # we may have changed a filter .PHONY: ${_DEPENDFILE} .endif # set this to 'no' and we will not capture any # local depends LOCAL_DEPENDS_GUARD ?= _{.MAKE.LEVEL} > 0 # 'cat .depend' should suffice, but if we are mixing build modes # .depend may contain things we don't want. # The sed command at the end of the stream, allows for the filters # to output _{VAR} tokens which we will turn into proper ${VAR} references. ${_DEPENDFILE}: .NOMETA ${CAT_DEPEND:M.depend} ${META_FILES:O:u:@m@${exists($m):?$m:}@} ${_this} ${META2DEPS} @(${GENDIRDEPS_HEADER} echo '# Autogenerated - do NOT edit!'; echo; \ echo 'DIRDEPS = \'; \ echo '${DIRDEPS:@d@ $d \\${.newline}@}'; echo; \ ${_include_src_dirdeps} \ echo '.include '; \ [ "${LOCAL_DEPENDS_GUARD:[1]:tl}" != no ] || exit 0; \ echo; \ echo '.if ${LOCAL_DEPENDS_GUARD}'; \ echo '# local dependencies - needed for -jN in clean tree'; \ [ -s ${CAT_DEPEND} ] && { grep : ${CAT_DEPEND} | grep -v '[/\\]'; }; \ echo '.endif' ) | sed 's,_\([{(]\),$$\1,g' > $@.new${.MAKE.PID} @${InstallNew}; InstallNew -s $@.new${.MAKE.PID} .endif # meta2deps failed .elif !empty(SUBDIR) DIRDEPS := ${SUBDIR:S,^,${RELDIR}/,:O:u} all: ${_DEPENDFILE} ${_DEPENDFILE}: .NOMETA ${MAKEFILE} ${_this} @(${GENDIRDEPS_HEADER} echo '# Autogenerated - do NOT edit!'; echo; \ echo 'DIRDEPS = \'; \ echo '${DIRDEPS:@d@ $d \\${.newline}@}'; echo; \ echo '.include '; \ echo ) | sed 's,_\([{(]\),$$\1,g' > $@.new @${InstallNew}; InstallNew $@.new .else # nothing to do all ${_DEPENDFILE}: .endif ${_DEPENDFILE}: .PRECIOUS # don't waste time looking for ways to make .meta files .SUFFIXES: diff --git a/contrib/bmake/mk/genfiles.mk b/contrib/bmake/mk/genfiles.mk new file mode 100644 index 000000000000..d35e8be74763 --- /dev/null +++ b/contrib/bmake/mk/genfiles.mk @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# $Id: genfiles.mk,v 1.3 2024/09/21 21:14:19 sjg Exp $ +# +# @(#) Copyright (c) 2024, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +# Pipe the sources though egrep -v if EXCLUDES.${.TARGET} is defined +# and/or sed if SED_CMDS.${.TARGET} is defined +# Note: this works best in meta mode as any change to EXCLUDES or +# SED_CMDS will make the target out-of-date. +_GENFILES_USE: .USE + @cat ${SRCS.${.TARGET}:U${.ALLSRC:u}} \ + ${EXCLUDES.${.TARGET}:D| ${EGREP:Uegrep} -v '${EXCLUDS.${.TARGET}:ts|}'} \ + ${SED_CMDS.${.TARGET}:D| ${SED:Used} ${SED_CMDS.${.TARGET}}} \ + > ${.TARGET} diff --git a/contrib/bmake/mk/init.mk b/contrib/bmake/mk/init.mk index b6619721c943..acccf8d5cf6b 100644 --- a/contrib/bmake/mk/init.mk +++ b/contrib/bmake/mk/init.mk @@ -1,121 +1,128 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: init.mk,v 1.38 2024/04/09 17:18:24 sjg Exp $ +# $Id: init.mk,v 1.39 2024/12/12 19:56:36 sjg Exp $ # # @(#) Copyright (c) 2002-2024, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # should be set properly in sys.mk _this ?= ${.PARSEFILE:S,bsd.,,} .if !target(__${_this}__) __${_this}__: .NOTMAIN .if ${MAKE_VERSION:U0} > 20100408 _this_mk_dir := ${.PARSEDIR:tA} .else _this_mk_dir := ${.PARSEDIR} .endif .-include .-include <${.CURDIR:H}/Makefile.inc> .include .include # should have been set by sys.mk CXX_SUFFIXES ?= .cc .cpp .cxx .C CCM_SUFFIXES ?= .ccm PCM ?= .pcm # ${PICO} is used for PIC object files. PICO ?= .pico # SRCS which do not end up in OBJS NO_OBJS_SRCS_SUFFIXES ?= .h ${CCM_SUFFIXES} .sh -OBJS_SRCS_FILTER += ${NO_OBJS_SRCS_SUFFIXES:@x@N*$x@:ts:} +OBJS_SRCS_PRE_FILTER += ${NO_OBJS_SRCS_SUFFIXES:@x@N*$x@} +# makefiles that actually *want* .o's in subdirs +# (it can be useful if multiple SRCS have same basename) +# can just set OBJS_SRCS_FILTER = +# we apply this as ${OBJS_SRCS_FILTER:ts:} +OBJS_SRCS_FILTER ?= T +OBJS_SRCS_FILTER += ${OBJS_SRCS_PRE_FILTER} +OBJS_SRCS_FILTER += R .if defined(PROG_CXX) || ${SRCS:Uno:${CXX_SUFFIXES:S,^,N*,:ts:}} != ${SRCS:Uno:N/} _CCLINK ?= ${CXX} .endif _CCLINK ?= ${CC} .if !empty(WARNINGS_SET) || !empty(WARNINGS_SET_${MACHINE_ARCH}) .include .endif # these are applied in order, least specific to most VAR_QUALIFIER_LIST += \ ${TARGET_SPEC_VARS:UMACHINE:@v@${$v}@} \ ${COMPILER_TYPE} \ ${.TARGET:T:R} \ ${.TARGET:T} \ ${.IMPSRC:T} \ ${VAR_QUALIFIER_XTRA_LIST} QUALIFIED_VAR_LIST += \ CFLAGS \ COPTS \ CPPFLAGS \ CPUFLAGS \ LDFLAGS \ SRCS \ # a final :U avoids errors if someone uses := .for V in ${QUALIFIED_VAR_LIST:O:u:@q@$q $q_LAST@} .for Q in ${VAR_QUALIFIER_LIST:u} $V += ${$V_$Q:U${$V.$Q:U}} ${V_$Q_${COMPILER_TYPE}:U${$V.$Q.${COMPILER_TYPE}:U}} .endfor .endfor CC_PG?= -pg CXX_PG?= ${CC_PG} CC_PIC?= -DPIC CXX_PIC?= ${CC_PIC} PROFFLAGS?= -DGPROF -DPROF .if ${.MAKE.LEVEL:U1} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes" .if ${RELDIR} == "." # top-level targets that are ok at level 0 DIRDEPS_BUILD_LEVEL0_TARGETS += clean* destroy* M_ListToSkip?= O:u:S,^,N,:ts: .if ${.TARGETS:Uall:${DIRDEPS_BUILD_LEVEL0_TARGETS:${M_ListToSkip}}} != "" # this tells lib.mk and prog.mk to not actually build anything _SKIP_BUILD = not building at level 0 .endif .elif ${.TARGETS:U:Nall} == "" _SKIP_BUILD = not building at level 0 # first .MAIN is what counts .MAIN: dirdeps .endif .endif .MAIN: all .if !defined(.PARSEDIR) # no-op is the best we can do if not bmake. .WAIT: .endif # define this once for consistency .if !defined(_SKIP_BUILD) # beforebuild is a hook for things that must be done early all: beforebuild .WAIT realbuild .else all: .PHONY .if !empty(_SKIP_BUILD) && ${.MAKEFLAGS:M-V} == "" .warning ${_SKIP_BUILD} .endif .endif beforebuild: realbuild: .endif diff --git a/contrib/bmake/mk/install-mk b/contrib/bmake/mk/install-mk old mode 100755 new mode 100644 index bb150e3041a3..b455832b7726 --- a/contrib/bmake/mk/install-mk +++ b/contrib/bmake/mk/install-mk @@ -1,198 +1,198 @@ : # NAME: # install-mk - install mk files # # SYNOPSIS: # install-mk [options] [var=val] [dest] # # DESCRIPTION: # This tool installs mk files in a semi-intelligent manner into # "dest". # # Options: # # -n just say what we want to do, but don't touch anything. # # -f use -f when copying sys,mk. # # -v be verbose # # -q be quiet # # -m "mode" # Use "mode" for installed files (444). # # -o "owner" # Use "owner" for installed files. # # -g "group" # Use "group" for installed files. # # -U "umask" # Use "umask" so directories are created with suitable # mode (default is 022). # # var=val # Set "var" to "val". See below. # # All our *.mk files are copied to "dest" with appropriate # ownership and permissions. # # By default if a sys.mk can be found in a standard location # (that bmake will find) then no sys.mk will be put in "dest". # # SKIP_SYS_MK: # If set, we will avoid installing our 'sys.mk' # This is probably a bad idea. # # SKIP_BSD_MK: # If set, we will skip making bsd.*.mk links to *.mk # # sys.mk: # # By default (and provided we are not installing to the system # mk dir - '/usr/share/mk') we install our own 'sys.mk' which # includes a sys specific file, or a generic one. # # # AUTHOR: # Simon J. Gerraty # RCSid: -# $Id: install-mk,v 1.255 2024/06/24 02:21:00 sjg Exp $ +# $Id: install-mk,v 1.262 2025/01/05 01:16:19 sjg Exp $ # # @(#) Copyright (c) 1994-2024 Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # -MK_VERSION=20240616 +MK_VERSION=20250101 OWNER= GROUP= MODE=444 BINMODE=555 ECHO=: SKIP= cp_f=-f umask 022 while : do case "$1" in *=*) eval "$1"; shift;; +f) cp_f=; shift;; -f) cp_f=-f; shift;; -m) MODE=$2; shift 2;; -o) OWNER=$2; shift 2;; -g) GROUP=$2; shift 2;; -v) ECHO=echo; shift;; -q) ECHO=:; shift;; -n) ECHO=echo SKIP=:; shift;; -U) umask $2; shift;; --) shift; break;; *) break;; esac done case $# in 0) echo "$0 [options] []" echo "eg." echo "$0 -o bin -g bin -m 444 /usr/local/share/mk" exit 1 ;; esac dest=$1 os=${2:-`uname`} osrel=${3:-`uname -r`} Do() { $ECHO "$@" $SKIP "$@" } Error() { echo "ERROR: $@" >&2 exit 1 } Warning() { echo "WARNING: $@" >&2 } [ "$FORCE_SYS_MK" ] && Warning "ignoring: FORCE_{BSD,SYS}_MK (no longer supported)" SYS_MK_DIR=${SYS_MK_DIR:-/usr/share/mk} SYS_MK=${SYS_MK:-$SYS_MK_DIR/sys.mk} realpath() { [ -d $1 ] && cd $1 && 'pwd' && return echo $1 } # some Linux systems have deprecated egrep in favor of grep -E case "`echo bmake | egrep 'a' 2>&1`" in *"grep -E"*) egrep='grep -E';; *) egrep=egrep;; esac if [ -s $SYS_MK -a -d $dest ]; then # if this is a BSD system we don't want to touch $SYS_MK dest=`realpath $dest` sys_mk_dir=`realpath $SYS_MK_DIR` if [ $dest = $sys_mk_dir ]; then case "$os" in *BSD*) SKIP_SYS_MK=: SKIP_BSD_MK=: ;; *) # could be fake? if [ ! -d $dest/sys -a ! -s $dest/Generic.sys.mk ]; then SKIP_SYS_MK=: # play safe SKIP_BSD_MK=: fi ;; esac fi fi [ -d $dest/sys ] || Do mkdir -p $dest/sys [ -d $dest/sys ] || Do mkdir $dest/sys || exit 1 [ -z "$SKIP" ] && dest=`realpath $dest` cd `dirname $0` mksrc=`'pwd'` if [ $mksrc = $dest ]; then SKIP_MKFILES=: else # we do not install the examples mk_files=`grep '^[a-z].*\.mk' FILES | $egrep -v '(examples/|^sys\.mk|sys/)'` mk_scripts=`$egrep '^[a-z].*[.-](sh|py)' FILES | $egrep -v '/'` sys_mk_files=`grep 'sys/.*\.mk' FILES` SKIP_MKFILES= [ -z "$SKIP_SYS_MK" ] && mk_files="sys.mk $mk_files" fi $SKIP_MKFILES Do cp $cp_f $mk_files $dest $SKIP_MKFILES Do cp $cp_f $sys_mk_files $dest/sys $SKIP_MKFILES Do cp $cp_f $mk_scripts $dest $SKIP cd $dest $SKIP_MKFILES Do chmod $MODE $mk_files $sys_mk_files $SKIP_MKFILES Do chmod $BINMODE $mk_scripts [ "$GROUP" ] && $SKIP_MKFILES Do chgrp $GROUP $mk_files $sys_mk_files [ "$OWNER" ] && $SKIP_MKFILES Do chown $OWNER $mk_files $sys_mk_files # if this is a BSD system the bsd.*.mk should exist and be used. if [ -z "$SKIP_BSD_MK" ]; then for f in dep doc files inc init lib links man nls obj own prog subdir do b=bsd.$f.mk [ -s $b ] || Do ln -s $f.mk $b done fi exit 0 diff --git a/contrib/bmake/mk/lib.mk b/contrib/bmake/mk/lib.mk index 95f95118c028..3f6a749a12d1 100644 --- a/contrib/bmake/mk/lib.mk +++ b/contrib/bmake/mk/lib.mk @@ -1,621 +1,623 @@ -# $Id: lib.mk,v 1.84 2024/02/19 00:06:19 sjg Exp $ +# $Id: lib.mk,v 1.85 2024/12/12 19:56:36 sjg Exp $ # should be set properly in sys.mk _this ?= ${.PARSEFILE:S,bsd.,,} .if !target(__${_this}__) __${_this}__: .NOTMAIN .include .if ${OBJECT_FMT} == "ELF" NEED_SOLINKS?= yes .endif SHLIB_VERSION_FILE?= ${.CURDIR}/shlib_version .if !defined(SHLIB_MAJOR) && exists(${SHLIB_VERSION_FILE}) SHLIB_MAJOR != . ${SHLIB_VERSION_FILE} ; echo $$major SHLIB_MINOR != . ${SHLIB_VERSION_FILE} ; echo $$minor SHLIB_TEENY != . ${SHLIB_VERSION_FILE} ; echo $$teeny .endif .for x in major minor teeny print-shlib-$x: .if defined(SHLIB_${x:tu}) && ${MK_PIC} != "no" @echo ${SHLIB_${x:tu}} .else @false .endif .endfor SHLIB_FULLVERSION ?= ${${SHLIB_MAJOR} ${SHLIB_MINOR} ${SHLIB_TEENY}:L:ts.} SHLIB_FULLVERSION := ${SHLIB_FULLVERSION} # add additional suffixes not exported. # .po is used for profiling object files. .SUFFIXES: .out .a .ln ${PICO} ${PCM} .po .o .s .S .c ${CXX_SUFFIXES} \ ${CCM_SUFFIXES} .m .F .f .r .y .l .cl .p .h \ .sh .m4 .m CFLAGS+= ${COPTS} META_NOECHO?= echo # Originally derrived from NetBSD-1.6 # Set PICFLAGS to cc flags for producing position-independent code, # if not already set. Includes -DPIC, if required. # Data-driven table using make variables to control how shared libraries # are built for different platforms and object formats. # OBJECT_FMT: currently either "ELF" or "a.out", from # SHLIB_SOVERSION: version number to be compiled into a shared library # via -soname. Usually ${SHLIB_MAJOR} on ELF. # NetBSD/pmax used to use ${SHLIB_MAJOR}[.${SHLIB_MINOR} # [.${SHLIB_TEENY}]] # SHLIB_SHFLAGS: Flags to tell ${LD} to emit shared library. # with ELF, also set shared-lib version for ld.so. # SHLIB_LDSTARTFILE: support .o file, call C++ file-level constructors # SHLIB_LDENDFILE: support .o file, call C++ file-level destructors # FPICFLAGS: flags for ${FC} to compile .[fF] files to ${PICO} objects. # CPPICFLAGS: flags for ${CPP} to preprocess .[sS] files for ${AS} # CPICFLAGS: flags for ${CC} to compile .[cC] files to ${PICO} objects. # CAPICFLAGS flags for {$CC} to compiling .[Ss] files # (usually just ${CPPPICFLAGS} ${CPICFLAGS}) # APICFLAGS: flags for ${AS} to assemble .[sS] to ${PICO} objects. # we simplify life by letting the toolchain do most of the work # _CCLINK is set by init.mk based on whether we are doing C++ or not SHLIB_LD ?= ${_CCLINK} .if ${TARGET_OSNAME} == "NetBSD" .if ${MACHINE_ARCH} == "alpha" # Alpha-specific shared library flags FPICFLAGS ?= -fPIC CPICFLAGS ?= -fPIC -DPIC CPPPICFLAGS?= -DPIC CAPICFLAGS?= ${CPPPICFLAGS} ${CPICFLAGS} APICFLAGS ?= .elif ${MACHINE_ARCH} == "mipsel" || ${MACHINE_ARCH} == "mipseb" # mips-specific shared library flags # On mips, all libs are compiled with ABIcalls, not just sharedlibs. MKPICLIB= no # so turn shlib PIC flags on for ${AS}. AINC+=-DABICALLS AFLAGS+= -fPIC AS+= -KPIC .elif ${MACHINE_ARCH} == "vax" && ${OBJECT_FMT} == "ELF" # On the VAX, all object are PIC by default, not just sharedlibs. MKPICLIB= no .elif (${MACHINE_ARCH} == "sparc" || ${MACHINE_ARCH} == "sparc64") && \ ${OBJECT_FMT} == "ELF" # If you use -fPIC you need to define BIGPIC to turn on 32-bit # relocations in asm code FPICFLAGS ?= -fPIC CPICFLAGS ?= -fPIC -DPIC CPPPICFLAGS?= -DPIC -DBIGPIC CAPICFLAGS?= ${CPPPICFLAGS} ${CPICFLAGS} APICFLAGS ?= -KPIC .else # Platform-independent flags for NetBSD a.out shared libraries SHLIB_SOVERSION=${SHLIB_FULLVERSION} SHLIB_SHFLAGS= FPICFLAGS ?= -fPIC CPICFLAGS?= -fPIC -DPIC CPPPICFLAGS?= -DPIC CAPICFLAGS?= ${CPPPICFLAGS} ${CPICFLAGS} APICFLAGS?= -k .endif # Platform-independent linker flags for ELF shared libraries .if ${OBJECT_FMT} == "ELF" SHLIB_SOVERSION= ${SHLIB_MAJOR} SHLIB_SHFLAGS= -Wl,-soname,lib${LIB}.so.${SHLIB_SOVERSION} SHLIB_LDSTARTFILE?= /usr/lib/crtbeginS.o SHLIB_LDENDFILE?= /usr/lib/crtendS.o .endif # for compatibility with the following CC_PIC?= ${CPICFLAGS} LD_shared=${SHLIB_SHFLAGS} .endif # NetBSD .if ${TARGET_OSNAME} == "FreeBSD" .if ${OBJECT_FMT} == "ELF" SHLIB_SOVERSION= ${SHLIB_MAJOR} SHLIB_SHFLAGS= -Wl,-soname,lib${LIB}.so.${SHLIB_SOVERSION} .else SHLIB_SHFLAGS= -assert pure-text .endif SHLIB_LDSTARTFILE= SHLIB_LDENDFILE= CC_PIC?= -fpic LD_shared=${SHLIB_SHFLAGS} .endif # FreeBSD MKPICLIB?= yes # sys.mk can override these LD_X?=-X LD_x?=-x LD_r?=-r # Non BSD machines will be using bmake. .if ${TARGET_OSNAME} == "SunOS" LD_shared=-assert pure-text .if ${OBJECT_FMT} == "ELF" || ${MACHINE} == "solaris" # Solaris LD_shared=-h lib${LIB}.so.${SHLIB_MAJOR} -G .endif .elif ${TARGET_OSNAME} == "HP-UX" LD_shared=-b LD_so=sl DLLIB= # HPsUX lorder does not grok anything but .o LD_sobjs=`${LORDER} ${OBJS} | ${TSORT} | sed 's,\.o,${PICO},'` LD_pobjs=`${LORDER} ${OBJS} | ${TSORT} | sed 's,\.o,.po,'` .elif ${TARGET_OSNAME} == "OSF1" LD_shared= -msym -shared -expect_unresolved '*' LD_solib= -all lib${LIB}_pic.a DLLIB= # lorder does not grok anything but .o LD_sobjs=`${LORDER} ${OBJS} | ${TSORT} | sed 's,\.o,${PICO},'` LD_pobjs=`${LORDER} ${OBJS} | ${TSORT} | sed 's,\.o,.po,'` AR_cq= -cqs .elif ${TARGET_OSNAME} == "FreeBSD" LD_solib= lib${LIB}_pic.a .elif ${TARGET_OSNAME} == "Linux" # this is ambiguous of course LD_shared=-shared -Wl,-soname,lib${LIB}.so.${SHLIB_MAJOR} LD_solib= -Wl,--whole-archive lib${LIB}_pic.a -Wl,--no-whole-archive .if ${COMPILER_TYPE} == "gcc" # Linux uses GNU ld, which is a multi-pass linker # so we don't need to use lorder or tsort LD_objs = ${OBJS} LD_pobjs = ${POBJS} LD_sobjs = ${SOBJS} .endif .elif ${TARGET_OSNAME} == "Darwin" SHLIB_INSTALL_VERSION ?= ${SHLIB_MAJOR} SHLIB_COMPATABILITY_VERSION ?= ${SHLIB_MAJOR}.${SHLIB_MINOR:U0} SHLIB_COMPATABILITY ?= \ -compatibility_version ${SHLIB_COMPATABILITY_VERSION} \ -current_version ${SHLIB_FULLVERSION} LD_shared = -dynamiclib \ -flat_namespace -undefined suppress \ -install_name ${LIBDIR}/lib${LIB}.${SHLIB_INSTALL_VERSION}.${LD_solink} \ ${SHLIB_COMPATABILITY} SHLIB_LINKS = .for v in ${SHLIB_COMPATABILITY_VERSION} ${SHLIB_INSTALL_VERSION} .if "$v" != "${SHLIB_FULLVERSION}" SHLIB_LINKS += lib${LIB}.$v.${LD_solink} .endif .endfor .if ${MK_LINKLIB} != "no" SHLIB_LINKS += lib${LIB}.${LD_solink} .endif LD_so = ${SHLIB_FULLVERSION}.dylib LD_sobjs = ${SOBJS:O:u} LD_solib = ${LD_sobjs} SOLIB = ${LD_sobjs} LD_solink = dylib .if ${MACHINE_ARCH} == "i386" PICFLAG ?= -fPIC .else PICFLAG ?= -fPIC -fno-common .endif RANLIB = : .endif .if !empty(SHLIB_MAJOR) .if ${NEED_SOLINKS} && empty(SHLIB_LINKS) .if ${MK_LINKLIB} != "no" SHLIB_LINKS = lib${LIB}.${LD_solink} .endif .if "${SHLIB_FULLVERSION}" != "${SHLIB_MAJOR}" SHLIB_LINKS += lib${LIB}.${LD_solink}.${SHLIB_MAJOR} .endif .endif .endif LIBTOOL?=libtool LD_shared ?= -Bshareable -Bforcearchive LD_so ?= so.${SHLIB_FULLVERSION} LD_solink ?= so .if empty(LORDER) LD_objs ?= ${OBJS} LD_pobjs ?= ${POBJS} LD_sobjs ?= ${SOBJS} .else LD_objs ?= `${LORDER} ${OBJS} | ${TSORT}` LD_sobjs ?= `${LORDER} ${SOBJS} | ${TSORT}` LD_pobjs ?= `${LORDER} ${POBJS} | ${TSORT}` .endif LD_solib ?= ${LD_sobjs} AR_cq ?= cq .if exists(/netbsd) && exists(${DESTDIR}/usr/lib/libdl.so) DLLIB ?= -ldl .endif # some libs have lots of objects, and scanning all .o, .po and ${PICO} meta files # is a waste of time, this tells meta.autodep.mk to just pick one # (typically ${PICO}) # yes, 42 is a random number. .if ${MK_DIRDEPS_BUILD} == "yes" && ${SRCS:Uno:[\#]} > 42 OPTIMIZE_OBJECT_META_FILES ?= yes .endif .if ${MK_LIBTOOL} == "yes" # because libtool is so fascist about naming the object files, # we cannot (yet) build profiled libs MK_PROFILE=no _LIBS=lib${LIB}.a .if exists(${.CURDIR}/shlib_version) SHLIB_AGE != . ${.CURDIR}/shlib_version ; echo $$age .endif .else # for the normal .a we do not want to strip symbols .c.o: ${COMPILE.c} ${.IMPSRC} # precompiled C++ Modules ${CCM_SUFFIXES:%=%${PCM}}: ${COMPILE.pcm} ${.IMPSRC} # for the normal .a we do not want to strip symbols ${CXX_SUFFIXES:%=%.o}: ${COMPILE.cc} ${.IMPSRC} .S.o .s.o: ${COMPILE.S} ${CFLAGS:M-[ID]*} ${AINC} ${.IMPSRC} .if (${LD_X} == "") .c.po: ${COMPILE.c} ${CC_PG} ${PROFFLAGS} ${.IMPSRC} -o ${.TARGET} ${CXX_SUFFIXES:%=%.po}: ${COMPILE.cc} -pg ${.IMPSRC} -o ${.TARGET} .S${PICO} .s${PICO}: ${COMPILE.S} ${PICFLAG} ${CC_PIC} ${CFLAGS:M-[ID]*} ${AINC} ${.IMPSRC} -o ${.TARGET} .else .c.po: ${COMPILE.c} ${CC_PG} ${PROFFLAGS} ${.IMPSRC} -o ${.TARGET}.o @${LD} ${LD_X} ${LD_r} ${.TARGET}.o -o ${.TARGET} @rm -f ${.TARGET}.o ${CXX_SUFFIXES:%=%.po}: ${COMPILE.cc} ${CXX_PG} ${.IMPSRC} -o ${.TARGET}.o ${LD} ${LD_X} ${LD_r} ${.TARGET}.o -o ${.TARGET} @rm -f ${.TARGET}.o .S${PICO} .s${PICO}: ${COMPILE.S} ${PICFLAG} ${CC_PIC} ${CFLAGS:M-[ID]*} ${AINC} ${.IMPSRC} -o ${.TARGET}.o ${LD} ${LD_x} ${LD_r} ${.TARGET}.o -o ${.TARGET} @rm -f ${.TARGET}.o .endif .if (${LD_x} == "") .c${PICO}: ${COMPILE.c} ${PICFLAG} ${CC_PIC} ${.IMPSRC} -o ${.TARGET} ${CXX_SUFFIXES:%=%${PICO}}: ${COMPILE.cc} ${PICFLAG} ${CC_PIC} ${.IMPSRC} -o ${.TARGET} .S.po .s.po: ${COMPILE.S} ${PROFFLAGS} ${CFLAGS:M-[ID]*} ${AINC} ${.IMPSRC} -o ${.TARGET} .else .c${PICO}: ${COMPILE.c} ${PICFLAG} ${CC_PIC} ${.IMPSRC} -o ${.TARGET}.o ${LD} ${LD_x} ${LD_r} ${.TARGET}.o -o ${.TARGET} @rm -f ${.TARGET}.o ${CXX_SUFFIXES:%=%${PICO}}: ${COMPILE.cc} ${PICFLAG} ${CC_PIC} ${.IMPSRC} -o ${.TARGET}.o ${LD} ${LD_x} ${LD_r} ${.TARGET}.o -o ${.TARGET} @rm -f ${.TARGET}.o .S.po .s.po: ${COMPILE.S} ${PROFFLAGS} ${CFLAGS:M-[ID]*} ${AINC} ${.IMPSRC} -o ${.TARGET}.o ${LD} ${LD_X} ${LD_r} ${.TARGET}.o -o ${.TARGET} @rm -f ${.TARGET}.o .endif .endif .c.ln: ${LINT} ${LINTFLAGS} ${CFLAGS:M-[IDU]*} -i ${.IMPSRC} .if ${MK_LIBTOOL} != "yes" .if !defined(PICFLAG) PICFLAG=-fpic .endif _LIBS= .if ${MK_ARCHIVE} != "no" _LIBS += lib${LIB}.a .endif .if ${MK_PROFILE} != "no" _LIBS+=lib${LIB}_p.a POBJS+=${OBJS:.o=.po} .endif .if ${MK_PIC} != "no" .if ${MK_PICLIB} == "no" SOLIB ?= lib${LIB}.a .else SOLIB=lib${LIB}_pic.a _LIBS+=${SOLIB} .endif .if !empty(SHLIB_FULLVERSION) _LIBS+=lib${LIB}.${LD_so} .endif .endif .if ${MK_LINT} != "no" _LIBS+=llib-l${LIB}.ln .endif # here is where you can define what LIB* are .-include .if ${MK_DPADD_MK} == "yes" # lots of cool magic, but might not suit everyone. .include .endif .if empty(LIB) _LIBS= .elif ${MK_LDORDER_MK} != "no" # Record any libs that we need to be linked with _LIBS+= ${libLDORDER_INC} .include .endif # avoid -dL errors LDADD_LDORDER ?= .if !defined(_SKIP_BUILD) realbuild: ${_LIBS} .endif all: _SUBDIRUSE -OBJS_SRCS = ${SRCS:${OBJS_SRCS_FILTER}} - -.for s in ${OBJS_SRCS:M*/*} -${.o ${PICO} .po .lo:L:@o@${s:T:R}$o@}: $s +.for s in ${SRCS:${OBJS_SRCS_PRE_FILTER:ts:}:M*/*} +${.o .po .lo:L:@o@${s:${OBJS_SRCS_FILTER:ts:}}$o@}: $s .endfor -OBJS+= ${OBJS_SRCS:T:R:S/$/.o/g} +OBJS_SRCS = ${SRCS:${OBJS_SRCS_FILTER:ts:}} +.if !empty(OBJS_SRCS) +OBJS+= ${OBJS_SRCS:S/$/.o/g} +.endif + .NOPATH: ${OBJS} .if ${MK_LIBTOOL} == "yes" .if ${MK_PIC} == "no" LT_STATIC=-static .else LT_STATIC= .endif SHLIB_AGE?=0 # .lo's are created as a side effect .s.o .S.o .c.o: ${LIBTOOL} --mode=compile ${CC} ${LT_STATIC} ${CFLAGS} ${CPPFLAGS} ${IMPFLAGS} -c ${.IMPSRC} # can't really do profiled libs with libtool - its too fascist about # naming the output... lib${LIB}.a: ${OBJS} @rm -f ${.TARGET} ${LIBTOOL} --mode=link ${CC} ${LT_STATIC} -o ${.TARGET:.a=.la} ${OBJS:.o=.lo} -rpath ${SHLIBDIR}:/usr/lib -version-info ${SHLIB_MAJOR}:${SHLIB_MINOR}:${SHLIB_AGE} @ln .libs/${.TARGET} . lib${LIB}.${LD_so}: lib${LIB}.a @[ -s ${.TARGET}.${SHLIB_AGE} ] || { ln -s .libs/lib${LIB}.${LD_so}* . 2>/dev/null; : } @[ -s ${.TARGET} ] || ln -s ${.TARGET}.${SHLIB_AGE} ${.TARGET} .else # MK_LIBTOOL=yes lib${LIB}.a: ${OBJS} @${META_NOECHO} building standard ${LIB} library @rm -f ${.TARGET} @${AR} ${AR_cq} ${.TARGET} ${LD_objs} ${RANLIB} ${.TARGET} POBJS+= ${OBJS:.o=.po} .NOPATH: ${POBJS} lib${LIB}_p.a: ${POBJS} @${META_NOECHO} building profiled ${LIB} library @rm -f ${.TARGET} @${AR} ${AR_cq} ${.TARGET} ${LD_pobjs} ${RANLIB} ${.TARGET} SOBJS+= ${OBJS:.o=${PICO}} .NOPATH: ${SOBJS} lib${LIB}_pic.a: ${SOBJS} @${META_NOECHO} building shared object ${LIB} library @rm -f ${.TARGET} @${AR} ${AR_cq} ${.TARGET} ${LD_sobjs} ${RANLIB} ${.TARGET} # bound to be non-portable... # this is known to work for NetBSD 1.6 and FreeBSD 4.2 lib${LIB}.${LD_so}: ${SOLIB} ${DPADD} @${META_NOECHO} building shared ${LIB} library \(version ${SHLIB_FULLVERSION}\) @rm -f ${.TARGET} .if ${TARGET_OSNAME:NFreeBSD:NNetBSD} == "" .if ${OBJECT_FMT} == "ELF" ${SHLIB_LD} -shared -Wl,-x ${SHLIB_SHFLAGS} ${LDFLAGS} -o ${.TARGET} \ -Wl,--whole-archive ${SOLIB} -Wl,--no-whole-archive \ ${LDADD} ${SHLIB_LDADD} .else ${SHLIB_LD} ${LD_x} ${LD_shared} ${LDFLAGS} \ -o ${.TARGET} ${SOLIB} ${LDADD} ${SHLIB_LDADD} .endif .else ${SHLIB_LD} ${LDFLAGS} -o ${.TARGET} \ ${LD_shared} ${LD_solib} ${DLLIB} ${LDADD} ${SHLIB_LDADD} .endif .endif .if !empty(SHLIB_LINKS) rm -f ${SHLIB_LINKS}; ${SHLIB_LINKS:O:u:@x@ln -s ${.TARGET} $x;@} .endif LOBJS+= ${LSRCS:.c=.ln} ${SRCS:M*.c:.c=.ln} .NOPATH: ${LOBJS} LLIBS?= -lc llib-l${LIB}.ln: ${LOBJS} @${META_NOECHO} building llib-l${LIB}.ln @rm -f llib-l${LIB}.ln @${LINT} -C${LIB} ${LOBJS} ${LLIBS} .if !target(clean) cleanlib: .PHONY rm -f a.out [Ee]rrs mklog core *.core ${CLEANFILES} rm -f lib${LIB}.a ${OBJS} rm -f lib${LIB}_p.a ${POBJS} rm -f lib${LIB}_pic.a lib${LIB}*${LD_solink} lib${LIB}*${LD_solink}.* ${SOBJS} rm -f llib-l${LIB}.ln ${LOBJS} .if !empty(SHLIB_LINKS) rm -f ${SHLIB_LINKS} .endif clean: _SUBDIRUSE cleanlib cleandir: _SUBDIRUSE cleanlib .else cleandir: _SUBDIRUSE clean .endif .if defined(SRCS) && (!defined(MKDEP) || ${MKDEP} != autodep) afterdepend: .depend @(TMP=/tmp/_depend$$$$; \ sed -e 's/^\([^\.]*\).o[ ]*:/\1.o \1.po \1${PICO} \1.ln:/' \ < .depend > $$TMP; \ mv $$TMP .depend) .endif .if !target(install) .if !target(beforeinstall) beforeinstall: .endif .if !empty(LIBOWN) LIB_INSTALL_OWN ?= -o ${LIBOWN} -g ${LIBGRP} .endif .include .if !target(libinstall) && !empty(LIB) realinstall: libinstall libinstall: [ -d ${DESTDIR}/${LIBDIR} ] || \ ${INSTALL} -d ${LIB_INSTALL_OWN} -m 775 ${DESTDIR}${LIBDIR} .if ${MK_ARCHIVE} != "no" ${INSTALL} ${COPY} ${LIB_INSTALL_OWN} -m 644 lib${LIB}.a \ ${DESTDIR}${LIBDIR} ${RANLIB} ${DESTDIR}${LIBDIR}/lib${LIB}.a chmod ${LIBMODE} ${DESTDIR}${LIBDIR}/lib${LIB}.a .endif .if ${MK_PROFILE} != "no" ${INSTALL} ${COPY} ${LIB_INSTALL_OWN} -m 644 \ lib${LIB}_p.a ${DESTDIR}${LIBDIR} ${RANLIB} ${DESTDIR}${LIBDIR}/lib${LIB}_p.a chmod ${LIBMODE} ${DESTDIR}${LIBDIR}/lib${LIB}_p.a .endif .if ${MK_LDORDER_MK} != "no" ${INSTALL} ${COPY} ${LIB_INSTALL_OWN} -m 644 \ lib${LIB}.ldorder.inc ${DESTDIR}${LIBDIR} .endif .if ${MK_PIC} != "no" .if ${MK_PICLIB} != "no" ${INSTALL} ${COPY} ${LIB_INSTALL_OWN} -m 644 \ lib${LIB}_pic.a ${DESTDIR}${LIBDIR} ${RANLIB} ${DESTDIR}${LIBDIR}/lib${LIB}_pic.a chmod ${LIBMODE} ${DESTDIR}${LIBDIR}/lib${LIB}_pic.a .endif .if !empty(SHLIB_MAJOR) ${INSTALL} ${COPY} ${LIB_INSTALL_OWN} -m ${LIBMODE} \ lib${LIB}.${LD_so} ${DESTDIR}${LIBDIR} .if !empty(SHLIB_LINKS) (cd ${DESTDIR}${LIBDIR} && { rm -f ${SHLIB_LINKS}; ${SHLIB_LINKS:O:u:@x@ln -s lib${LIB}.${LD_so} $x;@} }) .endif .endif .endif .if ${MK_LINT} != "no" && ${MK_LINKLIB} != "no" && !empty(LOBJS) ${INSTALL} ${COPY} ${LIB_INSTALL_OWN} -m ${LIBMODE} \ llib-l${LIB}.ln ${DESTDIR}${LINTLIBDIR} .endif .if defined(LINKS) && !empty(LINKS) @set ${LINKS}; ${_LINKS_SCRIPT} .endif .endif .if ${MK_MAN} != "no" install: maninstall _SUBDIRUSE maninstall: afterinstall .endif afterinstall: realinstall libinstall: beforeinstall realinstall: beforeinstall .endif .if defined(FILES) || defined(FILESGROUPS) .include .endif .if ${MK_MAN} != "no" .include .endif .if ${MK_NLS} != "no" .include .endif .include .include .include .include .endif # during building we usually need/want to install libs somewhere central # note that we do NOT ch{own,grp} as that would likely fail at this point. # otherwise it is the same as realinstall # Note that we don't need this when using dpadd.mk .libinstall: ${_LIBS} test -d ${DESTDIR}${LIBDIR} || ${INSTALL} -d -m775 ${DESTDIR}${LIBDIR} .for _lib in ${_LIBS:M*.a} ${INSTALL} ${COPY} -m 644 ${_lib} ${DESTDIR}${LIBDIR} ${RANLIB} ${DESTDIR}${LIBDIR}/${_lib} .endfor .for _lib in ${_LIBS:M*.${LD_solink}*:O:u} ${INSTALL} ${COPY} -m ${LIBMODE} ${_lib} ${DESTDIR}${LIBDIR} .if !empty(SHLIB_LINKS) (cd ${DESTDIR}${LIBDIR} && { ${SHLIB_LINKS:O:u:@x@ln -sf ${_lib} $x;@}; }) .endif .endfor @touch ${.TARGET} .if !empty(LIB) STAGE_LIBDIR?= ${STAGE_OBJTOP}${LIBDIR} stage_libs: ${_LIBS} __libtoken ?= __lib${LIB:C,[^a-zA-Z0-9_],_,g}__ __libtoken := ${__libtoken} COPTS += -D${__libtoken} .endif .include .endif diff --git a/contrib/bmake/mk/links.mk b/contrib/bmake/mk/links.mk index cbf9685ccd37..dfcedec7a434 100644 --- a/contrib/bmake/mk/links.mk +++ b/contrib/bmake/mk/links.mk @@ -1,82 +1,82 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: links.mk,v 1.8 2024/02/17 17:26:57 sjg Exp $ +# $Id: links.mk,v 1.10 2024/08/23 21:24:27 sjg Exp $ # -# @(#) Copyright (c) 2005, Simon J. Gerraty +# @(#) Copyright (c) 2005-2024, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # some platforms need something special -LN?= ln -ECHO?= echo +LN ?= ln +ECHO ?= echo -LINKS?= -SYMLINKS?= +LINKS ?= +SYMLINKS ?= -__SYMLINK_SCRIPT= \ - ${ECHO} "$$t -> $$l"; \ +__SYMLINK_SCRIPT = \ case `'ls' -l $$t 2> /dev/null` in \ *"> $$l") ;; \ *) \ + ${ECHO} "$$t -> $$l"; \ mkdir -p `dirname $$t`; \ rm -f $$t; \ ${LN} -s $$l $$t;; \ esac -__LINK_SCRIPT= \ +__LINK_SCRIPT = \ ${ECHO} "$$t -> $$l"; \ mkdir -p `dirname $$t`; \ rm -f $$t; \ ${LN} $$l $$t -_SYMLINKS_SCRIPT= \ +_SYMLINKS_SCRIPT = \ while test $$\# -ge 2; do \ l=$$1; shift; \ t=${DESTDIR}$$1; shift; \ ${__SYMLINK_SCRIPT}; \ done; :; -_LINKS_SCRIPT= \ +_LINKS_SCRIPT = \ while test $$\# -ge 2; do \ l=${DESTDIR}$$1; shift; \ t=${DESTDIR}$$1; shift; \ ${__LINK_SCRIPT}; \ done; :; _SYMLINKS_USE: .USE - @set ${$@_SYMLINKS:U${SYMLINKS}}; ${_SYMLINKS_SCRIPT} + @set ${$@_SYMLINKS:U${SYMLINKS}:${SYMLINKS_FILTER:U:ts:}}; ${_SYMLINKS_SCRIPT} _LINKS_USE: .USE - @set ${$@_LINKS:U${LINKS}}; ${_LINKS_SCRIPT} + @set ${$@_LINKS:U${LINKS}:${LINKS_FILTER:U:ts:}}; ${_LINKS_SCRIPT} # sometimes we want to ensure DESTDIR is ignored -_BUILD_SYMLINKS_SCRIPT= \ +_BUILD_SYMLINKS_SCRIPT = \ while test $$\# -ge 2; do \ l=$$1; shift; \ t=$$1; shift; \ ${__SYMLINK_SCRIPT}; \ done; :; -_BUILD_LINKS_SCRIPT= \ +_BUILD_LINKS_SCRIPT = \ while test $$\# -ge 2; do \ l=$$1; shift; \ t=$$1; shift; \ ${__LINK_SCRIPT}; \ done; :; _BUILD_SYMLINKS_USE: .USE - @set ${$@_SYMLINKS:U${SYMLINKS}}; ${_BUILD_SYMLINKS_SCRIPT} + @set ${$@_SYMLINKS:U${SYMLINKS}:${BUILD_SYMLINKS_FILTER:U:ts:}}; ${_BUILD_SYMLINKS_SCRIPT} _BUILD_LINKS_USE: .USE - @set ${$@_LINKS:U${LINKS}}; ${_BUILD_LINKS_SCRIPT} + @set ${$@_LINKS:U${LINKS}:${BUILD_LINKS_FILTER:U:ts:}}; ${_BUILD_LINKS_SCRIPT} diff --git a/contrib/bmake/mk/meta.sys.mk b/contrib/bmake/mk/meta.sys.mk index 6e4216ab5383..1104204dc8c9 100644 --- a/contrib/bmake/mk/meta.sys.mk +++ b/contrib/bmake/mk/meta.sys.mk @@ -1,159 +1,170 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: meta.sys.mk,v 1.54 2024/03/10 15:53:51 sjg Exp $ +# $Id: meta.sys.mk,v 1.56 2024/11/22 23:51:48 sjg Exp $ # # @(#) Copyright (c) 2010-2023, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # include this if you want to enable meta mode # for maximum benefit, requires filemon(4) driver. # absolute path to what we are reading. _PARSEDIR ?= ${.PARSEDIR:tA} .-include .if !defined(SYS_MK_DIR) SYS_MK_DIR := ${_PARSEDIR} .endif .if !target(.ERROR) META_MODE += meta .if empty(.MAKEFLAGS:M-s) META_MODE += verbose .endif .if ${MAKE_VERSION:U0} > 20130323 && empty(.MAKE.PATH_FILEMON) # we do not support filemon META_MODE += nofilemon MKDEP_MK ?= auto.dep.mk .endif +# META_MODE_XTRAS makes it easier to add things like 'env' +# from the command line when debugging +# :U avoids problems from := below +META_MODE += ${META_MODE_XTRAS:U} + .MAKE.MODE ?= ${META_MODE} _filemon := ${.MAKE.PATH_FILEMON:U/dev/filemon} .if empty(UPDATE_DEPENDFILE) _make_mode := ${.MAKE.MODE} ${META_MODE} .if ${_make_mode:M*read*} != "" || ${_make_mode:M*nofilemon*} != "" # tell everyone we are not updating Makefile.depend* UPDATE_DEPENDFILE = NO .export UPDATE_DEPENDFILE .endif .if ${_filemon:T:Mfilemon} == "filemon" .if ${UPDATE_DEPENDFILE:Uyes:tl} == "no" && !exists(${_filemon}) # we should not get upset META_MODE += nofilemon .export META_MODE .endif .endif .endif .if !defined(NO_SILENT) .if ${MAKE_VERSION} > 20110818 # only be silent when we have a .meta file META_MODE += silent=yes .else .SILENT: .endif .endif .if ${MK_DIRDEPS_BUILD:Uno} == "yes" .if !defined(META2DEPS) .if defined(PYTHON) && exists(${PYTHON}) # we prefer the python version of this - it is much faster META2DEPS ?= ${.PARSEDIR}/meta2deps.py .else META2DEPS ?= ${.PARSEDIR}/meta2deps.sh .endif META2DEPS := ${META2DEPS} .export META2DEPS .endif MAKE_PRINT_VAR_ON_ERROR += \ .ERROR_TARGET \ .ERROR_EXIT \ .ERROR_META_FILE \ .MAKE.LEVEL \ MAKEFILE \ .MAKE.MODE MK_META_ERROR_TARGET = yes .endif .if ${MK_META_ERROR_TARGET:Uno} == "yes" .if !defined(SB) && defined(SRCTOP) SB = ${SRCTOP:H} .endif ERROR_LOGDIR ?= ${SB}/error meta_error_log = ${ERROR_LOGDIR}/meta-${.MAKE.PID}.log +.if ${.MAKE.LEVEL} == 0 && !empty(NEWLOG_SH) && exists(${ERROR_LOGDIR}) +.BEGIN: _rotateErrorLog +_rotateErrorLog: .NOMETA .NOTMAIN + @${NEWLOG_SH} -d -S -n ${ERROR_LOG_GENS:U4} ${ERROR_LOGDIR} +.endif + .ERROR: _metaError # We are interested here in the target(s) that caused the build to fail. # We want to ignore targets that were "aborted" due to failure # elsewhere per the message below or a sub-make may just exit 6. _metaError: .NOMETA .NOTMAIN -@[ ${.ERROR_EXIT:U0} = 6 ] && exit 0; \ [ "${.ERROR_META_FILE}" ] && { \ grep -q 'failure has been detected in another branch' ${.ERROR_META_FILE} && exit 0; \ mkdir -p ${meta_error_log:H}; \ cp ${.ERROR_META_FILE} ${meta_error_log}; \ echo "ERROR: log ${meta_error_log}" >&2; }; : .endif .endif # Are we, after all, in meta mode? .if ${.MAKE.MODE:Uno:Mmeta*} != "" MKDEP_MK ?= meta.autodep.mk # we can afford to use cookies to prevent some targets # re-running needlessly META_COOKIE_TOUCH?= touch ${COOKIE.${.TARGET}:U${.OBJDIR}/${.TARGET:T}} META_NOPHONY= META_NOECHO= : # some targets involve old pre-built targets # ignore mtime of shell # and mtime of makefiles does not matter in meta mode .MAKE.META.IGNORE_PATHS += \ ${MAKEFILE} \ ${MAKE_SHELL} \ ${SHELL} \ ${SYS_MK_DIR} \ .if ${UPDATE_DEPENDFILE:Uyes:tl} != "no" .if ${.MAKEFLAGS:Uno:M-k} != "" # make this more obvious .warning Setting UPDATE_DEPENDFILE=NO due to -k UPDATE_DEPENDFILE= NO .export UPDATE_DEPENDFILE .elif ${_filemon:T} == "filemon" && !exists(${_filemon}) .error ${.newline}ERROR: The filemon module (${_filemon}) is not loaded. .endif .endif .else # in meta mode? META_COOKIE_TOUCH= # some targets need to be .PHONY in non-meta mode META_NOPHONY= .PHONY META_NOECHO= echo .endif # in meta mode? .-include diff --git a/contrib/bmake/mk/meta2deps.py b/contrib/bmake/mk/meta2deps.py index f188d0f01de8..44c752d0e7eb 100755 --- a/contrib/bmake/mk/meta2deps.py +++ b/contrib/bmake/mk/meta2deps.py @@ -1,831 +1,839 @@ #!/usr/bin/env python from __future__ import print_function """ This script parses each "meta" file and extracts the information needed to deduce build and src dependencies. It works much the same as the original shell script, but is *much* more efficient. The parsing work is handled by the class MetaFile. We only pay attention to a subset of the information in the "meta" files. Specifically: 'CWD' to initialize our notion. 'C' to track chdir(2) on a per process basis 'R' files read are what we really care about. directories read, provide a clue to resolving subsequent relative paths. That is if we cannot find them relative to 'cwd', we check relative to the last dir read. 'W' files opened for write or read-write, for filemon V3 and earlier. 'E' files executed. 'L' files linked 'V' the filemon version, this record is used as a clue that we have reached the interesting bit. """ """ SPDX-License-Identifier: BSD-2-Clause RCSid: - $Id: meta2deps.py,v 1.47 2024/02/17 17:26:57 sjg Exp $ + $Id: meta2deps.py,v 1.50 2024/09/27 00:08:36 sjg Exp $ Copyright (c) 2011-2020, Simon J. Gerraty Copyright (c) 2011-2017, Juniper Networks, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import os import re import sys import stat def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. Cleanup any leading ``./`` and trailing ``/.`` """ while path.endswith('/.'): path = path[0:-2] if len(path) > 0 and path[0] == '/': if os.path.exists(path): return path if debug > 2: print("skipping non-existent:", path, file=debug_out) return None if path == '.': return cwd if path.startswith('./'): while path.startswith('./'): path = path[1:] return cwd + path if last_dir == cwd: last_dir = None for d in [last_dir, cwd]: if not d: continue if path == '..': dw = d.split('/') p = '/'.join(dw[:-1]) if not p: p = '/' return p p = '/'.join([d,path]) if debug > 2: print("looking for:", p, end=' ', file=debug_out) if not os.path.exists(p): if debug > 2: print("nope", file=debug_out) p = None continue if debug > 2: print("found:", p, file=debug_out) return p return None def cleanpath(path): """cleanup path without using realpath(3)""" if path.startswith('/'): r = '/' else: r = '' p = [] w = path.split('/') for d in w: if not d or d == '.': continue if d == '..': try: p.pop() continue except: break p.append(d) return r + '/'.join(p) def abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): """ Return an absolute path, resolving via cwd or last_dir if needed. this gets called a lot, so we try to avoid calling realpath. """ rpath = resolve(path, cwd, last_dir, debug, debug_out) if rpath: path = rpath elif len(path) > 0 and path[0] == '/': return None if (path.find('/') < 0 or path.find('./') > 0 or path.find('/../') > 0 or path.endswith('/..')): path = cleanpath(path) return path def sort_unique(list, cmp=None, key=None, reverse=False): if sys.version_info[0] == 2: list.sort(cmp, key, reverse) else: list.sort(reverse=reverse) nl = [] le = None for e in list: if e == le: continue le = e nl.append(e) return nl def add_trims(x): return ['/' + x + '/', '/' + x, x + '/', x] def target_spec_exts(target_spec): """return a list of dirdep extensions that could match target_spec""" if target_spec.find(',') < 0: return ['.'+target_spec] w = target_spec.split(',') n = len(w) e = [] while n > 0: e.append('.'+','.join(w[0:n])) n -= 1 return e class MetaFile: """class to parse meta files generated by bmake.""" conf = None dirdep_re = None host_target = None srctops = [] objroots = [] excludes = [] seen = {} obj_deps = [] src_deps = [] file_deps = [] def __init__(self, name, conf={}): """if name is set we will parse it now. conf can have the follwing keys: SRCTOPS list of tops of the src tree(s). CURDIR the src directory 'bmake' was run from. RELDIR the relative path from SRCTOP to CURDIR MACHINE the machine we built for. set to 'none' if we are not cross-building. More specifically if machine cannot be deduced from objdirs. TARGET_SPEC Sometimes MACHINE isn't enough. HOST_TARGET when we build for the pseudo machine 'host' the object tree uses HOST_TARGET rather than MACHINE. OBJROOTS a list of the common prefix for all obj dirs it might end in '/' or '-'. DPDEPS names an optional file to which per file dependencies will be appended. For example if 'some/path/foo.h' is read from SRCTOP then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output. This can allow 'bmake' to learn all the dirs within the tree that depend on 'foo.h' EXCLUDES A list of paths to ignore. ccache(1) can otherwise be trouble. debug desired debug level debug_out open file to send debug output to (sys.stderr) """ self.name = name self.debug = conf.get('debug', 0) self.debug_out = conf.get('debug_out', sys.stderr) self.machine = conf.get('MACHINE', '') self.machine_arch = conf.get('MACHINE_ARCH', '') self.target_spec = conf.get('TARGET_SPEC', self.machine) self.exts = target_spec_exts(self.target_spec) self.curdir = conf.get('CURDIR') self.reldir = conf.get('RELDIR') self.dpdeps = conf.get('DPDEPS') self.pids = {} self.line = 0 if not self.conf: # some of the steps below we want to do only once self.conf = conf self.host_target = conf.get('HOST_TARGET') for srctop in conf.get('SRCTOPS', []): if srctop[-1] != '/': srctop += '/' if not srctop in self.srctops: self.srctops.append(srctop) _srctop = os.path.realpath(srctop) if _srctop[-1] != '/': _srctop += '/' if not _srctop in self.srctops: self.srctops.append(_srctop) trim_list = add_trims(self.machine) if self.machine == 'host': trim_list += add_trims(self.host_target) if self.target_spec != self.machine: trim_list += add_trims(self.target_spec) for objroot in conf.get('OBJROOTS', []): for e in trim_list: if objroot.endswith(e): # this is not what we want - fix it objroot = objroot[0:-len(e)] if objroot[-1] != '/': objroot += '/' if not objroot in self.objroots: self.objroots.append(objroot) _objroot = os.path.realpath(objroot) if objroot[-1] == '/': _objroot += '/' if not _objroot in self.objroots: self.objroots.append(_objroot) + self.sb = conf.get('SB', '') # we want the longest match self.srctops.sort(reverse=True) self.objroots.sort(reverse=True) self.excludes = conf.get('EXCLUDES', []) if self.debug: print("host_target=", self.host_target, file=self.debug_out) print("srctops=", self.srctops, file=self.debug_out) print("objroots=", self.objroots, file=self.debug_out) print("excludes=", self.excludes, file=self.debug_out) print("ext_list=", self.exts, file=self.debug_out) self.dirdep_re = re.compile(r'([^/]+)/(.+)') if self.dpdeps and not self.reldir: if self.debug: print("need reldir:", end=' ', file=self.debug_out) if self.curdir: srctop = self.find_top(self.curdir, self.srctops) if srctop: self.reldir = self.curdir.replace(srctop,'') if self.debug: print(self.reldir, file=self.debug_out) if not self.reldir: self.dpdeps = None # we cannot do it? self.cwd = os.getcwd() # make sure this is initialized self.last_dir = self.cwd if name: self.try_parse() def reset(self): """reset state if we are being passed meta files from multiple directories.""" self.seen = {} self.obj_deps = [] self.src_deps = [] self.file_deps = [] def dirdeps(self, sep='\n'): """return DIRDEPS""" return sep.strip() + sep.join(self.obj_deps) def src_dirdeps(self, sep='\n'): """return SRC_DIRDEPS""" return sep.strip() + sep.join(self.src_deps) def file_depends(self, out=None): """Append DPDEPS_${file} += ${RELDIR} for each file we saw, to the output file.""" if not self.reldir: return None for f in sort_unique(self.file_deps): print('DPDEPS_%s += %s' % (f, self.reldir), file=out) # these entries provide for reverse DIRDEPS lookup for f in self.obj_deps: print('DEPDIRS_%s += %s' % (f, self.reldir), file=out) def seenit(self, dir): """rememer that we have seen dir.""" self.seen[dir] = 1 def add(self, list, data, clue=''): """add data to list if it isn't already there.""" if data not in list: list.append(data) if self.debug: print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out) def find_top(self, path, list): """the logical tree may be split across multiple trees""" for top in list: if path.startswith(top): if self.debug > 2: print("found in", top, file=self.debug_out) return top return None def find_obj(self, objroot, dir, path, input): """return path within objroot, taking care of .dirdep files""" ddep = None for ddepf in [path + '.dirdep', dir + '/.dirdep']: if not ddep and os.path.exists(ddepf): ddep = open(ddepf, 'r').readline().strip('# \n') if self.debug > 1: print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out) for e in self.exts: if ddep.endswith(e): ddep = ddep[0:-len(e)] break if not ddep: # no .dirdeps, so remember that we've seen the raw input self.seenit(input) self.seenit(dir) if self.machine == 'none': if dir.startswith(objroot): return dir.replace(objroot,'') return None m = self.dirdep_re.match(dir.replace(objroot,'')) if m: ddep = m.group(2) dmachine = m.group(1) if dmachine != self.machine: if not (self.machine == 'host' and dmachine == self.host_target): if self.debug > 2: print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out) ddep += '.' + dmachine return ddep def try_parse(self, name=None, file=None): """give file and line number causing exception""" try: self.parse(name, file) except: # give a useful clue print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr) raise def parse(self, name=None, file=None): """A meta file looks like: # Meta data file "path" CMD "command-line" CWD "cwd" TARGET "target" -- command output -- -- filemon acquired metadata -- # buildmon version 3 V 3 C "pid" "cwd" E "pid" "path" F "pid" "child" R "pid" "path" W "pid" "path" X "pid" "status" D "pid" "path" L "pid" "src" "target" M "pid" "old" "new" S "pid" "path" # Bye bye We go to some effort to avoid processing a dependency more than once. Of the above record types only C,E,F,L,R,V and W are of interest. """ version = 0 # unknown if name: self.name = name; if file: f = file cwd = self.last_dir = self.cwd else: f = open(self.name, 'r') skip = True pid_cwd = {} pid_last_dir = {} last_pid = 0 eof_token = False self.line = 0 if self.curdir: self.seenit(self.curdir) # we ignore this + if self.sb and self.name.startswith(self.sb): + error_name = self.name.replace(self.sb+'/','') + else: + error_name = self.name interesting = '#CEFLRVX' for line in f: self.line += 1 # ignore anything we don't care about if not line[0] in interesting: continue if self.debug > 2: print("input:", line, end=' ', file=self.debug_out) w = line.split() if skip: if w[0] == 'V': skip = False version = int(w[1]) """ if version < 4: # we cannot ignore 'W' records # as they may be 'rw' interesting += 'W' """ elif w[0] == 'CWD': self.cwd = cwd = self.last_dir = w[1] self.seenit(cwd) # ignore this if self.debug: print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out) continue if w[0] == '#': # check the file has not been truncated if line.find('Bye') > 0: eof_token = True continue pid = int(w[1]) if pid != last_pid: if last_pid: pid_last_dir[last_pid] = self.last_dir cwd = pid_cwd.get(pid, self.cwd) self.last_dir = pid_last_dir.get(pid, self.cwd) last_pid = pid # process operations if w[0] == 'F': npid = int(w[2]) pid_cwd[npid] = cwd pid_last_dir[npid] = cwd last_pid = npid continue elif w[0] == 'C': cwd = abspath(w[2], cwd, None, self.debug, self.debug_out) if not cwd: cwd = w[2] if self.debug > 1: print("missing cwd=", cwd, file=self.debug_out) if cwd.endswith('/.'): cwd = cwd[0:-2] self.last_dir = pid_last_dir[pid] = cwd pid_cwd[pid] = cwd if self.debug > 1: print("cwd=", cwd, file=self.debug_out) continue if w[0] == 'X': try: del self.pids[pid] except KeyError: pass continue if w[2] in self.seen: if self.debug > 2: print("seen:", w[2], file=self.debug_out) continue # file operations if w[0] in 'ML': # these are special, tread src as read and # target as write self.parse_path(w[2].strip("'"), cwd, 'R', w) self.parse_path(w[3].strip("'"), cwd, 'W', w) continue elif w[0] in 'ERWS': path = w[2] if w[0] == 'E': self.pids[pid] = path elif path == '.': continue self.parse_path(path, cwd, w[0], w) if version == 0: - raise AssertionError('missing filemon data') + raise AssertionError('missing filemon data: {}'.format(error_name)) if not eof_token: - raise AssertionError('truncated filemon data') + raise AssertionError('truncated filemon data: {}'.format(error_name)) setid_pids = [] # self.pids should be empty! for pid,path in self.pids.items(): try: # no guarantee that path is still valid if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID): # we do not expect anything after Exec setid_pids.append(pid) continue except: # we do not care why the above fails, # we do not want to miss the ERROR below. pass print("ERROR: missing eXit for {} pid {}".format(path, pid)) for pid in setid_pids: del self.pids[pid] - assert(len(self.pids) == 0) + if len(self.pids) > 0: + raise AssertionError('bad filemon data - missing eXits: {}'.format(error_name)) if not file: f.close() def is_src(self, base, dir, rdir): """is base in srctop""" for dir in [dir,rdir]: if not dir: continue path = '/'.join([dir,base]) srctop = self.find_top(path, self.srctops) if srctop: if self.dpdeps: self.add(self.file_deps, path.replace(srctop,''), 'file') self.add(self.src_deps, dir.replace(srctop,''), 'src') self.seenit(dir) return True return False def parse_path(self, path, cwd, op=None, w=[]): """look at a path for the op specified""" if not op: op = w[0] # we are never interested in .dirdep files as dependencies if path.endswith('.dirdep'): return for p in self.excludes: if p and path.startswith(p): if self.debug > 2: print("exclude:", p, path, file=self.debug_out) return # we don't want to resolve the last component if it is # a symlink path = resolve(path, cwd, self.last_dir, self.debug, self.debug_out) if not path: return dir,base = os.path.split(path) if dir in self.seen: if self.debug > 2: print("seen:", dir, file=self.debug_out) return # we can have a path in an objdir which is a link # to the src dir, we may need to add dependencies for each rdir = dir dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) if dir: rdir = os.path.realpath(dir) else: dir = rdir if rdir == dir: rdir = None # now put path back together path = '/'.join([dir,base]) if self.debug > 1: print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) if op in 'RWS': if path in [self.last_dir, cwd, self.cwd, self.curdir]: if self.debug > 1: print("skipping:", path, file=self.debug_out) return if os.path.isdir(path): if op in 'RW': self.last_dir = path; if self.debug > 1: print("ldir=", self.last_dir, file=self.debug_out) return if op in 'ER': # finally, we get down to it if dir == self.cwd or dir == self.curdir: return if self.is_src(base, dir, rdir): self.seenit(w[2]) if not rdir: return objroot = None for dir in [dir,rdir]: if not dir: continue objroot = self.find_top(dir, self.objroots) if objroot: break if objroot: ddep = self.find_obj(objroot, dir, path, w[2]) if ddep: self.add(self.obj_deps, ddep, 'obj') if self.dpdeps and objroot.endswith('/stage/'): sp = '/'.join(path.replace(objroot,'').split('/')[1:]) self.add(self.file_deps, sp, 'file') else: # don't waste time looking again self.seenit(w[2]) self.seenit(dir) def main(argv, klass=MetaFile, xopts='', xoptf=None): """Simple driver for class MetaFile. Usage: script [options] [key=value ...] "meta" ... Options and key=value pairs contribute to the dictionary passed to MetaFile. -S "SRCTOP" add "SRCTOP" to the "SRCTOPS" list. -C "CURDIR" -O "OBJROOT" add "OBJROOT" to the "OBJROOTS" list. -m "MACHINE" -a "MACHINE_ARCH" -H "HOST_TARGET" -D "DPDEPS" -d bumps debug level """ import getopt # import Psyco if we can # it can speed things up quite a bit have_psyco = 0 try: import psyco psyco.full() have_psyco = 1 except: pass conf = { 'SRCTOPS': [], 'OBJROOTS': [], 'EXCLUDES': [], } + conf['SB'] = os.getenv('SB', '') + try: machine = os.environ['MACHINE'] if machine: conf['MACHINE'] = machine machine_arch = os.environ['MACHINE_ARCH'] if machine_arch: conf['MACHINE_ARCH'] = machine_arch srctop = os.environ['SB_SRC'] if srctop: conf['SRCTOPS'].append(srctop) objroot = os.environ['SB_OBJROOT'] if objroot: conf['OBJROOTS'].append(objroot) except: pass debug = 0 output = True opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts) for o, a in opts: if o == '-a': conf['MACHINE_ARCH'] = a elif o == '-d': debug += 1 elif o == '-q': output = False elif o == '-H': conf['HOST_TARGET'] = a elif o == '-S': if a not in conf['SRCTOPS']: conf['SRCTOPS'].append(a) elif o == '-C': conf['CURDIR'] = a elif o == '-O': if a not in conf['OBJROOTS']: conf['OBJROOTS'].append(a) elif o == '-R': conf['RELDIR'] = a elif o == '-D': conf['DPDEPS'] = a elif o == '-m': conf['MACHINE'] = a elif o == '-T': conf['TARGET_SPEC'] = a elif o == '-X': if a not in conf['EXCLUDES']: conf['EXCLUDES'].append(a) elif xoptf: xoptf(o, a, conf) conf['debug'] = debug # get any var=val assignments eaten = [] for a in args: if a.find('=') > 0: k,v = a.split('=') if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']: if k == 'SRCTOP': k = 'SRCTOPS' elif k == 'OBJROOT': k = 'OBJROOTS' if v not in conf[k]: conf[k].append(v) else: conf[k] = v eaten.append(a) continue break for a in eaten: args.remove(a) debug_out = conf.get('debug_out', sys.stderr) if debug: print("config:", file=debug_out) print("psyco=", have_psyco, file=debug_out) for k,v in list(conf.items()): print("%s=%s" % (k,v), file=debug_out) m = None for a in args: if a.endswith('.meta'): if not os.path.exists(a): continue m = klass(a, conf) elif a.startswith('@'): # there can actually multiple files per line for line in open(a[1:]): for f in line.strip().split(): if not os.path.exists(f): continue m = klass(f, conf) if output and m: print(m.dirdeps()) print(m.src_dirdeps('\nsrc:')) dpdeps = conf.get('DPDEPS') if dpdeps: m.file_depends(open(dpdeps, 'w')) return m if __name__ == '__main__': try: main(sys.argv) except: # yes, this goes to stdout print("ERROR: ", sys.exc_info()[1]) raise diff --git a/contrib/bmake/mk/options.mk b/contrib/bmake/mk/options.mk index 363fc53baf14..07a6e0e340b2 100644 --- a/contrib/bmake/mk/options.mk +++ b/contrib/bmake/mk/options.mk @@ -1,114 +1,120 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: options.mk,v 1.20 2024/02/17 17:26:57 sjg Exp $ +# $Id: options.mk,v 1.22 2024/10/27 17:33:03 sjg Exp $ # # @(#) Copyright (c) 2012, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # Inspired by FreeBSD bsd.own.mk, but intentionally simpler and more flexible. # Options are normally listed in either OPTIONS_DEFAULT_{YES,NO} # We convert these to ${OPTION}/{yes,no} in OPTIONS_DEFAULT_VALUES. # We add the OPTIONS_DEFAULT_NO first so they take precedence. # This allows override of an OPTIONS_DEFAULT_YES by adding it to # OPTIONS_DEFAULT_NO or adding ${OPTION}/no to OPTIONS_DEFAULT_VALUES. # An OPTIONS_DEFAULT_NO option can only be overridden by putting # ${OPTION}/yes in OPTIONS_DEFAULT_VALUES. # A makefile may set NO_* (or NO*) to indicate it cannot do something. # User sets WITH_* and WITHOUT_* to indicate what they want. # We set ${OPTION_PREFIX:UMK_}* which is then all we need care about. OPTIONS_DEFAULT_VALUES += \ ${OPTIONS_DEFAULT_NO:U:O:u:S,$,/no,} \ ${OPTIONS_DEFAULT_YES:U:O:u:S,$,/yes,} OPTION_PREFIX ?= MK_ # NO_* takes precedence # If both WITH_* and WITHOUT_* are defined, WITHOUT_ wins unless # DOMINANT_* is set to "yes" # Otherwise WITH_* and WITHOUT_* override the default. .for o in ${OPTIONS_DEFAULT_VALUES:M*/*} .if defined(WITH_${o:H}) && ${WITH_${o:H}} == "no" # a common miss-use - point out correct usage .warning use WITHOUT_${o:H}=1 not WITH_${o:H}=no .endif .if defined(NO_${o:H}) || defined(NO${o:H}) # we cannot do it ${OPTION_PREFIX}${o:H} ?= no .elif defined(WITH_${o:H}) && defined(WITHOUT_${o:H}) # normally WITHOUT_ wins DOMINANT_${o:H} ?= no ${OPTION_PREFIX}${o:H} ?= ${DOMINANT_${o:H}} .elif ${o:T:tl} == "no" .if defined(WITH_${o:H}) ${OPTION_PREFIX}${o:H} ?= yes .else ${OPTION_PREFIX}${o:H} ?= no .endif .else .if defined(WITHOUT_${o:H}) ${OPTION_PREFIX}${o:H} ?= no .else ${OPTION_PREFIX}${o:H} ?= yes .endif .endif +.if defined(DEBUG_OPTIONS) && ${DEBUG_OPTIONS:@x@${o:H:M$x}@} != "" +.info ${.INCLUDEDFROMFILE}: ${OPTION_PREFIX}${o:H}=${${OPTION_PREFIX}${o:H}} +.endif .endfor # OPTIONS_DEFAULT_DEPENDENT += FOO_UTILS/FOO # If neither WITH[OUT]_FOO_UTILS is set, (see rules above) # use the value of ${OPTION_PREFIX}FOO .for o in ${OPTIONS_DEFAULT_DEPENDENT:M*/*:O:u} .if defined(NO_${o:H}) || defined(NO${o:H}) # we cannot do it ${OPTION_PREFIX}${o:H} ?= no .elif defined(WITH_${o:H}) && defined(WITHOUT_${o:H}) # normally WITHOUT_ wins DOMINANT_${o:H} ?= no ${OPTION_PREFIX}${o:H} ?= ${DOMINANT_${o:H}} .elif defined(WITH_${o:H}) ${OPTION_PREFIX}${o:H} ?= yes .elif defined(WITHOUT_${o:H}) ${OPTION_PREFIX}${o:H} ?= no .else ${OPTION_PREFIX}${o:H} ?= ${${OPTION_PREFIX}${o:T}} .endif +.if defined(DEBUG_OPTIONS) && ${DEBUG_OPTIONS:@x@${o:H:M$x}@} != "" +.info ${.INCLUDEDFROMFILE}: ${OPTION_PREFIX}${o:H}=${${OPTION_PREFIX}${o:H}} (${OPTION_PREFIX}${o:T}=${${OPTION_PREFIX}${o:T}}) +.endif .endfor # allow displaying/describing set options .set_options := ${.set_options} \ ${OPTIONS_DEFAULT_VALUES:H:N.} \ ${OPTIONS_DEFAULT_DEPENDENT:U:H:N.} \ # this can be used in .info as well as target below OPTIONS_SHOW ?= ${.set_options:O:u:@o@${OPTION_PREFIX}$o=${${OPTION_PREFIX}$o}@} # prefix for variables describing options OPTION_DESCRIPTION_PREFIX ?= DESCRIPTION_ OPTION_DESCRIPTION_SEPARATOR ?= == OPTIONS_DESCRIBE ?= ${.set_options:O:u:@o@${OPTION_PREFIX}$o=${${OPTION_PREFIX}$o}${${OPTION_DESCRIPTION_PREFIX}$o:S,^, ${OPTION_DESCRIPTION_SEPARATOR} ,1}${.newline}@} .if !commands(show-options) show-options: .NOTMAIN .PHONY @echo; echo "${OPTIONS_SHOW:ts\n}"; echo .endif .if !commands(describe-options) describe-options: .NOTMAIN .PHONY @echo; echo "${OPTIONS_DESCRIBE}"; echo .endif # we expect to be included more than once .undef OPTIONS_DEFAULT_DEPENDENT .undef OPTIONS_DEFAULT_NO .undef OPTIONS_DEFAULT_VALUES .undef OPTIONS_DEFAULT_YES diff --git a/contrib/bmake/mk/own.mk b/contrib/bmake/mk/own.mk index 72b2f70d4bbe..f3014f9e7adf 100644 --- a/contrib/bmake/mk/own.mk +++ b/contrib/bmake/mk/own.mk @@ -1,255 +1,258 @@ -# $Id: own.mk,v 1.48 2024/04/09 21:52:52 sjg Exp $ +# $Id: own.mk,v 1.51 2024/11/12 17:40:13 sjg Exp $ # should be set properly in sys.mk _this ?= ${.PARSEFILE:S,bsd.,,} .if !target(__${_this}__) __${_this}__: .NOTMAIN .if !target(__init.mk__) .include "init.mk" .endif .if !defined(NOMAKECONF) && !defined(NO_MAKECONF) MAKECONF?= /etc/mk.conf .-include "${MAKECONF}" .endif .include TARGET_OSNAME?= ${_HOST_OSNAME} TARGET_OSREL?= ${_HOST_OSREL} TARGET_OSTYPE?= ${HOST_OSTYPE} TARGET_HOST?= ${HOST_TARGET} # these may or may not exist .-include <${TARGET_HOST}.mk> .-include RM?= rm LN?= ln INSTALL?= install prefix?= /usr .if exists(${prefix}/lib) libprefix?= ${prefix} .else libprefix?= /usr .endif # FreeBSD at least does not set this MACHINE_ARCH?= ${MACHINE} # we need to make sure these are defined too in case sys.mk fails to. # for suffix rules IMPFLAGS?= ${COPTS.${.IMPSRC:T}} ${CPUFLAGS.${.IMPSRC:T}} ${CPPFLAGS.${.IMPSRC:T}} .for s in .c .cc COMPILE.$s += ${IMPFLAGS} LINK.$s += ${IMPFLAGS} .endfor PRINT.VAR.MAKE = MAKESYSPATH=${MAKESYSPATH:U${.PARSEDIR}} ${.MAKE} .if empty(.MAKEFLAGS:M-V*) .if defined(MAKEOBJDIRPREFIX) || defined(MAKEOBJDIR) PRINTOBJDIR= ${PRINT.VAR.MAKE} -r -V .OBJDIR -f /dev/null xxx .else PRINTOBJDIR= ${PRINT.VAR.MAKE} -V .OBJDIR .endif .else PRINTOBJDIR= echo # prevent infinite recursion .endif # we really like to have SRCTOP and OBJTOP defined... .if !defined(SRCTOP) || !defined(OBJTOP) .-include .endif .if !defined(SRCTOP) || !defined(OBJTOP) # dpadd.mk is rather pointless without these OPTIONS_DEFAULT_NO+= DPADD_MK .endif # process options OPTIONS_DEFAULT_NO+= \ + DEBUG \ INSTALL_AS_USER \ GPROF \ PROG_LDORDER_MK \ LIBTOOL \ LINT \ OPTIONS_DEFAULT_YES+= \ ARCHIVE \ AUTODEP \ CRYPTO \ DOC \ DPADD_MK \ GDB \ KERBEROS \ LINKLIB \ MAN \ NLS \ OBJ \ PIC \ SHARE \ SKEY \ YP \ OPTIONS_DEFAULT_DEPENDENT+= \ CATPAGES/MAN \ + DEBUG_RUST/DEBUG \ LDORDER_MK/PROG_LDORDER_MK \ OBJDIRS/OBJ \ PICINSTALL/LINKLIB \ PICLIB/PIC \ PROFILE/LINKLIB \ STAGING_MAN/STAGING \ STAGING_PROG/STAGING \ + STAGING_RUST/STAGING_PROG \ .include .if ${MK_INSTALL_AS_USER} == "yes" # We ignore this if user is root. _uid:= ${.MAKE.UID:U${id -u:L:sh}} .if ${_uid} != 0 .if !defined(USERGRP) USERGRP:= ${.MAKE.GID:U${id -g:L:sh}} .export USERGRP .endif .for x in BIN CONF DOC INC INFO FILES KMOD LIB MAN NLS PROG SHARE $xOWN= ${USER} $xGRP= ${USERGRP} $x_INSTALL_OWN= .endfor .endif .endif # override this in sys.mk ROOT_GROUP?= wheel BINGRP?= ${ROOT_GROUP} BINOWN?= root BINMODE?= 555 NONBINMODE?= 444 DIRMODE?= 755 INCLUDEDIR?= ${prefix}/include INCDIR?= ${INCLUDEDIR} # Define MANZ to have the man pages compressed (gzip) #MANZ= 1 MANDIR?= ${prefix}/share/man MANGRP?= ${BINGRP} MANOWN?= ${BINOWN} MANMODE?= ${NONBINMODE} INCLUDEDIR?= ${libprefix}/include LIBDIR?= ${libprefix}/lib SHLIBDIR?= ${libprefix}/lib .if ${USE_SHLIBDIR:Uno} == "yes" _LIBSODIR?= ${SHLIBDIR} .else _LIBSODIR?= ${LIBDIR} .endif # this is where ld.*so lives SHLINKDIR?= /usr/libexec LINTLIBDIR?= ${libprefix}/libdata/lint LIBGRP?= ${BINGRP} LIBOWN?= ${BINOWN} LIBMODE?= ${NONBINMODE} DOCDIR?= ${prefix}/share/doc DOCGRP?= ${BINGRP} DOCOWN?= ${BINOWN} DOCMODE?= ${NONBINMODE} NLSDIR?= ${prefix}/share/nls NLSGRP?= ${BINGRP} NLSOWN?= ${BINOWN} NLSMODE?= ${NONBINMODE} KMODDIR?= ${prefix}/lkm KMODGRP?= ${BINGRP} KMODOWN?= ${BINOWN} KMODMODE?= ${NONBINMODE} SHAREGRP?= ${BINGRP} SHAREOWN?= ${BINOWN} SHAREMODE?= ${NONBINMODE} COPY?= -c STRIP_FLAG?= -s .if ${TARGET_OSNAME} == "NetBSD" .if exists(/usr/libexec/ld.elf_so) OBJECT_FMT=ELF .endif OBJECT_FMT?=a.out .endif # sys.mk should set something appropriate if need be. OBJECT_FMT?=ELF .if (${_HOST_OSNAME} == "FreeBSD") CFLAGS+= ${CPPFLAGS} .endif # allow for per target flags # apply the :T:R first, so the more specific :T can override if needed CPPFLAGS += ${CPPFLAGS_${.TARGET:T:R}} ${CPPFLAGS_${.TARGET:T}} CFLAGS += ${CFLAGS_${.TARGET:T:R}} ${CFLAGS_${.TARGET:T}} # Define SYS_INCLUDE to indicate whether you want symbolic links to the system # source (``symlinks''), or a separate copy (``copies''); (latter useful # in environments where it's not possible to keep /sys publicly readable) #SYS_INCLUDE= symlinks # don't try to generate PIC versions of libraries on machines # which don't support PIC. .if (${MACHINE_ARCH} == "vax") || \ ((${MACHINE_ARCH} == "mips") && defined(STATIC_TOOLCHAIN)) || \ ((${MACHINE_ARCH} == "alpha") && defined(ECOFF_TOOLCHAIN)) MK_PIC=no .endif # No lint, for now. NOLINT= .if ${MK_LINKLIB} == "no" MK_PICINSTALL= no MK_PROFILE= no .endif .if ${MK_MAN} == "no" MK_CATPAGES= no .endif .if ${MK_OBJ} == "no" MK_OBJDIRS= no MK_AUTO_OBJ= no .endif .if ${MK_SHARE} == "no" MK_CATPAGES= no MK_DOC= no MK_INFO= no MK_MAN= no MK_NLS= no .endif # :U incase not using our sys.mk .if ${MK_META_MODE:Uno} == "yes" # should all be set by sys.mk if not default TARGET_SPEC_VARS ?= MACHINE .if ${MAKE_VERSION} >= 20120325 .if ${TARGET_SPEC_VARS:[#]} > 1 TARGET_SPEC_VARS_REV := ${TARGET_SPEC_VARS:[-1..1]} .else TARGET_SPEC_VARS_REV = ${TARGET_SPEC_VARS} .endif .endif .if ${MK_STAGING} == "yes" STAGE_ROOT?= ${OBJROOT}/stage -STAGE_OBJTOP?= ${STAGE_ROOT}/${TARGET_SPEC_VARS_REV:ts/} +STAGE_OBJTOP?= ${STAGE_ROOT}/${TARGET_SPEC_VARS_REV:@v@${$v}@:ts/} .endif .endif .endif diff --git a/contrib/bmake/mk/prog.mk b/contrib/bmake/mk/prog.mk index aac2a4d3da28..119645c49859 100644 --- a/contrib/bmake/mk/prog.mk +++ b/contrib/bmake/mk/prog.mk @@ -1,268 +1,270 @@ -# $Id: prog.mk,v 1.44 2024/02/19 00:06:19 sjg Exp $ +# $Id: prog.mk,v 1.45 2024/12/12 19:56:36 sjg Exp $ # should be set properly in sys.mk _this ?= ${.PARSEFILE:S,bsd.,,} .if !target(__${_this}__) __${_this}__: .NOTMAIN .include # FreeBSD at least expects MAN8 etc. .if defined(MAN) && !empty(MAN) _sect:=${MAN:E} MAN${_sect}=${MAN} .endif .SUFFIXES: .out .o .c ${CXX_SUFFIXES} .y .l ${CCM_SUFFIXES} ${PCM} CFLAGS+= ${COPTS} .if ${TARGET_OSNAME} == "NetBSD" .if ${MACHINE_ARCH} == "sparc64" CFLAGS+= -mcmodel=medlow .endif # ELF platforms depend on crtbegin.o and crtend.o .if ${OBJECT_FMT} == "ELF" .ifndef LIBCRTBEGIN LIBCRTBEGIN= ${DESTDIR}/usr/lib/crtbegin.o .MADE: ${LIBCRTBEGIN} .endif .ifndef LIBCRTEND LIBCRTEND= ${DESTDIR}/usr/lib/crtend.o .MADE: ${LIBCRTEND} .endif _SHLINKER= ${SHLINKDIR}/ld.elf_so .else LIBCRTBEGIN?= LIBCRTEND?= _SHLINKER= ${SHLINKDIR}/ld.so .endif .ifndef LIBCRT0 LIBCRT0= ${DESTDIR}/usr/lib/crt0.o .MADE: ${LIBCRT0} .endif .endif # NetBSD # here is where you can define what LIB* are .-include .if ${MK_DPADD_MK} == "yes" # lots of cool magic, but might not suit everyone. .include .endif .if ${MK_GPROF} == "yes" CFLAGS+= ${CC_PG} ${PROFFLAGS} LDADD+= ${CC_PG} .if ${MK_DPADD_MK} == "no" LDADD_LIBC_P?= -lc_p LDADD_LAST+= ${LDADD_LIBC_P} .endif .endif .if defined(SHAREDSTRINGS) CLEANFILES+=strings .c.o: @${COMPILE.c:N-c} -E ${.IMPSRC} | xstr -c - @${COMPILE.c} x.c -o ${.TARGET} @rm -f x.c # precompiled C++ Modules ${CCM_SUFFIXES:%=%${PCM}}: @${COMIPILE.cc:N-c} -E ${.IMPSRC} | xstr -c - @mv -f x.c x.cc @${COMPILE.pcm} x.cc -o ${.TARGET} @rm -f x.cc ${CXX_SUFFIXES:N.c*m:%=%.o}: @${COMIPILE.cc:N-c} -E ${.IMPSRC} | xstr -c - @mv -f x.c x.cc @${COMPILE.cc} x.cc -o ${.TARGET} @rm -f x.cc .endif .if defined(PROG_CXX) PROG= ${PROG_CXX} _SUPCXX?= -lstdc++ -lm .endif .if defined(PROG) BINDIR ?= ${prefix}/bin .if empty(SRCS) # init.mk handling of QUALIFIED_VAR_LIST means # SRCS will be defined - even if empty. SRCS = ${PROG}.c .endif SRCS ?= ${PROG}.c -OBJS_SRCS = ${SRCS:${OBJS_SRCS_FILTER}} -.for s in ${OBJS_SRCS:M*/*} -${.o .po .lo:L:@o@${s:T:R}$o@}: $s + +.for s in ${SRCS:${OBJS_SRCS_PRE_FILTER:ts:}:M*/*} +${.o .po .lo:L:@o@${s:${OBJS_SRCS_FILTER:ts:}}$o@}: $s .endfor + +OBJS_SRCS = ${SRCS:${OBJS_SRCS_FILTER:ts:}} .if !empty(OBJS_SRCS) -OBJS+= ${OBJS_SRCS:T:R:S/$/.o/g} +OBJS+= ${OBJS_SRCS:S/$/.o/g} LOBJS+= ${LSRCS:.c=.ln} ${SRCS:M*.c:.c=.ln} .endif .if defined(OBJS) && !empty(OBJS) .NOPATH: ${OBJS} ${PROG} ${SRCS:M*.[ly]:C/\..$/.c/} ${YHEADER:D${SRCS:M*.y:.y=.h}} # this is known to work for NetBSD 1.6 and FreeBSD 4.2 .if ${TARGET_OSNAME:NFreeBSD:NNetBSD} == "" _PROGLDOPTS= .if ${SHLINKDIR} != "/usr/libexec" # XXX: change or remove if ld.so moves _PROGLDOPTS+= -Wl,-dynamic-linker=${_SHLINKER} .endif .if defined(LIBDIR) && ${SHLIBDIR} != ${LIBDIR} _PROGLDOPTS+= -Wl,-rpath-link,${DESTDIR}${SHLIBDIR}:${DESTDIR}/usr/lib \ -L${DESTDIR}${SHLIBDIR} .endif _PROGLDOPTS+= -Wl,-rpath,${SHLIBDIR}:/usr/lib .endif # NetBSD .if ${MK_PROG_LDORDER_MK} != "no" ${PROG}: ldorder .include .endif # avoid -dL errors LDADD_LDORDER ?= LDSTATIC ?= .if defined(DESTDIR) && exists(${LIBCRT0}) && ${LIBCRT0} != "/dev/null" ${PROG}: ${LIBCRT0} ${OBJS} ${LIBC} ${DPADD} ${_CCLINK} ${LDFLAGS} ${LDSTATIC} -o ${.TARGET} -nostdlib ${_PROGLDOPTS} -L${DESTDIR}/usr/lib ${LIBCRT0} ${LIBCRTBEGIN} ${OBJS} ${LDADD_LDORDER} ${LDADD} -L${DESTDIR}/usr/lib ${_SUPCXX} -lgcc -lc -lgcc ${LIBCRTEND} .else ${PROG}: ${LIBCRT0} ${OBJS} ${LIBC} ${DPADD} ${_CCLINK} ${LDFLAGS} ${LDSTATIC} -o ${.TARGET} ${_PROGLDOPTS} ${OBJS} ${LDADD_LDORDER} ${LDADD} .endif # defined(DESTDIR) .endif # defined(OBJS) && !empty(OBJS) .if !defined(MAN) MAN= ${PROG}.1 .endif # !defined(MAN) .endif # defined(PROG) .if !defined(_SKIP_BUILD) realbuild: ${PROG} .endif all: _SUBDIRUSE .if !target(clean) cleanprog: rm -f a.out [Ee]rrs mklog core *.core \ ${PROG} ${OBJS} ${LOBJS} ${CLEANFILES} clean: _SUBDIRUSE cleanprog cleandir: _SUBDIRUSE cleanprog .else cleandir: _SUBDIRUSE clean .endif .if defined(SRCS) && (!defined(MKDEP) || ${MKDEP} != autodep) afterdepend: .depend @(TMP=/tmp/_depend$$$$; \ sed -e 's/^\([^\.]*\).o[ ]*:/\1.o \1.ln:/' \ < .depend > $$TMP; \ mv $$TMP .depend) .endif .if !target(install) .if !target(beforeinstall) beforeinstall: .endif .if !target(afterinstall) afterinstall: .endif .if !empty(BINOWN) PROG_INSTALL_OWN ?= -o ${BINOWN} -g ${BINGRP} .endif .if !target(realinstall) realinstall: proginstall .endif .if !target(proginstall) proginstall: .if defined(PROG) [ -d ${DESTDIR}${BINDIR} ] || \ ${INSTALL} -d ${PROG_INSTALL_OWN} -m 775 ${DESTDIR}${BINDIR} ${INSTALL} ${COPY} ${STRIP_FLAG} ${PROG_INSTALL_OWN} -m ${BINMODE} \ ${PROG} ${DESTDIR}${BINDIR}/${PROG_NAME} .endif .if defined(HIDEGAME) (cd ${DESTDIR}/usr/games; rm -f ${PROG}; ln -s dm ${PROG}) .endif .endif .include install: maninstall install_links _SUBDIRUSE install_links: .if !empty(SYMLINKS) @set ${SYMLINKS}; ${_SYMLINKS_SCRIPT} .endif .if !empty(LINKS) @set ${LINKS}; ${_LINKS_SCRIPT} .endif maninstall: afterinstall afterinstall: realinstall install_links: realinstall proginstall: beforeinstall realinstall: beforeinstall .endif .if !target(lint) lint: ${LOBJS} .if defined(LOBJS) && !empty(LOBJS) @${LINT} ${LINTFLAGS} ${LDFLAGS:M-L*} ${LOBJS} ${LDADD} .endif .endif .NOPATH: ${PROG} .if defined(OBJS) && !empty(OBJS) .NOPATH: ${OBJS} .endif .if defined(FILES) || defined(FILESGROUPS) .include .endif .if ${MK_MAN} != "no" .include .endif .if ${MK_NLS} != "no" .include .endif .include .include .include .if !empty(PROG) && ${MK_STAGING_PROG} == "yes" STAGE_BINDIR ?= ${STAGE_OBJTOP}${BINDIR} STAGE_DIR.prog ?= ${STAGE_BINDIR} .if ${PROG_NAME:U${PROG}} != ${PROG} STAGE_AS_SETS += prog STAGE_AS_${PROG} = ${PROG_NAME} stage_as.prog: ${PROG} .else STAGE_SETS += prog stage_files.prog: ${PROG} .endif .endif .include .endif diff --git a/contrib/bmake/mk/rust.mk b/contrib/bmake/mk/rust.mk new file mode 100644 index 000000000000..5fc340239c30 --- /dev/null +++ b/contrib/bmake/mk/rust.mk @@ -0,0 +1,203 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# RCSid: +# $Id: rust.mk,v 1.37 2025/01/11 03:17:36 sjg Exp $ +# +# @(#) Copyright (c) 2024, Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +## +# This makefile is used when a build includes one or more Rust projects. +# +# We first include local.rust.mk to allow for customization. +# You can get very fancy - the logic/functionality here is minimal but +# can be extended via local.rust.mk +# +# If RUST_PROJECT_DIR (where we find Cargo.toml) is not set, we will +# make it ${.CURDIR:C,/src.*,,} actually we use +# ${SRCTOP}/${RELDIR:C,/src.*,,} to ensure we don't confuse ${SRCTOP} +# with ${RUST_PROJECT_DIR}/src. +# +# If ${.OBJDIR} is not ${.CURDIR} we will default CARGO_TARGET_DIR +# to ${.OBJDIR}. +# +# First, if ${.CURDIR} is a subdir of ${RUST_PROJECT_DIR} (will happen +# if an Emacs user does 'M-x compile' while visiting a src file) we +# will need to adjust ${.OBJDIR} (and hence CARGO_TARGET_DIR). +# +# We assume that RUST_CARGO will be used to build Rust projects, +# so we default RUST_CARGO_PROJECT_DIR to ${RUST_PROJECT_DIR} and +# provide a _CARGO_USE that we automatically associate with +# targets named 'cargo.*' the default is 'cargo.build'. +# +# _CARGO_USE will chdir to ${RUST_CARGO_PROJECT_DIR} and run +# ${RUST_CARGO} with ENV, FLAGS and ARGS variables derived from +# ${.TARGET:E:tu} so in the case of 'cargo.build' we get: +# RUST_CARGO_BUILD_ENV, RUST_CARGO_BUILD_FLAGS and RUST_CARGO_BUILD_ARGS +# +# _CARGO_USE will "just work" for additional targets like +# 'cargo.test', 'cargo.clippy', ... which will run '${RUST_CARGO} test', +# '${RUST_CARGO} clippy' etc. +# +# If MK_META_MODE is "yes" 'cargo.build' will touch ${.TARGET} +# so the default make rules will not consider it always out-of-date. +# In META MODE, 'bmake' will know if anything changed that should +# cause the target to be re-built. +# +# If MK_STAGING_RUST is "yes" we will stage the binary we +# built to a suitable location under ${STAGE_OBJTOP}. +# + +all: +.MAIN: all + +# allow for customization +.-include + +RUST_CARGO ?= cargo +RUSTC ?= rustc +.if ${.CURDIR} == ${SRCTOP} +RELDIR ?= . +.else +RELDIR ?= ${.CURDIR:S,${SRCTOP}/,,} +.endif +.if empty(RUST_PROJECT_DIR) +# we want this set correctly from anywhere within +# using RELDIR avoids confusing ${SRCTOP} with ${RUST_PROJECT_DIR}/src +RUST_PROJECT_DIR := ${SRCTOP}/${RELDIR:C,/src.*,,} +.if ${RUST_PROJECT_DIR:T:Nsrc:N.} == "" +RUST_PROJECT_DIR := ${RUST_PROJECT_DIR:H} +.endif +.endif + +.if ${.OBJDIR} != ${.CURDIR} +.if ${.CURDIR:M${RUST_PROJECT_DIR}/*} != "" +# Our .CURDIR is below RUST_PROJECT_DIR and thus our +# .OBJDIR is likely not what we want either. +# This can easily happen if in Emacs we do 'M-x compile' while +# visiting a src file. +# It is easily fixed. +__objdir := ${.OBJDIR:S,${.CURDIR:S,${RUST_PROJECT_DIR},,},,} +.OBJDIR: ${__objdir} +.endif +# tell cargo where to drop build artifacts +CARGO_TARGET_DIR ?= ${.OBJDIR} +.if !empty(OBJROOT) && exists(${OBJROOT}) +CARGO_HOME_RELDIR ?= rust/cargo_home +CARGO_HOME ?= ${OBJROOT}/common/${RUST_CARGO_HOME_RELDIR} +.endif +.elif ${.CURDIR} != ${RUST_PROJECT_DIR} +.OBJDIR: ${RUST_PROJECT_DIR} +.endif +CARGO_TARGET_DIR ?= target + +.if ${MK_DIRDEPS_BUILD:Uno} == "no" || ${.MAKE.LEVEL} > 0 +.export CARGO_HOME CARGO_TARGET_DIR RUST_PROJECT_DIR RUSTC + +all: cargo.build + +.if empty(RUST_PROJECT_FILES) +RUST_PROJECT_FILES != find ${RUST_PROJECT_DIR} -type f \( \ + -name '*.rs' -o \ + -name Cargo.lock -o \ + -name Cargo.toml \) | sort +.endif +RUST_CARGO_BUILD_DEPS += ${RUST_PROJECT_FILES:U} +.endif + +RUST_CARGO_PROJECT_DIR ?= ${RUST_PROJECT_DIR} + +.if ${RUSTC:M/*} +# make sure we find all the other toolchain bits in the same place +RUST_CARGO_ENV += PATH=${RUSTC:H}:${PATH} + +# cargo clippy needs extra help finding the sysroot +# https://github.com/rust-lang/rust-clippy/issues/3523 +RUST_CARGO_CLIPPY_ENV += RUSTC_SYSROOT=${${RUSTC} --print sysroot:L:sh} +.endif + +.if ${LDFLAGS:U:M-[BL]*} != "" +# we may need to tell rustc where to find the native libs needed +# rustc documents a space after -L so put it back +RUST_LDFLAGS := ${LDFLAGS:C/(-[BL]) /\1/gW:M-[BL]*:S/-L/& /:S/-B/-C link-arg=&/} +.endif +.if !empty(RUST_LDFLAGS) +RUSTFLAGS += ${RUST_LDFLAGS} +.endif +.if !empty(RUSTFLAGS) +RUST_CARGO_BUILD_ENV += RUSTFLAGS="${RUSTFLAGS}" +.endif + +_CARGO_USE: .USEBEFORE + @(cd ${RUST_CARGO_PROJECT_DIR} && ${RUST_CARGO_ENV} \ + ${RUST_CARGO_${.TARGET:E:tu}_ENV} \ + ${RUST_CARGO} ${RUST_CARGO_${.TARGET:E:tu}_FLAGS:U${RUST_CARGO_FLAGS}} \ + ${.TARGET:E} ${RUST_CARGO_${.TARGET:E:tu}_ARGS}) + +RUST_CARGO_TARGETS += cargo.build +cargo.build: ${RUST_CARGO_BUILD_DEPS} +.if ${.OBJDIR} != ${RUST_PROJECT_DIR} + test ! -s Cargo.lock || cp -p Cargo.lock ${RUST_CARGO_PROJECT_DIR} +.endif + @${META_COOKIE_TOUCH} + +# handle cargo.{run,test,...} +RUST_CARGO_TARGETS += ${.TARGETS:Mcargo.*} +${RUST_CARGO_TARGETS:O:u}: _CARGO_USE + +.if ${MK_DEBUG_RUST:Uno} == "no" && \ + ${DEBUG_RUST_DIRS:Unone:@x@${RELDIR:M$x}@} == "" +RUST_CARGO_BUILD_ARGS += --release +.endif + +.if ${RUST_CARGO_BUILD_ARGS:U:M--release} != "" +RUST_CARGO_TARGET = release +.else +RUST_CARGO_TARGET = debug +.endif + +# do we want cargo.build to depend on cargo.fmt --check ? +# if user did make cargo.fmt the target would exist by now +.if ${MK_RUST_CARGO_FMT_CHECK:Uno} == "yes" && !target(cargo.fmt) +RUST_CARGO_FMT_CHECK_ARGS ?= --check +RUST_CARGO_FMT_ARGS += ${RUST_CARGO_FMT_CHECK_ARGS} +cargo.fmt: _CARGO_USE +cargo.build: cargo.fmt +.endif + +# useful? defaults +RUST_CARGO_CLIPPY_ARGS ?= -- -D warnings --no-deps + +# do we want cargo.clippy to be run after cargo.build? +.if ${MK_RUST_CARGO_CLIPPY:Uno} == "yes" && !target(cargo.clippy) +cargo.clippy: _CARGO_USE +cargo.clippy: cargo.build +all: cargo.clippy +.endif + +.if !defined(RUST_LIBS) +RUST_PROGS ?= ${RUST_PROJECT_DIR:T} +.endif +.if !empty(RUST_PROGS) +BINDIR ?= ${prefix}/bin +# there could be a target triple involved +RUST_CARGO_TARGET_DIR ?= ${CARGO_TARGET_DIR} +RUST_CARGO_OUTPUT_DIR ?= ${RUST_CARGO_TARGET_DIR}/${RUST_CARGO_TARGET} + +RUST_CARGO_BUILD_OUTPUT_LIST := ${RUST_PROGS:S,^,${RUST_CARGO_OUTPUT_DIR}/,} + +${RUST_CARGO_BUILD_OUTPUT_LIST}: cargo.build +.endif + +# for late customizations +.-include diff --git a/contrib/bmake/mk/setopts.sh b/contrib/bmake/mk/setopts.sh new file mode 100644 index 000000000000..91c65c776438 --- /dev/null +++ b/contrib/bmake/mk/setopts.sh @@ -0,0 +1,175 @@ +: +# NAME: +# setopts.sh - set opt_* for shell scripts +# +# SYNOPSIS: +# opt_str=s:a.b^cl,z= +# opt_a=default +# +# . setopts.sh +# +# DESCRIPTION: +# This module sets shell variables for each option specified in +# "opt_str". +# +# If the option is followed by a ``:'' it requires an argument. +# It defaults to an empty string and specifying that option on +# the command line overrides the current value. +# +# If the option is followed by a ``.'' then it is treated as for +# ``:'' except that any argument provided on the command line is +# appended to the current value using the value of "opt_dot" as +# separator (default is a space). +# +# If the option is followed by a ``,'' then it is treated as for +# a ``.'' except that the separator is "opt_comma" (default ,). +# +# If the option is followed by ``='' it requires an argument +# of the form "var=val" which will be evaluated. +# +# If the option is followed by a ``^'' then it is treated as a +# boolean and defaults to 0. +# +# Options that have no qualifier are set to the flag if present +# otherwise they are unset. That is if '-c' is given then +# "opt_c" will be set to '-c'. +# +# If "opt_assign_eval" is set (and to something other than +# 'no'), args of the form "var=val" will be evaluated. +# +# NOTES: +# The implementation uses the getopts builtin if available. +# +# Also it does not work when loaded via a function call as "$@" +# will be the args to that function. In such cases set +# _SETOPTS_DELAY and call 'setopts "$@"; shift $__shift' +# afterwards. +# +# AUTHOR: +# Simon J. Gerraty +# + +# RCSid: +# $Id: setopts.sh,v 1.13 2023/02/20 19:30:06 sjg Exp $ +# +# @(#) Copyright (c) 1995-2023 Simon J. Gerraty +# +# This file is provided in the hope that it will +# be of use. There is absolutely NO WARRANTY. +# Permission to copy, redistribute or otherwise +# use this file is hereby granted provided that +# the above copyright notice and this notice are +# left intact. +# +# Please send copies of changes and bug-fixes to: +# sjg@crufty.net +# + +# the case checks just skip the sed(1) commands unless needed +case "$opt_str" in +*\^*) # the only ones we need to set are the booleans x, + eval `echo $opt_str | sed -e 's/[^^]*$//' -e 's/[^^]*\([^^]^\)/\1/g' -e 's/\(.\)^/opt_\1=${opt_\1-0}; /g'` + ;; +esac +case "$opt_str" in +*[=,.\^]*) + _opt_str=`echo $opt_str | sed -e 's/[=,.]/:/g' -e 's/\^//g'`;; +*) _opt_str=$opt_str;; +esac + +opt_append=${opt_append:-" "} +opt_dot=${opt_dot:-$opt_append} +opt_comma=${opt_comma:-,} + +set1opt() { + o=$1 + a="$2" + + case "$opt_str" in + *${o}:*) eval "opt_$o=\"$a\"";; + *${o}.*) eval "opt_$o=\"\${opt_$o}\${opt_$o:+$opt_dot}$a\"";; + *${o},*) eval "opt_$o=\"\${opt_$o}\${opt_$o:+$opt_comma}$a\"";; + *${o}=*) + case "$a" in + *=*) eval "$a";; + *) Myname=${Myname:-`basename $0 .sh`} + echo "$Myname: -$o requires argument of form var=val" >&2 + exit 1 + ;; + esac + ;; + *${o}\^*) eval opt_$o=1;; + *) eval opt_$o=-$o;; + esac +} + +setopts() { + __shift=$# + # use getopts builtin if we can + case `type getopts 2>&1` in + *builtin*) + : OPTIND=$OPTIND @="$@" + while getopts $_opt_str o + do + case "$o" in + \?) exit 1;; + esac + set1opt $o "$OPTARG" + done + shift $(($OPTIND - 1)) + while : + do + case "$1" in + *=*) + case "$opt_assign_eval" in + ""|no) break;; + *) eval "$1"; shift;; + esac + ;; + *) break;; + esac + done + ;; + *) # likely not a POSIX shell either + # getopt(1) isn't as good + set -- `getopt $_opt_str "$@" 2>&1` + case "$1" in + getopt:) + Myname=${Myname:-`basename $0 .sh`} + echo "$*" | tr ':' '\012' | sed -e '/^getopt/d' -e 's/ getopt$//' -e "s/^/$Myname:/" -e 's/ --/:/' -e 's/-.*//' 2>&2 + exit 1 + ;; + esac + + while : + do + : 1="$1" + case "$1" in + --) shift; break;; + -*) + # Most shells give you ' ' in IFS whether you + # want it or not, but at least one, doesn't. + # So the following gives us consistency. + o=`IFS=" -"; set -- $1; echo $*` # lose the '-' + set1opt $o "$2" + case "$_opt_str" in + *${o}:*) shift;; + esac + ;; + *=*) case "$opt_assign_eval" in + ""|no) break;; + *) eval "$1";; + esac + ;; + *) break;; + esac + shift + done + ;; + esac + # let caller know how many args we consumed + __shift=`expr $__shift - $#` +} + +${_SETOPTS_DELAY:+:} setopts "$@" +${_SETOPTS_DELAY:+:} shift $__shift diff --git a/contrib/bmake/mk/subdir.mk b/contrib/bmake/mk/subdir.mk index 0feb63af44d2..09542f147826 100644 --- a/contrib/bmake/mk/subdir.mk +++ b/contrib/bmake/mk/subdir.mk @@ -1,137 +1,139 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: subdir.mk,v 1.24 2024/04/10 01:47:23 sjg Exp $ +# $Id: subdir.mk,v 1.27 2024/09/01 05:02:43 sjg Exp $ # # @(#) Copyright (c) 2002-2024, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # if SUBDIR=@auto replace that with each subdir that has # a [Mm]akefile. # # Unless SUBDIR_MUST_EXIST is defined, missing subdirs # are ignored (to allow for sparse checkout). # # If you use _SUBDIRUSE for a target you may need to add it to # SUBDIR_TARGETS. # should be set properly in sys.mk _this ?= ${.PARSEFILE:S,bsd.,,} .if !target(__${_this}__) __${_this}__: .NOTMAIN -.if defined(SUBDIR) +.if defined(SUBDIR) || defined(SUBDIR.yes) .if ${.MAKE.LEVEL} == 0 && ${MK_DIRDEPS_BUILD:Uno} == "yes" .include # keep everyone happy _SUBDIRUSE: .elif !commands(_SUBDIRUSE) && !defined(NO_SUBDIR) && !defined(NOSUBDIR) .-include .if !target(.MAIN) .MAIN: all .endif ECHO_DIR ?= echo .ifdef SUBDIR_MUST_EXIST MISSING_DIR=echo "Missing ===> ${.CURDIR}/$$_dir"; exit 1 .else MISSING_DIR=echo "Skipping ===> ${.CURDIR}/$$_dir"; exit 0 .endif # the actual implementation # our target should be of the form ${_target}-${_dir} _SUBDIR_USE: .USE @Exists() { test -f $$1; }; \ - _dir=${.TARGET:C/^.*-//} \ + _dir=${.TARGET:C/^[^-]*-//} \ _target=${.TARGET:C/-.*//:S/real//:S/.depend/depend/}; \ if ! Exists ${.CURDIR}/$$_dir/[mM]akefile; then \ ${MISSING_DIR}; \ fi; \ if test X"${_THISDIR_}" = X""; then \ _nextdir_="$$_dir"; \ else \ _nextdir_="$${_THISDIR_}/$$_dir"; \ fi; \ ${ECHO_DIR} "===> $${_nextdir_} ($$_target)"; \ (cd ${.CURDIR}/$$_dir && \ ${.MAKE} _THISDIR_="$${_nextdir_}" $$_target) .if !target(install) .if !target(beforeinstall) beforeinstall: .endif .if !target(afterinstall) afterinstall: .endif install: maninstall maninstall: afterinstall afterinstall: realinstall realinstall: beforeinstall _SUBDIRUSE .endif # the interface from others # this may require additions to SUBDIR_TAREGTS _SUBDIRUSE: .USE subdir-${.TARGET:C/-.*//:S/real//:S/.depend/depend/} SUBDIR_TARGETS += \ all \ clean \ cleandir \ includes \ install \ depend \ lint \ obj \ tags \ etags -.if ${SUBDIR} == "@auto" +.if ${SUBDIR:U} == "@auto" SUBDIR = ${echo ${.CURDIR}/*/[Mm]akefile:L:sh:H:T:O:N\*} .endif +# allow for things like SUBDIR.${MK_TESTS} +SUBDIR += ${SUBDIR.yes:U} __subdirs = .for d in ${SUBDIR} .if $d != ".WAIT" && exists(${.CURDIR}/$d.${MACHINE}) __subdirs += $d.${MACHINE} .else __subdirs += $d .endif .endfor .for t in ${SUBDIR_TARGETS:O:u} __subdir_$t = .for d in ${__subdirs} .if $d == ".WAIT" __subdir_$t += $d .elif !commands($t-$d) $t-$d: .PHONY .MAKE _SUBDIR_USE __subdir_$t += $t-$d .endif .endfor subdir-$t: .PHONY ${__subdir_$t} $t: subdir-$t .endfor .else _SUBDIRUSE: .endif # SUBDIR .include .if make(destroy*) .include .endif .endif # make sure this exists all: .endif diff --git a/contrib/bmake/mk/sys.mk b/contrib/bmake/mk/sys.mk index b950d991e321..cf6ef061f406 100644 --- a/contrib/bmake/mk/sys.mk +++ b/contrib/bmake/mk/sys.mk @@ -1,174 +1,175 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: sys.mk,v 1.60 2024/02/17 17:26:57 sjg Exp $ +# $Id: sys.mk,v 1.61 2024/10/30 23:46:26 sjg Exp $ # # @(#) Copyright (c) 2003-2023, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # Avoid putting anything platform specific in here. # just in case we are an older bmake .MAKE.OS ?= ${HOST_OS} # _DEBUG_MAKE_FLAGS etc. .include .if !empty(_DEBUG_MAKE_FLAGS) .if ${_DEBUG_MAKE_SYS_DIRS:Uno:@x@${.CURDIR:M$x}@} != "" .MAKEFLAGS: ${_DEBUG_MAKE_FLAGS} .endif .endif # useful modifiers .include # we expect a recent bmake .if !defined(_TARGETS) # some things we do only once _TARGETS := ${.TARGETS} .-include .endif # we need HOST_TARGET etc below. .include # early customizations .-include # Popular suffixes for C++ CXX_SUFFIXES += .cc .cpp .cxx .C CXX_SUFFIXES := ${CXX_SUFFIXES:O:u} # and C++ Modules CCM_SUFFIXES += .ccm CCM_SUFFIXES := ${CCM_SUFFIXES:O:u} # precompiled modules PCM ?= .pcm SYS_MK ?= ${.PARSEDIR:tA}/${.PARSEFILE} SYS_MK := ${SYS_MK} # for systems that have an incompatible install INSTALL_SH ?= ${SYS_MK:H}/install-sh # find the OS specifics .if defined(SYS_OS_MK) .include <${SYS_OS_MK}> .else _sys_mk = .for x in ${HOST_TARGET} ${.MAKE.OS} ${.MAKE.OS:S,64,,} ${HOST_OSTYPE} ${MACHINE} Generic .if empty(_sys_mk) .-include _sys_mk := ${.MAKE.MAKEFILES:M*/$x.mk} .if !empty(_sys_mk) _sys_mk := sys/${_sys_mk:T} .endif .endif .if empty(_sys_mk) # might be an old style .-include <$x.sys.mk> _sys_mk := ${.MAKE.MAKEFILES:M*/$x.sys.mk:T} .endif .if !empty(_sys_mk) && ${MAKE_VERSION} >= 20220924 .break .endif .endfor SYS_OS_MK := ${_sys_mk} .export SYS_OS_MK .endif # some sys/ may have set this to grep -E EGREP ?= egrep # some options we need to know early OPTIONS_DEFAULT_NO += \ DIRDEPS_BUILD \ DIRDEPS_CACHE OPTIONS_DEFAULT_DEPENDENT += \ AUTO_OBJ/DIRDEPS_BUILD \ + META_ERROR_TARGET/DIRDEPS_BUILD \ META_MODE/DIRDEPS_BUILD \ STAGING/DIRDEPS_BUILD \ STATIC_DIRDEPS_CACHE/DIRDEPS_CACHE \ .-include # :Uno incase options.mk not installed .if ${MK_DIRDEPS_BUILD:Uno} == "yes" .-include .endif .if ${MK_META_MODE:Uno} == "yes" .-include .MAKE.MODE ?= meta verbose {META_MODE} .endif # make sure we have a harmless value .MAKE.MODE ?= normal # if you want objdirs make them automatic # and do it early before we compute .PATH .if ${MK_AUTO_OBJ:Uno} == "yes" || ${MKOBJDIRS:Uno} == "auto" .include .endif .if !empty(SRCTOP) .if ${.CURDIR} == ${SRCTOP} RELDIR = . .elif ${.CURDIR:M${SRCTOP}/*} RELDIR := ${.CURDIR:S,${SRCTOP}/,,} .endif .endif MACHINE_ARCH.host ?= ${_HOST_ARCH} MACHINE_ARCH.${MACHINE} ?= ${MACHINE} .if empty(MACHINE_ARCH) MACHINE_ARCH = ${MACHINE_ARCH.${MACHINE}} .endif .ifndef ROOT_GROUP ROOT_GROUP != sed -n '/:0:/{s/:.*//p;q;}' /etc/group .export ROOT_GROUP .endif unix ?= We run ${_HOST_OSNAME}. # We need a Bourne/POSIX shell MAKE_SHELL ?= ${.SHELL:Ush} SHELL := ${MAKE_SHELL} # A race condition in mkdir, means that it can bail if another # process made a dir that mkdir expected to. # We repeat the mkdir -p a number of times to try and work around this. # We stop looping as soon as the dir exists. # If we get to the end of the loop, a plain mkdir will issue an error. Mkdirs= Mkdirs() { \ for d in $$*; do \ for i in 1 2 3 4 5 6; do \ mkdir -p $$d; \ test -d $$d && return 0; \ done; \ mkdir $$d || exit $$?; \ done; } # pick up generic suffix rules .include # late customizations .-include # if .CURDIR is matched by any entry in DEBUG_MAKE_DIRS we # will apply DEBUG_MAKE_FLAGS, now. .if !empty(_DEBUG_MAKE_FLAGS) .if ${_DEBUG_MAKE_DIRS:Uno:@x@${.CURDIR:M$x}@} != "" .MAKEFLAGS: ${_DEBUG_MAKE_FLAGS} .endif .endif diff --git a/contrib/bmake/mk/sys.vars.mk b/contrib/bmake/mk/sys.vars.mk index d8e0dd4f2930..49d1a71b1d98 100644 --- a/contrib/bmake/mk/sys.vars.mk +++ b/contrib/bmake/mk/sys.vars.mk @@ -1,142 +1,147 @@ # SPDX-License-Identifier: BSD-2-Clause # -# $Id: sys.vars.mk,v 1.16 2024/02/17 17:26:57 sjg Exp $ +# $Id: sys.vars.mk,v 1.17 2024/09/28 04:06:47 sjg Exp $ # # @(#) Copyright (c) 2003-2023, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # We use the following paradigm for preventing multiple inclusion. # It relies on the fact that conditionals and dependencies are resolved # at the time they are read. # # _this ?= ${.PARSEDIR:tA}/${.PARSEFILE} # .if !target(__${_this}__) # __${_this}__: .NOTMAIN # # if this is an ancient version of bmake MAKE_VERSION ?= 0 .if ${MAKE_VERSION:M*make-*} # turn it into what we want - just the date MAKE_VERSION := ${MAKE_VERSION:[1]:C,.*-,,} .endif .if ${MAKE_VERSION} < 20100414 _this = ${.PARSEDIR}/${.PARSEFILE} .else _this = ${.PARSEDIR:tA}/${.PARSEFILE} .endif # some useful modifiers # A useful trick for testing multiple :M's against something # :L says to use the variable's name as its value - ie. literal # got = ${clean* destroy:${M_ListToMatch:S,V,.TARGETS,}} M_ListToMatch = L:@m@$${V:U:M$$m}@ # match against our initial targets (see above) M_L_TARGETS = ${M_ListToMatch:S,V,_TARGETS,} # turn a list into a set of :N modifiers # NskipFoo = ${Foo:${M_ListToSkip}} M_ListToSkip= O:u:S,^,N,:ts: # type should be a builtin in any sh since about 1980, # but sadly there are exceptions! .if ${.MAKE.OS:Unknown:NBSD/OS} == "" _type_sh = which .endif # AUTOCONF := ${autoconf:L:${M_whence}} M_type = @x@(${_type_sh:Utype} $$x) 2> /dev/null; echo;@:sh:[0]:N* found*:[@]:C,[()],,g M_whence = ${M_type}:M/*:[1] # produce similar output to jot(1) or seq(1) # eg. ${LIST:[#]:${M_JOT}} # would be 1 2 3 4 5 if LIST has 5 words # ${9:L:${M_JOT}} # would be 1 2 3 4 5 6 7 8 9 .if ${.MAKE.LEVEL} == 0 .for x in jot seq .if empty(JOT_CMD) JOT_CMD := ${$x:L:${M_whence}} .endif .endfor .if !empty(JOT_CMD) .export JOT_CMD .endif .endif .if !empty(JOT_CMD) M_JOT = [1]:S,^,${JOT_CMD} ,:sh .else M_JOT = [1]:@x@i=1;while [ $$$$i -le $$x ]; do echo $$$$i; i=$$$$((i + 1)); done;@:sh .endif # ${LIST:${M_RANGE}} is 1 2 3 4 5 if LIST has 5 words .if ${MAKE_VERSION} < 20170130 M_RANGE = [#]:${M_JOT} .else M_RANGE = range .endif # convert a path to a valid shell variable M_P2V = tu:C,[./-],_,g # convert path to absolute .if ${MAKE_VERSION} < 20100414 M_tA = C,.*,('cd' & \&\& 'pwd') 2> /dev/null || echo &,:sh .else M_tA = tA .endif # absoulte path to what we are reading. _PARSEDIR = ${.PARSEDIR:${M_tA}} .if ${MAKE_VERSION} >= 20170130 # M_cmpv allows comparing dotted versions like 3.1.2 # ${3.1.2:L:${M_cmpv}} -> 3001002 # we use big jumps to handle 3 digits per dot: # ${123.456.789:L:${M_cmpv}} -> 123456789 M_cmpv.units = 1 1000 1000000 1000000000 1000000000000 M_cmpv = S,., ,g:C,^0*([0-9]),\1,:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh .endif # many projects use MAJOR MINOR PATCH versioning # ${OPENSSL:${M_M.M.P_VERSION}} is equivalent to # ${OPENSSL_MAJOR_VERSION}.${OPENSSL_MINOR_VERSION}.${OPENSSL_PATCH_VERSION} M_M.M.P_VERSION = L:@v@$${MAJOR MINOR PATCH:L:@t@$${$$v_$$t_VERSION:U0}@}@:ts. # numeric sort .if ${MAKE_VERSION} < 20210803 M_On = O M_Onr = O .else M_On = On M_Onr = Onr .endif # Index of a word in a list. # eg. ${LIST:${M_Index:S,K,key,}} is the index of # the word "key" in ${LIST}, of course any pattern can be used. # If "key" appears more than once, there will be multiple # index values use ${M_Index:S,K,key,}:[1] to select only the first. M_Index = _:${M_RANGE}:@i@$${"$${_:[$$i]:MK}":?$$i:}@ # mtime of each word - assumed to be a valid pathname .if ${.MAKE.LEVEL} < 20230510 M_mtime = tW:S,^,${STAT:Ustat} -f %m ,:sh .else # M_mtime_fallback can be =error to throw an error # or =0 to use 0, default is to use current time M_mtime = mtime${M_mtime_fallback:U} .endif +# This line noise allows doing .tab and .bel below +# don't expect sane results if input is more than a single word +M_CharLiteral = L:@c@$${x x:L:$${:Uts$$c}:S/x//Wg}@ +.tab := ${\t:${M_CharLiteral}} +.bel := ${\007:${M_CharLiteral}} diff --git a/contrib/bmake/os.sh b/contrib/bmake/os.sh old mode 100755 new mode 100644 index 648ea1d5993c..8216c774cd96 --- a/contrib/bmake/os.sh +++ b/contrib/bmake/os.sh @@ -1,272 +1,274 @@ : # NAME: # os.sh - operating system specifics # # DESCRIPTION: # This file is included at the start of processing. Its role is # to set the variables OS, OSREL, OSMAJOR, MACHINE and MACHINE_ARCH to # reflect the current system. # # It also sets variables such as MAILER, LOCAL_FS, PS_AXC to hide # certain aspects of different UNIX flavours. # # SEE ALSO: # site.sh,funcs.sh # # AUTHOR: # Simon J. Gerraty # RCSid: -# $Id: os.sh,v 1.64 2024/03/19 16:03:23 sjg Exp $ +# $Id: os.sh,v 1.66 2024/09/25 18:16:09 sjg Exp $ # # @(#) Copyright (c) 1994 Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. # Permission to copy, redistribute or otherwise # use this file is hereby granted provided that # the above copyright notice and this notice are # left intact. # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # # this lets us skip sourcing it again _OS_SH=: OS=`uname` OSREL=`uname -r` OSMAJOR=`IFS=.; set $OSREL; echo $1` MACHINE=`uname -m` MACHINE_ARCH=`uname -p 2>/dev/null || echo $MACHINE` # there is at least one case of `uname -p` # and even `uname -m` outputting usless info # fortunately not both together case "$MACHINE" in *[!A-Za-z0-9_-]*) MACHINE="$MACHINE_ARCH";; esac case "$MACHINE_ARCH" in unknown|*[!A-Za-z0-9_-]*) MACHINE_ARCH="$MACHINE";; esac # we need this here, and it is not always available... Which() { case "$1" in -*) t=$1; shift;; *) t=-x;; esac case "$1" in /*) test $t $1 && echo $1;; *) # some shells cannot correctly handle `IFS` # in conjunction with the for loop. _dirs=`IFS=:; echo ${2:-$PATH}` for d in $_dirs do test $t $d/$1 && { echo $d/$1; break; } done ;; esac } # tr is insanely non-portable wrt char classes, so we need to # spell out the alphabet. sed y/// would work too. toUpper() { ${TR:-tr} abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ } toLower() { ${TR:-tr} ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz } K= case "$OS" in AIX) # everyone loves to be different... OSMAJOR=`uname -v` OSMINOR=`uname -r` OSREL="$OSMAJOR.$OSMINOR" LOCAL_FS=jfs PS_AXC=-e SHARE_ARCH=$OS/$OSMAJOR.X ;; CYGWIN*) # uname -s not very useful # uname -o produces just Cygwin which is better OS=Cygwin ;; Darwin) # this is more explicit (arm64 vs arm) HOST_ARCH=$MACHINE ;; SunOS) CHOWN=`Which chown /usr/etc:/usr/bin` export CHOWN # Great! Solaris keeps moving arch(1) # should just bite the bullet and use uname -p arch=`Which arch /usr/bin:/usr/ucb` MAILER=/usr/ucb/Mail LOCAL_FS=4.2 case "$OSREL" in 4.0*) # uname -m just says sun which could be anything # so use arch(1). MACHINE_ARCH=`arch` MACHINE=$MACHINE_ARCH ;; 4*) MACHINE_ARCH=`arch` ;; 5*) K=-k LOCAL_FS=ufs MAILER=mailx PS_AXC=-e # can you believe that ln on Solaris defaults to # overwriting an existing file!!!!! We want one that works! test -x /usr/xpg4/bin/ln && LN=${LN:-/usr/xpg4/bin/ln} # wonderful, 5.8's tr again require's []'s # but /usr/xpg4/bin/tr causes problems if LC_COLLATE is set! # use toUpper/toLower instead. ;; esac case "$OS/$MACHINE_ARCH" in *sun386) SHARE_ARCH=$MACHINE_ARCH;; esac ;; *BSD) K=-k MAILER=/usr/bin/Mail LOCAL_FS=local : $-,$ENV case "$-,$ENV" in *i*,*) ;; *,|*ENVFILE*) ;; *) ENV=;; esac # NetBSD at least has good backward compatibility # so NetBSD/i386 is good enough # recent NetBSD uses x86_64 for MACHINE_ARCH case $OS in NetBSD) LOCALBASE=/usr/pkg SHARE_ARCH=$OS/$HOST_ARCH ;; OpenBSD) arch=`Which arch /usr/bin:/usr/ucb:$PATH` MACHINE_ARCH=`$arch -s` ;; esac NAWK=awk export NAWK ;; HP-UX) TMP_DIRS="/tmp /usr/tmp" LOCAL_FS=hfs MAILER=mailx # don't rely on /bin/sh, its broken _shell=/bin/ksh; ENV= # also, no one would be interested in OSMAJOR=A case "$OSREL" in ?.09*) OSMAJOR=9; PS_AXC=-e;; ?.10*) OSMAJOR=10; PS_AXC=-e;; esac ;; IRIX) LOCAL_FS=efs ;; Interix) MACHINE=i386 MACHINE_ARCH=i386 ;; UnixWare|SCO_SV) OSREL=`uname -v` OSMAJOR=`IFS=.; set $OSREL; echo $1` MACHINE_ARCH=`uname -m` ;; Linux) # Not really any such thing as Linux, but # this covers red-hat and hopefully others. case $MACHINE in i?86) MACHINE_ARCH=i386;; # we don't care about i686 vs i586 esac LOCAL_FS=ext2 PS_AXC=axc [ -x /usr/bin/md5sum ] && { MD5=/usr/bin/md5sum; export MD5; } ;; QNX) case $MACHINE in x86pc) MACHINE_ARCH=i386;; esac ;; Haiku) case $MACHINE in BeBox) MACHINE_ARCH=powerpc;; BeMac) MACHINE_ARCH=powerpc;; BePC) MACHINE_ARCH=i386;; esac ;; esac LOCALBASE=${LOCALBASE:-/usr/local} HOSTNAME=${HOSTNAME:-`( hostname ) 2>/dev/null`} HOSTNAME=${HOSTNAME:-`( uname -n ) 2>/dev/null`} case "$HOSTNAME" in *.*) HOST=`IFS=.; set -- $HOSTNAME; echo $1`;; *) HOST=$HOSTNAME;; esac TMP_DIRS=${TMP_DIRS:-"/tmp /var/tmp"} MACHINE_ARCH=${MACHINE_ARCH:-$MACHINE} HOST_ARCH=${HOST_ARCH:-$MACHINE_ARCH} case "$HOST_ARCH" in x86*64|amd64) MACHINE32_ARCH=i386;; -*64) MACHINE32_ARCH=`echo $MACHINE_ARCH | sed 's,64,32,'`;; +*64) MACHINE32_ARCH=${MACHINE32_ARCH:-`echo $MACHINE_ARCH | sed 's,64,32,'`};; *) MACHINE32_ARCH=$MACHINE_ARCH;; esac HOST_ARCH32=${HOST_ARCH32:-$MACHINE32_ARCH} +export HOST_ARCH HOST_ARCH32 # we mount server:/share/arch/$SHARE_ARCH as /usr/local SHARE_ARCH_DEFAULT=$OS/$OSMAJOR.X/$HOST_ARCH SHARE_ARCH=${SHARE_ARCH:-$SHARE_ARCH_DEFAULT} LN=${LN:-ln} TR=${TR:-tr} # Some people like have /share/$HOST_TARGET/bin etc. HOST_TARGET=`echo ${OS}${OSMAJOR}-$HOST_ARCH | tr -d / | toLower` HOST_TARGET32=`echo ${OS}${OSMAJOR}-$HOST_ARCH32 | tr -d / | toLower` export HOST_TARGET HOST_TARGET32 +case `echo -e .` in -e*) echo_e=;; *) echo_e=-e;; esac case `echo -n .` in -n*) echo_n=; echo_c="\c";; *) echo_n=-n; echo_c=;; esac Echo() { case "$1" in + -e) shift; echo $echo_e "$@";; -n) shift; echo $echo_n "$@$echo_c";; *) echo "$@";; esac } # for systems that deprecate egrep case "`echo egrep | egrep 'e|g' 2>&1`" in egrep) ;; *) egrep() { grep -E "$@"; };; esac export HOSTNAME HOST export OS MACHINE MACHINE_ARCH OSREL OSMAJOR LOCAL_FS TMP_DIRS MAILER N C K PS_AXC export LN SHARE_ARCH TR export LOCALBASE case /$0 in */os.sh) for v in $* do eval vv=\$$v echo "$v='$vv'" done ;; */host_target32) echo $HOST_TARGET32;; */host_target) echo $HOST_TARGET;; esac - diff --git a/contrib/bmake/parse.c b/contrib/bmake/parse.c index d530efa0ddce..c927f0f6adcf 100644 --- a/contrib/bmake/parse.c +++ b/contrib/bmake/parse.c @@ -1,3019 +1,2986 @@ -/* $NetBSD: parse.c,v 1.734 2024/07/09 19:43:01 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.738 2025/01/14 21:34:09 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Parsing of makefiles. * * Parse_File is the main entry point and controls most of the other * functions in this module. * * Interface: * Parse_Init Initialize the module * * Parse_End Clean up the module * * Parse_File Parse a top-level makefile. Included files are * handled by IncludeFile instead. * * Parse_VarAssign * Try to parse the given line as a variable assignment. * Used by MainParseArgs to determine if an argument is * a target or a variable assignment. Used internally * for pretty much the same thing. * * Parse_Error Report a parse error, a warning or an informational * message. * * Parse_MainName Populate the list of targets to create. */ #include #include #include #include #include "make.h" #ifdef HAVE_STDINT_H #include #endif #include "dir.h" #include "job.h" #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.734 2024/07/09 19:43:01 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.738 2025/01/14 21:34:09 rillig Exp $"); /* Detects a multiple-inclusion guard in a makefile. */ typedef enum { GS_START, /* at the beginning of the file */ GS_COND, /* after the guard condition */ GS_DONE, /* after the closing .endif */ GS_NO /* the file is not guarded */ } GuardState; /* A file being parsed. */ typedef struct IncludedFile { FStr name; /* absolute or relative to the cwd */ unsigned lineno; /* 1-based */ unsigned readLines; /* the number of physical lines that have * been read from the file */ unsigned forHeadLineno; /* 1-based */ unsigned forBodyReadLines; /* the number of physical lines that have * been read from the file above the body of * the .for loop */ unsigned int condMinDepth; /* depth of nested 'if' directives, at the * beginning of the file */ bool depending; /* state of doing_depend on EOF */ Buffer buf; /* the file's content or the body of the .for * loop; either empty or ends with '\n' */ char *buf_ptr; /* next char to be read from buf */ char *buf_end; /* buf_end[-1] == '\n' */ GuardState guardState; Guard *guard; struct ForLoop *forLoop; } IncludedFile; /* Special attributes for target nodes. */ typedef enum ParseSpecial { SP_ATTRIBUTE, /* Generic attribute */ SP_BEGIN, /* .BEGIN */ SP_DEFAULT, /* .DEFAULT */ SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */ SP_END, /* .END */ SP_ERROR, /* .ERROR */ SP_IGNORE, /* .IGNORE */ SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */ SP_INTERRUPT, /* .INTERRUPT */ SP_LIBS, /* .LIBS; not mentioned in the manual page */ SP_MAIN, /* .MAIN and no user-specified targets to make */ SP_META, /* .META */ SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */ SP_NOMETA, /* .NOMETA */ SP_NOMETA_CMP, /* .NOMETA_CMP */ SP_NOPATH, /* .NOPATH */ SP_NOREADONLY, /* .NOREADONLY */ SP_NOT, /* Not special */ SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */ SP_NULL, /* .NULL; not mentioned in the manual page */ SP_OBJDIR, /* .OBJDIR */ SP_ORDER, /* .ORDER */ SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */ SP_PATH, /* .PATH or .PATH.suffix */ SP_PHONY, /* .PHONY */ SP_POSIX, /* .POSIX; not mentioned in the manual page */ SP_PRECIOUS, /* .PRECIOUS */ SP_READONLY, /* .READONLY */ SP_SHELL, /* .SHELL */ SP_SILENT, /* .SILENT */ SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */ SP_STALE, /* .STALE */ SP_SUFFIXES, /* .SUFFIXES */ SP_SYSPATH, /* .SYSPATH */ SP_WAIT /* .WAIT */ } ParseSpecial; typedef List SearchPathList; typedef ListNode SearchPathListNode; typedef enum VarAssignOp { VAR_NORMAL, /* = */ VAR_APPEND, /* += */ VAR_DEFAULT, /* ?= */ VAR_SUBST, /* := */ VAR_SHELL /* != or :sh= */ } VarAssignOp; typedef struct VarAssign { char *varname; /* unexpanded */ VarAssignOp op; const char *value; /* unexpanded */ } VarAssign; static bool Parse_IsVar(const char *, VarAssign *); static void Parse_Var(VarAssign *, GNode *); /* * The target to be made if no targets are specified in the command line. * This is the first target defined in any of the makefiles. */ GNode *mainNode; /* * During parsing, the targets from the left-hand side of the currently * active dependency line, or NULL if the current line does not belong to a * dependency line, for example because it is a variable assignment. * * See unit-tests/deptgt.mk, keyword "parse.c:targets". */ static GNodeList *targets; #ifdef CLEANUP /* * All shell commands for all targets, in no particular order and possibly * with duplicate values. Kept in a separate list since the commands from * .USE or .USEBEFORE nodes are shared with other GNodes, thereby giving up * the easily understandable ownership over the allocated strings. */ static StringList targCmds = LST_INIT; #endif /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * is seen, then set to each successive source on the line. */ static GNode *order_pred; int parseErrors; /* * The include chain of makefiles. At index 0 is the top-level makefile from * the command line, followed by the included files or .for loops, up to and * including the current file. * * See PrintStackTrace for how to interpret the data. */ static Vector /* of IncludedFile */ includes; SearchPath *parseIncPath; /* directories for "..." includes */ SearchPath *sysIncPath; /* directories for <...> includes */ SearchPath *defSysIncPath; /* default for sysIncPath */ /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. */ static const struct { const char name[17]; ParseSpecial special; /* when used as a target */ GNodeType targetAttr; /* when used as a source */ } parseKeywords[] = { { ".BEGIN", SP_BEGIN, OP_NONE }, { ".DEFAULT", SP_DEFAULT, OP_NONE }, { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, OP_NONE }, { ".END", SP_END, OP_NONE }, { ".ERROR", SP_ERROR, OP_NONE }, { ".EXEC", SP_ATTRIBUTE, OP_EXEC }, { ".IGNORE", SP_IGNORE, OP_IGNORE }, { ".INCLUDES", SP_INCLUDES, OP_NONE }, { ".INTERRUPT", SP_INTERRUPT, OP_NONE }, { ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE }, { ".JOIN", SP_ATTRIBUTE, OP_JOIN }, { ".LIBS", SP_LIBS, OP_NONE }, { ".MADE", SP_ATTRIBUTE, OP_MADE }, { ".MAIN", SP_MAIN, OP_NONE }, { ".MAKE", SP_ATTRIBUTE, OP_MAKE }, { ".MAKEFLAGS", SP_MFLAGS, OP_NONE }, { ".META", SP_META, OP_META }, { ".MFLAGS", SP_MFLAGS, OP_NONE }, { ".NOMETA", SP_NOMETA, OP_NOMETA }, { ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP }, { ".NOPATH", SP_NOPATH, OP_NOPATH }, { ".NOREADONLY", SP_NOREADONLY, OP_NONE }, { ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN }, { ".NOTPARALLEL", SP_NOTPARALLEL, OP_NONE }, { ".NO_PARALLEL", SP_NOTPARALLEL, OP_NONE }, { ".NULL", SP_NULL, OP_NONE }, { ".OBJDIR", SP_OBJDIR, OP_NONE }, { ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL }, { ".ORDER", SP_ORDER, OP_NONE }, { ".PARALLEL", SP_PARALLEL, OP_NONE }, { ".PATH", SP_PATH, OP_NONE }, { ".PHONY", SP_PHONY, OP_PHONY }, { ".POSIX", SP_POSIX, OP_NONE }, { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, { ".READONLY", SP_READONLY, OP_NONE }, { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, { ".SHELL", SP_SHELL, OP_NONE }, { ".SILENT", SP_SILENT, OP_SILENT }, { ".SINGLESHELL", SP_SINGLESHELL, OP_NONE }, { ".STALE", SP_STALE, OP_NONE }, { ".SUFFIXES", SP_SUFFIXES, OP_NONE }, { ".SYSPATH", SP_SYSPATH, OP_NONE }, { ".USE", SP_ATTRIBUTE, OP_USE }, { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, { ".WAIT", SP_WAIT, OP_NONE }, }; enum PosixState posix_state = PS_NOT_YET; static HashTable /* full file name -> Guard */ guards; static List * Lst_New(void) { List *list = bmake_malloc(sizeof *list); Lst_Init(list); return list; } static void Lst_Free(List *list) { Lst_Done(list); free(list); } static IncludedFile * GetInclude(size_t i) { assert(i < includes.len); return Vector_Get(&includes, i); } /* The makefile or the body of a .for loop that is currently being read. */ static IncludedFile * CurFile(void) { return GetInclude(includes.len - 1); } unsigned int CurFile_CondMinDepth(void) { return CurFile()->condMinDepth; } static Buffer LoadFile(const char *path, int fd) { ssize_t n; Buffer buf; size_t bufSize; struct stat st; bufSize = fstat(fd, &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0 && st.st_size < 1024 * 1024 * 1024 ? (size_t)st.st_size : 1024; Buf_InitSize(&buf, bufSize); for (;;) { if (buf.len == buf.cap) { if (buf.cap >= 512 * 1024 * 1024) { Error("%s: file too large", path); exit(2); /* Not 1 so -q can distinguish error */ } Buf_Expand(&buf); } assert(buf.len < buf.cap); n = read(fd, buf.data + buf.len, buf.cap - buf.len); if (n < 0) { Error("%s: read error: %s", path, strerror(errno)); exit(2); /* Not 1 so -q can distinguish error */ } if (n == 0) break; buf.len += (size_t)n; } assert(buf.len <= buf.cap); if (buf.len > 0 && !Buf_EndsWith(&buf, '\n')) Buf_AddByte(&buf, '\n'); return buf; /* may not be null-terminated */ } /* * Print the current chain of .include and .for directives. In Parse_Fatal * or other functions that already print the location, includingInnermost * would be redundant, but in other cases like Error or Fatal it needs to be * included. */ void PrintStackTrace(bool includingInnermost) { const IncludedFile *entries; size_t i, n; + EvalStack_PrintDetails(); + n = includes.len; if (n == 0) return; entries = GetInclude(0); if (!includingInnermost && entries[n - 1].forLoop == NULL) n--; /* already in the diagnostic */ for (i = n; i-- > 0;) { const IncludedFile *entry = entries + i; const char *fname = entry->name.str; char dirbuf[MAXPATHLEN + 1]; if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) { const char *realPath = realpath(fname, dirbuf); if (realPath != NULL) fname = realPath; } if (entry->forLoop != NULL) { char *details = ForLoop_Details(entry->forLoop); debug_printf("\tin .for loop from %s:%u with %s\n", fname, entry->forHeadLineno, details); free(details); } else if (i + 1 < n && entries[i + 1].forLoop != NULL) { /* entry->lineno is not a useful line number */ } else debug_printf("\tin %s:%u\n", fname, entry->lineno); } if (makelevel > 0) debug_printf("\tin directory %s\n", curdir); } /* Check if the current character is escaped on the current line. */ static bool IsEscaped(const char *line, const char *p) { bool escaped = false; while (p > line && *--p == '\\') escaped = !escaped; return escaped; } /* * Remember the location (filename and lineno) where the last command was * added or where the node was mentioned in a .depend file. */ static void RememberLocation(GNode *gn) { IncludedFile *curFile = CurFile(); gn->fname = Str_Intern(curFile->name.str); gn->lineno = curFile->lineno; } /* * Look in the table of keywords for one matching the given string. * Return the index of the keyword, or -1 if it isn't there. */ static int FindKeyword(const char *str) { int start = 0; int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; while (start <= end) { int curr = start + (end - start) / 2; int diff = strcmp(str, parseKeywords[curr].name); if (diff == 0) return curr; if (diff < 0) end = curr - 1; else start = curr + 1; } return -1; } void PrintLocation(FILE *f, bool useVars, const GNode *gn) { char dirbuf[MAXPATHLEN + 1]; FStr dir, base; const char *fname; unsigned lineno; if (gn != NULL) { fname = gn->fname; lineno = gn->lineno; } else if (includes.len > 0) { IncludedFile *curFile = CurFile(); fname = curFile->name.str; lineno = curFile->lineno; } else return; if (!useVars || fname[0] == '/' || strcmp(fname, "(stdin)") == 0) { (void)fprintf(f, "\"%s\" line %u: ", fname, lineno); return; } dir = Var_Value(SCOPE_GLOBAL, ".PARSEDIR"); if (dir.str == NULL) dir.str = "."; if (dir.str[0] != '/') dir.str = realpath(dir.str, dirbuf); base = Var_Value(SCOPE_GLOBAL, ".PARSEFILE"); if (base.str == NULL) base.str = str_basename(fname); (void)fprintf(f, "\"%s/%s\" line %u: ", dir.str, base.str, lineno); FStr_Done(&base); FStr_Done(&dir); } static void MAKE_ATTR_PRINTFLIKE(5, 0) ParseVErrorInternal(FILE *f, bool useVars, const GNode *gn, ParseErrorLevel level, const char *fmt, va_list ap) { static bool fatal_warning_error_printed = false; (void)fprintf(f, "%s: ", progname); PrintLocation(f, useVars, gn); if (level == PARSE_WARNING) (void)fprintf(f, "warning: "); - fprintf(f, "%s", EvalStack_Details()); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); if (level == PARSE_FATAL) parseErrors++; if (level == PARSE_WARNING && opts.parseWarnFatal) { if (!fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); fatal_warning_error_printed = true; } parseErrors++; } if (level == PARSE_FATAL || DEBUG(PARSE)) PrintStackTrace(false); } static void MAKE_ATTR_PRINTFLIKE(3, 4) ParseErrorInternal(const GNode *gn, ParseErrorLevel level, const char *fmt, ...) { va_list ap; (void)fflush(stdout); va_start(ap, fmt); ParseVErrorInternal(stderr, false, gn, level, fmt, ap); va_end(ap); if (opts.debug_file != stdout && opts.debug_file != stderr) { va_start(ap, fmt); ParseVErrorInternal(opts.debug_file, false, gn, level, fmt, ap); va_end(ap); } } /* * Print a message, including location information. * * If the level is PARSE_FATAL, continue parsing until the end of the * current top-level makefile, then exit (see Parse_File). * * Fmt is given without a trailing newline. */ void Parse_Error(ParseErrorLevel level, const char *fmt, ...) { va_list ap; (void)fflush(stdout); va_start(ap, fmt); ParseVErrorInternal(stderr, true, NULL, level, fmt, ap); va_end(ap); if (opts.debug_file != stdout && opts.debug_file != stderr) { va_start(ap, fmt); ParseVErrorInternal(opts.debug_file, true, NULL, level, fmt, ap); va_end(ap); } } /* * Handle an .info, .warning or .error directive. For an .error directive, * exit immediately. */ static void HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg) { char *xmsg; if (umsg[0] == '\0') { Parse_Error(PARSE_FATAL, "Missing argument for \".%s\"", levelName); return; } xmsg = Var_Subst(umsg, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ Parse_Error(level, "%s", xmsg); free(xmsg); if (level == PARSE_FATAL) { PrintOnError(NULL, "\n"); exit(1); } } /* * Add the child to the parent's children, and for non-special targets, vice * versa. */ static void LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) { if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&pgn->cohorts)) pgn = pgn->cohorts.last->datum; Lst_Append(&pgn->children, cgn); pgn->unmade++; /* * Special targets like .END do not need to be informed once the child * target has been made. */ if (!isSpecial) Lst_Append(&cgn->parents, pgn); if (DEBUG(PARSE)) { debug_printf("Target \"%s\" depends on \"%s\"\n", pgn->name, cgn->name); Targ_PrintNode(pgn, 0); Targ_PrintNode(cgn, 0); } } /* Add the node to each target from the current dependency group. */ static void LinkToTargets(GNode *gn, bool isSpecial) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) LinkSource(ln->datum, gn, isSpecial); } static bool TryApplyDependencyOperator(GNode *gn, GNodeType op) { /* * If the node occurred on the left-hand side of a dependency and the * operator also defines a dependency, they must match. */ if ((op & OP_OPMASK) && (gn->type & OP_OPMASK) && ((op & OP_OPMASK) != (gn->type & OP_OPMASK))) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); return false; } if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { /* * If the node was on the left-hand side of a '::' operator, * create a new node for the children and commands on this * dependency line, since each of these dependency groups has * its own attributes and commands, separate from the others. * * The new instance is placed on the 'cohorts' list of the * initial one (note the initial one is not on its own * cohorts list) and the new instance is linked to all * parents of the initial instance. */ GNode *cohort; /* * Propagate copied bits to the initial node. They'll be * propagated back to the rest of the cohorts later. */ gn->type |= op & (unsigned)~OP_OPMASK; cohort = Targ_NewInternalNode(gn->name); if (doing_depend) RememberLocation(cohort); /* * Make the cohort invisible to avoid duplicating it * into other variables. True, parents of this target won't * tend to do anything with their local variables, but better * safe than sorry. * * (I think this is pointless now, since the relevant list * traversals will no longer see this node anyway. -mycroft) */ cohort->type = op | OP_INVISIBLE; Lst_Append(&gn->cohorts, cohort); cohort->centurion = gn; gn->unmade_cohorts++; snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", (unsigned int)gn->unmade_cohorts % 1000000); } else { gn->type |= op; /* preserve any previous flags */ } return true; } static void ApplyDependencyOperator(GNodeType op) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) if (!TryApplyDependencyOperator(ln->datum, op)) break; } /* * Add a .WAIT node in the dependency list. After any dynamic dependencies * (and filename globbing) have happened, it is given a dependency on each * previous child, back until the previous .WAIT node. The next child won't * be scheduled until the .WAIT node is built. * * Give each .WAIT node a unique name (mainly for diagnostics). */ static void ApplyDependencySourceWait(bool isSpecial) { static unsigned wait_number = 0; char name[6 + 10 + 1]; GNode *gn; snprintf(name, sizeof name, ".WAIT_%u", ++wait_number); gn = Targ_NewInternalNode(name); if (doing_depend) RememberLocation(gn); gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; LinkToTargets(gn, isSpecial); } static bool ApplyDependencySourceKeyword(const char *src, ParseSpecial special) { int keywd; GNodeType targetAttr; if (*src != '.' || !ch_isupper(src[1])) return false; keywd = FindKeyword(src); if (keywd == -1) return false; targetAttr = parseKeywords[keywd].targetAttr; if (targetAttr != OP_NONE) { ApplyDependencyOperator(targetAttr); return true; } if (parseKeywords[keywd].special == SP_WAIT) { ApplyDependencySourceWait(special != SP_NOT); return true; } return false; } /* * In a line like ".MAIN: source1 source2", add all sources to the list of * things to create, but only if the user didn't specify a target on the * command line and .MAIN occurs for the first time. * * See HandleDependencyTargetSpecial, branch SP_MAIN. * See unit-tests/cond-func-make-main.mk. */ static void ApplyDependencySourceMain(const char *src) { Lst_Append(&opts.create, bmake_strdup(src)); /* * Add the name to the .TARGETS variable as well, so the user can * employ that, if desired. */ Global_Append(".TARGETS", src); } /* * For the sources of a .ORDER target, create predecessor/successor links * between the previous source and the current one. */ static void ApplyDependencySourceOrder(const char *src) { GNode *gn; gn = Targ_GetNode(src); if (doing_depend) RememberLocation(gn); if (order_pred != NULL) { Lst_Append(&order_pred->order_succ, gn); Lst_Append(&gn->order_pred, order_pred); if (DEBUG(PARSE)) { debug_printf( "# .ORDER forces '%s' to be made before '%s'\n", order_pred->name, gn->name); Targ_PrintNode(order_pred, 0); Targ_PrintNode(gn, 0); } } /* The current source now becomes the predecessor for the next one. */ order_pred = gn; } /* The source is not an attribute, so find/create a node for it. */ static void ApplyDependencySourceOther(const char *src, GNodeType targetAttr, ParseSpecial special) { GNode *gn; gn = Targ_GetNode(src); if (doing_depend) RememberLocation(gn); if (targetAttr != OP_NONE) gn->type |= targetAttr; else LinkToTargets(gn, special != SP_NOT); } /* * Given the name of a source in a dependency line, figure out if it is an * attribute (such as .SILENT) and if so, apply it to all targets. Otherwise * decide if there is some attribute which should be applied *to* the source * because of some special target (such as .PHONY) and apply it if so. * Otherwise, make the source a child of the targets. */ static void ApplyDependencySource(GNodeType targetAttr, const char *src, ParseSpecial special) { if (ApplyDependencySourceKeyword(src, special)) return; if (special == SP_MAIN) ApplyDependencySourceMain(src); else if (special == SP_ORDER) ApplyDependencySourceOrder(src); else ApplyDependencySourceOther(src, targetAttr, special); } /* * If we have yet to decide on a main target to make, in the absence of any * user input, we want the first target on the first dependency line that is * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. */ static void MaybeUpdateMainTarget(void) { GNodeListNode *ln; if (mainNode != NULL) return; for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (GNode_IsMainCandidate(gn)) { DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name); mainNode = gn; return; } } } static void InvalidLineType(const char *line, const char *unexpanded_line) { if (unexpanded_line[0] == '.') { const char *dirstart = unexpanded_line + 1; const char *dirend; cpp_skip_whitespace(&dirstart); dirend = dirstart; while (ch_isalnum(*dirend) || *dirend == '-') dirend++; Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", (int)(dirend - dirstart), dirstart); } else if (strcmp(line, unexpanded_line) == 0) Parse_Error(PARSE_FATAL, "Invalid line '%s'", line); else Parse_Error(PARSE_FATAL, "Invalid line '%s', expanded to '%s'", unexpanded_line, line); } static void ParseDependencyTargetWord(char **pp, const char *lstart) { const char *p = *pp; while (*p != '\0') { if ((ch_isspace(*p) || *p == '!' || *p == ':' || *p == '(') && !IsEscaped(lstart, p)) break; if (*p == '$') { FStr val = Var_Parse(&p, SCOPE_CMDLINE, VARE_PARSE); /* TODO: handle errors */ FStr_Done(&val); } else p++; } *pp += p - *pp; } /* * Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. * * See the tests deptgt-*.mk. */ static void HandleDependencyTargetSpecial(const char *targetName, ParseSpecial *inout_special, SearchPathList **inout_paths) { switch (*inout_special) { case SP_PATH: if (*inout_paths == NULL) *inout_paths = Lst_New(); Lst_Append(*inout_paths, &dirSearchPath); break; case SP_SYSPATH: if (*inout_paths == NULL) *inout_paths = Lst_New(); Lst_Append(*inout_paths, sysIncPath); break; case SP_MAIN: /* * Allow targets from the command line to override the * .MAIN node. */ if (!Lst_IsEmpty(&opts.create)) *inout_special = SP_NOT; break; case SP_BEGIN: case SP_END: case SP_STALE: case SP_ERROR: case SP_INTERRUPT: { GNode *gn = Targ_GetNode(targetName); if (doing_depend) RememberLocation(gn); gn->type |= OP_NOTMAIN | OP_SPECIAL; Lst_Append(targets, gn); break; } case SP_DEFAULT: { /* * Need to create a node to hang commands on, but we don't * want it in the graph, nor do we want it to be the Main * Target. We claim the node is a transformation rule to make * life easier later, when we'll use Make_HandleUse to * actually apply the .DEFAULT commands. */ GNode *gn = GNode_New(".DEFAULT"); gn->type |= OP_NOTMAIN | OP_TRANSFORM; Lst_Append(targets, gn); defaultNode = gn; break; } case SP_DELETE_ON_ERROR: deleteOnError = true; break; case SP_NOTPARALLEL: opts.maxJobs = 1; break; case SP_SINGLESHELL: opts.compatMake = true; break; case SP_ORDER: order_pred = NULL; break; default: break; } } static bool HandleDependencyTargetPath(const char *suffixName, SearchPathList **inout_paths) { SearchPath *path; path = Suff_GetPath(suffixName); if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", suffixName); return false; } if (*inout_paths == NULL) *inout_paths = Lst_New(); Lst_Append(*inout_paths, path); return true; } /* See if it's a special target and if so set inout_special to match it. */ static bool HandleDependencyTarget(const char *targetName, ParseSpecial *inout_special, GNodeType *inout_targetAttr, SearchPathList **inout_paths) { int keywd; if (!(targetName[0] == '.' && ch_isupper(targetName[1]))) return true; /* * See if the target is a special target that must have it * or its sources handled specially. */ keywd = FindKeyword(targetName); if (keywd != -1) { if (*inout_special == SP_PATH && parseKeywords[keywd].special != SP_PATH) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); return false; } *inout_special = parseKeywords[keywd].special; *inout_targetAttr = parseKeywords[keywd].targetAttr; HandleDependencyTargetSpecial(targetName, inout_special, inout_paths); } else if (strncmp(targetName, ".PATH", 5) == 0) { *inout_special = SP_PATH; if (!HandleDependencyTargetPath(targetName + 5, inout_paths)) return false; } return true; } static void HandleSingleDependencyTargetMundane(const char *name) { GNode *gn = Suff_IsTransform(name) ? Suff_AddTransform(name) : Targ_GetNode(name); if (doing_depend) RememberLocation(gn); Lst_Append(targets, gn); } static void HandleDependencyTargetMundane(const char *targetName) { if (Dir_HasWildcards(targetName)) { StringList targetNames = LST_INIT; SearchPath *emptyPath = SearchPath_New(); SearchPath_Expand(emptyPath, targetName, &targetNames); SearchPath_Free(emptyPath); while (!Lst_IsEmpty(&targetNames)) { char *targName = Lst_Dequeue(&targetNames); HandleSingleDependencyTargetMundane(targName); free(targName); } } else HandleSingleDependencyTargetMundane(targetName); } static void SkipExtraTargets(char **pp, const char *lstart) { bool warning = false; const char *p = *pp; while (*p != '\0') { if (!IsEscaped(lstart, p) && (*p == '!' || *p == ':')) break; if (IsEscaped(lstart, p) || (*p != ' ' && *p != '\t')) warning = true; p++; } if (warning) { const char *start = *pp; cpp_skip_whitespace(&start); Parse_Error(PARSE_WARNING, "Extra target '%.*s' ignored", (int)(p - start), start); } *pp += p - *pp; } static void CheckSpecialMundaneMixture(ParseSpecial special) { switch (special) { case SP_DEFAULT: case SP_STALE: case SP_BEGIN: case SP_END: case SP_ERROR: case SP_INTERRUPT: /* * These create nodes on which to hang commands, so targets * shouldn't be empty. */ case SP_NOT: /* Nothing special here -- targets may be empty. */ break; default: Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. " "Mundane ones ignored"); break; } } /* * In a dependency line like 'targets: sources' or 'targets! sources', parse * the operator ':', '::' or '!' from between the targets and the sources. */ static GNodeType ParseDependencyOp(char **pp) { if (**pp == '!') return (*pp)++, OP_FORCE; if (**pp == ':' && (*pp)[1] == ':') return *pp += 2, OP_DOUBLEDEP; else if (**pp == ':') return (*pp)++, OP_DEPENDS; else return OP_NONE; } static void ClearPaths(ParseSpecial special, SearchPathList *paths) { if (paths != NULL) { SearchPathListNode *ln; for (ln = paths->first; ln != NULL; ln = ln->next) SearchPath_Clear(ln->datum); } if (special == SP_SYSPATH) Dir_SetSYSPATH(); else Dir_SetPATH(); } static char * FindInDirOfIncludingFile(const char *file) { char *fullname, *incdir, *slash, *newName; int i; fullname = NULL; incdir = bmake_strdup(CurFile()->name.str); slash = strrchr(incdir, '/'); if (slash != NULL) { *slash = '\0'; /* * Now do lexical processing of leading "../" on the * filename. */ for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { slash = strrchr(incdir + 1, '/'); if (slash == NULL || strcmp(slash, "/..") == 0) break; *slash = '\0'; } newName = str_concat3(incdir, "/", file + i); fullname = Dir_FindFile(newName, parseIncPath); if (fullname == NULL) fullname = Dir_FindFile(newName, &dirSearchPath); free(newName); } free(incdir); return fullname; } static char * FindInQuotPath(const char *file) { const char *suff; SearchPath *suffPath; char *fullname; fullname = FindInDirOfIncludingFile(file); if (fullname == NULL && (suff = strrchr(file, '.')) != NULL && (suffPath = Suff_GetPath(suff)) != NULL) fullname = Dir_FindFile(file, suffPath); if (fullname == NULL) fullname = Dir_FindFile(file, parseIncPath); if (fullname == NULL) fullname = Dir_FindFile(file, &dirSearchPath); return fullname; } static bool SkipGuarded(const char *fullname) { Guard *guard = HashTable_FindValue(&guards, fullname); if (guard != NULL && guard->kind == GK_VARIABLE && GNode_ValueDirect(SCOPE_GLOBAL, guard->name) != NULL) goto skip; if (guard != NULL && guard->kind == GK_TARGET && Targ_FindNode(guard->name) != NULL) goto skip; return false; skip: DEBUG2(PARSE, "Skipping '%s' because '%s' is defined\n", fullname, guard->name); return true; } /* * Handle one of the .[-ds]include directives by remembering the current file * and pushing the included file on the stack. After the included file has * finished, parsing continues with the including file; see Parse_PushInput * and ParseEOF. * * System includes are looked up in sysIncPath, any other includes are looked * up in the parsedir and then in the directories specified by the -I command * line options. */ static void IncludeFile(const char *file, bool isSystem, bool depinc, bool silent) { Buffer buf; char *fullname; /* full pathname of file */ int fd; fullname = file[0] == '/' ? bmake_strdup(file) : NULL; if (fullname == NULL && !isSystem) fullname = FindInQuotPath(file); if (fullname == NULL) { SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath; fullname = Dir_FindInclude(file, path); } if (fullname == NULL) { if (!silent) Parse_Error(PARSE_FATAL, "Could not find %s", file); return; } if (SkipGuarded(fullname)) goto done; if ((fd = open(fullname, O_RDONLY)) == -1) { if (!silent) Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); goto done; } buf = LoadFile(fullname, fd); (void)close(fd); Parse_PushInput(fullname, 1, 0, buf, NULL); if (depinc) doing_depend = depinc; /* only turn it on */ done: free(fullname); } /* Handle a "dependency" line like '.SPECIAL:' without any sources. */ static void HandleDependencySourcesEmpty(ParseSpecial special, SearchPathList *paths) { switch (special) { case SP_SUFFIXES: Suff_ClearSuffixes(); break; case SP_PRECIOUS: allPrecious = true; break; case SP_IGNORE: opts.ignoreErrors = true; break; case SP_SILENT: opts.silent = true; break; case SP_PATH: case SP_SYSPATH: ClearPaths(special, paths); break; case SP_POSIX: if (posix_state == PS_NOW_OR_NEVER) { /* * With '-r', 'posix.mk' (if it exists) * can effectively substitute for 'sys.mk', * otherwise it is an extension. */ Global_Set("%POSIX", "1003.2"); IncludeFile("posix.mk", true, false, true); } break; default: break; } } static void AddToPaths(const char *dir, SearchPathList *paths) { if (paths != NULL) { SearchPathListNode *ln; for (ln = paths->first; ln != NULL; ln = ln->next) (void)SearchPath_Add(ln->datum, dir); } } /* * If the target was one that doesn't take files as its sources but takes * something like suffixes, we take each space-separated word on the line as * a something and deal with it accordingly. */ static void ParseDependencySourceSpecial(ParseSpecial special, const char *word, SearchPathList *paths) { switch (special) { case SP_SUFFIXES: Suff_AddSuffix(word); break; case SP_PATH: case SP_SYSPATH: AddToPaths(word, paths); break; case SP_INCLUDES: Suff_AddInclude(word); break; case SP_LIBS: Suff_AddLib(word); break; case SP_NOREADONLY: Var_ReadOnly(word, false); break; case SP_NULL: Suff_SetNull(word); break; case SP_OBJDIR: Main_SetObjdir(false, "%s", word); break; case SP_READONLY: Var_ReadOnly(word, true); break; default: break; } } static bool ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special, GNodeType *inout_targetAttr, SearchPathList **inout_paths) { char savedNameEnd = *nameEnd; *nameEnd = '\0'; if (!HandleDependencyTarget(name, inout_special, inout_targetAttr, inout_paths)) return false; if (*inout_special == SP_NOT && *name != '\0') HandleDependencyTargetMundane(name); else if (*inout_special == SP_PATH && *name != '.' && *name != '\0') Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", name); *nameEnd = savedNameEnd; return true; } static bool ParseDependencyTargets(char **pp, const char *lstart, ParseSpecial *inout_special, GNodeType *inout_targetAttr, SearchPathList **inout_paths, const char *unexpanded_line) { char *p = *pp; for (;;) { char *tgt = p; ParseDependencyTargetWord(&p, lstart); /* * If the word is followed by a left parenthesis, it's the * name of one or more files inside an archive. */ if (!IsEscaped(lstart, p) && *p == '(') { p = tgt; if (!Arch_ParseArchive(&p, targets, SCOPE_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", tgt); return false; } continue; } if (*p == '\0') { InvalidLineType(lstart, unexpanded_line); return false; } if (!ApplyDependencyTarget(tgt, p, inout_special, inout_targetAttr, inout_paths)) return false; if (*inout_special != SP_NOT && *inout_special != SP_PATH) SkipExtraTargets(&p, lstart); else pp_skip_whitespace(&p); if (*p == '\0') break; if ((*p == '!' || *p == ':') && !IsEscaped(lstart, p)) break; } *pp = p; return true; } static void ParseDependencySourcesSpecial(char *start, ParseSpecial special, SearchPathList *paths) { while (*start != '\0') { char savedEnd; char *end = start; while (*end != '\0' && !ch_isspace(*end)) end++; savedEnd = *end; *end = '\0'; ParseDependencySourceSpecial(special, start, paths); *end = savedEnd; if (savedEnd != '\0') end++; pp_skip_whitespace(&end); start = end; } } static void LinkVarToTargets(VarAssign *var) { GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) Parse_Var(var, ln->datum); } static bool ParseDependencySourcesMundane(char *start, ParseSpecial special, GNodeType targetAttr) { while (*start != '\0') { char *end = start; VarAssign var; /* * Check for local variable assignment, * rest of the line is the value. */ if (Parse_IsVar(start, &var)) { bool targetVarsEnabled = GetBooleanExpr( "${.MAKE.TARGET_LOCAL_VARIABLES}", true); if (targetVarsEnabled) LinkVarToTargets(&var); free(var.varname); if (targetVarsEnabled) return true; } /* * The targets take real sources, so we must beware of archive * specifications (i.e. things with left parentheses in them) * and handle them accordingly. */ for (; *end != '\0' && !ch_isspace(*end); end++) { if (*end == '(' && end > start && end[-1] != '$') { /* * Only stop for a left parenthesis if it * isn't at the start of a word (that'll be * for variable changes later) and isn't * preceded by a dollar sign (a dynamic * source). */ break; } } if (*end == '(') { GNodeList sources = LST_INIT; if (!Arch_ParseArchive(&start, &sources, SCOPE_CMDLINE)) { Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", start); return false; } while (!Lst_IsEmpty(&sources)) { GNode *gn = Lst_Dequeue(&sources); ApplyDependencySource(targetAttr, gn->name, special); } Lst_Done(&sources); end = start; } else { if (*end != '\0') { *end = '\0'; end++; } ApplyDependencySource(targetAttr, start, special); } pp_skip_whitespace(&end); start = end; } return true; } /* * From a dependency line like 'targets: sources', parse the sources. * * See the tests depsrc-*.mk. */ static void ParseDependencySources(char *p, GNodeType targetAttr, ParseSpecial special, SearchPathList **inout_paths) { if (*p == '\0') { HandleDependencySourcesEmpty(special, *inout_paths); } else if (special == SP_MFLAGS) { Main_ParseArgLine(p); return; } else if (special == SP_SHELL) { if (!Job_ParseShell(p)) { Parse_Error(PARSE_FATAL, "improper shell specification"); return; } return; } else if (special == SP_NOTPARALLEL || special == SP_SINGLESHELL || special == SP_DELETE_ON_ERROR) { return; } switch (special) { case SP_INCLUDES: case SP_LIBS: case SP_NOREADONLY: case SP_NULL: case SP_OBJDIR: case SP_PATH: case SP_READONLY: case SP_SUFFIXES: case SP_SYSPATH: ParseDependencySourcesSpecial(p, special, *inout_paths); if (*inout_paths != NULL) { Lst_Free(*inout_paths); *inout_paths = NULL; } if (special == SP_PATH) Dir_SetPATH(); if (special == SP_SYSPATH) Dir_SetSYSPATH(); break; default: assert(*inout_paths == NULL); if (!ParseDependencySourcesMundane(p, special, targetAttr)) return; break; } MaybeUpdateMainTarget(); } /* * Parse a dependency line consisting of targets, followed by a dependency * operator, optionally followed by sources. * * The nodes of the sources are linked as children to the nodes of the * targets. Nodes are created as necessary. * * The operator is applied to each node in the global 'targets' list, * which is where the nodes found for the targets are kept. * * The sources are parsed in much the same way as the targets, except * that they are expanded using the wildcarding scheme of the C-Shell, * and a target is created for each expanded word. Each of the resulting * nodes is then linked to each of the targets as one of its children. * * Certain targets and sources such as .PHONY or .PRECIOUS are handled * specially, see ParseSpecial. * * Transformation rules such as '.c.o' are also handled here, see * Suff_AddTransform. * * Upon return, the value of expandedLine is unspecified. */ static void ParseDependency(char *expandedLine, const char *unexpandedLine) { char *p; SearchPathList *paths; /* search paths to alter when parsing a list * of .PATH targets */ GNodeType targetAttr; /* from special sources */ ParseSpecial special; /* in special targets, the children are * linked as children of the parent but not * vice versa */ GNodeType op; DEBUG1(PARSE, "ParseDependency(%s)\n", expandedLine); p = expandedLine; paths = NULL; targetAttr = OP_NONE; special = SP_NOT; if (!ParseDependencyTargets(&p, expandedLine, &special, &targetAttr, &paths, unexpandedLine)) goto out; if (!Lst_IsEmpty(targets)) CheckSpecialMundaneMixture(special); op = ParseDependencyOp(&p); if (op == OP_NONE) { InvalidLineType(expandedLine, unexpandedLine); goto out; } ApplyDependencyOperator(op); pp_skip_whitespace(&p); ParseDependencySources(p, targetAttr, special, &paths); out: if (paths != NULL) Lst_Free(paths); } /* * Determine the assignment operator and adjust the end of the variable * name accordingly. */ static VarAssign AdjustVarassignOp(const char *name, const char *nameEnd, const char *op, const char *value) { VarAssignOp type; VarAssign va; if (op > name && op[-1] == '+') { op--; type = VAR_APPEND; } else if (op > name && op[-1] == '?') { op--; type = VAR_DEFAULT; } else if (op > name && op[-1] == ':') { op--; type = VAR_SUBST; } else if (op > name && op[-1] == '!') { op--; type = VAR_SHELL; } else { type = VAR_NORMAL; while (op > name && ch_isspace(op[-1])) op--; if (op - name >= 3 && memcmp(op - 3, ":sh", 3) == 0) { op -= 3; type = VAR_SHELL; } } va.varname = bmake_strsedup(name, nameEnd < op ? nameEnd : op); va.op = type; va.value = value; return va; } /* * Parse a variable assignment, consisting of a single-word variable name, * optional whitespace, an assignment operator, optional whitespace and the * variable value. * * Note: There is a lexical ambiguity with assignment modifier characters * in variable names. This routine interprets the character before the = * as a modifier. Therefore, an assignment like * C++=/usr/bin/CC * is interpreted as "C+ +=" instead of "C++ =". * * Used for both lines in a file and command line arguments. */ static bool Parse_IsVar(const char *p, VarAssign *out_var) { const char *nameStart, *nameEnd, *firstSpace, *eq; int level = 0; cpp_skip_hspace(&p); /* Skip to variable name */ /* * During parsing, the '+' of the operator '+=' is initially parsed * as part of the variable name. It is later corrected, as is the * ':sh' modifier. Of these two (nameEnd and eq), the earlier one * determines the actual end of the variable name. */ nameStart = p; firstSpace = NULL; /* Scan for one of the assignment operators outside an expression. */ while (*p != '\0') { char ch = *p++; if (ch == '(' || ch == '{') { level++; continue; } if (ch == ')' || ch == '}') { level--; continue; } if (level != 0) continue; if ((ch == ' ' || ch == '\t') && firstSpace == NULL) firstSpace = p - 1; while (ch == ' ' || ch == '\t') ch = *p++; if (ch == '\0') return false; if (ch == ':' && p[0] == 's' && p[1] == 'h') { p += 2; continue; } if (ch == '=') eq = p - 1; else if (*p == '=' && (ch == '+' || ch == ':' || ch == '?' || ch == '!')) eq = p; else if (firstSpace != NULL) return false; else continue; nameEnd = firstSpace != NULL ? firstSpace : eq; p = eq + 1; cpp_skip_whitespace(&p); *out_var = AdjustVarassignOp(nameStart, nameEnd, eq, p); return true; } return false; } /* * Check for syntax errors such as unclosed expressions or unknown modifiers. */ static void VarCheckSyntax(VarAssignOp op, const char *uvalue, GNode *scope) { if (opts.strict) { if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) { char *parsedValue = Var_Subst(uvalue, scope, VARE_PARSE); /* TODO: handle errors */ free(parsedValue); } } } /* Perform a variable assignment that uses the operator ':='. */ static void VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, FStr *out_avalue) { char *evalue; /* * Make sure that we set the variable the first time to nothing * so that it gets substituted. * * TODO: Add a test that demonstrates why this code is needed, * apart from making the debug log longer. * * XXX: The variable name is expanded up to 3 times. */ if (!Var_ExistsExpand(scope, name)) Var_SetExpand(scope, name, ""); evalue = Var_Subst(uvalue, scope, VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED); /* TODO: handle errors */ Var_SetExpand(scope, name, evalue); *out_avalue = FStr_InitOwn(evalue); } /* Perform a variable assignment that uses the operator '!='. */ static void VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, FStr *out_avalue) { FStr cmd; char *output, *error; cmd = FStr_InitRefer(uvalue); - Var_Expand(&cmd, SCOPE_CMDLINE, VARE_EVAL_DEFINED); + Var_Expand(&cmd, SCOPE_CMDLINE, VARE_EVAL); output = Cmd_Exec(cmd.str, &error); Var_SetExpand(scope, name, output); *out_avalue = FStr_InitOwn(output); if (error != NULL) { Parse_Error(PARSE_WARNING, "%s", error); free(error); } FStr_Done(&cmd); } /* * Perform a variable assignment. * * The actual value of the variable is returned in *out_true_avalue. * Especially for VAR_SUBST and VAR_SHELL this can differ from the literal * value. * * Return whether the assignment was actually performed, which is usually * the case. It is only skipped if the operator is '?=' and the variable * already exists. */ static bool VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, GNode *scope, FStr *out_true_avalue) { FStr avalue = FStr_InitRefer(uvalue); if (op == VAR_APPEND) Var_AppendExpand(scope, name, uvalue); else if (op == VAR_SUBST) VarAssign_EvalSubst(scope, name, uvalue, &avalue); else if (op == VAR_SHELL) VarAssign_EvalShell(name, uvalue, scope, &avalue); else { /* XXX: The variable name is expanded up to 2 times. */ if (op == VAR_DEFAULT && Var_ExistsExpand(scope, name)) return false; /* Normal assignment -- just do it. */ Var_SetExpand(scope, name, uvalue); } *out_true_avalue = avalue; return true; } static void VarAssignSpecial(const char *name, const char *avalue) { if (strcmp(name, ".MAKEOVERRIDES") == 0) Main_ExportMAKEFLAGS(false); /* re-export MAKEFLAGS */ else if (strcmp(name, ".CURDIR") == 0) { /* * Someone is being (too?) clever... * Let's pretend they know what they are doing and * re-initialize the 'cur' CachedDir. */ Dir_InitCur(avalue); Dir_SetPATH(); } else if (strcmp(name, ".MAKE.JOB.PREFIX") == 0) Job_SetPrefix(); else if (strcmp(name, ".MAKE.EXPORTED") == 0) Var_ExportVars(avalue); } /* Perform the variable assignment in the given scope. */ static void Parse_Var(VarAssign *var, GNode *scope) { FStr avalue; /* actual value (maybe expanded) */ VarCheckSyntax(var->op, var->value, scope); if (VarAssign_Eval(var->varname, var->op, var->value, scope, &avalue)) { VarAssignSpecial(var->varname, avalue.str); FStr_Done(&avalue); } } /* * See if the command possibly calls a sub-make by using the * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ static bool MaybeSubMake(const char *cmd) { const char *start; for (start = cmd; *start != '\0'; start++) { const char *p = start; char endc; /* XXX: What if progname != "make"? */ if (strncmp(p, "make", 4) == 0) if (start == cmd || !ch_isalnum(p[-1])) if (!ch_isalnum(p[4])) return true; if (*p != '$') continue; p++; if (*p == '{') endc = '}'; else if (*p == '(') endc = ')'; else continue; p++; if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */ p++; if (strncmp(p, "MAKE", 4) == 0 && p[4] == endc) return true; } return false; } /* Append the command to the target node. */ static void GNode_AddCommand(GNode *gn, char *cmd) { if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL) gn = gn->cohorts.last->datum; /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) { Lst_Append(&gn->commands, cmd); if (MaybeSubMake(cmd)) gn->type |= OP_SUBMAKE; RememberLocation(gn); } else { Parse_Error(PARSE_WARNING, "duplicate script for target \"%s\" ignored", gn->name); ParseErrorInternal(gn, PARSE_WARNING, "using previous script for \"%s\" defined here", gn->name); } } /* * Parse a directive like '.include' or '.-include'. * * .include "user-makefile.mk" * .include */ static void ParseInclude(char *directive) { char endc; /* '>' or '"' */ char *p; bool silent = directive[0] != 'i'; FStr file; p = directive + (silent ? 8 : 7); pp_skip_hspace(&p); if (*p != '"' && *p != '<') { Parse_Error(PARSE_FATAL, ".include filename must be delimited by '\"' or '<'"); return; } endc = *p++ == '<' ? '>' : '"'; file = FStr_InitRefer(p); while (*p != '\0' && *p != endc) p++; if (*p != endc) { Parse_Error(PARSE_FATAL, "Unclosed .include filename. '%c' expected", endc); return; } *p = '\0'; Var_Expand(&file, SCOPE_CMDLINE, VARE_EVAL); IncludeFile(file.str, endc == '>', directive[0] == 'd', silent); FStr_Done(&file); } /* * Split filename into dirname + basename, then assign these to the * given variables. */ static void SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) { const char *slash, *basename; FStr dirname; slash = strrchr(filename, '/'); if (slash == NULL) { dirname = FStr_InitRefer(curdir); basename = filename; } else { dirname = FStr_InitOwn(bmake_strsedup(filename, slash)); basename = slash + 1; } Global_Set(dirvar, dirname.str); Global_Set(filevar, basename); DEBUG4(PARSE, "SetFilenameVars: ${%s} = `%s' ${%s} = `%s'\n", dirvar, dirname.str, filevar, basename); FStr_Done(&dirname); } /* * Return the immediately including file. * * This is made complicated since the .for loop is implemented as a special * kind of .include; see For_Run. */ static const char * GetActuallyIncludingFile(void) { size_t i; const IncludedFile *incs = GetInclude(0); for (i = includes.len; i >= 2; i--) if (incs[i - 1].forLoop == NULL) return incs[i - 2].name.str; return NULL; } /* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */ static void SetParseFile(const char *filename) { const char *including; SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE"); including = GetActuallyIncludingFile(); if (including != NULL) { SetFilenameVars(including, ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE"); } else { Global_Delete(".INCLUDEDFROMDIR"); Global_Delete(".INCLUDEDFROMFILE"); } } static bool StrContainsWord(const char *str, const char *word) { size_t strLen = strlen(str); size_t wordLen = strlen(word); const char *p; if (strLen < wordLen) return false; for (p = str; p != NULL; p = strchr(p, ' ')) { if (*p == ' ') p++; if (p > str + strLen - wordLen) return false; if (memcmp(p, word, wordLen) == 0 && (p[wordLen] == '\0' || p[wordLen] == ' ')) return true; } return false; } /* * XXX: Searching through a set of words with this linear search is * inefficient for variables that contain thousands of words. * * XXX: The paths in this list don't seem to be normalized in any way. */ static bool VarContainsWord(const char *varname, const char *word) { FStr val = Var_Value(SCOPE_GLOBAL, varname); bool found = val.str != NULL && StrContainsWord(val.str, word); FStr_Done(&val); return found; } /* * Track the makefiles we read - so makefiles can set dependencies on them. * Avoid adding anything more than once. * * Time complexity: O(n) per call, in total O(n^2), where n is the number * of makefiles that have been loaded. */ static void TrackInput(const char *name) { if (!VarContainsWord(".MAKE.MAKEFILES", name)) Global_Append(".MAKE.MAKEFILES", name); } /* Parse from the given buffer, later return to the current file. */ void Parse_PushInput(const char *name, unsigned lineno, unsigned readLines, Buffer buf, struct ForLoop *forLoop) { IncludedFile *curFile; if (forLoop != NULL) name = CurFile()->name.str; else TrackInput(name); DEBUG3(PARSE, "Parse_PushInput: %s %s, line %u\n", forLoop != NULL ? ".for loop in": "file", name, lineno); curFile = Vector_Push(&includes); curFile->name = FStr_InitOwn(bmake_strdup(name)); curFile->lineno = lineno; curFile->readLines = readLines; curFile->forHeadLineno = lineno; curFile->forBodyReadLines = readLines; curFile->buf = buf; curFile->depending = doing_depend; /* restore this on EOF */ curFile->guardState = forLoop == NULL ? GS_START : GS_NO; curFile->guard = NULL; curFile->forLoop = forLoop; if (forLoop != NULL && !For_NextIteration(forLoop, &curFile->buf)) abort(); /* see For_Run */ curFile->buf_ptr = curFile->buf.data; curFile->buf_end = curFile->buf.data + curFile->buf.len; curFile->condMinDepth = cond_depth; SetParseFile(name); } /* Check if the directive is an include directive. */ static bool IsInclude(const char *dir, bool sysv) { if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) dir++; if (strncmp(dir, "include", 7) != 0) return false; /* Space is not mandatory for BSD .include */ return !sysv || ch_isspace(dir[7]); } /* Check if the line is a SYSV include directive. */ static bool IsSysVInclude(const char *line) { const char *p; if (!IsInclude(line, true)) return false; /* Avoid interpreting a dependency line as an include */ for (p = line; (p = strchr(p, ':')) != NULL;) { /* end of line -> it's a dependency */ if (*++p == '\0') return false; /* '::' operator or ': ' -> it's a dependency */ if (*p == ':' || ch_isspace(*p)) return false; } return true; } /* Push to another file. The line points to the word "include". */ static void ParseTraditionalInclude(char *line) { char *p; /* current position in file spec */ bool done = false; bool silent = line[0] != 'i'; char *file = line + (silent ? 8 : 7); char *all_files; DEBUG1(PARSE, "ParseTraditionalInclude: %s\n", file); pp_skip_whitespace(&file); all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ for (file = all_files; !done; file = p + 1) { /* Skip to end of line or next whitespace */ for (p = file; *p != '\0' && !ch_isspace(*p); p++) continue; if (*p != '\0') *p = '\0'; else done = true; IncludeFile(file, false, false, silent); } free(all_files); } /* Parse "export =", and actually export it. */ static void ParseGmakeExport(char *line) { char *variable = line + 6; char *value; DEBUG1(PARSE, "ParseGmakeExport: %s\n", variable); pp_skip_whitespace(&variable); for (value = variable; *value != '\0' && *value != '='; value++) continue; if (*value != '=') { Parse_Error(PARSE_FATAL, "Variable/Value missing from \"export\""); return; } *value++ = '\0'; /* terminate variable */ /* * Expand the value before putting it in the environment. */ value = Var_Subst(value, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ setenv(variable, value, 1); free(value); } /* * When the end of the current file or .for loop is reached, continue reading * the previous file at the previous location. * * Results: * true to continue parsing, i.e. it had only reached the end of an * included file, false if the main file has been parsed completely. */ static bool ParseEOF(void) { IncludedFile *curFile = CurFile(); doing_depend = curFile->depending; if (curFile->forLoop != NULL && For_NextIteration(curFile->forLoop, &curFile->buf)) { curFile->buf_ptr = curFile->buf.data; curFile->buf_end = curFile->buf.data + curFile->buf.len; curFile->readLines = curFile->forBodyReadLines; return true; } Cond_EndFile(); if (curFile->guardState == GS_DONE) { HashEntry *he = HashTable_CreateEntry(&guards, curFile->name.str, NULL); if (he->value != NULL) { free(((Guard *)he->value)->name); free(he->value); } HashEntry_Set(he, curFile->guard); } else if (curFile->guard != NULL) { free(curFile->guard->name); free(curFile->guard); } FStr_Done(&curFile->name); Buf_Done(&curFile->buf); if (curFile->forLoop != NULL) ForLoop_Free(curFile->forLoop); Vector_Pop(&includes); if (includes.len == 0) { /* We've run out of input */ Global_Delete(".PARSEDIR"); Global_Delete(".PARSEFILE"); Global_Delete(".INCLUDEDFROMDIR"); Global_Delete(".INCLUDEDFROMFILE"); return false; } curFile = CurFile(); DEBUG2(PARSE, "ParseEOF: returning to file %s, line %u\n", curFile->name.str, curFile->readLines + 1); SetParseFile(curFile->name.str); return true; } typedef enum ParseRawLineResult { PRLR_LINE, PRLR_EOF, PRLR_ERROR } ParseRawLineResult; /* * Parse until the end of a line, taking into account lines that end with * backslash-newline. The resulting line goes from out_line to out_line_end; * the line is not null-terminated. */ static ParseRawLineResult ParseRawLine(IncludedFile *curFile, char **out_line, char **out_line_end, char **out_firstBackslash, char **out_commentLineEnd) { char *line = curFile->buf_ptr; char *buf_end = curFile->buf_end; char *p = line; char *line_end = line; char *firstBackslash = NULL; char *commentLineEnd = NULL; ParseRawLineResult res = PRLR_LINE; curFile->readLines++; for (;;) { char ch; if (p == buf_end) { res = PRLR_EOF; break; } ch = *p; if (ch == '\0' || (ch == '\\' && p[1] == '\0')) { Parse_Error(PARSE_FATAL, "Zero byte read from file"); exit(2); } /* Treat next character after '\' as literal. */ if (ch == '\\') { if (firstBackslash == NULL) firstBackslash = p; if (p[1] == '\n') { curFile->readLines++; if (p + 2 == buf_end) { line_end = p; *line_end = '\n'; p += 2; continue; } } p += 2; line_end = p; assert(p <= buf_end); continue; } /* * Remember the first '#' for comment stripping, unless * the previous char was '[', as in the modifier ':[#]'. */ if (ch == '#' && commentLineEnd == NULL && !(p > line && p[-1] == '[')) commentLineEnd = line_end; p++; if (ch == '\n') break; /* We are not interested in trailing whitespace. */ if (!ch_isspace(ch)) line_end = p; } curFile->buf_ptr = p; *out_line = line; *out_line_end = line_end; *out_firstBackslash = firstBackslash; *out_commentLineEnd = commentLineEnd; return res; } /* * Beginning at start, unescape '\#' to '#' and replace backslash-newline * with a single space. */ static void UnescapeBackslash(char *line, char *start) { const char *src = start; char *dst = start; char *spaceStart = line; for (;;) { char ch = *src++; if (ch != '\\') { if (ch == '\0') break; *dst++ = ch; continue; } ch = *src++; if (ch == '\0') { /* Delete '\\' at the end of the buffer. */ dst--; break; } /* Delete '\\' from before '#' on non-command lines. */ if (ch == '#' && line[0] != '\t') *dst++ = ch; else if (ch == '\n') { cpp_skip_hspace(&src); *dst++ = ' '; } else { /* Leave '\\' in the buffer for later. */ *dst++ = '\\'; *dst++ = ch; /* Keep an escaped ' ' at the line end. */ spaceStart = dst; } } /* Delete any trailing spaces - eg from empty continuations */ while (dst > spaceStart && ch_isspace(dst[-1])) dst--; *dst = '\0'; } typedef enum LineKind { /* * Return the next line that is neither empty nor a comment. * Backslash line continuations are folded into a single space. * A trailing comment, if any, is discarded. */ LK_NONEMPTY, /* * Return the next line, even if it is empty or a comment. * Preserve backslash-newline to keep the line numbers correct. * * Used in .for loops to collect the body of the loop while waiting * for the corresponding .endfor. */ LK_FOR_BODY, /* * Return the next line that starts with a dot. * Backslash line continuations are folded into a single space. * A trailing comment, if any, is discarded. * * Used in .if directives to skip over irrelevant branches while * waiting for the corresponding .endif. */ LK_DOT } LineKind; /* * Return the next "interesting" logical line from the current file. The * returned string will be freed at the end of including the file. */ static char * ReadLowLevelLine(LineKind kind) { IncludedFile *curFile = CurFile(); ParseRawLineResult res; char *line; char *line_end; char *firstBackslash; char *commentLineEnd; for (;;) { curFile->lineno = curFile->readLines + 1; res = ParseRawLine(curFile, &line, &line_end, &firstBackslash, &commentLineEnd); if (res == PRLR_ERROR) return NULL; if (line == line_end || line == commentLineEnd) { if (res == PRLR_EOF) return NULL; if (kind != LK_FOR_BODY) continue; } /* We now have a line of data */ assert(ch_isspace(*line_end)); *line_end = '\0'; if (kind == LK_FOR_BODY) return line; /* Don't join the physical lines. */ if (kind == LK_DOT && line[0] != '.') continue; break; } if (commentLineEnd != NULL && line[0] != '\t') *commentLineEnd = '\0'; if (firstBackslash != NULL) UnescapeBackslash(line, firstBackslash); return line; } static bool SkipIrrelevantBranches(void) { const char *line; while ((line = ReadLowLevelLine(LK_DOT)) != NULL) if (Cond_EvalLine(line) == CR_TRUE) return true; return false; } static bool ParseForLoop(const char *line) { int rval; unsigned forHeadLineno; unsigned bodyReadLines; int forLevel; rval = For_Eval(line); if (rval == 0) return false; /* Not a .for line */ if (rval < 0) return true; /* Syntax error - error printed, ignore line */ forHeadLineno = CurFile()->lineno; bodyReadLines = CurFile()->readLines; /* Accumulate the loop body until the matching '.endfor'. */ forLevel = 1; do { line = ReadLowLevelLine(LK_FOR_BODY); if (line == NULL) { Parse_Error(PARSE_FATAL, "Unexpected end of file in .for loop"); break; } } while (For_Accum(line, &forLevel)); For_Run(forHeadLineno, bodyReadLines); return true; } /* * Read an entire line from the input file. * * Empty lines, .if and .for are handled by this function, while variable * assignments, other directives, dependency lines and shell commands are * handled by the caller. * * Return a line without trailing whitespace, or NULL for EOF. The returned * string will be freed at the end of including the file. */ static char * ReadHighLevelLine(void) { char *line; CondResult condResult; for (;;) { IncludedFile *curFile = CurFile(); line = ReadLowLevelLine(LK_NONEMPTY); if (posix_state == PS_MAYBE_NEXT_LINE) posix_state = PS_NOW_OR_NEVER; else posix_state = PS_TOO_LATE; if (line == NULL) return NULL; - DEBUG2(PARSE, "Parsing line %u: %s\n", curFile->lineno, line); + DEBUG3(PARSE, "Parsing %s:%u: %s\n", + curFile->name.str, curFile->lineno, line); if (curFile->guardState != GS_NO && ((curFile->guardState == GS_START && line[0] != '.') || curFile->guardState == GS_DONE)) curFile->guardState = GS_NO; if (line[0] != '.') return line; condResult = Cond_EvalLine(line); if (curFile->guardState == GS_START) { Guard *guard; if (condResult != CR_ERROR && (guard = Cond_ExtractGuard(line)) != NULL) { curFile->guardState = GS_COND; curFile->guard = guard; } else curFile->guardState = GS_NO; } switch (condResult) { case CR_FALSE: /* May also mean a syntax error. */ if (!SkipIrrelevantBranches()) return NULL; continue; case CR_TRUE: continue; case CR_ERROR: /* Not a conditional line */ if (ParseForLoop(line)) continue; break; } return line; } } static void FinishDependencyGroup(void) { GNodeListNode *ln; if (targets == NULL) return; for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; Suff_EndTransform(gn); /* * Mark the target as already having commands if it does, to * keep from having shell commands on multiple dependency * lines. */ if (!Lst_IsEmpty(&gn->commands)) gn->type |= OP_HAS_COMMANDS; } Lst_Free(targets); targets = NULL; } #ifdef CLEANUP void Parse_RegisterCommand(char *cmd) { Lst_Append(&targCmds, cmd); } #endif /* Add the command to each target from the current dependency spec. */ static void ParseLine_ShellCommand(const char *p) { cpp_skip_whitespace(&p); if (*p == '\0') return; /* skip empty commands */ if (targets == NULL) { Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", p); return; } { char *cmd = bmake_strdup(p); GNodeListNode *ln; for (ln = targets->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; GNode_AddCommand(gn, cmd); } Parse_RegisterCommand(cmd); } } static void HandleBreak(const char *arg) { IncludedFile *curFile = CurFile(); if (arg[0] != '\0') Parse_Error(PARSE_FATAL, "The .break directive does not take arguments"); if (curFile->forLoop != NULL) { /* pretend we reached EOF */ For_Break(curFile->forLoop); cond_depth = CurFile_CondMinDepth(); ParseEOF(); } else Parse_Error(PARSE_FATAL, "break outside of for loop"); } /* * See if the line starts with one of the known directives, and if so, handle * the directive. */ static bool ParseDirective(char *line) { char *p = line + 1; const char *arg; Substring dir; pp_skip_whitespace(&p); if (IsInclude(p, false)) { ParseInclude(p); return true; } dir.start = p; while (ch_islower(*p) || *p == '-') p++; dir.end = p; if (*p != '\0' && !ch_isspace(*p)) return false; pp_skip_whitespace(&p); arg = p; if (Substring_Equals(dir, "break")) HandleBreak(arg); else if (Substring_Equals(dir, "undef")) Var_Undef(arg); else if (Substring_Equals(dir, "export")) Var_Export(VEM_PLAIN, arg); else if (Substring_Equals(dir, "export-all")) Var_Export(VEM_ALL, arg); else if (Substring_Equals(dir, "export-env")) Var_Export(VEM_ENV, arg); else if (Substring_Equals(dir, "export-literal")) Var_Export(VEM_LITERAL, arg); else if (Substring_Equals(dir, "unexport")) Var_UnExport(false, arg); else if (Substring_Equals(dir, "unexport-env")) Var_UnExport(true, arg); else if (Substring_Equals(dir, "info")) HandleMessage(PARSE_INFO, "info", arg); else if (Substring_Equals(dir, "warning")) HandleMessage(PARSE_WARNING, "warning", arg); else if (Substring_Equals(dir, "error")) HandleMessage(PARSE_FATAL, "error", arg); else return false; return true; } bool Parse_VarAssign(const char *line, bool finishDependencyGroup, GNode *scope) { VarAssign var; if (!Parse_IsVar(line, &var)) return false; if (finishDependencyGroup) FinishDependencyGroup(); Parse_Var(&var, scope); free(var.varname); return true; } void Parse_GuardElse(void) { IncludedFile *curFile = CurFile(); if (cond_depth == curFile->condMinDepth + 1) curFile->guardState = GS_NO; } void Parse_GuardEndif(void) { IncludedFile *curFile = CurFile(); if (cond_depth == curFile->condMinDepth && curFile->guardState == GS_COND) curFile->guardState = GS_DONE; } static char * FindSemicolon(char *p) { int depth = 0; for (; *p != '\0'; p++) { if (*p == '\\' && p[1] != '\0') { p++; continue; } if (*p == '$' && (p[1] == '(' || p[1] == '{')) depth++; else if (depth > 0 && (*p == ')' || *p == '}')) depth--; else if (depth == 0 && *p == ';') break; } return p; } static void ParseDependencyLine(char *line) { - VarEvalMode emode; char *expanded_line; const char *shellcmd = NULL; { char *semicolon = FindSemicolon(line); if (*semicolon != '\0') { /* Terminate the dependency list at the ';' */ *semicolon = '\0'; shellcmd = semicolon + 1; } } - /* - * We now know it's a dependency line, so it needs to have all - * variables expanded before being parsed. - * - * XXX: Ideally the dependency line would first be split into - * its left-hand side, dependency operator and right-hand side, - * and then each side would be expanded on its own. This would - * allow for the left-hand side to allow only defined variables - * and to allow variables on the right-hand side to be undefined - * as well. - * - * Parsing the line first would also prevent that targets - * generated from expressions are interpreted as the - * dependency operator, such as in "target${:U\:} middle: source", - * in which the middle is interpreted as a source, not a target. - */ - - /* - * In lint mode, allow undefined variables to appear in dependency - * lines. - * - * Ideally, only the right-hand side would allow undefined variables - * since it is common to have optional dependencies. Having undefined - * variables on the left-hand side is more unusual though. Since - * both sides are expanded in a single pass, there is not much choice - * what to do here. - * - * In normal mode, it does not matter whether undefined variables are - * allowed or not since as of 2020-09-14, Var_Parse does not print - * any parse errors in such a case. It simply returns the special - * empty string var_Error, which cannot be detected in the result of - * Var_Subst. - */ - emode = opts.strict ? VARE_EVAL : VARE_EVAL_DEFINED; - expanded_line = Var_Subst(line, SCOPE_CMDLINE, emode); + expanded_line = Var_Subst(line, SCOPE_CMDLINE, VARE_EVAL); /* TODO: handle errors */ /* Need a fresh list for the target nodes */ if (targets != NULL) Lst_Free(targets); targets = Lst_New(); ParseDependency(expanded_line, line); free(expanded_line); if (shellcmd != NULL) ParseLine_ShellCommand(shellcmd); } static void ParseLine(char *line) { if (line[0] == '.' && ParseDirective(line)) return; if (line[0] == '\t') { ParseLine_ShellCommand(line + 1); return; } if (IsSysVInclude(line)) { ParseTraditionalInclude(line); return; } if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && strchr(line, ':') == NULL) { ParseGmakeExport(line); return; } if (Parse_VarAssign(line, true, SCOPE_GLOBAL)) return; FinishDependencyGroup(); ParseDependencyLine(line); } /* Interpret a top-level makefile. */ void Parse_File(const char *name, int fd) { char *line; Buffer buf; buf = LoadFile(name, fd != -1 ? fd : STDIN_FILENO); if (fd != -1) (void)close(fd); assert(targets == NULL); Parse_PushInput(name, 1, 0, buf, NULL); do { while ((line = ReadHighLevelLine()) != NULL) { ParseLine(line); } } while (ParseEOF()); FinishDependencyGroup(); if (parseErrors != 0) { (void)fflush(stdout); (void)fprintf(stderr, "%s: Fatal errors encountered -- cannot continue\n", progname); PrintOnError(NULL, ""); exit(1); } } /* Initialize the parsing module. */ void Parse_Init(void) { mainNode = NULL; parseIncPath = SearchPath_New(); sysIncPath = SearchPath_New(); defSysIncPath = SearchPath_New(); Vector_Init(&includes, sizeof(IncludedFile)); HashTable_Init(&guards); } #ifdef CLEANUP /* Clean up the parsing module. */ void Parse_End(void) { HashIter hi; Lst_DoneFree(&targCmds); assert(targets == NULL); SearchPath_Free(defSysIncPath); SearchPath_Free(sysIncPath); SearchPath_Free(parseIncPath); assert(includes.len == 0); Vector_Done(&includes); HashIter_Init(&hi, &guards); while (HashIter_Next(&hi)) { Guard *guard = hi.entry->value; free(guard->name); free(guard); } HashTable_Done(&guards); } #endif /* Populate the list with the single main target to create, or error out. */ void Parse_MainName(GNodeList *mainList) { if (mainNode == NULL) Punt("no target to make."); Lst_Append(mainList, mainNode); if (mainNode->type & OP_DOUBLEDEP) Lst_AppendAll(mainList, &mainNode->cohorts); Global_Append(".TARGETS", mainNode->name); } diff --git a/contrib/bmake/suff.c b/contrib/bmake/suff.c index 2ca5bb06da47..c24a0ac5afdc 100644 --- a/contrib/bmake/suff.c +++ b/contrib/bmake/suff.c @@ -1,2146 +1,2146 @@ -/* $NetBSD: suff.c,v 1.382 2024/07/07 07:50:57 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.383 2025/01/14 21:39:24 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Maintain suffix lists and find implicit dependents using suffix * transformation rules such as ".c.o". * * Interface: * Suff_Init Initialize the module. * * Suff_End Clean up the module. * * Suff_ExtendPaths * Extend the search path of each suffix to include the * default search path. * * Suff_ClearSuffixes * Clear out all the suffixes and transformations. * * Suff_IsTransform * See if the passed string is a transformation rule. * * Suff_AddSuffix Add the passed string as another known suffix. * * Suff_GetPath Return the search path for the given suffix. * * Suff_AddInclude * Mark the given suffix as denoting an include file. * * Suff_AddLib Mark the given suffix as denoting a library. * * Suff_AddTransform * Add another transformation to the suffix graph. * * Suff_SetNull Define the suffix to consider the suffix of * any file that doesn't have a known one. * * Suff_FindDeps Find implicit sources for and the location of * a target based on its suffix. Returns the * bottom-most node added to the graph or NULL * if the target had no implicit sources. * * Suff_FindPath Return the appropriate path to search in order to * find the node. */ #include "make.h" #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.382 2024/07/07 07:50:57 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.383 2025/01/14 21:39:24 rillig Exp $"); typedef List SuffixList; typedef ListNode SuffixListNode; typedef List CandidateList; typedef ListNode CandidateListNode; /* The defined suffixes, such as '.c', '.o', '.l'. */ static SuffixList sufflist = LST_INIT; #ifdef CLEANUP /* The suffixes to be cleaned up at the end. */ static SuffixList suffClean = LST_INIT; #endif /* * The transformation rules, such as '.c.o' to transform '.c' into '.o', * or simply '.c' to transform 'file.c' into 'file'. */ static GNodeList transforms = LST_INIT; /* * Counter for assigning suffix numbers. * TODO: What are these suffix numbers used for? */ static int sNum = 0; /* * A suffix such as ".c" or ".o" that may be used in suffix transformation * rules such as ".c.o:". */ typedef struct Suffix { /* The suffix itself, such as ".c" */ char *name; /* Length of the name, to avoid strlen calls */ size_t nameLen; /* * This suffix marks include files. Their search path ends up in the * undocumented special variable '.INCLUDES'. */ bool include:1; /* * This suffix marks library files. Their search path ends up in the * undocumented special variable '.LIBS'. */ bool library:1; /* * The empty suffix. * * XXX: What is the difference between the empty suffix and the null * suffix? * * XXX: Why is SUFF_NULL needed at all? Wouldn't nameLen == 0 mean * the same? */ bool isNull:1; /* The path along which files of this suffix may be found */ SearchPath *searchPath; /* The suffix number; TODO: document the purpose of this number */ int sNum; /* Reference count of list membership and several other places */ int refCount; /* Suffixes we have a transformation to */ SuffixList parents; /* Suffixes we have a transformation from */ SuffixList children; } Suffix; /* * A candidate when searching for implied sources. * * For example, when "src.o" is to be made, a typical candidate is "src.c" * via the transformation rule ".c.o". If that doesn't exist, maybe there is * another transformation rule ".pas.c" that would make "src.pas" an indirect * candidate as well. The first such chain that leads to an existing file or * node is finally chosen to be made. */ typedef struct Candidate { /* The file or node to look for. */ char *file; /* * The prefix from which file was formed. Its memory is shared among * all candidates. */ char *prefix; /* The suffix on the file. */ Suffix *suff; /* * The candidate that can be made from this, or NULL for the * top-level candidate. */ struct Candidate *parent; /* The node describing the file. */ GNode *node; /* * Count of existing children, only used for memory management, so we * don't free this candidate too early or too late. */ int numChildren; #ifdef DEBUG_SRC CandidateList childrenList; #endif } Candidate; typedef struct CandidateSearcher { CandidateList list; /* * TODO: Add HashSet for seen entries, to avoid endless loops such as * in suff-transform-endless.mk. */ } CandidateSearcher; /* TODO: Document the difference between nullSuff and emptySuff. */ /* The NULL suffix is used when a file has no known suffix */ static Suffix *nullSuff; /* The empty suffix required for POSIX single-suffix transformation rules */ static Suffix *emptySuff; static Suffix * Suffix_Ref(Suffix *suff) { suff->refCount++; return suff; } /* Change the value of a Suffix variable, adjusting the reference counts. */ static void Suffix_Reassign(Suffix **var, Suffix *suff) { if (*var != NULL) (*var)->refCount--; *var = suff; suff->refCount++; } /* Set a Suffix variable to NULL, adjusting the reference count. */ static void Suffix_Unassign(Suffix **var) { if (*var != NULL) (*var)->refCount--; *var = NULL; } /* * See if pref is a prefix of str. * Return NULL if it ain't, pointer to character in str after prefix if so. */ static const char * StrTrimPrefix(const char *pref, const char *str) { while (*str != '\0' && *pref == *str) { pref++; str++; } return *pref != '\0' ? NULL : str; } /* * See if suff is a suffix of str, and if so, return the pointer to the suffix * in str, which at the same time marks the end of the prefix. */ static const char * StrTrimSuffix(const char *str, size_t strLen, const char *suff, size_t suffLen) { const char *suffInStr; size_t i; if (strLen < suffLen) return NULL; suffInStr = str + strLen - suffLen; for (i = 0; i < suffLen; i++) if (suff[i] != suffInStr[i]) return NULL; return suffInStr; } /* * See if suff is a suffix of name, and if so, return the end of the prefix * in name. */ static const char * Suffix_TrimSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { return StrTrimSuffix(nameEnd - nameLen, nameLen, suff->name, suff->nameLen); } static bool Suffix_IsSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { return Suffix_TrimSuffix(suff, nameLen, nameEnd) != NULL; } static Suffix * FindSuffixByNameLen(const char *name, size_t nameLen) { SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (suff->nameLen == nameLen && memcmp(suff->name, name, nameLen) == 0) return suff; } return NULL; } static Suffix * FindSuffixByName(const char *name) { return FindSuffixByNameLen(name, strlen(name)); } static GNode * FindTransformByName(const char *name) { GNodeListNode *ln; for (ln = transforms.first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (strcmp(gn->name, name) == 0) return gn; } return NULL; } static void SuffixList_Unref(SuffixList *list, Suffix *suff) { SuffixListNode *ln = Lst_FindDatum(list, suff); if (ln != NULL) { Lst_Remove(list, ln); suff->refCount--; } } static void Suffix_Free(Suffix *suff) { if (suff == nullSuff) nullSuff = NULL; if (suff == emptySuff) emptySuff = NULL; #if 0 /* We don't delete suffixes in order, so we cannot use this */ if (suff->refCount != 0) Punt("Internal error deleting suffix `%s' with refcount = %d", suff->name, suff->refCount); #endif Lst_Done(&suff->children); Lst_Done(&suff->parents); SearchPath_Free(suff->searchPath); free(suff->name); free(suff); } /* Remove the suffix from the list, and free if it is otherwise unused. */ static void SuffixList_Remove(SuffixList *list, Suffix *suff) { SuffixList_Unref(list, suff); if (suff->refCount == 0) { /* XXX: can lead to suff->refCount == -1 */ SuffixList_Unref(&sufflist, suff); DEBUG1(SUFF, "Removing suffix \"%s\"\n", suff->name); Suffix_Free(suff); } } /* * Insert the suffix into the list, keeping the list ordered by suffix * number. */ static void SuffixList_Insert(SuffixList *list, Suffix *suff) { SuffixListNode *ln; Suffix *listSuff = NULL; for (ln = list->first; ln != NULL; ln = ln->next) { listSuff = ln->datum; if (listSuff->sNum >= suff->sNum) break; } if (ln == NULL) { DEBUG2(SUFF, "inserting \"%s\" (%d) at end of list\n", suff->name, suff->sNum); Lst_Append(list, Suffix_Ref(suff)); } else if (listSuff->sNum != suff->sNum) { DEBUG4(SUFF, "inserting \"%s\" (%d) before \"%s\" (%d)\n", suff->name, suff->sNum, listSuff->name, listSuff->sNum); Lst_InsertBefore(list, ln, Suffix_Ref(suff)); } else { DEBUG2(SUFF, "\"%s\" (%d) is already there\n", suff->name, suff->sNum); } } static void Relate(Suffix *srcSuff, Suffix *targSuff) { SuffixList_Insert(&targSuff->children, srcSuff); SuffixList_Insert(&srcSuff->parents, targSuff); } static Suffix * Suffix_New(const char *name) { Suffix *suff = bmake_malloc(sizeof *suff); suff->name = bmake_strdup(name); suff->nameLen = strlen(suff->name); suff->searchPath = SearchPath_New(); Lst_Init(&suff->children); Lst_Init(&suff->parents); suff->sNum = sNum++; suff->include = false; suff->library = false; suff->isNull = false; suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */ return suff; } /* * Nuke the list of suffixes but keep all transformation rules around. The * transformation graph is destroyed in this process, but we leave the list * of rules so when a new graph is formed, the rules will remain. This * function is called when a line '.SUFFIXES:' with an empty suffixes list is * encountered in a makefile. */ void Suff_ClearSuffixes(void) { #ifdef CLEANUP Lst_MoveAll(&suffClean, &sufflist); #endif DEBUG0(SUFF, "Clearing all suffixes\n"); Lst_Init(&sufflist); sNum = 0; if (nullSuff != NULL) Suffix_Free(nullSuff); emptySuff = nullSuff = Suffix_New(""); SearchPath_AddAll(nullSuff->searchPath, &dirSearchPath); nullSuff->include = false; nullSuff->library = false; nullSuff->isNull = true; } /* * Parse a transformation string such as ".c.o" to find its two component * suffixes (the source ".c" and the target ".o"). If there are no such * suffixes, try a single-suffix transformation as well. * * Return true if the string is a valid transformation. */ static bool ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ) { SuffixListNode *ln; Suffix *single = NULL; /* * Loop looking first for a suffix that matches the start of the * string and then for one that exactly matches the rest of it. If * we can find two that meet these criteria, we've successfully * parsed the string. */ for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *src = ln->datum; if (StrTrimPrefix(src->name, str) == NULL) continue; if (str[src->nameLen] == '\0') { single = src; } else { Suffix *targ = FindSuffixByName(str + src->nameLen); if (targ != NULL) { *out_src = src; *out_targ = targ; return true; } } } if (single != NULL) { /* * There was a suffix that encompassed the entire string, so we * assume it was a transformation to the null suffix (thank you * POSIX; search for "single suffix" or "single-suffix"). * * We still prefer to find a double rule over a singleton, * hence we leave this check until the end. * * XXX: Use emptySuff over nullSuff? */ *out_src = single; *out_targ = nullSuff; return true; } return false; } /* * Return true if the given string is a transformation rule, that is, a * concatenation of two known suffixes such as ".c.o" or a single suffix * such as ".o". */ bool Suff_IsTransform(const char *str) { Suffix *src, *targ; return ParseTransform(str, &src, &targ); } /* * Add the transformation rule to the list of rules and place the * transformation itself in the graph. * * The transformation is linked to the two suffixes mentioned in the name. * * Input: * name must have the form ".from.to" or just ".from" * * Results: * The created or existing transformation node in the transforms list */ GNode * Suff_AddTransform(const char *name) { Suffix *srcSuff; Suffix *targSuff; GNode *gn = FindTransformByName(name); if (gn == NULL) { /* * Make a new graph node for the transformation. It will be * filled in by the Parse module. */ gn = GNode_New(name); Lst_Append(&transforms, gn); } else { /* * New specification for transformation rule. Just nuke the * old list of commands so they can be filled in again. We * don't actually free the commands themselves, because a * given command can be attached to several different * transformations. */ Lst_Done(&gn->commands); Lst_Init(&gn->commands); Lst_Done(&gn->children); Lst_Init(&gn->children); } gn->type = OP_TRANSFORM; { /* TODO: Avoid the redundant parsing here. */ bool ok = ParseTransform(name, &srcSuff, &targSuff); assert(ok); /* LINTED 129 *//* expression has null effect */ (void)ok; } /* Link the two together in the proper relationship and order. */ DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n", srcSuff->name, targSuff->name); Relate(srcSuff, targSuff); return gn; } /* * Handle the finish of a transformation definition, removing the * transformation from the graph if it has neither commands nor sources. * * If the node has no commands or children, the children and parents lists * of the affected suffixes are altered. * * Input: * gn Node for transformation */ void Suff_EndTransform(GNode *gn) { Suffix *srcSuff, *targSuff; SuffixList *srcSuffParents; if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&gn->cohorts)) gn = gn->cohorts.last->datum; if (!(gn->type & OP_TRANSFORM)) return; if (!Lst_IsEmpty(&gn->commands) || !Lst_IsEmpty(&gn->children)) { DEBUG1(SUFF, "transformation %s complete\n", gn->name); return; } /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules. (e.g. .DEFAULT) */ if (!ParseTransform(gn->name, &srcSuff, &targSuff)) return; DEBUG2(SUFF, "deleting incomplete transformation from `%s' to `%s'\n", srcSuff->name, targSuff->name); /* * Remember the parents since srcSuff could be deleted in * SuffixList_Remove. */ srcSuffParents = &srcSuff->parents; SuffixList_Remove(&targSuff->children, srcSuff); SuffixList_Remove(srcSuffParents, targSuff); } /* * Called from Suff_AddSuffix to search through the list of * existing transformation rules and rebuild the transformation graph when * it has been destroyed by Suff_ClearSuffixes. If the given rule is a * transformation involving this suffix and another, existing suffix, the * proper relationship is established between the two. * * The appropriate links will be made between this suffix and others if * transformation rules exist for it. * * Input: * transform Transformation to test * suff Suffix to rebuild */ static void RebuildGraph(GNode *transform, Suffix *suff) { const char *name = transform->name; size_t nameLen = strlen(name); const char *toName; /* * See if it is a transformation from this suffix to another suffix. */ toName = StrTrimPrefix(suff->name, name); if (toName != NULL) { Suffix *to = FindSuffixByName(toName); if (to != NULL) { Relate(suff, to); return; } } /* * See if it is a transformation from another suffix to this suffix. */ toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen); if (toName != NULL) { Suffix *from = FindSuffixByNameLen(name, (size_t)(toName - name)); if (from != NULL) Relate(from, suff); } } /* * During Suff_AddSuffix, search through the list of existing targets and find * if any of the existing targets can be turned into a transformation rule. * * If such a target is found and the target is the current main target, the * main target is set to NULL and the next target examined (if that exists) * becomes the main target. * * Results: * true iff a new main target has been selected. */ static bool UpdateTarget(GNode *target, Suffix *suff, bool *inout_removedMain) { Suffix *srcSuff, *targSuff; char *ptr; if (mainNode == NULL && *inout_removedMain && GNode_IsMainCandidate(target)) { DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name); mainNode = target; /* * XXX: Why could it be a good idea to return true here? * The main task of this function is to turn ordinary nodes * into transformations, no matter whether or not a new .MAIN * node has been found. */ /* * XXX: Even when changing this to false, none of the existing * unit tests fails. */ return true; } if (target->type == OP_TRANSFORM) return false; /* * XXX: What about a transformation ".cpp.c"? If ".c" is added as * a new suffix, it seems wrong that this transformation would be * skipped just because ".c" happens to be a prefix of ".cpp". */ ptr = strstr(target->name, suff->name); if (ptr == NULL) return false; /* * XXX: In suff-rebuild.mk, in the line '.SUFFIXES: .c .b .a', this * condition prevents the rule '.b.c' from being added again during * Suff_AddSuffix(".b"). * * XXX: Removing this paragraph makes suff-add-later.mk use massive * amounts of memory. */ if (ptr == target->name) return false; if (ParseTransform(target->name, &srcSuff, &targSuff)) { if (mainNode == target) { DEBUG1(MAKE, "Setting main node from \"%s\" back to null\n", target->name); *inout_removedMain = true; mainNode = NULL; } Lst_Done(&target->children); Lst_Init(&target->children); target->type = OP_TRANSFORM; /* * Link the two together in the proper relationship and order. */ DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n", srcSuff->name, targSuff->name); Relate(srcSuff, targSuff); } return false; } /* * Look at all existing targets to see if adding this suffix will make one * of the current targets mutate into a suffix rule. * * This is ugly, but other makes treat all targets that start with a '.' as * suffix rules. */ static void UpdateTargets(Suffix *suff) { bool removedMain = false; GNodeListNode *ln; for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; if (UpdateTarget(gn, suff, &removedMain)) break; } } /* Add the suffix to the end of the list of known suffixes. */ void Suff_AddSuffix(const char *name) { GNodeListNode *ln; Suffix *suff = FindSuffixByName(name); if (suff != NULL) return; suff = Suffix_New(name); Lst_Append(&sufflist, suff); DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name); UpdateTargets(suff); /* * Look for any existing transformations from or to this suffix. * XXX: Only do this after a Suff_ClearSuffixes? */ for (ln = transforms.first; ln != NULL; ln = ln->next) RebuildGraph(ln->datum, suff); } /* Return the search path for the given suffix, or NULL. */ SearchPath * Suff_GetPath(const char *name) { Suffix *suff = FindSuffixByName(name); return suff != NULL ? suff->searchPath : NULL; } /* * Extend the search paths for all suffixes to include the default search * path (dirSearchPath). * * The default search path can be defined using the special target '.PATH'. * The search path of each suffix can be defined using the special target * '.PATH'. * * If paths were specified for the ".h" suffix, the directories are stuffed * into a global variable called ".INCLUDES" with each directory preceded by * '-I'. The same is done for the ".a" suffix, except the variable is called * ".LIBS" and the flag is '-L'. */ void Suff_ExtendPaths(void) { SuffixListNode *ln; char *flags; SearchPath *includesPath = SearchPath_New(); SearchPath *libsPath = SearchPath_New(); for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (!Lst_IsEmpty(&suff->searchPath->dirs)) { if (suff->include) SearchPath_AddAll(includesPath, suff->searchPath); if (suff->library) SearchPath_AddAll(libsPath, suff->searchPath); SearchPath_AddAll(suff->searchPath, &dirSearchPath); } else { SearchPath_Free(suff->searchPath); suff->searchPath = Dir_CopyDirSearchPath(); } } flags = SearchPath_ToFlags(includesPath, "-I"); Global_Set(".INCLUDES", flags); free(flags); flags = SearchPath_ToFlags(libsPath, "-L"); Global_Set(".LIBS", flags); free(flags); SearchPath_Free(includesPath); SearchPath_Free(libsPath); } /* * Add the given suffix as a type of file which gets included. * Called when a '.INCLUDES: .h' line is parsed. * To have an effect, the suffix must already exist. * This affects the magic variable '.INCLUDES'. */ void Suff_AddInclude(const char *suffName) { Suffix *suff = FindSuffixByName(suffName); if (suff != NULL) suff->include = true; } /* * Add the given suffix as a type of file which is a library. * Called when a '.LIBS: .a' line is parsed. * To have an effect, the suffix must already exist. * This affects the magic variable '.LIBS'. */ void Suff_AddLib(const char *suffName) { Suffix *suff = FindSuffixByName(suffName); if (suff != NULL) suff->library = true; } /********** Implicit Source Search Functions *********/ static void CandidateSearcher_Init(CandidateSearcher *cs) { Lst_Init(&cs->list); } static void CandidateSearcher_Done(CandidateSearcher *cs) { Lst_Done(&cs->list); } static void CandidateSearcher_Add(CandidateSearcher *cs, Candidate *cand) { /* TODO: filter duplicates */ Lst_Append(&cs->list, cand); } static void CandidateSearcher_AddIfNew(CandidateSearcher *cs, Candidate *cand) { /* TODO: filter duplicates */ if (Lst_FindDatum(&cs->list, cand) == NULL) Lst_Append(&cs->list, cand); } static void CandidateSearcher_MoveAll(CandidateSearcher *cs, CandidateList *list) { /* TODO: filter duplicates */ Lst_MoveAll(&cs->list, list); } #ifdef DEBUG_SRC static void CandidateList_PrintAddrs(CandidateList *list) { CandidateListNode *ln; for (ln = list->first; ln != NULL; ln = ln->next) { Candidate *cand = ln->datum; debug_printf(" %p:%s", cand, cand->file); } debug_printf("\n"); } #endif static Candidate * Candidate_New(char *name, char *prefix, Suffix *suff, Candidate *parent, GNode *gn) { Candidate *cand = bmake_malloc(sizeof *cand); cand->file = name; cand->prefix = prefix; cand->suff = Suffix_Ref(suff); cand->parent = parent; cand->node = gn; cand->numChildren = 0; #ifdef DEBUG_SRC Lst_Init(&cand->childrenList); #endif return cand; } /* Add a new candidate to the list. */ static void CandidateList_Add(CandidateList *list, char *srcName, Candidate *targ, Suffix *suff, const char *debug_tag MAKE_ATTR_UNUSED) { Candidate *cand = Candidate_New(srcName, targ->prefix, suff, targ, NULL); targ->numChildren++; Lst_Append(list, cand); #ifdef DEBUG_SRC Lst_Append(&targ->childrenList, cand); debug_printf("%s add suff %p:%s candidate %p:%s to list %p:", debug_tag, targ, targ->file, cand, cand->file, list); CandidateList_PrintAddrs(list); #endif } /* * Add all candidates to the list that can be formed by applying a suffix to * the candidate. */ static void CandidateList_AddCandidatesFor(CandidateList *list, Candidate *cand) { SuffixListNode *ln; for (ln = cand->suff->children.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (suff->isNull && suff->name[0] != '\0') { /* * If the suffix has been marked as the NULL suffix, * also create a candidate for a file with no suffix * attached. */ CandidateList_Add(list, bmake_strdup(cand->prefix), cand, suff, "1"); } CandidateList_Add(list, str_concat2(cand->prefix, suff->name), cand, suff, "2"); } } /* * Free the first candidate in the list that is not referenced anymore. * Return whether a candidate was removed. */ static bool RemoveCandidate(CandidateList *srcs) { CandidateListNode *ln; #ifdef DEBUG_SRC debug_printf("cleaning list %p:", srcs); CandidateList_PrintAddrs(srcs); #endif for (ln = srcs->first; ln != NULL; ln = ln->next) { Candidate *src = ln->datum; if (src->numChildren == 0) { if (src->parent == NULL) free(src->prefix); else { #ifdef DEBUG_SRC /* XXX: Lst_RemoveDatum */ CandidateListNode *ln2; ln2 = Lst_FindDatum(&src->parent->childrenList, src); if (ln2 != NULL) Lst_Remove(&src->parent->childrenList, ln2); #endif src->parent->numChildren--; } #ifdef DEBUG_SRC debug_printf("free: list %p src %p:%s children %d\n", srcs, src, src->file, src->numChildren); Lst_Done(&src->childrenList); #endif Lst_Remove(srcs, ln); free(src->file); free(src); return true; } #ifdef DEBUG_SRC else { debug_printf("keep: list %p src %p:%s children %d:", srcs, src, src->file, src->numChildren); CandidateList_PrintAddrs(&src->childrenList); } #endif } return false; } /* Find the first existing file/target in srcs. */ static Candidate * FindThem(CandidateList *srcs, CandidateSearcher *cs) { HashSet seen; HashSet_Init(&seen); while (!Lst_IsEmpty(srcs)) { Candidate *src = Lst_Dequeue(srcs); #ifdef DEBUG_SRC debug_printf("remove from list %p src %p:%s\n", srcs, src, src->file); #endif DEBUG1(SUFF, "\ttrying %s...", src->file); /* * A file is considered to exist if either a node exists in the * graph for it or the file actually exists. */ if (Targ_FindNode(src->file) != NULL) { found: HashSet_Done(&seen); DEBUG0(SUFF, "got it\n"); return src; } { char *file = Dir_FindFile(src->file, src->suff->searchPath); if (file != NULL) { free(file); goto found; } } DEBUG0(SUFF, "not there\n"); if (HashSet_Add(&seen, src->file)) CandidateList_AddCandidatesFor(srcs, src); else { DEBUG1(SUFF, "FindThem: skipping duplicate \"%s\"\n", src->file); } CandidateSearcher_Add(cs, src); } HashSet_Done(&seen); return NULL; } /* * See if any of the children of the candidate's GNode is one from which the * target can be transformed. If there is one, a candidate is put together * for it and returned. */ static Candidate * FindCmds(Candidate *targ, CandidateSearcher *cs) { GNodeListNode *gln; GNode *tgn; /* Target GNode */ GNode *sgn; /* Source GNode */ size_t prefLen; /* The length of the defined prefix */ Suffix *suff; /* Suffix of the matching candidate */ Candidate *ret; /* Return value */ tgn = targ->node; prefLen = strlen(targ->prefix); for (gln = tgn->children.first; gln != NULL; gln = gln->next) { const char *base; sgn = gln->datum; if (sgn->type & OP_OPTIONAL && Lst_IsEmpty(&tgn->commands)) { /* * We haven't looked to see if .OPTIONAL files exist * yet, so don't use one as the implicit source. * This allows us to use .OPTIONAL in .depend files so * make won't complain "don't know how to make xxx.h" * when a dependent file has been moved/deleted. */ continue; } base = str_basename(sgn->name); if (strncmp(base, targ->prefix, prefLen) != 0) continue; /* * The node matches the prefix, see if it has a known suffix. */ suff = FindSuffixByName(base + prefLen); if (suff == NULL) continue; /* * It even has a known suffix, see if there's a transformation * defined between the node's suffix and the target's suffix. * * XXX: Handle multi-stage transformations here, too. */ if (Lst_FindDatum(&suff->parents, targ->suff) != NULL) break; } if (gln == NULL) return NULL; ret = Candidate_New(bmake_strdup(sgn->name), targ->prefix, suff, targ, sgn); targ->numChildren++; #ifdef DEBUG_SRC debug_printf("3 add targ %p:%s ret %p:%s\n", targ, targ->file, ret, ret->file); Lst_Append(&targ->childrenList, ret); #endif CandidateSearcher_Add(cs, ret); DEBUG1(SUFF, "\tusing existing source %s\n", sgn->name); return ret; } static void ExpandWildcards(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; StringList expansions; if (!Dir_HasWildcards(cgn->name)) return; /* Expand the word along the chosen path. */ Lst_Init(&expansions); SearchPath_Expand(Suff_FindPath(cgn), cgn->name, &expansions); while (!Lst_IsEmpty(&expansions)) { GNode *gn; /* * Fetch next expansion off the list and find its GNode */ char *name = Lst_Dequeue(&expansions); DEBUG1(SUFF, "%s...", name); gn = Targ_GetNode(name); free(name); /* Insert gn before the original child. */ Lst_InsertBefore(&pgn->children, cln, gn); Lst_Append(&gn->parents, pgn); pgn->unmade++; } Lst_Done(&expansions); DEBUG0(SUFF, "\n"); /* * Now that the source is expanded, remove it from the list of * children, to keep it from being processed. */ pgn->unmade--; Lst_Remove(&pgn->children, cln); Lst_Remove(&cgn->parents, Lst_FindDatum(&cgn->parents, pgn)); } /* * Break the result into a vector of strings whose nodes we can find, then * add those nodes to the members list. * * Unfortunately, we can't use Str_Words because it doesn't understand about * expressions with spaces in them. */ static void ExpandChildrenRegular(char *p, GNode *pgn, GNodeList *members) { char *start; pp_skip_hspace(&p); start = p; while (*p != '\0') { if (*p == ' ' || *p == '\t') { GNode *gn; /* * White-space -- terminate element, find the node, * add it, skip any further spaces. */ *p++ = '\0'; gn = Targ_GetNode(start); Lst_Append(members, gn); pp_skip_hspace(&p); /* Continue at the next non-space. */ start = p; } else if (*p == '$') { /* Skip over the expression. */ const char *nested_p = p; FStr junk = Var_Parse(&nested_p, pgn, VARE_PARSE); /* TODO: handle errors */ if (junk.str == var_Error) { Parse_Error(PARSE_FATAL, "Malformed expression at \"%s\"", p); p++; } else { p += nested_p - p; } FStr_Done(&junk); } else if (p[0] == '\\' && p[1] != '\0') { /* Escaped something -- skip over it. */ /* * XXX: In other places, escaping at this syntactical * position is done by a '$', not a '\'. The '\' is * only used in variable modifiers. */ p += 2; } else { p++; } } if (p != start) { /* * Stuff left over -- add it to the list too */ GNode *gn = Targ_GetNode(start); Lst_Append(members, gn); } } /* * Expand the names of any children of a given node that contain * expressions or file wildcards into actual targets. * * The expanded node is removed from the parent's list of children, and the * parent's unmade counter is decremented, but other nodes may be added. * * Input: * cln Child to examine * pgn Parent node being processed */ static void ExpandChildren(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; char *expanded; if (!Lst_IsEmpty(&cgn->order_pred) || !Lst_IsEmpty(&cgn->order_succ)) /* It is all too hard to process the result of .ORDER */ return; if (cgn->type & OP_WAIT) /* Ignore these (& OP_PHONY ?) */ return; /* * First do variable expansion -- this takes precedence over wildcard * expansion. If the result contains wildcards, they'll be gotten to * later since the resulting words are tacked on to the end of the * children list. */ if (strchr(cgn->name, '$') == NULL) { ExpandWildcards(cln, pgn); return; } DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); - expanded = Var_Subst(cgn->name, pgn, VARE_EVAL_DEFINED); + expanded = Var_Subst(cgn->name, pgn, VARE_EVAL); /* TODO: handle errors */ { GNodeList members = LST_INIT; if (cgn->type & OP_ARCHV) { /* * Node was an 'archive(member)' target, so * call on the Arch module to find the nodes for us, * expanding variables in the parent's scope. */ char *ap = expanded; (void)Arch_ParseArchive(&ap, &members, pgn); } else { ExpandChildrenRegular(expanded, pgn, &members); } /* Add all members to the parent node. */ while (!Lst_IsEmpty(&members)) { GNode *gn = Lst_Dequeue(&members); DEBUG1(SUFF, "%s...", gn->name); Lst_InsertBefore(&pgn->children, cln, gn); Lst_Append(&gn->parents, pgn); pgn->unmade++; ExpandWildcards(cln->prev, pgn); } Lst_Done(&members); free(expanded); } DEBUG0(SUFF, "\n"); /* * The source is expanded now, so remove it from the list of children, * to keep it from being processed. */ pgn->unmade--; Lst_Remove(&pgn->children, cln); Lst_Remove(&cgn->parents, Lst_FindDatum(&cgn->parents, pgn)); } static void ExpandAllChildren(GNode *gn) { GNodeListNode *ln, *nln; for (ln = gn->children.first; ln != NULL; ln = nln) { nln = ln->next; ExpandChildren(ln, gn); } } /* * Find a path along which to search or expand the node. * * If the node has a known suffix, use that path, * otherwise use the default system search path. */ SearchPath * Suff_FindPath(GNode *gn) { Suffix *suff = gn->suffix; if (suff == NULL) { char *name = gn->name; size_t nameLen = strlen(gn->name); SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) if (Suffix_IsSuffix(ln->datum, nameLen, name + nameLen)) break; DEBUG1(SUFF, "Wildcard expanding \"%s\"...", gn->name); if (ln != NULL) suff = ln->datum; /* * XXX: Here we can save the suffix so we don't have to do * this again. */ } if (suff != NULL) { DEBUG1(SUFF, "suffix is \"%s\"...\n", suff->name); return suff->searchPath; } else { DEBUG0(SUFF, "\n"); return &dirSearchPath; /* Use default search path */ } } /* * Apply a transformation rule, given the source and target nodes and * suffixes. * * The source and target are linked and the commands from the transformation * are added to the target node's commands list. The target also inherits all * the sources for the transformation rule. * * Results: * true if successful, false if not. */ static bool ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) { GNodeListNode *ln; char *tname; /* Name of transformation rule */ GNode *gn; /* Node for the transformation rule */ /* Form the proper links between the target and source. */ Lst_Append(&tgn->children, sgn); Lst_Append(&sgn->parents, tgn); tgn->unmade++; /* Locate the transformation rule itself. */ tname = str_concat2(ssuff->name, tsuff->name); gn = FindTransformByName(tname); free(tname); /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */ if (gn == NULL) return false; DEBUG3(SUFF, "\tapplying %s -> %s to \"%s\"\n", ssuff->name, tsuff->name, tgn->name); /* Record last child; Make_HandleUse may add child nodes. */ ln = tgn->children.last; /* Apply the rule. */ Make_HandleUse(gn, tgn); /* Deal with wildcards and expressions in any acquired sources. */ ln = ln != NULL ? ln->next : NULL; while (ln != NULL) { GNodeListNode *nln = ln->next; ExpandChildren(ln, tgn); ln = nln; } /* * Keep track of another parent to which this node is transformed so * the .IMPSRC variable can be set correctly for the parent. */ Lst_Append(&sgn->implicitParents, tgn); return true; } /* * Member has a known suffix, so look for a transformation rule from * it to a possible suffix of the archive. * * Rather than searching through the entire list, we just look at * suffixes to which the member's suffix may be transformed. */ static void ExpandMember(GNode *gn, const char *eoarch, GNode *mem, Suffix *memSuff) { SuffixListNode *ln; size_t nameLen = (size_t)(eoarch - gn->name); /* Use first matching suffix... */ for (ln = memSuff->parents.first; ln != NULL; ln = ln->next) if (Suffix_IsSuffix(ln->datum, nameLen, eoarch)) break; if (ln != NULL) { Suffix *suff = ln->datum; if (!ApplyTransform(gn, mem, suff, memSuff)) { DEBUG2(SUFF, "\tNo transformation from %s -> %s\n", memSuff->name, suff->name); } } } static void FindDeps(GNode *, CandidateSearcher *); /* * Locate dependencies for an OP_ARCHV node. * * Side Effects: * Same as Suff_FindDeps */ static void FindDepsArchive(GNode *gn, CandidateSearcher *cs) { char *eoarch; /* End of archive portion */ char *eoname; /* End of member portion */ GNode *mem; /* Node for member */ Suffix *memSuff; const char *name; /* Start of member's name */ /* * The node is an 'archive(member)' pair, so we must find a * suffix for both of them. */ eoarch = strchr(gn->name, '('); eoname = strchr(eoarch, ')'); /* * Caller guarantees the format `libname(member)', via * Arch_ParseArchive. */ assert(eoarch != NULL); assert(eoname != NULL); *eoname = '\0'; /* Nuke parentheses during suffix search */ *eoarch = '\0'; /* So a suffix can be found */ name = eoarch + 1; /* * To simplify things, call Suff_FindDeps recursively on the member * now, so we can simply compare the member's .PREFIX and .TARGET * variables to locate its suffix. This allows us to figure out the * suffix to use for the archive without having to do a quadratic * search over the suffix list, backtracking for each one. */ mem = Targ_GetNode(name); FindDeps(mem, cs); /* Create the link between the two nodes right off. */ Lst_Append(&gn->children, mem); Lst_Append(&mem->parents, gn); gn->unmade++; /* Copy in the variables from the member node to this one. */ Var_Set(gn, PREFIX, GNode_VarPrefix(mem)); Var_Set(gn, TARGET, GNode_VarTarget(mem)); memSuff = mem->suffix; if (memSuff == NULL) { /* Didn't know what it was. */ DEBUG0(SUFF, "using null suffix\n"); memSuff = nullSuff; } /* Set the other two local variables required for this target. */ Var_Set(gn, MEMBER, name); Var_Set(gn, ARCHIVE, gn->name); /* Set $@ for compatibility with other makes. */ Var_Set(gn, TARGET, gn->name); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ ExpandAllChildren(gn); if (memSuff != NULL) ExpandMember(gn, eoarch, mem, memSuff); /* * Replace the opening and closing parens now we've no need of the * separate pieces. */ *eoarch = '('; *eoname = ')'; /* * Pretend gn appeared to the left of a dependency operator so the * user needn't provide a transformation from the member to the * archive. */ if (!GNode_IsTarget(gn)) gn->type |= OP_DEPENDS; /* * Flag the member as such so we remember to look in the archive for * its modification time. The OP_JOIN | OP_MADE is needed because * this target should never get made. */ mem->type |= OP_MEMBER | OP_JOIN | OP_MADE; } /* * If the node is a library, it is the arch module's job to find it * and set the TARGET variable accordingly. We merely provide the * search path, assuming all libraries end in ".a" (if the suffix * hasn't been defined, there's nothing we can do for it, so we just * set the TARGET variable to the node's name in order to give it a * value). */ static void FindDepsLib(GNode *gn) { Suffix *suff = FindSuffixByName(LIBSUFF); if (suff != NULL) { Suffix_Reassign(&gn->suffix, suff); Arch_FindLib(gn, suff->searchPath); } else { Suffix_Unassign(&gn->suffix); Var_Set(gn, TARGET, gn->name); } /* * Because a library (-lfoo) target doesn't follow the standard * filesystem conventions, we don't set the regular variables for * the thing. .PREFIX is simply made empty. */ Var_Set(gn, PREFIX, ""); } static void FindDepsRegularKnown(const char *name, size_t nameLen, GNode *gn, CandidateList *srcs, CandidateList *targs) { SuffixListNode *ln; Candidate *targ; char *pref; for (ln = sufflist.first; ln != NULL; ln = ln->next) { Suffix *suff = ln->datum; if (!Suffix_IsSuffix(suff, nameLen, name + nameLen)) continue; pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen)); targ = Candidate_New(bmake_strdup(gn->name), pref, suff, NULL, gn); CandidateList_AddCandidatesFor(srcs, targ); /* Record the target so we can nuke it. */ Lst_Append(targs, targ); } } static void FindDepsRegularUnknown(GNode *gn, const char *sopref, CandidateList *srcs, CandidateList *targs) { Candidate *targ; if (!Lst_IsEmpty(targs) || nullSuff == NULL) return; DEBUG1(SUFF, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name); targ = Candidate_New(bmake_strdup(gn->name), bmake_strdup(sopref), nullSuff, NULL, gn); /* * Only use the default suffix rules if we don't have commands * defined for this gnode; traditional make programs used to not * define suffix rules if the gnode had children but we don't do * this anymore. */ if (Lst_IsEmpty(&gn->commands)) CandidateList_AddCandidatesFor(srcs, targ); else { DEBUG0(SUFF, "not "); } DEBUG0(SUFF, "adding suffix rules\n"); Lst_Append(targs, targ); } /* * Deal with finding the thing on the default search path. We always do * that, not only if the node is only a source (not on the lhs of a * dependency operator or [XXX] it has neither children or commands) as * the old pmake did. */ static void FindDepsRegularPath(GNode *gn, Candidate *targ) { if (gn->type & (OP_PHONY | OP_NOPATH)) return; free(gn->path); gn->path = Dir_FindFile(gn->name, targ == NULL ? &dirSearchPath : targ->suff->searchPath); if (gn->path == NULL) return; Var_Set(gn, TARGET, gn->path); if (targ != NULL) { /* * Suffix known for the thing -- trim the suffix off * the path to form the proper .PREFIX variable. */ size_t savep = strlen(gn->path) - targ->suff->nameLen; char savec; Suffix_Reassign(&gn->suffix, targ->suff); savec = gn->path[savep]; gn->path[savep] = '\0'; Var_Set(gn, PREFIX, str_basename(gn->path)); gn->path[savep] = savec; } else { /* * The .PREFIX gets the full path if the target has no * known suffix. */ Suffix_Unassign(&gn->suffix); Var_Set(gn, PREFIX, str_basename(gn->path)); } } /* * Locate implicit dependencies for regular targets. * * Input: * gn Node for which to find sources * * Side Effects: * Same as Suff_FindDeps */ static void FindDepsRegular(GNode *gn, CandidateSearcher *cs) { /* List of sources at which to look */ CandidateList srcs = LST_INIT; /* * List of targets to which things can be transformed. * They all have the same file, but different suff and prefix fields. */ CandidateList targs = LST_INIT; Candidate *bottom; /* Start of found transformation path */ Candidate *src; Candidate *targ; const char *name = gn->name; size_t nameLen = strlen(name); #ifdef DEBUG_SRC DEBUG1(SUFF, "FindDepsRegular \"%s\"\n", gn->name); #endif /* * We're caught in a catch-22 here. On the one hand, we want to use * any transformation implied by the target's sources, but we can't * examine the sources until we've expanded any variables/wildcards * they may hold, and we can't do that until we've set up the * target's local variables and we can't do that until we know what * the proper suffix for the target is (in case there are two * suffixes one of which is a suffix of the other) and we can't know * that until we've found its implied source, which we may not want * to use if there's an existing source that implies a different * transformation. * * In an attempt to get around this, which may not work all the time, * but should work most of the time, we look for implied sources * first, checking transformations to all possible suffixes of the * target, use what we find to set the target's local variables, * expand the children, then look for any overriding transformations * they imply. Should we find one, we discard the one we found before. */ bottom = NULL; targ = NULL; if (!(gn->type & OP_PHONY)) { FindDepsRegularKnown(name, nameLen, gn, &srcs, &targs); /* Handle target of unknown suffix... */ FindDepsRegularUnknown(gn, name, &srcs, &targs); /* * Using the list of possible sources built up from the target * suffix(es), try and find an existing file/target that * matches. */ bottom = FindThem(&srcs, cs); if (bottom == NULL) { /* * No known transformations -- use the first suffix * found for setting the local variables. */ if (targs.first != NULL) targ = targs.first->datum; else targ = NULL; } else { /* * Work up the transformation path to find the suffix * of the target to which the transformation was made. */ for (targ = bottom; targ->parent != NULL; targ = targ->parent) continue; } } Var_Set(gn, TARGET, GNode_Path(gn)); Var_Set(gn, PREFIX, targ != NULL ? targ->prefix : gn->name); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ { GNodeListNode *ln, *nln; for (ln = gn->children.first; ln != NULL; ln = nln) { nln = ln->next; ExpandChildren(ln, gn); } } if (targ == NULL) { DEBUG1(SUFF, "\tNo valid suffix on %s\n", gn->name); sfnd_abort: FindDepsRegularPath(gn, targ); goto sfnd_return; } /* * If the suffix indicates that the target is a library, mark that in * the node's type field. */ if (targ->suff->library) gn->type |= OP_LIB; /* * Check for overriding transformation rule implied by sources */ if (!Lst_IsEmpty(&gn->children)) { src = FindCmds(targ, cs); if (src != NULL) { /* * Free up all the candidates in the transformation * path, up to but not including the parent node. */ while (bottom != NULL && bottom->parent != NULL) { CandidateSearcher_AddIfNew(cs, bottom); bottom = bottom->parent; } bottom = src; } } if (bottom == NULL) { /* No idea from where it can come -- return now. */ goto sfnd_abort; } /* * We now have a list of candidates headed by 'bottom' and linked via * their 'parent' pointers. What we do next is create links between * source and target nodes (which may or may not have been created) * and set the necessary local variables in each target. * * The commands for each target are set from the commands of the * transformation rule used to get from the src suffix to the targ * suffix. Note that this causes the commands list of the original * node, gn, to be replaced with the commands of the final * transformation rule. */ if (bottom->node == NULL) bottom->node = Targ_GetNode(bottom->file); for (src = bottom; src->parent != NULL; src = src->parent) { targ = src->parent; Suffix_Reassign(&src->node->suffix, src->suff); if (targ->node == NULL) targ->node = Targ_GetNode(targ->file); ApplyTransform(targ->node, src->node, targ->suff, src->suff); if (targ->node != gn) { /* * Finish off the dependency-search process for any * nodes between bottom and gn (no point in questing * around the filesystem for their implicit source * when it's already known). Note that the node * can't have any sources that need expanding, since * SuffFindThem will stop on an existing node, so all * we need to do is set the standard variables. */ targ->node->type |= OP_DEPS_FOUND; Var_Set(targ->node, PREFIX, targ->prefix); Var_Set(targ->node, TARGET, targ->node->name); } } Suffix_Reassign(&gn->suffix, src->suff); /* * Nuke the transformation path and the candidates left over in the * two lists. */ sfnd_return: if (bottom != NULL) CandidateSearcher_AddIfNew(cs, bottom); while (RemoveCandidate(&srcs) || RemoveCandidate(&targs)) continue; CandidateSearcher_MoveAll(cs, &srcs); CandidateSearcher_MoveAll(cs, &targs); } static void CandidateSearcher_CleanUp(CandidateSearcher *cs) { while (RemoveCandidate(&cs->list)) continue; assert(Lst_IsEmpty(&cs->list)); } /* * Find implicit sources for the target. * * Nodes are added to the graph as children of the passed-in node. The nodes * are marked to have their IMPSRC variable filled in. The PREFIX variable * is set for the given node and all its implied children. * * The path found by this target is the shortest path in the transformation * graph, which may pass through nonexistent targets, to an existing target. * The search continues on all paths from the root suffix until a file is * found. I.e. if there's a path .o -> .c -> .l -> .l,v from the root and the * .l,v file exists but the .c and .l files don't, the search will branch out * in all directions from .o and again from all the nodes on the next level * until the .l,v node is encountered. */ void Suff_FindDeps(GNode *gn) { CandidateSearcher cs; CandidateSearcher_Init(&cs); FindDeps(gn, &cs); CandidateSearcher_CleanUp(&cs); CandidateSearcher_Done(&cs); } static void FindDeps(GNode *gn, CandidateSearcher *cs) { if (gn->type & OP_DEPS_FOUND) return; gn->type |= OP_DEPS_FOUND; /* Make sure we have these set, may get revised below. */ Var_Set(gn, TARGET, GNode_Path(gn)); Var_Set(gn, PREFIX, gn->name); DEBUG1(SUFF, "SuffFindDeps \"%s\"\n", gn->name); if (gn->type & OP_ARCHV) FindDepsArchive(gn, cs); else if (gn->type & OP_LIB) FindDepsLib(gn); else FindDepsRegular(gn, cs); } /* * Define which suffix is the null suffix. * * Need to handle the changing of the null suffix gracefully so the old * transformation rules don't just go away. */ void Suff_SetNull(const char *name) { Suffix *suff = FindSuffixByName(name); if (suff == NULL) { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined", name); return; } if (nullSuff != NULL) nullSuff->isNull = false; suff->isNull = true; /* XXX: Here's where the transformation mangling would take place. */ nullSuff = suff; } /* Initialize the suffixes module. */ void Suff_Init(void) { /* * Create null suffix for single-suffix rules (POSIX). The thing * doesn't actually go on the suffix list or everyone will think * that's its suffix. */ Suff_ClearSuffixes(); } #ifdef CLEANUP /* Clean up the suffixes module. */ void Suff_End(void) { SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) Suffix_Free(ln->datum); Lst_Done(&sufflist); for (ln = suffClean.first; ln != NULL; ln = ln->next) Suffix_Free(ln->datum); Lst_Done(&suffClean); if (nullSuff != NULL) Suffix_Free(nullSuff); Lst_Done(&transforms); } #endif static void PrintSuffNames(const char *prefix, const SuffixList *suffs) { SuffixListNode *ln; debug_printf("#\t%s: ", prefix); for (ln = suffs->first; ln != NULL; ln = ln->next) { const Suffix *suff = ln->datum; debug_printf("%s ", suff->name); } debug_printf("\n"); } static void Suffix_Print(const Suffix *suff) { Buffer buf; Buf_Init(&buf); Buf_AddFlag(&buf, suff->include, "SUFF_INCLUDE"); Buf_AddFlag(&buf, suff->library, "SUFF_LIBRARY"); Buf_AddFlag(&buf, suff->isNull, "SUFF_NULL"); debug_printf("# \"%s\" (num %d, ref %d)", suff->name, suff->sNum, suff->refCount); if (buf.len > 0) debug_printf(" (%s)", buf.data); debug_printf("\n"); Buf_Done(&buf); PrintSuffNames("To", &suff->parents); PrintSuffNames("From", &suff->children); debug_printf("#\tSearch Path: "); SearchPath_Print(suff->searchPath); debug_printf("\n"); } static void PrintTransformation(GNode *t) { debug_printf("%-16s:", t->name); Targ_PrintType(t->type); debug_printf("\n"); Targ_PrintCmds(t); debug_printf("\n"); } void Suff_PrintAll(void) { debug_printf("#*** Suffixes:\n"); { SuffixListNode *ln; for (ln = sufflist.first; ln != NULL; ln = ln->next) Suffix_Print(ln->datum); } debug_printf("#*** Transformations:\n"); { GNodeListNode *ln; for (ln = transforms.first; ln != NULL; ln = ln->next) PrintTransformation(ln->datum); } } char * Suff_NamesStr(void) { Buffer buf; SuffixListNode *ln; Suffix *suff; Buf_Init(&buf); for (ln = sufflist.first; ln != NULL; ln = ln->next) { suff = ln->datum; if (ln != sufflist.first) Buf_AddByte(&buf, ' '); Buf_AddStr(&buf, suff->name); } return Buf_DoneData(&buf); } diff --git a/contrib/bmake/unit-tests/Makefile b/contrib/bmake/unit-tests/Makefile index 319643b135a0..e83758871bba 100644 --- a/contrib/bmake/unit-tests/Makefile +++ b/contrib/bmake/unit-tests/Makefile @@ -1,908 +1,915 @@ -# $Id: Makefile,v 1.224 2024/07/13 05:27:35 sjg Exp $ +# $Id: Makefile,v 1.230 2025/01/30 18:04:20 sjg Exp $ # -# $NetBSD: Makefile,v 1.350 2024/07/07 09:37:00 rillig Exp $ +# $NetBSD: Makefile,v 1.355 2025/01/25 22:46:30 sjg Exp $ # # Unit tests for make(1) # # The main targets are: # # all: # run all the tests # test: # run 'all', and compare to expected results # accept: # move generated output to expected results # # Settable variables # # TEST_MAKE # The make program to be tested. # # # Adding a test case # # Each feature should get its own set of tests in its own suitably # named makefile (*.mk), with its own set of expected results (*.exp), # and it should be added to the TESTS list. # .MAIN: all # we use these below but we might be an older make .MAKE.OS?= ${uname -s:L:sh} .MAKE.UID?= ${id -u:L:sh} # for many tests we need a TMPDIR that will not collide # with other users. .if ${.OBJDIR} != ${.CURDIR} # easy TMPDIR:= ${.OBJDIR}/tmp .elif defined(TMPDIR) TMPDIR:= ${TMPDIR}/uid${.MAKE.UID} .else TMPDIR:= /tmp/uid${.MAKE.UID} .endif # make sure it exists .if !exist(${TMPDIR}) _!= mkdir -p ${TMPDIR} .endif +# and clean it up - outside the context of +# any target that might be using it. +.END: rm-tmpdir +rm-tmpdir: .NOMETA + @rm -rf ${TMPDIR} # Each test is in a sub-makefile. # Keep the list sorted. # Any test that is commented out must be ignored in # src/tests/usr.bin/make/t_make.sh as well. #TESTS+= archive #TESTS+= archive-suffix TESTS+= cmd-errors TESTS+= cmd-errors-jobs TESTS+= cmd-errors-lint TESTS+= cmd-interrupt TESTS+= cmdline TESTS+= cmdline-redirect-stdin TESTS+= cmdline-undefined TESTS+= comment TESTS+= compat-error TESTS+= cond-cmp-numeric TESTS+= cond-cmp-numeric-eq TESTS+= cond-cmp-numeric-ge TESTS+= cond-cmp-numeric-gt TESTS+= cond-cmp-numeric-le TESTS+= cond-cmp-numeric-lt TESTS+= cond-cmp-numeric-ne TESTS+= cond-cmp-string TESTS+= cond-cmp-unary TESTS+= cond-eof TESTS+= cond-func TESTS+= cond-func-commands TESTS+= cond-func-defined TESTS+= cond-func-empty TESTS+= cond-func-exists TESTS+= cond-func-make TESTS+= cond-func-make-main TESTS+= cond-func-target TESTS+= cond-late TESTS+= cond-op TESTS+= cond-op-and TESTS+= cond-op-and-lint TESTS+= cond-op-not TESTS+= cond-op-or TESTS+= cond-op-or-lint TESTS+= cond-op-parentheses TESTS+= cond-short TESTS+= cond-token-number TESTS+= cond-token-plain TESTS+= cond-token-string TESTS+= cond-token-var TESTS+= cond-undef-lint TESTS+= counter TESTS+= counter-append TESTS+= dep TESTS+= dep-colon TESTS+= dep-colon-bug-cross-file TESTS+= dep-double-colon TESTS+= dep-double-colon-indep TESTS+= dep-duplicate TESTS+= dep-exclam TESTS+= dep-none TESTS+= dep-op-missing TESTS+= dep-percent TESTS+= dep-var TESTS+= dep-wildcards TESTS+= depsrc TESTS+= depsrc-end TESTS+= depsrc-exec TESTS+= depsrc-ignore TESTS+= depsrc-made TESTS+= depsrc-make TESTS+= depsrc-meta TESTS+= depsrc-nometa TESTS+= depsrc-nometa_cmp TESTS+= depsrc-nopath TESTS+= depsrc-notmain TESTS+= depsrc-optional TESTS+= depsrc-phony TESTS+= depsrc-precious TESTS+= depsrc-recursive TESTS+= depsrc-silent TESTS+= depsrc-use TESTS+= depsrc-usebefore TESTS+= depsrc-usebefore-double-colon TESTS+= depsrc-wait TESTS+= deptgt TESTS+= deptgt-begin TESTS+= deptgt-begin-fail TESTS+= deptgt-begin-fail-indirect TESTS+= deptgt-default TESTS+= deptgt-delete_on_error TESTS+= deptgt-end TESTS+= deptgt-end-fail TESTS+= deptgt-end-fail-all TESTS+= deptgt-end-fail-indirect TESTS+= deptgt-end-jobs TESTS+= deptgt-error TESTS+= deptgt-ignore TESTS+= deptgt-interrupt TESTS+= deptgt-main TESTS+= deptgt-makeflags TESTS+= deptgt-no_parallel TESTS+= deptgt-nopath TESTS+= deptgt-notparallel TESTS+= deptgt-objdir TESTS+= deptgt-order TESTS+= deptgt-path TESTS+= deptgt-path-suffix TESTS+= deptgt-phony TESTS+= deptgt-posix TESTS+= deptgt-precious TESTS+= deptgt-shell TESTS+= deptgt-silent TESTS+= deptgt-silent-jobs TESTS+= deptgt-stale TESTS+= deptgt-suffixes TESTS+= dir TESTS+= dir-expand-path TESTS+= directive TESTS+= directive-dinclude TESTS+= directive-elif TESTS+= directive-elifdef TESTS+= directive-elifmake TESTS+= directive-elifndef TESTS+= directive-elifnmake TESTS+= directive-else TESTS+= directive-endfor TESTS+= directive-endif TESTS+= directive-error TESTS+= directive-export TESTS+= directive-export-env TESTS+= directive-export-impl TESTS+= directive-export-gmake TESTS+= directive-export-literal TESTS+= directive-for TESTS+= directive-for-break TESTS+= directive-for-empty TESTS+= directive-for-errors TESTS+= directive-for-escape TESTS+= directive-for-generating-endif TESTS+= directive-for-if TESTS+= directive-for-lines TESTS+= directive-for-null TESTS+= directive-hyphen-include TESTS+= directive-if TESTS+= directive-if-nested TESTS+= directive-ifdef TESTS+= directive-ifmake TESTS+= directive-ifndef TESTS+= directive-ifnmake TESTS+= directive-include TESTS+= directive-include-fatal TESTS+= directive-include-guard TESTS+= directive-info TESTS+= directive-misspellings TESTS+= directive-sinclude TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env TESTS+= directive-warning TESTS+= dollar TESTS+= doterror TESTS+= dotwait TESTS+= error TESTS+= # escape # broken by reverting POSIX changes TESTS+= export TESTS+= export-all TESTS+= export-env TESTS+= export-variants TESTS+= gnode-submake TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main TESTS+= job-flags TESTS+= job-output-long-lines TESTS+= job-output-null TESTS+= jobs-empty-commands TESTS+= jobs-empty-commands-error TESTS+= jobs-error-indirect TESTS+= jobs-error-nested TESTS+= jobs-error-nested-make TESTS+= lint TESTS+= make-exported TESTS+= meta-cmd-cmp TESTS+= moderrs TESTS+= modmisc .if ${.MAKE.UID} > 0 TESTS+= objdir-writable .endif TESTS+= opt TESTS+= opt-backwards TESTS+= opt-chdir TESTS+= opt-debug TESTS+= opt-debug-all TESTS+= opt-debug-archive TESTS+= opt-debug-curdir TESTS+= opt-debug-cond TESTS+= opt-debug-dir TESTS+= opt-debug-errors TESTS+= opt-debug-errors-jobs TESTS+= opt-debug-file TESTS+= opt-debug-for TESTS+= opt-debug-graph1 TESTS+= opt-debug-graph2 TESTS+= opt-debug-graph3 TESTS+= opt-debug-hash TESTS+= opt-debug-jobs TESTS+= opt-debug-lint TESTS+= opt-debug-loud TESTS+= opt-debug-meta TESTS+= opt-debug-making TESTS+= opt-debug-no-rm TESTS+= opt-debug-parse TESTS+= opt-debug-suff TESTS+= opt-debug-targets TESTS+= opt-debug-varraw TESTS+= opt-debug-var TESTS+= opt-debug-x-trace TESTS+= opt-define TESTS+= opt-env TESTS+= opt-file TESTS+= opt-ignore TESTS+= opt-include-dir TESTS+= opt-jobs TESTS+= opt-jobs-internal TESTS+= opt-jobs-no-action TESTS+= opt-keep-going TESTS+= opt-keep-going-indirect TESTS+= opt-keep-going-multiple TESTS+= opt-m-include-dir TESTS+= opt-no-action TESTS+= opt-no-action-at-all TESTS+= opt-no-action-runflags TESTS+= opt-no-action-touch TESTS+= opt-query TESTS+= opt-raw TESTS+= opt-silent TESTS+= opt-touch TESTS+= opt-touch-jobs TESTS+= opt-tracefile TESTS+= opt-var-expanded TESTS+= opt-var-literal TESTS+= opt-version TESTS+= opt-warnings-as-errors TESTS+= opt-where-am-i TESTS+= opt-x-reduce-exported TESTS+= order TESTS+= parse TESTS+= parse-var TESTS+= phony-end TESTS+= posix TESTS+= # posix1 # broken by reverting POSIX changes TESTS+= recursive TESTS+= sh TESTS+= sh-dots TESTS+= sh-errctl TESTS+= sh-flags TESTS+= sh-jobs TESTS+= sh-jobs-error TESTS+= sh-leading-at TESTS+= sh-leading-hyphen TESTS+= sh-leading-plus TESTS+= sh-meta-chars TESTS+= sh-multi-line TESTS+= sh-single-line TESTS+= shell-csh TESTS+= shell-custom .if exists(/bin/ksh) TESTS+= shell-ksh .endif TESTS+= shell-sh +TESTS+= suff TESTS+= suff-add-later TESTS+= suff-clear-regular TESTS+= suff-clear-single TESTS+= suff-incomplete TESTS+= suff-lookup TESTS+= suff-main TESTS+= suff-main-several TESTS+= suff-phony TESTS+= suff-rebuild TESTS+= suff-self TESTS+= suff-transform-debug TESTS+= suff-transform-endless TESTS+= suff-transform-expand TESTS+= suff-transform-select TESTS+= suff-use TESTS+= sunshcmd TESTS+= ternary TESTS+= unexport TESTS+= unexport-env TESTS+= use-inference TESTS+= var-readonly TESTS+= var-scope TESTS+= var-scope-cmdline TESTS+= var-scope-env TESTS+= var-scope-global TESTS+= var-scope-local TESTS+= var-scope-local-legacy TESTS+= var-eval-short TESTS+= var-op TESTS+= var-op-append TESTS+= var-op-assign TESTS+= var-op-default TESTS+= var-op-expand TESTS+= var-op-shell TESTS+= var-op-sunsh TESTS+= var-recursive TESTS+= varcmd TESTS+= vardebug TESTS+= varfind TESTS+= varmisc TESTS+= varmod TESTS+= varmod-assign TESTS+= varmod-assign-shell TESTS+= varmod-defined TESTS+= varmod-edge TESTS+= varmod-exclam-shell TESTS+= varmod-extension TESTS+= varmod-gmtime TESTS+= varmod-hash TESTS+= varmod-head TESTS+= varmod-ifelse TESTS+= varmod-indirect TESTS+= varmod-l-name-to-value TESTS+= varmod-localtime TESTS+= varmod-loop TESTS+= varmod-loop-delete TESTS+= varmod-loop-varname TESTS+= varmod-match TESTS+= varmod-match-escape TESTS+= varmod-mtime TESTS+= varmod-no-match TESTS+= varmod-order TESTS+= varmod-order-numeric TESTS+= varmod-order-reverse TESTS+= varmod-order-shuffle TESTS+= varmod-order-string TESTS+= varmod-path TESTS+= varmod-quote TESTS+= varmod-quote-dollar TESTS+= varmod-range TESTS+= varmod-remember TESTS+= varmod-root TESTS+= varmod-select-words TESTS+= varmod-shell TESTS+= varmod-subst TESTS+= varmod-subst-regex TESTS+= varmod-sun-shell TESTS+= varmod-sysv TESTS+= varmod-tail TESTS+= varmod-to-abs TESTS+= varmod-to-lower TESTS+= varmod-to-many-words TESTS+= varmod-to-one-word TESTS+= varmod-to-separator TESTS+= varmod-to-title TESTS+= varmod-to-upper TESTS+= varmod-undefined TESTS+= varmod-unique TESTS+= varname TESTS+= varname-dollar TESTS+= varname-dot-alltargets TESTS+= varname-dot-curdir TESTS+= varname-dot-includes TESTS+= varname-dot-includedfromdir TESTS+= varname-dot-includedfromfile TESTS+= varname-dot-libs TESTS+= varname-dot-make-dependfile TESTS+= varname-dot-make-expand_variables TESTS+= varname-dot-make-exported TESTS+= varname-dot-make-jobs TESTS+= varname-dot-make-jobs-prefix TESTS+= varname-dot-make-level TESTS+= varname-dot-make-makefile_preference TESTS+= varname-dot-make-makefiles TESTS+= varname-dot-make-meta-bailiwick TESTS+= varname-dot-make-meta-created TESTS+= varname-dot-make-meta-files .if ${.MAKE.PATH_FILEMON:Uno:Nktrace:N/dev*} == "" && ${TMPDIR:N/tmp*:N/var/tmp*} != "" # these tests will not work if TMPDIR is or is a subdir of # /tmp or /var/tmp .if ${.MAKE.PATH_FILEMON:N/dev/*} != "" || exists(${.MAKE.PATH_FILEMON}) TESTS+= varname-dot-make-meta-ignore_filter TESTS+= varname-dot-make-meta-ignore_paths TESTS+= varname-dot-make-meta-ignore_patterns TESTS+= varname-dot-make-path_filemon .else .warning Skipping tests that require ${.MAKE.PATH_FILEMON} .endif .endif TESTS+= varname-dot-make-meta-prefix TESTS+= varname-dot-make-mode TESTS+= varname-dot-make-pid TESTS+= varname-dot-make-ppid TESTS+= varname-dot-make-save_dollars TESTS+= varname-dot-makeflags TESTS+= varname-dot-makeoverrides TESTS+= varname-dot-newline TESTS+= varname-dot-objdir TESTS+= varname-dot-parsedir TESTS+= varname-dot-parsefile TESTS+= varname-dot-path TESTS+= varname-dot-shell TESTS+= varname-dot-suffixes TESTS+= varname-dot-targets TESTS+= varname-empty TESTS+= varname-make TESTS+= varname-make_print_var_on_error TESTS+= varname-make_print_var_on_error-jobs TESTS+= varname-makefile TESTS+= varname-makeflags TESTS+= varname-pwd TESTS+= varname-vpath TESTS+= varparse-dynamic TESTS+= varparse-errors TESTS+= varparse-mod TESTS+= varparse-undef-partial # some shells have quirks _shell := ${.SHELL:tA:T} .if ${_shell} == "dash" # dash fails -x output BROKEN_TESTS+= opt-debug-x-trace .elif ${_shell:N*ksh*} == "" BROKEN_TESTS+= \ deptgt-silent-jobs \ job-flags \ job-output-long-lines \ opt-debug-x-trace \ sh-flags \ var-op-shell \ .if ${_shell:Nmksh} == "" # more broken that pdksh BROKEN_TESTS+= \ opt-jobs-no-action \ sh-errctl \ sh-leading-hyphen \ .endif .endif .if ${UTC_1:Uno} == "" # this will not work if UTC_1 is set empty BROKEN_TESTS+= varmod-localtime .endif .if ${.MAKE.OS:NDarwin} == "" BROKEN_TESTS+= shell-ksh .endif .if ${.MAKE.OS:NIRIX*} == "" BROKEN_TESTS+= \ cmd-interrupt \ deptgt-interrupt \ job-output-null \ opt-chdir \ opt-debug-x-trace \ sh-leading-hyphen \ .endif .if ${.MAKE.OS} == "SCO_SV" BROKEN_TESTS+= \ opt-debug-graph[23] \ varmod-localtime \ varmod-to-separator \ .if ${_shell} == "bash" BROKEN_TESTS+= job-output-null .else BROKEN_TESTS+= \ cmd-interrupt \ job-flags \ .endif .endif # Some tests just do not work on some platforms or environments # so allow for some filtering. .if !empty(BROKEN_TESTS) .warning Skipping broken tests: ${BROKEN_TESTS:O:u} TESTS:= ${TESTS:${BROKEN_TESTS:S,^,N,:ts:}} .endif # Ideas for more tests: # char-0020-space.mk # char-005C-backslash.mk # escape-cond-str.mk # escape-cond-func-arg.mk # escape-varmod.mk # escape-varmod-define.mk # escape-varmod-match.mk # escape-varname.mk # escape-varassign-varname.mk # escape-varassign-varname-cmdline.mk # escape-varassign-value.mk # escape-varassign-value-cmdline.mk # escape-dependency-source.mk # escape-dependency-target.mk # escape-for-varname.mk # escape-for-item.mk # posix-*.mk (see posix.mk and posix1.mk) # Additional environment variables for some of the tests. # The base environment is -i PATH="$PATH". ENV.depsrc-optional+= TZ=UTC ENV.deptgt-phony+= MAKESYSPATH=. ENV.directive-undef= ENV_VAR=env-value ENV.opt-env= FROM_ENV=value-from-env ENV.opt-m-include-dir= ${MAKEOBJDIR:DMAKEOBJDIR=${MAKEOBJDIR}} ENV.varmisc= FROM_ENV=env ENV.varmisc+= FROM_ENV_BEFORE=env ENV.varmisc+= FROM_ENV_AFTER=env ENV.varmod-localtime+= TZ=${UTC_1:UEurope/Berlin} ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 # Override make flags for some of the tests; default is -k. # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # settings FLAGS.test=-dv here, since that is closer to the test code. FLAGS.cond-func-make= via-cmdline FLAGS.doterror= # none, especially not -k FLAGS.jobs-error-indirect= # none, especially not -k FLAGS.jobs-error-nested= # none, especially not -k FLAGS.jobs-error-nested-make= # none, especially not -k FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' # Some tests need extra postprocessing. SED_CMDS.deptgt-phony= ${STD_SED_CMDS.dd} SED_CMDS.dir= ${STD_SED_CMDS.dd} SED_CMDS.directive-include-guard= \ -e '/\.MAKEFLAGS/d' \ - -e '/^Parsing line/d' \ + -e '/^Parsing .*:[1-9][0-9]*:/d' \ -e '/^SetFilenameVars:/d' \ -e '/^ParseDependency/d' \ -e '/^ParseEOF:/d' SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d' .if ${.MAKE.OS:NCygwin} == "" SED_CMDS.export+= -e '/^WINDIR=/d' -e '/^SYSTEMROOT=/d' .endif SED_CMDS.export-all= ${SED_CMDS.export} SED_CMDS.export-env= ${SED_CMDS.export} SED_CMDS.cmdline= -e 's,uid${.MAKE.UID}/,,' SED_CMDS.job-output-long-lines= \ ${:D Job separators on their own line are ok. } \ -e '/^--- job-[ab] ---$$/d' \ ${:D Plain output lines are ok as well. } \ ${:D They may come in multiples of 1024 or as 10000. } \ -e '/^aa*$$/d' \ -e '/^bb*$$/d' \ ${:D The following lines should rather not occur since the job } \ ${:D marker should always be at the beginning of the line. } \ -e '/^aa*--- job-b ---$$/d' \ -e '/^bb*--- job-a ---$$/d' SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \ -e '/name/s,file,File,' \ -e 's,no such,No such,' \ -e 's,Filename,File name,' # meta line numbers can vary based on filemon implementation SED_CMDS.meta-ignore= -e 's,\(\.meta:\) [1-9][0-9]*:,\1 :,' SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1} SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2} SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3} SED_CMDS.opt-debug-hash= -e 's,\(entries\)=[1-9][0-9],\1=,' SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(),' SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid ,' SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process ,' SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: ,' SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: ,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: \) -q,\1,' SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex} SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-where-am-i= -e '/usr.obj/d' # For Compat_RunCommand, useShell == false. SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' # For Compat_RunCommand, useShell == true. SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,,' SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1,' SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj} SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output} SED_CMDS.shell-csh= ${STD_SED_CMDS.white-space} SED_CMDS.sh-leading-hyphen= ${STD_SED_CMDS.shell} SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1} SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1} SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1} SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell} SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,' SED_CMDS.var-op-shell+= ${STD_SED_CMDS.white-space} SED_CMDS.vardebug+= -e 's,${.SHELL},,' SED_CMDS.varmod-mtime+= -e "s,\(.*\)': .*,\1': ," SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex} SED_CMDS.varparse-errors+= ${STD_SED_CMDS.timestamp} SED_CMDS.varname-dot-make-meta-ignore_filter+= ${SED_CMDS.meta-ignore} SED_CMDS.varname-dot-make-meta-ignore_paths+= ${SED_CMDS.meta-ignore} SED_CMDS.varname-dot-make-meta-ignore_patterns+= ${SED_CMDS.meta-ignore} SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g' SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' SED_CMDS.varname-empty= ${.OBJDIR .PARSEDIR .PATH .SHELL .SYSPATH:L:@v@-e '/\\$v/d'@} # Some tests need an additional round of postprocessing. POSTPROC.depsrc-wait= sed -e '/^---/d' -e 's,^\(: Making 3[abc]\)[123]$$,\1,' POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/' POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' POSTPROC.varname-dot-make-mode= sed 's,^\(: Making [abc]\)[123]$$,\1,' # Some tests reuse other tests, which makes them unnecessarily fragile. export-all.rawout: export.mk unexport.rawout: export.mk unexport-env.rawout: export.mk # End of the configuration section. # Some standard sed commands, to be used in the SED_CMDS above. # In tests that use the debugging option -dd, ignore debugging output that is # only logged in -DCLEANUP mode. STD_SED_CMDS.dd= -e '/^OpenDirs_Done:/d' STD_SED_CMDS.dd+= -e '/^CachedDir /d' STD_SED_CMDS.dd+= -e 's, ${DEFSYSPATH:U/usr/share/mk} , ,' # Omit details such as process IDs from the output of the -dg1 option. STD_SED_CMDS.dg1= -e '/\#.* \.$$/d' STD_SED_CMDS.dg1+= -e '/\.MAKE.PATH_FILEMON/d' STD_SED_CMDS.dg1+= -e '/^\#.*\/mk/d' STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, ,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.[A-Z_]* *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.JOBS\.C *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(MACHINE[_ARCH]* *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(MAKE *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.SHELL *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e '/\.SYSPATH/d' STD_SED_CMDS.dg2= ${STD_SED_CMDS.dg1} STD_SED_CMDS.dg2+= -e 's,\(last modified\) ..:..:.. ... ..\, ....,\1 ,' STD_SED_CMDS.dg3= ${STD_SED_CMDS.dg2} # Omit details such as process IDs from the output of the -dj option. STD_SED_CMDS.dj= \ -e '/Process/d;/JobFinish:/d' \ -e 's,^\(Job_TokenWithdraw\)([0-9]*),\1(),' \ -e 's,^([0-9][0-9]*) \(withdrew token\),() \1,' \ -e 's, \(pid\) [0-9][0-9]*, \1 ,' \ -e 's,^\( Command:\) .*,\1 ,' # Reduce the noise for tests running with the -n option, since there is no # other way to suppress the echoing of the commands. STD_SED_CMDS.hide-from-output= \ -e '/^echo hide-from-output/d' \ -e 's,hide-from-output ,,' \ -e 's,hide-from-output,,' # Normalize the output for error messages from the shell. # # $shell -c '...' # NetBSD sh ...: not found # NetBSD ksh ksh: ...: not found # bash 5.0.18 bash: ...: command not found # bash 5.1.0 bash: line 1: ...: command not found # dash dash: 1: ...: not found # # $shell -c '< /nonexistent' # NetBSD sh sh: cannot open /nonexistent: no such file # NetBSD ksh ksh: cannot open /nonexistent: No such file or directory # bash 5.0.18 bash: /nonexistent: No such file or directory # bash 5.1.0 bash: line 1: /nonexistent: No such file or directory # dash dash: 1: cannot open /nonexistent: No such file # # echo '< /nonexistent' | $shell # NetBSD sh sh: cannot open /nonexistent: no such file # NetBSD ksh ksh: [1]: cannot open /nonexistent: No such file or directory # bash 5.0.18 bash: line 1: /nonexistent: No such file or directory # bash 5.1.0 bash: line 1: /nonexistent: No such file or directory # dash dash: 1: cannot open /nonexistent: No such file # STD_SED_CMDS.shell+= -e 's,^${.SHELL},${.SHELL:T},' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,' STD_SED_CMDS.shell+= -e 's,: command not found,: not found,' STD_SED_CMDS.white-space= -e 's, *, ,g' -e 's, *$$,,' # The actual error messages for a failed regcomp or regexec differ between the # implementations. STD_SED_CMDS.regex= \ -e 's,\(Regex compilation error:\).*,\1 (details omitted),' # Normalize timestamps from ':gmtime' or ':localtime' to ''. # See STD_SED_CMDS.dg2 for timestamps from the debug log. STD_SED_CMDS.timestamp= \ -e 's,[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [12][0-9][0-9][0-9],,' # End of the configuration helpers section. .-include "Makefile.inc" .-include "Makefile.config" UNIT_TESTS:= ${.PARSEDIR} .PATH: ${UNIT_TESTS} .if ${USE_ABSOLUTE_TESTNAMES:Uno} == yes OUTFILES= ${TESTS:@test@${.CURDIR:tA}/${test}.out@} .else OUTFILES= ${TESTS:=.out} .endif all: ${OUTFILES} CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp CLEANFILES+= obj*.[och] lib*.a # posix1.mk CLEANFILES+= issue* .[ab]* # suffixes.mk CLEANDIRS= dir dummy *.tmp # posix1.mk clean: rm -rf ${CLEANDIRS} rm -f ${CLEANFILES} TEST_MAKE?= ${.MAKE} TOOL_SED?= sed TOOL_TR?= tr TOOL_DIFF?= diff DIFF_FLAGS?= -u # ensure consistent results from sort(1) LC_ALL= C LANG= C .export LANG LC_ALL .if ${.MAKE.MODE:Unormal:Mmeta} != "" # we don't need the noise _MKMSG_TEST= : .endif # Some Linux systems such as Fedora have deprecated egrep in favor of grep -E. .if ${.MAKE.OS:NLinux} == "" EGREP= grep -E .endif # Keep the classical definition for all other systems. Just as the bmake code # is kept compatible with C90, the tests are kept compatible with systems that # are several decades old and don't follow modern POSIX standards. EGREP?= egrep MAKE_TEST_ENV= EGREP="${EGREP}" MAKE_TEST_ENV+= MALLOC_OPTIONS="JA" # for jemalloc 100 MAKE_TEST_ENV+= MALLOC_CONF="junk:true" # for jemalloc 510 MAKE_TEST_ENV+= TMPDIR=${TMPDIR} .if ${.MAKE.OS} == "NetBSD" LIMIT_RESOURCES?= ulimit -v 300000 .endif LIMIT_RESOURCES?= : # Each test is run in a sub-make, to keep the tests from interfering with # each other, and because they use different environment variables and # command line options. .SUFFIXES: .mk .rawout .out .mk.rawout: @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX} @set -eu; \ ${LIMIT_RESOURCES}; \ cd ${.OBJDIR}; \ env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \ ${TEST_MAKE} \ -r -C ${.CURDIR} -f ${.IMPSRC} \ ${FLAGS.${.PREFIX:T}:U-k} \ > ${.TARGET}.tmp 2>&1 \ && status=$$? || status=$$?; \ echo $$status > ${.TARGET:R}.status @mv ${.TARGET}.tmp ${.TARGET} # Postprocess the test output to make the output platform-independent. # # Replace anything after 'stopped in' with unit-tests _SED_CMDS+= -e '/stopped/s, in /.*, in unit-tests,' # Allow the test files to be placed anywhere. _SED_CMDS+= -e 's,\(\.PARSEDIR}\) = `'"/[^']*'"',\1 = ,' _SED_CMDS+= -e 's,\(\.INCLUDEDFROMDIR}\) = `'"/[^']*'"',\1 = ,' _SED_CMDS+= -e 's,${TMPDIR},,g' -e 's,${TMPDIR:tA},,g' # canonicalize ${.OBJDIR} and ${.CURDIR} _SED_CMDS+= -e 's,${.CURDIR},,g' .if ${.OBJDIR} != ${.CURDIR} # yes this is inaccurate but none of the tests expect anywhere # which we get depending on how MAKEOBJDIR is set. _SED_CMDS+= -e 's,${.OBJDIR},,g' -e 's,${.OBJDIR:tA},,g' .endif # always pretend .MAKE was called 'make' _SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' _SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' _SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' _SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}\(\[[1-9][0-9]*\]:\),make\1,' _SED_CMDS+= -e 's,/,,g' _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' _SED_CMDS+= -e '/MAKE_VERSION/d' _SED_CMDS+= -e '/EGREP=/d' # on AT&T derived systems: false exits 255 not 1 .if ${.MAKE.OS:N*BSD} != "" _SED_CMDS+= -e 's,\(Error code\) 255,\1 1,' .endif .if ${_shell:N*ksh*} == "" _SED_CMDS+= -e '/^set [+-]v/d' SED_CMDS.opt-debug-jobs+= -e 's,Command: ksh -v,Command: ,' SED_CMDS.opt-debug-jobs+= -e 's,Command: -v,Command: ,' .endif .rawout.out: @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \ - < ${.IMPSRC} > ${.TARGET}.tmp1 - @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 - @rm ${.TARGET}.tmp1 - @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2 - @mv ${.TARGET}.tmp2 ${.TARGET} + < ${.IMPSRC} > ${.TARGET}.tmp + @${POSTPROC.${.PREFIX:T}:D \ + ${POSTPROC.${.PREFIX:T}} < ${.TARGET}.tmp > ${.TARGET}.post \ + && mv ${.TARGET}.post ${.TARGET}.tmp} + @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} .if empty(DIFF_FLAGS) DIFF_ECHO= echo .else DIFF_ECHO= : .endif # Compare all output files test: ${OUTFILES} .PHONY @failed= ; \ for test in ${TESTS}; do \ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out && continue || \ ${DIFF_ECHO} diff ${UNIT_TESTS}/$${test}.exp $${test}.out; \ ${TOOL_DIFF} ${DIFF_FLAGS} ${UNIT_TESTS}/$${test}.exp $${test}.out \ || failed="$${failed}$${failed:+ }$${test}" ; \ done ; \ if [ -n "$${failed}" ]; then \ echo "Failed tests: $${failed}" ; false ; \ else \ echo "All tests passed" ; \ lua=${LUA:Ulua} ; \ have_lua=$$("$$lua" -e 'print "yes"' 2>&1) ; \ if [ "$$have_lua" = "yes" -a -s ${.CURDIR}/check-expect.lua ]; then \ (cd ${.CURDIR} && "$$lua" ./check-expect.lua *.mk); \ fi; \ fi accept: @for test in ${TESTS}; do \ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \ || { echo "Replacing $${test}.exp" ; \ cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \ done .if exists(${TEST_MAKE}) ${TESTS:=.rawout}: ${TEST_MAKE} # in meta mode, we *know* if a target script is impacted # by a makefile change. .if ${.MAKE.MODE:Unormal:Mmeta} == "" ${TESTS:=.rawout}: ${.PARSEDIR}/Makefile .endif .endif .-include diff --git a/contrib/bmake/unit-tests/archive.exp b/contrib/bmake/unit-tests/archive.exp index 5cf847388544..89e7b7878b4e 100644 --- a/contrib/bmake/unit-tests/archive.exp +++ b/contrib/bmake/unit-tests/archive.exp @@ -1,36 +1,48 @@ Making remove-archive rm -f libprog.a Making libprog.a out-of-date archive.mk modmisc.mk varmisc.mk ar cru libprog.a archive.mk modmisc.mk varmisc.mk ranlib libprog.a Making create-archive out-of-date libprog.a Making list-archive out-of-date libprog.a ar t libprog.a archive.mk modmisc.mk varmisc.mk Making list-archive-wildcard out-of-date archive-suffix.mk archive.mk ternary.mk list-archive-wildcard: archive-suffix.mk list-archive-wildcard: archive.mk list-archive-wildcard: ternary.mk +make: "archive.mk" line 61: Error in source archive spec "libprog.a${UNDEF}(archive.mk) pre post" + in directory +make: Fatal errors encountered -- cannot continue +make: stopped making "list-archive-undef-archive" in unit-tests +exit 1 + +make: "archive.mk" line 68: Error in source archive spec "libprog.a" + in directory +make: Fatal errors encountered -- cannot continue +make: stopped making "list-archive-undef-member" in unit-tests +exit 1 + Making depend-on-existing-member out-of-date archive.mk depend-on-existing-member `depend-on-nonexistent-member' is up to date. Making remove-archive rm -f libprog.a begin library Examining libbad.a...up-to-date. Examining -lbad...up-to-date. Examining libgood.a...library...up-to-date. Examining -lgood...library...up-to-date. Examining library...nonexistent....PHONY node...out-of-date. Examining .END...nonexistent...nonexistent and no sources...out-of-date. end library exit status 0 diff --git a/contrib/bmake/unit-tests/archive.mk b/contrib/bmake/unit-tests/archive.mk index eef64396f419..f19c63f29632 100644 --- a/contrib/bmake/unit-tests/archive.mk +++ b/contrib/bmake/unit-tests/archive.mk @@ -1,91 +1,109 @@ -# $NetBSD: archive.mk,v 1.13 2024/04/27 20:23:22 rillig Exp $ +# $NetBSD: archive.mk,v 1.14 2025/01/10 23:00:38 rillig Exp $ # # Very basic demonstration of handling archives, based on the description # in PSD.doc/tutorial.ms. # # This test aims at covering the code, not at being an introduction to # archive handling. That's why it deviates from the tutorial style of # several other tests. ARCHIVE= libprog.a FILES= archive.mk archive-suffix.mk modmisc.mk ternary.mk varmisc.mk all: .if ${.PARSEDIR:tA} != ${.CURDIR:tA} @cd ${MAKEFILE:H} && cp ${FILES} ${.CURDIR} .endif # The following targets create and remove files. The filesystem cache in # dir.c would probably not handle this correctly, therefore each of the # targets is run in its separate sub-make. @${MAKE} -f ${MAKEFILE} remove-archive @${MAKE} -f ${MAKEFILE} create-archive @${MAKE} -f ${MAKEFILE} list-archive @${MAKE} -f ${MAKEFILE} list-archive-wildcard + @${MAKE} -f ${MAKEFILE} list-archive-undef-archive || echo "exit $$?" + @echo + @${MAKE} -f ${MAKEFILE} list-archive-undef-member || echo "exit $$?" + @echo @${MAKE} -f ${MAKEFILE} depend-on-existing-member @${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member @${MAKE} -f ${MAKEFILE} remove-archive @${MAKE} -f ${MAKEFILE} set-up-library @${MAKE} -f ${MAKEFILE} -dm library 2>&1 \ | sed -n '/^Examining/p' \ | sed 's,\.\.\.modified[^.]*,,' @${MAKE} -f ${MAKEFILE} tear-down-library create-archive: ${ARCHIVE} pre post # The indirect references with the $$ cover the code in Arch_ParseArchive # that calls Var_Parse. It's an esoteric scenario since at the point where # Arch_ParseArchive is called, the dependency line is already fully expanded. # ${ARCHIVE}: $${:Ulibprog.a}(archive.mk modmisc.mk $${:Uvarmisc.mk}) pre post ar cru ${.TARGET} ${.OODATE:O} ranlib ${.TARGET} list-archive: ${ARCHIVE} pre post ar t ${.ALLSRC} # XXX: I had expected that this dependency would select all *.mk files from # the archive. Instead, the globbing is done in the current directory. # # To prevent an overly long file list, the pattern is restricted to [at]*.mk. list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} +.if make(list-archive-undef-archive) +# TODO: Be more specific: mention that the variable "UNDEF" is not defined. +# expect+1: Error in source archive spec "libprog.a${UNDEF}(archive.mk) pre post" +list-archive-undef-archive: ${ARCHIVE}$${UNDEF}(archive.mk) pre post + @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} +.endif + +.if make(list-archive-undef-member) +# TODO: Be more specific: mention that the variable "UNDEF" is not defined. +# expect+1: Error in source archive spec "libprog.a" +list-archive-undef-member: ${ARCHIVE}(archive$${UNDEF}.mk) pre post + @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} +.endif + depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post @echo $@ depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post @echo $@ remove-archive: pre post rm -f ${ARCHIVE} pre: .USEBEFORE @echo Making ${.TARGET} ${.OODATE:C,.+,out-of-date,W} ${.OODATE:O} post: .USE @echo set-up-library: .PHONY @echo "member" > member.txt @echo "not a library" > libbad.a @ar cr libgood.a member.txt @echo "begin library" .if make(library) .SUFFIXES: .a .LIBS: .a .endif # The two lines for libgood contain the word "library", the two lines for # libbad don't. # # expect: Examining libbad.a...up-to-date. # expect: Examining -lbad...up-to-date. # expect: Examining libgood.a...library...up-to-date. # expect: Examining -lgood...library...up-to-date. library: .PHONY libbad.a -lbad libgood.a -lgood : Making ${.TARGET} from ${.ALLSRC} tear-down-library: .PHONY @echo "end library" @rm member.txt libbad.a libgood.a diff --git a/contrib/bmake/unit-tests/cmd-errors-jobs.exp b/contrib/bmake/unit-tests/cmd-errors-jobs.exp index 074ceb3bcf28..7177cd4d18f8 100644 --- a/contrib/bmake/unit-tests/cmd-errors-jobs.exp +++ b/contrib/bmake/unit-tests/cmd-errors-jobs.exp @@ -1,9 +1,67 @@ -: undefined--eol -make: in target "unclosed-expression": Unclosed variable "UNCLOSED" -: unclosed-expression- -make: in target "unclosed-modifier": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' -: unclosed-modifier- -make: in target "unknown-modifier": while evaluating variable "UNKNOWN" with value "": Unknown modifier "Z" -: unknown-modifier--eol -: end-eol -exit status 2 +begin undefined-direct +: undefined-direct--eol +end undefined-direct with status 0 + +begin undefined-indirect +: undefined-direct--eol +end undefined-indirect with status 0 + +begin parse-error-direct +make: Unclosed variable "UNCLOSED" + in target "parse-error-unclosed-expression" +make: Unclosed expression, expecting '}' + while evaluating variable "UNCLOSED" with value "" + in target "parse-error-unclosed-modifier" +make: Unknown modifier "Z" + while evaluating variable "UNKNOWN" with value "" + in target "parse-error-unknown-modifier" +end parse-error-direct with status 2 + +begin parse-error-indirect +make: Unclosed variable "UNCLOSED" + in target "parse-error-unclosed-expression" +make: Unclosed expression, expecting '}' + while evaluating variable "UNCLOSED" with value "" + in target "parse-error-unclosed-modifier" +make: Unknown modifier "Z" + while evaluating variable "UNKNOWN" with value "" + in target "parse-error-unknown-modifier" +end parse-error-indirect with status 2 + +begin begin-direct +(exit 13) # .BEGIN +*** Error code 13 (continuing) + + +Stop. +make: stopped making "begin-direct" in unit-tests +end begin-direct with status 1 + +begin begin-indirect +(exit 13) # before-begin +*** Error code 13 (continuing) + + +Stop. +make: stopped making "begin-indirect" in unit-tests +end begin-indirect with status 1 + +begin end-direct +(exit 13) # .END +*** Error code 13 (continuing) + + +Stop. +make: stopped making "end-direct" in unit-tests +end end-direct with status 1 + +begin end-indirect +(exit 13) # before-end +*** Error code 13 (continuing) + + +Stop. +make: stopped making "end-indirect" in unit-tests +end end-indirect with status 1 + +exit status 0 diff --git a/contrib/bmake/unit-tests/cmd-errors-jobs.mk b/contrib/bmake/unit-tests/cmd-errors-jobs.mk index c24aa51907a2..471f2cbb327c 100644 --- a/contrib/bmake/unit-tests/cmd-errors-jobs.mk +++ b/contrib/bmake/unit-tests/cmd-errors-jobs.mk @@ -1,38 +1,105 @@ -# $NetBSD: cmd-errors-jobs.mk,v 1.8 2024/07/09 19:43:01 rillig Exp $ +# $NetBSD: cmd-errors-jobs.mk,v 1.13 2024/08/29 20:20:35 rillig Exp $ # # Demonstrate how errors in expressions affect whether the commands # are actually executed in jobs mode. -.MAKEFLAGS: -j1 +RUN= @ run() { \ + echo "begin $$*" \ + && ${MAKE} -f ${MAKEFILE} -j1 "$$*" \ + && echo "end $$* with status $$?" \ + || echo "end $$* with status $$?" \ + && echo; \ + } && run + +all: + ${RUN} undefined-direct + ${RUN} undefined-indirect + ${RUN} parse-error-direct + ${RUN} parse-error-indirect + ${RUN} begin-direct + ${RUN} begin-indirect + ${RUN} end-direct + ${RUN} end-indirect -all: undefined unclosed-expression unclosed-modifier unknown-modifier end # Undefined variables in expressions are not an error. They expand to empty # strings. -# expect: : undefined--eol -undefined: +# expect: : undefined-direct--eol +# expect: end undefined-direct with status 0 +# expect: : undefined-direct--eol +# expect: end undefined-indirect with status 0 +undefined-indirect: undefined-direct +undefined-direct: : $@-${UNDEFINED}-eol -unclosed-expression: -# expect: make: in target "unclosed-expression": Unclosed variable "UNCLOSED" -# XXX: This command is executed even though it contains parse errors. -# expect: : unclosed-expression- - : $@-${UNCLOSED - -unclosed-modifier: -# expect: make: in target "unclosed-modifier": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' -# XXX: This command is executed even though it contains parse errors. -# expect: : unclosed-modifier- - : $@-${UNCLOSED: - -unknown-modifier: -# expect: make: in target "unknown-modifier": while evaluating variable "UNKNOWN" with value "": Unknown modifier "Z" -# XXX: This command is executed even though it contains parse errors. -# expect: : unknown-modifier--eol - : $@-${UNKNOWN:Z}-eol - -# expect: : end-eol -end: - : $@-eol - -# expect: exit status 2 + +parse-error-indirect: parse-error-direct +parse-error-direct: parse-error-unclosed-expression +parse-error-direct: parse-error-unclosed-modifier +parse-error-direct: parse-error-unknown-modifier + +parse-error-unclosed-expression: + : unexpected $@-${UNCLOSED + +parse-error-unclosed-modifier: + : unexpected $@-${UNCLOSED: + +parse-error-unknown-modifier: + : unexpected $@-${UNKNOWN:Z}-eol + +# expect-not: : unexpected +# expect: make: Unclosed variable "UNCLOSED" +# expect: make: Unclosed expression, expecting '}' +# expect: make: Unknown modifier "Z" +# expect: end parse-error-direct with status 2 +# expect: make: Unclosed variable "UNCLOSED" +# expect: make: Unclosed expression, expecting '}' +# expect: make: Unknown modifier "Z" +# expect: end parse-error-indirect with status 2 + + +.if make(begin-direct) +begin-direct: +.BEGIN: + (exit 13) # $@ +.endif +# expect: begin begin-direct +# expect: make: stopped making "begin-direct" in unit-tests +# expect: end begin-direct with status 1 + + +.if make(begin-indirect) +begin-indirect: +.BEGIN: before-begin + : Making $@ +before-begin: + (exit 13) # $@ +.endif +# expect: begin begin-indirect +# expect: *** Error code 13 (continuing) +# expect: make: stopped making "begin-indirect" in unit-tests +# expect: end begin-indirect with status 1 + + +.if make(end-direct) +end-direct: +.END: + (exit 13) # $@ +.endif +# expect: begin end-direct +# expect: *** Error code 13 (continuing) +# expect: Stop. +# expect: make: stopped making "end-direct" in unit-tests +# expect: end end-direct with status 1 + +.if make(end-indirect) +end-indirect: +.END: before-end + : Making $@ +before-end: + (exit 13) # $@ +.endif +# expect: begin end-indirect +# expect: *** Error code 13 (continuing) +# expect: make: stopped making "end-indirect" in unit-tests +# expect: end end-indirect with status 1 diff --git a/contrib/bmake/unit-tests/cmd-errors-lint.exp b/contrib/bmake/unit-tests/cmd-errors-lint.exp index a5b129c78f92..116e9957a2c5 100644 --- a/contrib/bmake/unit-tests/cmd-errors-lint.exp +++ b/contrib/bmake/unit-tests/cmd-errors-lint.exp @@ -1,9 +1,11 @@ : undefined -make: in target "unclosed-expression": Unclosed variable "UNCLOSED" -: unclosed-expression -make: in target "unclosed-modifier": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' -: unclosed-modifier -make: in target "unknown-modifier": while evaluating variable "UNKNOWN" with value "": Unknown modifier "Z" -: unknown-modifier +make: Unclosed variable "UNCLOSED" + in target "unclosed-expression" +make: Unclosed expression, expecting '}' + while evaluating variable "UNCLOSED" with value "" + in target "unclosed-modifier" +make: Unknown modifier "Z" + while evaluating variable "UNKNOWN" with value "" + in target "unknown-modifier" : end exit status 2 diff --git a/contrib/bmake/unit-tests/cmd-errors-lint.mk b/contrib/bmake/unit-tests/cmd-errors-lint.mk index f870fceaa5cb..873f05c6186f 100644 --- a/contrib/bmake/unit-tests/cmd-errors-lint.mk +++ b/contrib/bmake/unit-tests/cmd-errors-lint.mk @@ -1,38 +1,35 @@ -# $NetBSD: cmd-errors-lint.mk,v 1.4 2024/07/05 18:59:33 rillig Exp $ +# $NetBSD: cmd-errors-lint.mk,v 1.6 2024/08/29 20:20:35 rillig Exp $ # # Demonstrate how errors in expressions affect whether the commands # are actually executed. .MAKEFLAGS: -dL all: undefined unclosed-expression unclosed-modifier unknown-modifier end # Undefined variables in expressions are not an error. They expand to empty # strings. undefined: # expect: : undefined : $@ ${UNDEFINED} unclosed-expression: -# expect: make: in target "unclosed-expression": Unclosed variable "UNCLOSED" -# XXX: This command is executed even though it contains parse errors. -# expect: : unclosed-expression +# expect: make: Unclosed variable "UNCLOSED" +# expect-not: : unclosed-expression : $@ ${UNCLOSED unclosed-modifier: -# expect: make: in target "unclosed-modifier": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' -# XXX: This command is executed even though it contains parse errors. -# expect: : unclosed-modifier +# expect: make: Unclosed expression, expecting '}' +# expect-not: : unclosed-modifier : $@ ${UNCLOSED: unknown-modifier: -# expect: make: in target "unknown-modifier": while evaluating variable "UNKNOWN" with value "": Unknown modifier "Z" -# XXX: This command is executed even though it contains parse errors. -# expect: : unknown-modifier +# expect: make: Unknown modifier "Z" +# expect-not: : unknown-modifier : $@ ${UNKNOWN:Z} end: # expect: : end : $@ # expect: exit status 2 diff --git a/contrib/bmake/unit-tests/cmd-errors.exp b/contrib/bmake/unit-tests/cmd-errors.exp index 074ceb3bcf28..865072ecbf86 100644 --- a/contrib/bmake/unit-tests/cmd-errors.exp +++ b/contrib/bmake/unit-tests/cmd-errors.exp @@ -1,9 +1,11 @@ : undefined--eol -make: in target "unclosed-expression": Unclosed variable "UNCLOSED" -: unclosed-expression- -make: in target "unclosed-modifier": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' -: unclosed-modifier- -make: in target "unknown-modifier": while evaluating variable "UNKNOWN" with value "": Unknown modifier "Z" -: unknown-modifier--eol +make: Unclosed variable "UNCLOSED" + in target "unclosed-expression" +make: Unclosed expression, expecting '}' + while evaluating variable "UNCLOSED" with value "" + in target "unclosed-modifier" +make: Unknown modifier "Z" + while evaluating variable "UNKNOWN" with value "" + in target "unknown-modifier" : end-eol exit status 2 diff --git a/contrib/bmake/unit-tests/cmd-errors.mk b/contrib/bmake/unit-tests/cmd-errors.mk index d9525df6064e..0acc811d43bc 100644 --- a/contrib/bmake/unit-tests/cmd-errors.mk +++ b/contrib/bmake/unit-tests/cmd-errors.mk @@ -1,36 +1,33 @@ -# $NetBSD: cmd-errors.mk,v 1.9 2024/07/09 19:43:01 rillig Exp $ +# $NetBSD: cmd-errors.mk,v 1.11 2024/08/29 20:20:35 rillig Exp $ # # Demonstrate how errors in expressions affect whether the commands # are actually executed in compat mode. all: undefined unclosed-expression unclosed-modifier unknown-modifier end # Undefined variables in expressions are not an error. They expand to empty # strings. undefined: # expect: : undefined--eol : $@-${UNDEFINED}-eol unclosed-expression: -# expect: make: in target "unclosed-expression": Unclosed variable "UNCLOSED" -# XXX: This command is executed even though it contains parse errors. -# expect: : unclosed-expression- +# expect: make: Unclosed variable "UNCLOSED" +# expect-not: : unclosed-expression- : $@-${UNCLOSED unclosed-modifier: -# expect: make: in target "unclosed-modifier": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' -# XXX: This command is executed even though it contains parse errors. -# expect: : unclosed-modifier- +# expect: make: Unclosed expression, expecting '}' +# expect-not: : unclosed-modifier- : $@-${UNCLOSED: unknown-modifier: -# expect: make: in target "unknown-modifier": while evaluating variable "UNKNOWN" with value "": Unknown modifier "Z" -# XXX: This command is executed even though it contains parse errors. -# expect: : unknown-modifier--eol +# expect: make: Unknown modifier "Z" +# expect-not: : unknown-modifier--eol : $@-${UNKNOWN:Z}-eol end: # expect: : end-eol : $@-eol # expect: exit status 2 diff --git a/contrib/bmake/unit-tests/cmd-interrupt.exp b/contrib/bmake/unit-tests/cmd-interrupt.exp index 242db1d9507d..e03b64870fa3 100755 --- a/contrib/bmake/unit-tests/cmd-interrupt.exp +++ b/contrib/bmake/unit-tests/cmd-interrupt.exp @@ -1,8 +1,9 @@ > cmd-interrupt-ordinary make: *** cmd-interrupt-ordinary removed interrupt-ordinary: ok > cmd-interrupt-phony interrupt-phony: ok > cmd-interrupt-precious interrupt-precious: ok +interrupt-compat expected-fail exit status 0 diff --git a/contrib/bmake/unit-tests/cmd-interrupt.mk b/contrib/bmake/unit-tests/cmd-interrupt.mk index 140651b55c62..7ee53c6c2105 100755 --- a/contrib/bmake/unit-tests/cmd-interrupt.mk +++ b/contrib/bmake/unit-tests/cmd-interrupt.mk @@ -1,50 +1,68 @@ -# $NetBSD: cmd-interrupt.mk,v 1.4 2023/03/18 22:20:12 sjg Exp $ +# $NetBSD: cmd-interrupt.mk,v 1.5 2024/07/13 15:10:06 rillig Exp $ # # Tests for interrupting a command. # # If a command is interrupted (usually by the user, here by itself), the # target is removed. This is to avoid having an unfinished target that # would be newer than all of its sources and would therefore not be # tried again in the next run. # # This happens for ordinary targets as well as for .PHONY targets, even # though the .PHONY targets usually do not correspond to a file. # # To protect the target from being removed, the target has to be marked with # the special source .PRECIOUS. These targets need to ensure for themselves # that interrupting them does not leave an inconsistent state behind. # # See also: # CompatDeleteTarget -all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-after +all: clean-before +all: interrupt-ordinary +all: interrupt-phony +all: interrupt-precious +all: interrupt-compat +all: clean-after clean-before clean-after: .PHONY - @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious + @rm -f cmd-interrupt-ordinary cmd-interrupt-phony + @rm -f cmd-interrupt-precious cmd-interrupt-compat interrupt-ordinary: @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok } interrupt-phony: .PHONY @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-phony || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-phony) :? ok : error } interrupt-precious: .PRECIOUS @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-precious || true # The ././ is necessary to work around the file cache. @echo ${.TARGET}: ${exists(././cmd-interrupt-precious) :? ok : error } +interrupt-compat: + @${MAKE} -f ${MAKEFILE} cmd-interrupt-compat || true + @echo ${.TARGET} ${exists(././cmd-interrupt-compat) :? expected-fail : unexpected-ok } + cmd-interrupt-ordinary: > ${.TARGET} @kill -INT ${.MAKE.PID} cmd-interrupt-phony: .PHONY > ${.TARGET} @kill -INT ${.MAKE.PID} cmd-interrupt-precious: .PRECIOUS > ${.TARGET} @kill -INT ${.MAKE.PID} + +# When the make process (and not the process group) is interrupted in compat +# mode, it first tries to interrupt the process group of the currently running +# child command, but that fails since there is no such process group, rather +# the child command runs in the same process group as make itself. The child +# command then continues, and after sleeping a bit creates the target file. +cmd-interrupt-compat: + @kill -INT ${.MAKE.PID} && sleep 1 && > ${.TARGET} diff --git a/contrib/bmake/unit-tests/cmdline.exp b/contrib/bmake/unit-tests/cmdline.exp index 5700da3295fb..063be7e83afe 100644 --- a/contrib/bmake/unit-tests/cmdline.exp +++ b/contrib/bmake/unit-tests/cmdline.exp @@ -1,8 +1,8 @@ makeobjdir-direct: -show-objdir: /6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 +show-objdir: /cmdline/6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 makeobjdir-indirect: -show-objdir: /a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45/ +show-objdir: /cmdline/a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45/ space-and-comment: value # no comment $ value # no comment $ exit status 0 diff --git a/contrib/bmake/unit-tests/cmdline.mk b/contrib/bmake/unit-tests/cmdline.mk index 40a0d7ccddbb..c0c4ee287f2f 100644 --- a/contrib/bmake/unit-tests/cmdline.mk +++ b/contrib/bmake/unit-tests/cmdline.mk @@ -1,58 +1,62 @@ -# $NetBSD: cmdline.mk,v 1.5 2024/04/23 22:51:28 rillig Exp $ +# $NetBSD: cmdline.mk,v 1.7 2024/08/29 17:56:37 sjg Exp $ # # Tests for command line parsing and related special variables. -TMPBASE?= ${TMPDIR:U/tmp/uid${.MAKE.UID}} +TMPBASE?= ${TMPDIR:U/tmp/uid${.MAKE.UID}}/cmdline SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID MAKE_CMD= env TMPBASE=${TMPBASE}/${SUB1} ${.MAKE} -f ${MAKEFILE} -r DIR2= ${TMPBASE}/${SUB2} DIR12= ${TMPBASE}/${SUB1}/${SUB2} all: prepare-dirs all: makeobjdir-direct makeobjdir-indirect all: space-and-comment +all: cleanup prepare-dirs: @rm -rf ${DIR2} ${DIR12} @mkdir -p ${DIR2} ${DIR12} # The .OBJDIR can be set via the MAKEOBJDIR command line variable. # It must be a command line variable; an environment variable would not work. makeobjdir-direct: @echo $@: @${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir # The .OBJDIR can be set via the MAKEOBJDIR command line variable, # and expressions based on that variable can contain the usual modifiers. # Since the .OBJDIR=MAKEOBJDIR assignment happens very early, # the SUB2 variable in the modifier is not defined yet and is therefore empty. # The SUB1 in the resulting path comes from the environment variable TMPBASE, # see MAKE_CMD. makeobjdir-indirect: @echo $@: @${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir show-objdir: @echo $@: ${.OBJDIR:Q} # Variable assignments in the command line are handled differently from # variable assignments in makefiles. In the command line, trailing whitespace # is preserved, and the '#' does not start a comment. This is because the # low-level parsing from ParseRawLine does not take place. # # Preserving '#' and trailing whitespace has the benefit that when passing # such values to sub-makes via MAKEFLAGS, no special encoding is needed. # Leading whitespace in the variable value is discarded though, which makes # the behavior inconsistent. space-and-comment: .PHONY @echo $@: @env -i \ ${MAKE} -r -f /dev/null ' VAR= value # no comment ' -v VAR \ | sed 's,$$,$$,' @env -i MAKEFLAGS="' VAR= value # no comment '" \ ${MAKE} -r -f /dev/null -v VAR \ | sed 's,$$,$$,' + +cleanup: + @rm -rf ${TMPBASE} diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp index 059007f00c84..e05961f14f87 100644 --- a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp +++ b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.exp @@ -1,5 +1,5 @@ -make: "cond-cmp-numeric-eq.mk" line 68: Malformed conditional (!(12345 = 12345)) -make: "cond-cmp-numeric-eq.mk" line 76: Malformed conditional (!(12345 === 12345)) +make: "cond-cmp-numeric-eq.mk" line 68: Malformed conditional '!(12345 = 12345)' +make: "cond-cmp-numeric-eq.mk" line 76: Malformed conditional '!(12345 === 12345)' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk index 1d0ade26087e..a8630cb37f1f 100755 --- a/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk +++ b/contrib/bmake/unit-tests/cond-cmp-numeric-eq.mk @@ -1,80 +1,80 @@ -# $NetBSD: cond-cmp-numeric-eq.mk,v 1.7 2023/09/07 05:36:33 rillig Exp $ +# $NetBSD: cond-cmp-numeric-eq.mk,v 1.8 2024/08/06 18:00:16 rillig Exp $ # # Tests for numeric comparisons with the == operator in .if conditions. # This comparison yields the same result, whether numeric or character-based. .if 1 == 1 .else . error .endif # This comparison yields the same result, whether numeric or character-based. .if 1 == 2 . error .endif .if 2 == 1 . error .endif # Scientific notation is supported, as per strtod. .if 2e7 == 2000e4 .else . error .endif .if 2000e4 == 2e7 .else . error .endif # Trailing zeroes after the decimal point are irrelevant for the numeric # value. .if 3.30000 == 3.3 .else . error .endif .if 3.3 == 3.30000 .else . error .endif # Numeric comparison works by parsing both sides # as double, and then performing a normal comparison. The range of double is # typically 16 or 17 significant digits, therefore these two numbers seem to # be equal. .if 1.000000000000000001 == 1.000000000000000002 .else . error .endif # Because an IEEE 754 double can only hold integers with a mantissa of 53 # bits, these two numbers are considered the same. The 993 is rounded down # to the 992. .if 9007199254740993 == 9007199254740992 .else . error .endif # The 995 is rounded up, the 997 is rounded down. .if 9007199254740995 == 9007199254740997 .else . error Probably a misconfiguration in the floating point environment, \ or maybe a machine without IEEE 754 floating point support. .endif # There is no = operator for numbers. -# expect+1: Malformed conditional (!(12345 = 12345)) +# expect+1: Malformed conditional '!(12345 = 12345)' .if !(12345 = 12345) . error .else . error .endif # There is no === operator for numbers either. -# expect+1: Malformed conditional (!(12345 === 12345)) +# expect+1: Malformed conditional '!(12345 === 12345)' .if !(12345 === 12345) . error .else . error .endif diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric.exp b/contrib/bmake/unit-tests/cond-cmp-numeric.exp index 77c0154ca29b..8bec6ed55216 100644 --- a/contrib/bmake/unit-tests/cond-cmp-numeric.exp +++ b/contrib/bmake/unit-tests/cond-cmp-numeric.exp @@ -1,15 +1,15 @@ CondParser_Eval: !(${:UINF} > 1e100) make: "cond-cmp-numeric.mk" line 15: Comparison with '>' requires both operands 'INF' and '1e100' to be numeric CondParser_Eval: ${:UNaN} > NaN make: "cond-cmp-numeric.mk" line 21: Comparison with '>' requires both operands 'NaN' and 'NaN' to be numeric CondParser_Eval: !(${:UNaN} == NaN) Comparing "NaN" == "NaN" CondParser_Eval: 123 ! 123 -make: "cond-cmp-numeric.mk" line 38: Malformed conditional (123 ! 123) +make: "cond-cmp-numeric.mk" line 38: Malformed conditional '123 ! 123' CondParser_Eval: ${:U 123} < 124 Comparing 123.000000 < 124.000000 CondParser_Eval: ${:U123 } < 124 make: "cond-cmp-numeric.mk" line 54: Comparison with '<' requires both operands '123 ' and '124' to be numeric make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-cmp-numeric.mk b/contrib/bmake/unit-tests/cond-cmp-numeric.mk index e025b99b27cd..63a3bff8d560 100644 --- a/contrib/bmake/unit-tests/cond-cmp-numeric.mk +++ b/contrib/bmake/unit-tests/cond-cmp-numeric.mk @@ -1,60 +1,60 @@ -# $NetBSD: cond-cmp-numeric.mk,v 1.7 2023/03/04 08:07:29 rillig Exp $ +# $NetBSD: cond-cmp-numeric.mk,v 1.8 2024/08/06 18:00:16 rillig Exp $ # # Tests for numeric comparisons in .if conditions. # # See also: # cond-token-number.mk .MAKEFLAGS: -dc # The ${:U...} on the left-hand side is necessary for the parser. # Even if strtod(3) parses "INF" as +Infinity, make does not accept this # since it is not really a number; see TryParseNumber. # expect+1: Comparison with '>' requires both operands 'INF' and '1e100' to be numeric .if !(${:UINF} > 1e100) . error .endif # Neither is NaN a number; see TryParseNumber. # expect+1: Comparison with '>' requires both operands 'NaN' and 'NaN' to be numeric .if ${:UNaN} > NaN . error .endif # Since NaN is not parsed as a number, both operands are interpreted # as strings and are therefore equal. If they were parsed as numbers, # they would compare unequal, since NaN is unequal to any and everything, # including itself. .if !(${:UNaN} == NaN) . error .endif # The parsing code in CondParser_Comparison only performs a light check on # whether the operator is valid, leaving the rest of the work to the # evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this # parse error is properly reported. -# expect+1: Malformed conditional (123 ! 123) +# expect+1: Malformed conditional '123 ! 123' .if 123 ! 123 . error .else . error .endif # Leading spaces are allowed for numbers. # See EvalCompare and TryParseNumber. .if ${:U 123} < 124 .else . error .endif # Trailing spaces are NOT allowed for numbers. # See EvalCompare and TryParseNumber. # expect+1: Comparison with '<' requires both operands '123 ' and '124' to be numeric .if ${:U123 } < 124 . error .else . error .endif all: diff --git a/contrib/bmake/unit-tests/cond-cmp-string.exp b/contrib/bmake/unit-tests/cond-cmp-string.exp index e0aabfdadca4..ce58422afd7e 100644 --- a/contrib/bmake/unit-tests/cond-cmp-string.exp +++ b/contrib/bmake/unit-tests/cond-cmp-string.exp @@ -1,11 +1,11 @@ -make: "cond-cmp-string.mk" line 19: Malformed conditional (str != str) -make: "cond-cmp-string.mk" line 44: Malformed conditional ("string" != "str""ing") -make: "cond-cmp-string.mk" line 52: Malformed conditional (!("value" = "value")) -make: "cond-cmp-string.mk" line 60: Malformed conditional (!("value" === "value")) +make: "cond-cmp-string.mk" line 19: Malformed conditional 'str != str' +make: "cond-cmp-string.mk" line 44: Malformed conditional '"string" != "str""ing"' +make: "cond-cmp-string.mk" line 52: Malformed conditional '!("value" = "value")' +make: "cond-cmp-string.mk" line 60: Malformed conditional '!("value" === "value")' make: "cond-cmp-string.mk" line 118: Comparison with '<' requires both operands 'string' and 'string' to be numeric make: "cond-cmp-string.mk" line 126: Comparison with '<=' requires both operands 'string' and 'string' to be numeric make: "cond-cmp-string.mk" line 134: Comparison with '>' requires both operands 'string' and 'string' to be numeric make: "cond-cmp-string.mk" line 142: Comparison with '>=' requires both operands 'string' and 'string' to be numeric make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-cmp-string.mk b/contrib/bmake/unit-tests/cond-cmp-string.mk index e3346e08b5a0..a913750a017f 100644 --- a/contrib/bmake/unit-tests/cond-cmp-string.mk +++ b/contrib/bmake/unit-tests/cond-cmp-string.mk @@ -1,154 +1,154 @@ -# $NetBSD: cond-cmp-string.mk,v 1.19 2024/04/23 22:51:28 rillig Exp $ +# $NetBSD: cond-cmp-string.mk,v 1.20 2024/08/06 18:00:16 rillig Exp $ # # Tests for string comparisons in .if conditions. # This is a simple comparison of string literals. # Nothing surprising here. .if "str" != "str" . error .endif # The right-hand side of the comparison may be written without quotes. .if "str" != str . error .endif # The left-hand side of the comparison must be enclosed in quotes. # This one is not enclosed in quotes and thus generates an error message. -# expect+1: Malformed conditional (str != str) +# expect+1: Malformed conditional 'str != str' .if str != str . error .endif # An expression that occurs on the left-hand side of the comparison must be # defined. # # The variable named "" is never defined, nevertheless it can be used as a # starting point for an expression. Applying the :U modifier to such an # undefined expression turns it into a defined expression. # # See ApplyModifier_Defined and DEF_DEFINED. .if ${:Ustr} != "str" . error .endif # Any character in a string literal may be escaped using a backslash. # This means that "\n" does not mean a newline but a simple "n". .if "string" != "\s\t\r\i\n\g" . error .endif # It is not possible to concatenate two string literals to form a single # string. In C, Python and the shell this is possible, but not in make. -# expect+1: Malformed conditional ("string" != "str""ing") +# expect+1: Malformed conditional '"string" != "str""ing"' .if "string" != "str""ing" . error .else . error .endif # There is no = operator for strings. -# expect+1: Malformed conditional (!("value" = "value")) +# expect+1: Malformed conditional '!("value" = "value")' .if !("value" = "value") . error .else . error .endif # There is no === operator for strings either. -# expect+1: Malformed conditional (!("value" === "value")) +# expect+1: Malformed conditional '!("value" === "value")' .if !("value" === "value") . error .else . error .endif # An expression can be enclosed in double quotes. .if ${:Uword} != "${:Uword}" . error .endif # Between 2003-01-01 (maybe even earlier) and 2020-10-30, adding one of the # characters " \t!=><" directly after an expression in a string literal # resulted in a "Malformed conditional", even though the string was # well-formed. .if ${:Uword } != "${:Uword} " . error .endif # Some other characters worked though, and some didn't. # Those that are mentioned in is_separator didn't work. .if ${:Uword0} != "${:Uword}0" . error .endif .if ${:Uword&} != "${:Uword}&" . error .endif .if ${:Uword!} != "${:Uword}!" . error .endif .if ${:Uword<} != "${:Uword}<" . error .endif # Adding another expression to the string literal works though. .if ${:Uword} != "${:Uwo}${:Urd}" . error .endif # Adding a space at the beginning of the quoted expression works though. .if ${:U word } != " ${:Uword} " . error .endif # If at least one side of the comparison is a string literal, the string # comparison is performed. .if 12345 != "12345" . error .endif # If at least one side of the comparison is a string literal, the string # comparison is performed. The ".0" in the left-hand side makes the two # sides of the equation unequal. .if 12345.0 == "12345" . error .endif # Strings cannot be compared relationally, only for equality. # expect+1: Comparison with '<' requires both operands 'string' and 'string' to be numeric .if "string" < "string" . error .else . error .endif # Strings cannot be compared relationally, only for equality. # expect+1: Comparison with '<=' requires both operands 'string' and 'string' to be numeric .if "string" <= "string" . error .else . error .endif # Strings cannot be compared relationally, only for equality. # expect+1: Comparison with '>' requires both operands 'string' and 'string' to be numeric .if "string" > "string" . error .else . error .endif # Strings cannot be compared relationally, only for equality. # expect+1: Comparison with '>=' requires both operands 'string' and 'string' to be numeric .if "string" >= "string" . error .else . error .endif # Two expressions with different values compare unequal. VAR1= value1 VAR2= value2 .if ${VAR1} != ${VAR2} .else . error .endif diff --git a/contrib/bmake/unit-tests/cond-eof.exp b/contrib/bmake/unit-tests/cond-eof.exp index 58a74d854d91..9e19dca175bd 100644 --- a/contrib/bmake/unit-tests/cond-eof.exp +++ b/contrib/bmake/unit-tests/cond-eof.exp @@ -1,6 +1,6 @@ -make: "cond-eof.mk" line 17: Malformed conditional (0 ${SIDE_EFFECT} ${SIDE_EFFECT2}) -make: "cond-eof.mk" line 20: Malformed conditional (1 ${SIDE_EFFECT} ${SIDE_EFFECT2}) -make: "cond-eof.mk" line 23: Malformed conditional ((0) ${SIDE_EFFECT} ${SIDE_EFFECT2}) +make: "cond-eof.mk" line 17: Malformed conditional '0 ${SIDE_EFFECT} ${SIDE_EFFECT2}' +make: "cond-eof.mk" line 20: Malformed conditional '1 ${SIDE_EFFECT} ${SIDE_EFFECT2}' +make: "cond-eof.mk" line 23: Malformed conditional '(0) ${SIDE_EFFECT} ${SIDE_EFFECT2}' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-eof.mk b/contrib/bmake/unit-tests/cond-eof.mk index a5f7ce95f89e..706a7deebd1a 100644 --- a/contrib/bmake/unit-tests/cond-eof.mk +++ b/contrib/bmake/unit-tests/cond-eof.mk @@ -1,24 +1,24 @@ -# $NetBSD: cond-eof.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: cond-eof.mk,v 1.7 2024/08/06 18:00:16 rillig Exp $ # # Tests for parsing the end of '.if' conditions, which are represented as the # token TOK_EOF. SIDE_EFFECT= ${:!echo 'side effect' 1>&2!} SIDE_EFFECT2= ${:!echo 'side effect 2' 1>&2!} # In the following conditions, ${SIDE_EFFECT} is the position of the first # parse error. Before cond.c 1.286 from 2021-12-10, it was always fully # evaluated, even if it was not necessary to expand the expression. # These syntax errors are an edge case that does not occur during normal # operation. Still, it is easy to avoid evaluating these expressions, just in # case they have side effects. -# expect+1: Malformed conditional (0 ${SIDE_EFFECT} ${SIDE_EFFECT2}) +# expect+1: Malformed conditional '0 ${SIDE_EFFECT} ${SIDE_EFFECT2}' .if 0 ${SIDE_EFFECT} ${SIDE_EFFECT2} .endif -# expect+1: Malformed conditional (1 ${SIDE_EFFECT} ${SIDE_EFFECT2}) +# expect+1: Malformed conditional '1 ${SIDE_EFFECT} ${SIDE_EFFECT2}' .if 1 ${SIDE_EFFECT} ${SIDE_EFFECT2} .endif -# expect+1: Malformed conditional ((0) ${SIDE_EFFECT} ${SIDE_EFFECT2}) +# expect+1: Malformed conditional '(0) ${SIDE_EFFECT} ${SIDE_EFFECT2}' .if (0) ${SIDE_EFFECT} ${SIDE_EFFECT2} .endif diff --git a/contrib/bmake/unit-tests/cond-func-commands.mk b/contrib/bmake/unit-tests/cond-func-commands.mk index e127a8ebdc03..d193587cd2c1 100644 --- a/contrib/bmake/unit-tests/cond-func-commands.mk +++ b/contrib/bmake/unit-tests/cond-func-commands.mk @@ -1,37 +1,42 @@ -# $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $ +# $NetBSD: cond-func-commands.mk,v 1.6 2025/01/10 23:00:38 rillig Exp $ # # Tests for the commands() function in .if conditions. .MAIN: all # At this point, the target 'target' does not exist yet, therefore it cannot # have commands. Sounds obvious, but good to know that it is really so. .if commands(target) . error .endif target: # Now the target exists, but it still has no commands. .if commands(target) . error .endif target: # not a command # Even after the comment, the target still has no commands. .if commands(target) . error .endif target: @:; # Finally the target has commands. .if !commands(target) . error .endif +# Expressions in the argument of a function call don't have to be defined. +.if commands(${UNDEF}) +. error +.endif + all: @:; diff --git a/contrib/bmake/unit-tests/cond-func-defined.exp b/contrib/bmake/unit-tests/cond-func-defined.exp index b9563ac1fa91..0b48906ce83f 100644 --- a/contrib/bmake/unit-tests/cond-func-defined.exp +++ b/contrib/bmake/unit-tests/cond-func-defined.exp @@ -1,5 +1,5 @@ -make: "cond-func-defined.mk" line 24: Missing closing parenthesis for defined() -make: "cond-func-defined.mk" line 34: Missing closing parenthesis for defined() +make: "cond-func-defined.mk" line 24: Missing ')' after argument 'A' for 'defined' +make: "cond-func-defined.mk" line 34: Missing ')' after argument 'DEF' for 'defined' make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-func-defined.mk b/contrib/bmake/unit-tests/cond-func-defined.mk index 6b24182c11c1..66f95f3380c1 100644 --- a/contrib/bmake/unit-tests/cond-func-defined.mk +++ b/contrib/bmake/unit-tests/cond-func-defined.mk @@ -1,58 +1,63 @@ -# $NetBSD: cond-func-defined.mk,v 1.12 2024/04/23 22:51:28 rillig Exp $ +# $NetBSD: cond-func-defined.mk,v 1.14 2025/01/10 23:00:38 rillig Exp $ # # Tests for the defined() function in .if conditions. DEF= defined ${:UA B}= variable name with spaces .if !defined(DEF) . error .endif # Horizontal whitespace (space tab) after the opening parenthesis is ignored. .if !defined( DEF) . error .endif # Horizontal whitespace (space tab) before the closing parenthesis is ignored. .if !defined(DEF ) . error .endif # The argument of a function must not directly contain whitespace. -# expect+1: Missing closing parenthesis for defined() +# expect+1: Missing ')' after argument 'A' for 'defined' .if !defined(A B) . error .endif # If necessary, the whitespace can be generated by an expression. .if !defined(${:UA B}) . error .endif -# expect+1: Missing closing parenthesis for defined() +# expect+1: Missing ')' after argument 'DEF' for 'defined' .if defined(DEF . error .else . error .endif # Variables from .for loops are not defined. # See directive-for.mk for more details. .for var in value . if defined(var) . error . else # In .for loops, expressions based on the loop variables are substituted at # evaluation time. There is no actual variable involved, even if the code in # the makefiles looks like it. . endif .endfor +# Expressions in the argument of a function call don't have to be defined. +.if defined(${UNDEF}) +. error +.endif + # Neither of the conditions is true. Before July 2020, the right-hand # condition was evaluated even though it was irrelevant. .if defined(UNDEF) && ${UNDEF:Mx} != "" . error .endif all: .PHONY diff --git a/contrib/bmake/unit-tests/cond-func-empty.exp b/contrib/bmake/unit-tests/cond-func-empty.exp index 1d955124d1c6..9102d4a35ae4 100644 --- a/contrib/bmake/unit-tests/cond-func-empty.exp +++ b/contrib/bmake/unit-tests/cond-func-empty.exp @@ -1,5 +1,4 @@ -make: "cond-func-empty.mk" line 168: Unclosed variable "WORD" -make: "cond-func-empty.mk" line 168: Malformed conditional (empty(WORD) +make: "cond-func-empty.mk" line 167: Unclosed variable "WORD" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-func-empty.mk b/contrib/bmake/unit-tests/cond-func-empty.mk index d377a84ff574..17e76a63f2e8 100644 --- a/contrib/bmake/unit-tests/cond-func-empty.mk +++ b/contrib/bmake/unit-tests/cond-func-empty.mk @@ -1,222 +1,225 @@ -# $NetBSD: cond-func-empty.mk,v 1.25 2024/06/02 15:31:26 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.28 2025/01/11 20:54:45 rillig Exp $ # # Tests for the empty() function in .if conditions, which tests an # expression for emptiness. # # Note that the argument in the parentheses is a variable name, not an # expression. That name may be followed by ':...' modifiers. # .undef UNDEF EMPTY= # empty SPACE= ${:U } ZERO= 0 WORD= word # An undefined variable counts as empty. .if !empty(UNDEF) . error .endif # An undefined variable has the empty string as the value, and the :M # variable modifier does not change that. # .if !empty(UNDEF:M*) . error .endif # The :S modifier replaces the empty value with an actual word. After # applying the :S modifier to the expression, its value is 'empty', so it is # no longer empty, but it is still based on an undefined variable. There are # a few modifiers that turn an undefined expression into a defined expression, # among them :U and :D, but not :S. Therefore, at the end of evaluating the # expression, the expression is still undefined, so its final value becomes an # empty string. # # XXX: This is hard to explain to someone who doesn't know these # implementation details. # .if !empty(UNDEF:S,^$,value,W) . error .endif # The :U modifier changes the state of a previously undefined expression from # DEF_UNDEF to DEF_DEFINED. This marks the expression as "being interesting # enough to be further processed". # .if empty(UNDEF:S,^$,value,W:Ufallback) . error .endif # When an expression is based on an undefined variable, its modifiers interact # in sometimes surprising ways. Applying the :S modifier to the undefined # expression makes its value non-empty, but doesn't change that the expression # is based on an undefined variable. The :U modifier that follows only looks # at the definedness state to decide whether the variable is defined or not. # This kind of makes sense since the :U modifier tests the _variable_, not the # _expression_. # # Since the variable was undefined to begin with, the fallback value from the # :U modifier is used in this expression, instead of keeping the 'value' from # the :S modifier. # .if ${UNDEF:S,^$,value,W:Ufallback} != "fallback" . error .endif # The variable EMPTY is completely empty (0 characters). .if !empty(EMPTY) . error .endif # The variable SPACE has a single space, which counts as being empty. .if !empty(SPACE) . error .endif # The variable .newline has a single newline, which counts as being empty. .if !empty(.newline) . error .endif # The variable ZERO has the numeric value 0, but is not empty. This is a # subtle difference between using either 'empty(ZERO)' or the expression # '${ZERO}' in a condition. .if empty(ZERO) . error .elif ${ZERO} . error .elif ${ZERO} == "" . error .endif # The following example constructs an expression with the variable name "" # and the value " ". This expression counts as empty since the value contains # only whitespace. # # Contrary to the other functions in conditionals, the trailing space is not # stripped off, as can be seen in the -dv debug log. If the space had been # stripped, it wouldn't make a difference in this case, but in other cases. # .if !empty(:U ) . error .endif # Now the variable named " " gets a non-empty value, which demonstrates that # neither leading nor trailing spaces are trimmed in the argument of the # function. If the spaces were trimmed, the variable name would be "", and # that variable is indeed undefined. Since CondParser_FuncCallEmpty allows # subexpressions to be based on undefined variables, the value of the # undefined variable "" would be returned as an empty string. ${:U }= space .if empty( ) . error .endif # The value of the following expression is " word", which is not empty. To be # empty, _all_ characters in the expression value have to be whitespace, not # only the first. .if empty(:U word) . error .endif # The :L modifier creates an expression that has the same value as # its name, which both are "VAR" in this case. The value is therefore not # empty. .if empty(VAR:L) . error .endif # The variable WORD has the value "word", which does not count as empty. .if empty(WORD) . error .endif # The expression ${} for a variable with the empty name always evaluates # to an empty string (see Var_Parse, varUndefined). .if !empty() . error .endif # Ensure that expressions that appear as part of the function call # argument are properly parsed. Typical use cases for this are .for loops, # which are expanded to exactly these ${:U} expressions. # # The argument expands to "WORD", and that variable is defined at the # beginning of this file. The surrounding 'W' and 'D' ensure that # CondParser_FuncCallEmpty keeps track of the parsing position, both before # and after the call to Var_Parse. .if empty(W${:UOR}D) . error .endif # There may be spaces outside the parentheses. # Spaces inside the parentheses are interpreted as part of the variable name. .if ! empty ( WORD ) . error .endif ${:U WORD }= variable name with spaces # Now there is a variable named " WORD ", and it is not empty. .if empty ( WORD ) . error .endif -# expect+2: Unclosed variable "WORD" -# expect+1: Malformed conditional (empty(WORD) +# expect+1: Unclosed variable "WORD" .if empty(WORD . error .else . error .endif # Since cond.c 1.76 from 2020-06-28 and before var.c 1.226 from 2020-07-02, # the following example generated a wrong error message "Variable VARNAME is # recursive". # # Since at least 1993, the manual page claimed that irrelevant parts of # conditions were not evaluated, but that was wrong for a long time. The # expressions in irrelevant parts of the condition were actually evaluated, # they just allowed undefined variables to be used in the conditions. These # unnecessary evaluations were fixed in several commits, starting with var.c # 1.226 from 2020-07-02. # # In this example, the variable "VARNAME2" is not defined, so evaluation of # the condition should have stopped at this point, and the rest of the # condition should have been processed in parse-only mode. The right-hand # side containing the '!empty' was evaluated though, as it had always been. # # When evaluating the !empty condition, the variable name was parsed as # "VARNAME${:U2}", but without expanding any nested expression, in # this case the ${:U2}. The expression '${:U2}' was replaced with an empty # string, the resulting variable name was thus "VARNAME". This conceptually # wrong variable name should have been discarded quickly after parsing it, to # prevent it from doing any harm. # # The expression was evaluated, and this was wrong. The evaluation was done # without VARE_EVAL (called VARF_WANTRES back then) though. This had the # effect that the ${:U1} from the value of VARNAME evaluated to an empty # string. This in turn created the seemingly recursive definition # VARNAME=${VARNAME}, and that definition was evaluated even though it was # never meant to be evaluated. # # This was fixed by evaluating nested expressions in the variable name only # when the whole expression was evaluated as well. VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) .endif +# Expressions in the argument of a function call don't have to be defined. +.if !empty(${UNDEF}) +. error +.endif # If the word 'empty' is not followed by '(', it is not a function call but an # ordinary bare word. This bare word is interpreted as 'defined(empty)', and # since there is no variable named 'empty', the condition evaluates to false. .if empty . error .endif empty= # defined but empty .if empty .else . error .endif diff --git a/contrib/bmake/unit-tests/cond-func-exists.mk b/contrib/bmake/unit-tests/cond-func-exists.mk index 40228cd44902..c68507ca1ab9 100644 --- a/contrib/bmake/unit-tests/cond-func-exists.mk +++ b/contrib/bmake/unit-tests/cond-func-exists.mk @@ -1,51 +1,56 @@ -# $NetBSD: cond-func-exists.mk,v 1.7 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: cond-func-exists.mk,v 1.8 2025/01/10 23:00:38 rillig Exp $ # # Tests for the exists() function in .if conditions. .if !exists(.) . error .endif # The argument to the function must not be enclosed in quotes. # Neither double quotes nor single quotes are allowed. .if exists(".") . error .endif .if exists('.') . error .endif # The only way to escape characters that would otherwise influence the parser # is to enclose them in an expression. For function arguments, # neither the backslash nor the dollar sign act as escape character. .if exists(\.) . error .endif .if !exists(${:U.}) . error .endif # The argument to the function can have several expressions. # See cond-func.mk for the characters that cannot be used directly. .if !exists(${.PARSEDIR}/${.PARSEFILE}) . error .endif # Whitespace is trimmed on both sides of the function argument. .if !exists( . ) . error .endif +# Expressions in the argument of a function call don't have to be defined. +.if exists(${UNDEF}) +. error +.endif + # The exists function does not really look up the file in the file system, # instead it uses a cache that is preloaded very early, before parsing the # first makefile. At that time, the file did not exist yet. _!= > cond-func-exists.just-created .if exists(cond-func-exists.just-created) . error .endif _!= rm cond-func-exists.just-created all: @:; diff --git a/contrib/bmake/unit-tests/cond-func-make.mk b/contrib/bmake/unit-tests/cond-func-make.mk index 15bc9f04d4e0..1a14fd320a3c 100644 --- a/contrib/bmake/unit-tests/cond-func-make.mk +++ b/contrib/bmake/unit-tests/cond-func-make.mk @@ -1,29 +1,34 @@ -# $NetBSD: cond-func-make.mk,v 1.5 2023/06/23 04:56:54 rillig Exp $ +# $NetBSD: cond-func-make.mk,v 1.6 2025/01/10 23:00:38 rillig Exp $ # # Tests for the make() function in .if conditions, which tests whether # the argument has been passed as a target via the command line or later # via the .MAKEFLAGS special dependency target. .if !make(via-cmdline) . error .endif .if make(via-dot-makeflags) . error .endif .MAKEFLAGS: via-dot-makeflags .if !make(via-cmdline) . error .endif .if !make(via-dot-makeflags) . error .endif # expect+1: warning: Unfinished character list in pattern argument '[' to function 'make' .if make([) . error .endif +# Expressions in the argument of a function call don't have to be defined. +.if make(${UNDEF}) +. error +.endif + via-cmdline via-dot-makeflags: : $@ diff --git a/contrib/bmake/unit-tests/cond-func-target.mk b/contrib/bmake/unit-tests/cond-func-target.mk index 62266839df9e..2216997e4393 100644 --- a/contrib/bmake/unit-tests/cond-func-target.mk +++ b/contrib/bmake/unit-tests/cond-func-target.mk @@ -1,38 +1,43 @@ -# $NetBSD: cond-func-target.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: cond-func-target.mk,v 1.5 2025/01/10 23:00:38 rillig Exp $ # # Tests for the target() function in .if conditions. .MAIN: all # The target "target" does not exist yet. .if target(target) . error .endif target: # The target exists, even though it does not have any commands. .if !target(target) . error .endif target: # not a command # Adding a comment to an existing target does not change whether the target # is defined or not. .if !target(target) . error .endif target: @:; # Adding a command to an existing target does not change whether the target # is defined or not. .if !target(target) . error .endif +# Expressions in the argument of a function call don't have to be defined. +.if target(${UNDEF}) +. error +.endif + all: @:; diff --git a/contrib/bmake/unit-tests/cond-func.exp b/contrib/bmake/unit-tests/cond-func.exp index c2c5e94fe7e2..0570a1c4fabc 100644 --- a/contrib/bmake/unit-tests/cond-func.exp +++ b/contrib/bmake/unit-tests/cond-func.exp @@ -1,12 +1,13 @@ -make: "cond-func.mk" line 37: Missing closing parenthesis for defined() -make: "cond-func.mk" line 53: Missing closing parenthesis for defined() -make: "cond-func.mk" line 57: Missing closing parenthesis for defined() +make: "cond-func.mk" line 37: Missing ')' after argument 'A' for 'defined' +make: "cond-func.mk" line 53: Missing ')' after argument 'A' for 'defined' +make: "cond-func.mk" line 57: Missing ')' after argument 'A' for 'defined' make: "cond-func.mk" line 91: Unknown operator '&' make: "cond-func.mk" line 107: A plain function name is parsed as defined(...). make: "cond-func.mk" line 115: A plain function name is parsed as defined(...). make: "cond-func.mk" line 126: Symbols may start with a function name. make: "cond-func.mk" line 132: Symbols may start with a function name. -make: "cond-func.mk" line 138: Missing closing parenthesis for defined() +make: "cond-func.mk" line 138: Missing ')' after argument '' for 'defined' +make: "cond-func.mk" line 145: Missing ')' after argument '${:UVARNAME}.param' for 'defined' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-func.mk b/contrib/bmake/unit-tests/cond-func.mk index 7ed6f6f5570d..3e4f8a9f151f 100644 --- a/contrib/bmake/unit-tests/cond-func.mk +++ b/contrib/bmake/unit-tests/cond-func.mk @@ -1,142 +1,149 @@ -# $NetBSD: cond-func.mk,v 1.15 2024/07/06 21:21:10 rillig Exp $ +# $NetBSD: cond-func.mk,v 1.18 2024/08/07 05:48:45 rillig Exp $ # # Tests for those parts of the functions in .if conditions that are common # among several functions. # # The below test uses the 'defined' function since it has no side-effects. # The other functions would work equally well, except for 'empty', which # parses its argument differently from the other functions. # DEF= defined ${:UA B}= variable name with spaces ${:UVAR(value)}= variable name with parentheses ${:UVAR{value}}= variable name with balanced braces # Really strange variable names must be given indirectly via another variable, # so that no unbalanced braces appear in the top-level expression. VARNAME_UNBALANCED_BRACES= VAR{{{value ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces .if !defined(DEF) . error .endif # Horizontal whitespace (space tab) after the opening parenthesis is ignored. .if !defined( DEF) . error .endif # Horizontal whitespace (space tab) before the closing parenthesis is ignored. .if !defined(DEF ) . error .endif # The argument of a function must not directly contain whitespace. -# expect+1: Missing closing parenthesis for defined() +# expect+1: Missing ')' after argument 'A' for 'defined' .if !defined(A B) . error .endif # If necessary, the whitespace can be generated by an expression. .if !defined(${:UA B}) . error .endif # Characters that could be mistaken for operators must not appear directly # in a function argument. As with whitespace, these can be generated # indirectly. # # It's not entirely clear why these characters are forbidden. # The most plausible reason seems to be typo detection. -# expect+1: Missing closing parenthesis for defined() +# expect+1: Missing ')' after argument 'A' for 'defined' .if !defined(A&B) . error .endif -# expect+1: Missing closing parenthesis for defined() +# expect+1: Missing ')' after argument 'A' for 'defined' .if !defined(A|B) . error .endif # Even parentheses may appear in variable names. # They must be balanced though. .if !defined(VAR(value)) . error .endif # Braces do not have any special meaning when parsing arguments. .if !defined(VAR{value}) . error .endif # Braces do not have any special meaning when parsing arguments. # They don't need to be balanced. .if !defined(VAR{{{value) . error .endif # There may be spaces around the operators and parentheses, and even # inside the parentheses. The spaces inside the parentheses are not # allowed for the 'empty' function (see cond-func-empty.mk), therefore # they are typically omitted for the other functions as well. .if ! defined ( DEF ) . error .endif # Before cond.c 1.366 from 2024-07-06, the following condition was # interpreted as defined(A) && defined(B). Each kind of .if directive has a # default function that is called when a bare word is parsed. For the plain # .if directive, this function is 'defined'; see "struct If ifs" in cond.c. # expect+1: Unknown operator '&' .if A&B . error .endif # The empty variable is never defined. .if defined() . error .endif # The plain word 'defined' is interpreted as 'defined(defined)', see # CondParser_ComparisonOrLeaf. # That variable is not defined (yet). .if defined . error .else # expect+1: A plain function name is parsed as defined(...). . info A plain function name is parsed as defined(...). .endif # If a variable named 'defined' is actually defined, the bare word 'defined' # is interpreted as 'defined(defined)', and the condition evaluates to true. defined= # defined but empty .if defined # expect+1: A plain function name is parsed as defined(...). . info A plain function name is parsed as defined(...). .else . error .endif # A plain symbol name may start with one of the function names, in this case # 'defined'. .if defined-var . error .else # expect+1: Symbols may start with a function name. . info Symbols may start with a function name. .endif defined-var= # defined but empty .if defined-var # expect+1: Symbols may start with a function name. . info Symbols may start with a function name. .else . error .endif -# expect+1: Missing closing parenthesis for defined() +# expect+1: Missing ')' after argument '' for 'defined' .if defined( . error .else . error .endif + +# expect+1: Missing ')' after argument '${:UVARNAME}.param' for 'defined' +.if defined(${:UVARNAME}.param extra) +. error +.else +. error +.endif diff --git a/contrib/bmake/unit-tests/cond-late.exp b/contrib/bmake/unit-tests/cond-late.exp index e97fd3789381..d8a24143c2b9 100644 --- a/contrib/bmake/unit-tests/cond-late.exp +++ b/contrib/bmake/unit-tests/cond-late.exp @@ -1,7 +1,9 @@ -make: "cond-late.mk" line 38: while evaluating variable "VAR" with value "${${UNDEF} != "no":?:}": while evaluating condition " != "no"": Bad condition +make: "cond-late.mk" line 38: Bad condition + while evaluating condition " != "no"" + while evaluating variable "VAR" with value "${${UNDEF} != "no":?:}" in directory make: Fatal errors encountered -- cannot continue make: stopped making "do-parse-time" in unit-tests yes no exit status 0 diff --git a/contrib/bmake/unit-tests/cond-late.mk b/contrib/bmake/unit-tests/cond-late.mk index 4d8c4fbd5d98..a8f381590a6e 100644 --- a/contrib/bmake/unit-tests/cond-late.mk +++ b/contrib/bmake/unit-tests/cond-late.mk @@ -1,40 +1,40 @@ -# $NetBSD: cond-late.mk,v 1.8 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: cond-late.mk,v 1.9 2024/08/29 20:20:35 rillig Exp $ # # Using the :? modifier, expressions can contain conditional # expressions that are evaluated late, at expansion time. # # Any expressions appearing in these conditions are expanded before parsing # the condition. This is different from conditions in .if directives, where # expressions are evaluated individually and only as far as necessary, see # cond-short.mk. # # Because of this, variables that are used in these lazy conditions # should not contain double-quotes, or the parser will probably fail. # # They should also not contain operators like == or <, since these are # actually interpreted as these operators. This is demonstrated below. # all: parse-time cond-literal parse-time: .PHONY @${MAKE} -f ${MAKEFILE} do-parse-time || true COND.true= "yes" == "yes" COND.false= "yes" != "yes" # If the order of evaluation were to change to first parse the condition # and then expand the variables, the output would change from the # current "yes no" to "yes yes", since both variables are non-empty. # expect: yes # expect: no cond-literal: @echo ${ ${COND.true} :?yes:no} @echo ${ ${COND.false} :?yes:no} .if make(do-parse-time) VAR= ${${UNDEF} != "no":?:} -# expect+1: while evaluating variable "VAR" with value "${${UNDEF} != "no":?:}": while evaluating condition " != "no"": Bad condition +# expect+1: Bad condition . if empty(VAR:Mpattern) . endif .endif diff --git a/contrib/bmake/unit-tests/cond-op-and.exp b/contrib/bmake/unit-tests/cond-op-and.exp index 6d2263dc5d56..ac085e77ce74 100644 --- a/contrib/bmake/unit-tests/cond-op-and.exp +++ b/contrib/bmake/unit-tests/cond-op-and.exp @@ -1,11 +1,11 @@ -make: "cond-op-and.mk" line 36: Malformed conditional (0 || (${DEF} && ${UNDEF})) -make: "cond-op-and.mk" line 41: Malformed conditional (0 || (${UNDEF} && ${UNDEF})) -make: "cond-op-and.mk" line 44: Malformed conditional (0 || (!${UNDEF} && ${UNDEF})) +make: "cond-op-and.mk" line 36: Variable "UNDEF" is undefined +make: "cond-op-and.mk" line 41: Variable "UNDEF" is undefined +make: "cond-op-and.mk" line 44: Variable "UNDEF" is undefined make: "cond-op-and.mk" line 60: Unknown operator '&' make: "cond-op-and.mk" line 66: Unknown operator '&' make: "cond-op-and.mk" line 72: Unknown operator '&' make: "cond-op-and.mk" line 78: Unknown operator '&' make: "cond-op-and.mk" line 87: Unknown operator '&' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-op-and.mk b/contrib/bmake/unit-tests/cond-op-and.mk index b49fb079b3cf..5d7f8a06800a 100644 --- a/contrib/bmake/unit-tests/cond-op-and.mk +++ b/contrib/bmake/unit-tests/cond-op-and.mk @@ -1,99 +1,99 @@ -# $NetBSD: cond-op-and.mk,v 1.10 2024/07/06 21:21:10 rillig Exp $ +# $NetBSD: cond-op-and.mk,v 1.12 2025/01/11 21:21:33 rillig Exp $ # # Tests for the && operator in .if conditions. .if 0 && 0 . error .endif .if 1 && 0 . error .endif .if 0 && 1 . error .endif .if !(1 && 1) . error .endif # The right-hand side is not evaluated since the left-hand side is already # false. .if 0 && ${UNDEF} .endif # When an outer condition makes the inner '&&' condition irrelevant, neither # of its operands is evaluated. .if 1 || (${UNDEF} && ${UNDEF}) .endif # Test combinations of outer '||' with inner '&&', to ensure that the operands # of the inner '&&' are only evaluated if necessary. DEF= defined -# expect+1: Malformed conditional (0 || (${DEF} && ${UNDEF})) +# expect+1: Variable "UNDEF" is undefined .if 0 || (${DEF} && ${UNDEF}) .endif .if 0 || (!${DEF} && ${UNDEF}) .endif -# expect+1: Malformed conditional (0 || (${UNDEF} && ${UNDEF})) +# expect+1: Variable "UNDEF" is undefined .if 0 || (${UNDEF} && ${UNDEF}) .endif -# expect+1: Malformed conditional (0 || (!${UNDEF} && ${UNDEF})) +# expect+1: Variable "UNDEF" is undefined .if 0 || (!${UNDEF} && ${UNDEF}) .endif .if 1 || (${DEF} && ${UNDEF}) .endif .if 1 || (!${DEF} && ${UNDEF}) .endif .if 1 || (${UNDEF} && ${UNDEF}) .endif .if 1 || (!${UNDEF} && ${UNDEF}) .endif # The && operator may be abbreviated as &. This is not widely known though # and is also not documented in the manual page. # expect+1: Unknown operator '&' .if 0 & 0 . error .else . error .endif # expect+1: Unknown operator '&' .if 1 & 0 . error .else . error .endif # expect+1: Unknown operator '&' .if 0 & 1 . error .else . error .endif # expect+1: Unknown operator '&' .if !(1 & 1) . error .else . error .endif # There is no operator '&&&'. The first two '&&' form an operator, the third # '&' forms the next (incomplete) token. # expect+1: Unknown operator '&' .if 0 &&& 0 . error .else . error .endif # The '&&' operator must be preceded by whitespace, otherwise it becomes part # of the preceding bare word. The condition starts with a digit and is thus # parsed as '"1&&" != "" && 1'. .if 1&& && 1 .else . error .endif diff --git a/contrib/bmake/unit-tests/cond-op-not.exp b/contrib/bmake/unit-tests/cond-op-not.exp index 86d8289d9233..747720159906 100644 --- a/contrib/bmake/unit-tests/cond-op-not.exp +++ b/contrib/bmake/unit-tests/cond-op-not.exp @@ -1,9 +1,9 @@ make: "cond-op-not.mk" line 30: Not empty evaluates to true. make: "cond-op-not.mk" line 39: Not space evaluates to false. make: "cond-op-not.mk" line 44: Not 0 evaluates to true. make: "cond-op-not.mk" line 53: Not 1 evaluates to false. make: "cond-op-not.mk" line 60: Not word evaluates to false. -make: "cond-op-not.mk" line 65: Malformed conditional (!) +make: "cond-op-not.mk" line 65: Malformed conditional '!' make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-op-not.mk b/contrib/bmake/unit-tests/cond-op-not.mk index 28f835fa3cbd..4565b60b115d 100644 --- a/contrib/bmake/unit-tests/cond-op-not.mk +++ b/contrib/bmake/unit-tests/cond-op-not.mk @@ -1,72 +1,72 @@ -# $NetBSD: cond-op-not.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-op-not.mk,v 1.9 2024/08/06 18:00:17 rillig Exp $ # # Tests for the ! operator in .if conditions, which negates its argument. # The exclamation mark negates its operand. .if !1 . error .endif # Exclamation marks can be chained. # This doesn't happen in practice though. .if !!!1 . error .endif # The ! binds more tightly than the &&. .if !!0 && 1 . error .endif # The operator '==' binds more tightly than '!'. # This is unusual since most other programming languages define the precedence # to be the other way round. .if !${:Uexpression} == "expression" . error .endif .if !${:U} # expect+1: Not empty evaluates to true. . info Not empty evaluates to true. .else . info Not empty evaluates to false. .endif .if !${:U } . info Not space evaluates to true. .else # expect+1: Not space evaluates to false. . info Not space evaluates to false. .endif .if !${:U0} # expect+1: Not 0 evaluates to true. . info Not 0 evaluates to true. .else . info Not 0 evaluates to false. .endif .if !${:U1} . info Not 1 evaluates to true. .else # expect+1: Not 1 evaluates to false. . info Not 1 evaluates to false. .endif .if !${:Uword} . info Not word evaluates to true. .else # expect+1: Not word evaluates to false. . info Not word evaluates to false. .endif # A single exclamation mark is a parse error. -# expect+1: Malformed conditional (!) +# expect+1: Malformed conditional '!' .if ! . error .else . error .endif all: @:; diff --git a/contrib/bmake/unit-tests/cond-op-or.exp b/contrib/bmake/unit-tests/cond-op-or.exp index dddd29d5bca0..f8b6de761755 100644 --- a/contrib/bmake/unit-tests/cond-op-or.exp +++ b/contrib/bmake/unit-tests/cond-op-or.exp @@ -1,11 +1,11 @@ -make: "cond-op-or.mk" line 36: Malformed conditional (1 && (!${DEF} || ${UNDEF})) -make: "cond-op-or.mk" line 41: Malformed conditional (1 && (!${UNDEF} || ${UNDEF})) -make: "cond-op-or.mk" line 44: Malformed conditional (1 && (${UNDEF} || ${UNDEF})) +make: "cond-op-or.mk" line 36: Variable "UNDEF" is undefined +make: "cond-op-or.mk" line 41: Variable "UNDEF" is undefined +make: "cond-op-or.mk" line 44: Variable "UNDEF" is undefined make: "cond-op-or.mk" line 60: Unknown operator '|' make: "cond-op-or.mk" line 66: Unknown operator '|' make: "cond-op-or.mk" line 72: Unknown operator '|' make: "cond-op-or.mk" line 78: Unknown operator '|' make: "cond-op-or.mk" line 87: Unknown operator '|' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-op-or.mk b/contrib/bmake/unit-tests/cond-op-or.mk index b01fcaf04a28..381516499093 100644 --- a/contrib/bmake/unit-tests/cond-op-or.mk +++ b/contrib/bmake/unit-tests/cond-op-or.mk @@ -1,99 +1,99 @@ -# $NetBSD: cond-op-or.mk,v 1.12 2024/07/06 21:21:10 rillig Exp $ +# $NetBSD: cond-op-or.mk,v 1.14 2025/01/11 21:21:33 rillig Exp $ # # Tests for the || operator in .if conditions. .if 0 || 0 . error .endif .if !(1 || 0) . error .endif .if !(0 || 1) . error .endif .if !(1 || 1) . error .endif # The right-hand side is not evaluated since the left-hand side is already # true. .if 1 || ${UNDEF} .endif # When an outer condition makes the inner '||' condition irrelevant, neither # of its operands is evaluated. .if 0 && (!defined(UNDEF) || ${UNDEF}) .endif # Test combinations of outer '&&' with inner '||', to ensure that the operands # of the inner '||' are only evaluated if necessary. DEF= defined -# expect+1: Malformed conditional (1 && (!${DEF} || ${UNDEF})) +# expect+1: Variable "UNDEF" is undefined .if 1 && (!${DEF} || ${UNDEF}) .endif .if 1 && (${DEF} || ${UNDEF}) .endif -# expect+1: Malformed conditional (1 && (!${UNDEF} || ${UNDEF})) +# expect+1: Variable "UNDEF" is undefined .if 1 && (!${UNDEF} || ${UNDEF}) .endif -# expect+1: Malformed conditional (1 && (${UNDEF} || ${UNDEF})) +# expect+1: Variable "UNDEF" is undefined .if 1 && (${UNDEF} || ${UNDEF}) .endif .if 0 && (!${DEF} || ${UNDEF}) .endif .if 0 && (${DEF} || ${UNDEF}) .endif .if 0 && (!${UNDEF} || ${UNDEF}) .endif .if 0 && (${UNDEF} || ${UNDEF}) .endif # The || operator may be abbreviated as |. This is not widely known though # and is also not documented in the manual page. # expect+1: Unknown operator '|' .if 0 | 0 . error .else . error .endif # expect+1: Unknown operator '|' .if !(1 | 0) . error .else . error .endif # expect+1: Unknown operator '|' .if !(0 | 1) . error .else . error .endif # expect+1: Unknown operator '|' .if !(1 | 1) . error .else . error .endif # There is no operator '|||'. The first two '||' form an operator, the third # '|' forms the next (incomplete) token. # expect+1: Unknown operator '|' .if 0 ||| 0 . error .else . error .endif # The '||' operator must be preceded by whitespace, otherwise it becomes part # of the preceding bare word. The condition starts with a digit and is thus # parsed as '"0||" != "" || 0'. .if 0|| || 0 .else . error .endif diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.exp b/contrib/bmake/unit-tests/cond-op-parentheses.exp index f14e62b6d4e3..c11c9eb1be1d 100644 --- a/contrib/bmake/unit-tests/cond-op-parentheses.exp +++ b/contrib/bmake/unit-tests/cond-op-parentheses.exp @@ -1,7 +1,7 @@ make: "cond-op-parentheses.mk" line 22: Comparison with '>' requires both operands '3' and '(2' to be numeric -make: "cond-op-parentheses.mk" line 25: Malformed conditional ((3) > 2) -make: "cond-op-parentheses.mk" line 44: Malformed conditional (() -make: "cond-op-parentheses.mk" line 58: Malformed conditional ()) +make: "cond-op-parentheses.mk" line 25: Malformed conditional '(3) > 2' +make: "cond-op-parentheses.mk" line 44: Malformed conditional '(' +make: "cond-op-parentheses.mk" line 58: Malformed conditional ')' make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-op-parentheses.mk b/contrib/bmake/unit-tests/cond-op-parentheses.mk index b6c9bd3c0e9d..17d5d767cd41 100644 --- a/contrib/bmake/unit-tests/cond-op-parentheses.mk +++ b/contrib/bmake/unit-tests/cond-op-parentheses.mk @@ -1,65 +1,65 @@ -# $NetBSD: cond-op-parentheses.mk,v 1.7 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-op-parentheses.mk,v 1.8 2024/08/06 18:00:17 rillig Exp $ # # Tests for parentheses in .if conditions, which group expressions to override # the precedence of the operators '!', '&&' and '||'. Parentheses cannot be # used to form arithmetic expressions such as '(3+4)' though. # Contrary to the C family of programming languages, the outermost condition # does not have to be enclosed in parentheses. .if defined(VAR) . error .elif !1 . error .endif # Parentheses cannot enclose numbers as there is no need for it. Make does # not implement any arithmetic functions in its condition parser. If # absolutely necessary, use expr(1). # # XXX: It's inconsistent that the right operand has unbalanced parentheses. # # expect+1: Comparison with '>' requires both operands '3' and '(2' to be numeric .if 3 > (2) .endif -# expect+1: Malformed conditional ((3) > 2) +# expect+1: Malformed conditional '(3) > 2' .if (3) > 2 .endif # Test for deeply nested conditions. .if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ 1 \ )))))))))))))))))))))))))))))))))))))))))))))))))))))))) \ )))))))))))))))))))))))))))))))))))))))))))))))))))))))) # Parentheses can be nested at least to depth 112. There is nothing special # about this number though, much higher numbers work as well, at least on # NetBSD. The actual limit depends on the allowed call stack depth for C code # of the platform. Anyway, 112 should be enough for all practical purposes. .else . error .endif # An unbalanced opening parenthesis is a parse error. -# expect+1: Malformed conditional (() +# expect+1: Malformed conditional '(' .if ( . error .else . error .endif # An unbalanced closing parenthesis is a parse error. # # Before cond.c 1.237 from 2021-01-19, CondParser_Term returned TOK_RPAREN # even though the documentation of that function promised to only ever return # TOK_TRUE, TOK_FALSE or TOK_ERROR. In cond.c 1.241, the return type of that # function was changed to a properly restricted enum type, to prevent this bug # from occurring again. -# expect+1: Malformed conditional ()) +# expect+1: Malformed conditional ')' .if ) . error .else . error .endif all: @:; diff --git a/contrib/bmake/unit-tests/cond-op.exp b/contrib/bmake/unit-tests/cond-op.exp index 1ce5436f023a..3747557789a7 100644 --- a/contrib/bmake/unit-tests/cond-op.exp +++ b/contrib/bmake/unit-tests/cond-op.exp @@ -1,21 +1,21 @@ -make: "cond-op.mk" line 51: Malformed conditional ("!word" == !word) -make: "cond-op.mk" line 72: Malformed conditional (0 ${ERR::=evaluated}) +make: "cond-op.mk" line 51: Malformed conditional '"!word" == !word' +make: "cond-op.mk" line 72: Malformed conditional '0 ${ERR::=evaluated}' make: "cond-op.mk" line 77: A misplaced expression after 0 is not evaluated. -make: "cond-op.mk" line 82: Malformed conditional (1 ${ERR::=evaluated}) +make: "cond-op.mk" line 82: Malformed conditional '1 ${ERR::=evaluated}' make: "cond-op.mk" line 87: A misplaced expression after 1 is not evaluated. make: "cond-op.mk" line 93: A B C => (A || B) && C A || B && C A || (B && C) make: "cond-op.mk" line 108: 0 0 0 => 0 0 0 make: "cond-op.mk" line 108: 0 0 1 => 0 0 0 make: "cond-op.mk" line 108: 0 1 0 => 0 0 0 make: "cond-op.mk" line 108: 0 1 1 => 1 1 1 make: "cond-op.mk" line 108: 1 0 0 => 0 1 1 make: "cond-op.mk" line 108: 1 0 1 => 1 1 1 make: "cond-op.mk" line 108: 1 1 0 => 0 1 1 make: "cond-op.mk" line 108: 1 1 1 => 1 1 1 -make: "cond-op.mk" line 120: Malformed conditional (1 &&) -make: "cond-op.mk" line 129: Malformed conditional (0 &&) -make: "cond-op.mk" line 138: Malformed conditional (1 ||) -make: "cond-op.mk" line 148: Malformed conditional (0 ||) +make: "cond-op.mk" line 120: Malformed conditional '1 &&' +make: "cond-op.mk" line 129: Malformed conditional '0 &&' +make: "cond-op.mk" line 138: Malformed conditional '1 ||' +make: "cond-op.mk" line 148: Malformed conditional '0 ||' make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-op.mk b/contrib/bmake/unit-tests/cond-op.mk index 6371e4db0594..6493d887c806 100644 --- a/contrib/bmake/unit-tests/cond-op.mk +++ b/contrib/bmake/unit-tests/cond-op.mk @@ -1,155 +1,155 @@ -# $NetBSD: cond-op.mk,v 1.16 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-op.mk,v 1.17 2024/08/06 18:00:17 rillig Exp $ # # Tests for operators like &&, ||, ! in .if conditions. # # See also: # cond-op-and.mk # cond-op-not.mk # cond-op-or.mk # cond-op-parentheses.mk # In make, && binds more tightly than ||, like in C. # If make had the same precedence for both && and ||, like in the shell, # the result would be different. # If || were to bind more tightly than &&, the result would be different # as well. .if !(1 || 1 && 0) . error .endif # If make were to interpret the && and || operators like the shell, the # previous condition would be interpreted as: .if (1 || 1) && 0 . error .endif # The precedence of the ! operator is different from C though. It has a # lower precedence than the comparison operators. Negating a condition # does not need parentheses. # # This kind of condition looks so unfamiliar that it doesn't occur in # practice. .if !"word" == "word" . error .endif # This is how the above condition is actually interpreted. .if !("word" == "word") . error .endif # TODO: Demonstrate that the precedence of the ! and == operators actually # makes a difference. There is a simple example for sure, I just cannot # wrap my head around it right now. See the truth table generator below # for an example that doesn't require much thought. # This condition is malformed because the '!' on the right-hand side must not # appear unquoted. If any, it must be enclosed in quotes. # In any case, it is not interpreted as a negation of an unquoted string. # See CondParser_String. -# expect+1: Malformed conditional ("!word" == !word) +# expect+1: Malformed conditional '"!word" == !word' .if "!word" == !word . error .endif # Surprisingly, the ampersand and pipe are allowed in bare strings. # That's another opportunity for writing confusing code. # See CondParser_String, which only has '!' in the list of stop characters. .if "a&&b||c" != a&&b||c . error .endif # In the following malformed conditions, as soon as the parser sees the '$' # after the '0' or the '1', it knows that the condition will be malformed. # Therefore there is no point in evaluating the misplaced expression. # # Before cond.c 1.286 from 2021-12-10, the extra expression was evaluated # nevertheless, since CondParser_Or and CondParser_And asked for the expanded # next token, even though in this position of the condition, only comparison # operators, TOK_AND, TOK_OR or TOK_RPAREN are allowed. .undef ERR -# expect+1: Malformed conditional (0 ${ERR::=evaluated}) +# expect+1: Malformed conditional '0 ${ERR::=evaluated}' .if 0 ${ERR::=evaluated} . error .endif .if ${ERR:Uundefined} == undefined # expect+1: A misplaced expression after 0 is not evaluated. . info A misplaced expression after 0 is not evaluated. .endif .undef ERR -# expect+1: Malformed conditional (1 ${ERR::=evaluated}) +# expect+1: Malformed conditional '1 ${ERR::=evaluated}' .if 1 ${ERR::=evaluated} . error .endif .if ${ERR:Uundefined} == undefined # expect+1: A misplaced expression after 1 is not evaluated. . info A misplaced expression after 1 is not evaluated. .endif # Demonstration that '&&' has higher precedence than '||'. # expect+1: A B C => (A || B) && C A || B && C A || (B && C) .info A B C => (A || B) && C A || B && C A || (B && C) .for a in 0 1 . for b in 0 1 . for c in 0 1 . for r1 in ${ ($a || $b) && $c :?1:0} . for r2 in ${ $a || $b && $c :?1:0} . for r3 in ${ $a || ($b && $c) :?1:0} # expect+8: 0 0 0 => 0 0 0 # expect+7: 0 0 1 => 0 0 0 # expect+6: 0 1 0 => 0 0 0 # expect+5: 0 1 1 => 1 1 1 # expect+4: 1 0 0 => 0 1 1 # expect+3: 1 0 1 => 1 1 1 # expect+2: 1 1 0 => 0 1 1 # expect+1: 1 1 1 => 1 1 1 . info $a $b $c => ${r1} ${r2} ${r3} . endfor . endfor . endfor . endfor . endfor .endfor # This condition is obviously malformed. It is properly detected and also # was properly detected before 2021-01-19, but only because the left hand # side of the '&&' evaluated to true. -# expect+1: Malformed conditional (1 &&) +# expect+1: Malformed conditional '1 &&' .if 1 && . error .else . error .endif # This obviously malformed condition was not detected as such before cond.c # 1.238 from 2021-01-19. -# expect+1: Malformed conditional (0 &&) +# expect+1: Malformed conditional '0 &&' .if 0 && . error .else . error .endif # This obviously malformed condition was not detected as such before cond.c # 1.238 from 2021-01-19. -# expect+1: Malformed conditional (1 ||) +# expect+1: Malformed conditional '1 ||' .if 1 || . error .else . error .endif # This condition is obviously malformed. It is properly detected and also # was properly detected before 2021-01-19, but only because the left hand # side of the '||' evaluated to false. -# expect+1: Malformed conditional (0 ||) +# expect+1: Malformed conditional '0 ||' .if 0 || . error .else . error .endif all: @:; diff --git a/contrib/bmake/unit-tests/cond-token-number.exp b/contrib/bmake/unit-tests/cond-token-number.exp index aaa4ec7fe184..eb0870521af1 100644 --- a/contrib/bmake/unit-tests/cond-token-number.exp +++ b/contrib/bmake/unit-tests/cond-token-number.exp @@ -1,7 +1,7 @@ -make: "cond-token-number.mk" line 16: Malformed conditional (-0) -make: "cond-token-number.mk" line 27: Malformed conditional (+0) -make: "cond-token-number.mk" line 38: Malformed conditional (!-1) -make: "cond-token-number.mk" line 49: Malformed conditional (!+1) +make: "cond-token-number.mk" line 16: Malformed conditional '-0' +make: "cond-token-number.mk" line 27: Malformed conditional '+0' +make: "cond-token-number.mk" line 38: Malformed conditional '!-1' +make: "cond-token-number.mk" line 49: Malformed conditional '!+1' make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-token-number.mk b/contrib/bmake/unit-tests/cond-token-number.mk index 7e73f8b76f94..329a51d73b2a 100644 --- a/contrib/bmake/unit-tests/cond-token-number.mk +++ b/contrib/bmake/unit-tests/cond-token-number.mk @@ -1,109 +1,109 @@ -# $NetBSD: cond-token-number.mk,v 1.10 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: cond-token-number.mk,v 1.11 2024/08/06 18:00:17 rillig Exp $ # # Tests for number tokens in .if conditions. # # TODO: Add introduction. .if 0 . error .endif # Even though -0 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. -# expect+1: Malformed conditional (-0) +# expect+1: Malformed conditional '-0' .if -0 . error .else . error .endif # Even though +0 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. -# expect+1: Malformed conditional (+0) +# expect+1: Malformed conditional '+0' .if +0 . error .else . error .endif # Even though -1 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. -# expect+1: Malformed conditional (!-1) +# expect+1: Malformed conditional '!-1' .if !-1 . error .else . error .endif # Even though +1 is a number and would be accepted by strtod, it is not # accepted by the condition parser. # # See the ch_isdigit call in CondParser_String. -# expect+1: Malformed conditional (!+1) +# expect+1: Malformed conditional '!+1' .if !+1 . error .else . error .endif # When the number comes from an expression though, it may be signed. # XXX: This is inconsistent. .if ${:U+0} . error .endif # When the number comes from an expression though, it may be signed. # XXX: This is inconsistent. .if !${:U+1} . error .endif # Hexadecimal numbers are accepted. .if 0x0 . error .endif .if 0x1 .else . error .endif # This is not a hexadecimal number, even though it has an x. It is # interpreted as a string instead. In a plain '.if', such a token evaluates # to true if it is non-empty. In other '.if' directives, such a token is # evaluated by either FuncDefined or FuncMake. .if 3x4 .else . error .endif # Make can do radix conversion from hex. HEX= dead .if 0x${HEX} == 57005 .else . error .endif # Very small numbers round to 0. .if 12345e-400 . error .endif .if 12345e-200 .else . error .endif # Very large numbers round up to infinity on IEEE 754 implementations, or to # the largest representable number (VAX); in particular, make does not fall # back to checking whether a variable of that name is defined. .if 12345e400 .else . error .endif all: diff --git a/contrib/bmake/unit-tests/cond-token-plain.exp b/contrib/bmake/unit-tests/cond-token-plain.exp index 8044f3bac826..f3425e7f899d 100644 --- a/contrib/bmake/unit-tests/cond-token-plain.exp +++ b/contrib/bmake/unit-tests/cond-token-plain.exp @@ -1,63 +1,63 @@ CondParser_Eval: ${:Uvalue} != value Comparing "value" != "value" CondParser_Eval: ${:U} != " Comparing "" != "" CondParser_Eval: ${:U#hash} != "#hash" Comparing "#hash" != "#hash" CondParser_Eval: ${:U\\} != "\\ Comparing "\" != "\" CondParser_Eval: ${:U#hash} != #hash Comparing "#hash" != "#hash" CondParser_Eval: 0 # This is treated as a comment, but why? CondParser_Eval: ${0 # comment:?yes:no} != no CondParser_Eval: 0 # comment Comparing "no" != "no" CondParser_Eval: ${1 # comment:?yes:no} != yes CondParser_Eval: 1 # comment Comparing "yes" != "yes" CondParser_Eval: ${UNDEF:Uundefined}!=undefined Comparing "undefined" != "undefined" CondParser_Eval: ${UNDEF:U12345}>12345 Comparing 12345.000000 > 12345.000000 CondParser_Eval: ${UNDEF:U12345}<12345 Comparing 12345.000000 < 12345.000000 CondParser_Eval: (${UNDEF:U0})||0 CondParser_Eval: ${:Uvar}&&name != "var&&name" Comparing "var&&name" != "var&&name" CondParser_Eval: ${:Uvar}||name != "var||name" Comparing "var||name" != "var||name" CondParser_Eval: bare make: "cond-token-plain.mk" line 106: A bare word is treated like defined(...), and the variable 'bare' is not defined. CondParser_Eval: VAR make: "cond-token-plain.mk" line 113: A bare word is treated like defined(...). CondParser_Eval: V${:UA}R make: "cond-token-plain.mk" line 121: ok CondParser_Eval: V${UNDEF}AR make: "cond-token-plain.mk" line 130: Undefined variables in bare words expand to an empty string. CondParser_Eval: 0${:Ux00} make: "cond-token-plain.mk" line 139: Numbers can be composed from literals and expressions. CondParser_Eval: 0${:Ux01} make: "cond-token-plain.mk" line 144: Numbers can be composed from literals and expressions. CondParser_Eval: "" == make: "cond-token-plain.mk" line 151: Missing right-hand side of operator '==' CondParser_Eval: == "" -make: "cond-token-plain.mk" line 160: Malformed conditional (== "") +make: "cond-token-plain.mk" line 160: Malformed conditional '== ""' CondParser_Eval: \\ make: "cond-token-plain.mk" line 176: The variable '\\' is not defined. CondParser_Eval: \\ make: "cond-token-plain.mk" line 182: Now the variable '\\' is defined. CondParser_Eval: "unquoted\"quoted" != unquoted"quoted Comparing "unquoted"quoted" != "unquoted"quoted" CondParser_Eval: $$$$$$$$ != "" -make: "cond-token-plain.mk" line 197: Malformed conditional ($$$$$$$$ != "") +make: "cond-token-plain.mk" line 197: Malformed conditional '$$$$$$$$ != ""' CondParser_Eval: left == right -make: "cond-token-plain.mk" line 206: Malformed conditional (left == right) +make: "cond-token-plain.mk" line 206: Malformed conditional 'left == right' CondParser_Eval: ${0:?:} || left == right CondParser_Eval: 0 -make: "cond-token-plain.mk" line 212: Malformed conditional (${0:?:} || left == right) +make: "cond-token-plain.mk" line 212: Malformed conditional '${0:?:} || left == right' CondParser_Eval: left == right || ${0:?:} -make: "cond-token-plain.mk" line 217: Malformed conditional (left == right || ${0:?:}) -make: "cond-token-plain.mk" line 236: Malformed conditional (VAR.${IF_COUNT::+=1} != "") +make: "cond-token-plain.mk" line 217: Malformed conditional 'left == right || ${0:?:}' +make: "cond-token-plain.mk" line 236: Malformed conditional 'VAR.${IF_COUNT::+=1} != ""' make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-token-plain.mk b/contrib/bmake/unit-tests/cond-token-plain.mk index 79fcc771a855..4509c1feca80 100644 --- a/contrib/bmake/unit-tests/cond-token-plain.mk +++ b/contrib/bmake/unit-tests/cond-token-plain.mk @@ -1,267 +1,267 @@ -# $NetBSD: cond-token-plain.mk,v 1.19 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: cond-token-plain.mk,v 1.20 2024/08/06 18:00:17 rillig Exp $ # # Tests for plain tokens (that is, string literals without quotes) # in .if conditions. These are also called bare words. .MAKEFLAGS: -dc # The word 'value' after the '!=' is a bare word. .if ${:Uvalue} != value . error .endif # Using a '#' in a string literal in a condition leads to a malformed # condition since comment parsing is done in an early phase and removes the # '#' and everything after it long before the condition parser gets to see it. # # XXX: The error message is missing for this malformed condition. # The right-hand side of the comparison is just a '"', before unescaping. .if ${:U} != "#hash" . error .endif # To get a '#' into a condition, it has to be escaped using a backslash. # This prevents the comment parser from removing it, and in turn, it becomes # visible to CondParser_String. .if ${:U\#hash} != "\#hash" . error .endif # Since 2002-12-30, and still as of 2020-09-11, CondParser_Token handles # the '#' specially, even though at this point, there should be no need for # comment handling anymore. The comments are supposed to be stripped off # in a very early parsing phase. # # See https://gnats.netbsd.org/19596 for example makefiles demonstrating the # original problems. At that time, the parser didn't recognize the comment in # the line '.else # comment3'. This workaround is not needed anymore since # comments are stripped in an earlier phase. See "case '#'" in # CondParser_Token. # # XXX: Missing error message for the malformed condition. The right-hand # side before unescaping is double-quotes, backslash, backslash. .if ${:U\\} != "\\#hash" . error .endif # The right-hand side of a comparison is not parsed as a token, therefore # the code from CondParser_Token does not apply to it. # TODO: Explain the consequences. # TODO: Does this mean that more syntactic variants are allowed here? .if ${:U\#hash} != \#hash . error .endif # XXX: What is the purpose of treating an escaped '#' in the following # condition as a comment? And why only at the beginning of a token, # just as in the shell? .if 0 \# This is treated as a comment, but why? . error .endif # Ah, ok, this can be used to add an end-of-condition comment. But does # anybody really use this? This is neither documented nor obvious since # the '#' is escaped. It's much clearer to write a comment in the line # above the condition. .if ${0 \# comment:?yes:no} != no . error .endif .if ${1 \# comment:?yes:no} != yes . error .endif # Usually there is whitespace around the comparison operator, but this is # not required. .if ${UNDEF:Uundefined}!=undefined . error .endif .if ${UNDEF:U12345}>12345 . error .endif .if ${UNDEF:U12345}<12345 . error .endif .if (${UNDEF:U0})||0 . error .endif # Only the comparison operator terminates the comparison operand, and it's # a coincidence that the '!' is both used in the '!=' comparison operator # as well as for negating a comparison result. # # The characters '&' and '|' are part of the comparison operand. .if ${:Uvar}&&name != "var&&name" . error .endif .if ${:Uvar}||name != "var||name" . error .endif # A bare word may occur alone in a condition, without any comparison # operator. It is interpreted as the function call 'defined(bare)'. .if bare . error .else # expect+1: A bare word is treated like defined(...), and the variable 'bare' is not defined. . info A bare word is treated like defined(...), and the variable $\ 'bare' is not defined. .endif VAR= defined .if VAR # expect+1: A bare word is treated like defined(...). . info A bare word is treated like defined(...). .else . error .endif # Bare words may be intermixed with expressions. .if V${:UA}R # expect+1: ok . info ok .else . error .endif # In bare words, even undefined variables are allowed. Without the bare # words, undefined variables are not allowed. That feels inconsistent. .if V${UNDEF}AR # expect+1: Undefined variables in bare words expand to an empty string. . info Undefined variables in bare words expand to an empty string. .else . error .endif .if 0${:Ux00} . error .else # expect+1: Numbers can be composed from literals and expressions. . info Numbers can be composed from literals and expressions. .endif .if 0${:Ux01} # expect+1: Numbers can be composed from literals and expressions. . info Numbers can be composed from literals and expressions. .else . error .endif # If the right-hand side is missing, it's a parse error. # expect+1: Missing right-hand side of operator '==' .if "" == . error .else . error .endif # If the left-hand side is missing, it's a parse error as well, but without # a specific error message. -# expect+1: Malformed conditional (== "") +# expect+1: Malformed conditional '== ""' .if == "" . error .else . error .endif # The '\\' is not a line continuation. Neither is it an unquoted string # literal. Instead, it is parsed as a bare word (ParseWord), # and in that context, the backslash is just an ordinary character. The # function argument thus stays '\\' (2 backslashes). This string is passed # to FuncDefined, and since there is no variable named '\\', the condition # evaluates to false. .if \\ . error .else # expect+1: The variable '\\' is not defined. . info The variable '\\' is not defined. .endif ${:U\\\\}= backslash .if \\ # expect+1: Now the variable '\\' is defined. . info Now the variable '\\' is defined. .else . error .endif # Anything that doesn't start with a double quote is considered a "bare word". # Strangely, a bare word may contain double quotes inside. Nobody should ever # depend on this since it may well be unintended. See CondParser_String. .if "unquoted\"quoted" != unquoted"quoted . error .endif # FIXME: In CondParser_String, Var_Parse returns var_Error without a # corresponding error message. -# expect+1: Malformed conditional ($$$$$$$$ != "") +# expect+1: Malformed conditional '$$$$$$$$ != ""' .if $$$$$$$$ != "" . error .else . error .endif # In a condition in an .if directive, the left-hand side must not be an # unquoted string literal. -# expect+1: Malformed conditional (left == right) +# expect+1: Malformed conditional 'left == right' .if left == right .endif # Before cond.c 1.276 from 2021-09-21, an expression containing the # modifier ':?:' allowed unquoted string literals for the rest of the # condition. This was an unintended implementation mistake. -# expect+1: Malformed conditional (${0:?:} || left == right) +# expect+1: Malformed conditional '${0:?:} || left == right' .if ${0:?:} || left == right .endif # This affected only the comparisons after the expression, so the following # was still a syntax error. -# expect+1: Malformed conditional (left == right || ${0:?:}) +# expect+1: Malformed conditional 'left == right || ${0:?:}' .if left == right || ${0:?:} .endif # See cond-token-string.mk for similar tests where the condition is enclosed # in "quotes". .MAKEFLAGS: -d0 # As of cond.c 1.320 from 2021-12-30, the code in CondParser_ComparisonOrLeaf # looks suspicious of evaluating the expression twice: first for parsing a # bare word and second for parsing the left-hand side of a comparison. # # In '.if' directives, the left-hand side of a comparison must not be a bare # word though, and this keeps CondParser_Leaf from evaluating the expression # for the second time. The right-hand side of a comparison may be a bare # word, but that side has no risk of being parsed more than once. # -# expect+1: Malformed conditional (VAR.${IF_COUNT::+=1} != "") +# expect+1: Malformed conditional 'VAR.${IF_COUNT::+=1} != ""' .if VAR.${IF_COUNT::+=1} != "" . error .else . error .endif .if ${IF_COUNT} != "1" . error .endif # A different situation is when CondParser.leftUnquotedOK is true. This # situation arises in expressions of the form ${cond:?yes:no}. As of # 2021-12-30, the condition in such an expression is evaluated before parsing # the condition, see varmod-ifelse.mk. To pass an expression to the # condition parser, it needs to be escaped. This rarely happens in practice, # in most cases the conditions are simple enough that it doesn't matter # whether the condition is first evaluated and then parsed, or vice versa. # A half-baked attempt at hiding this implementation detail is # CondParser.leftUnquotedOK, but that is a rather leaky abstraction. #.MAKEFLAGS: -dcv COND= VAR.$${MOD_COUNT::+=1} .if ${${COND} == "VAR.":?yes:no} != "yes" . error .endif # The value "1 1" demonstrates that the expression ${MOD_COUNT::+=1} was # evaluated twice. In practice, expressions that occur in conditions do not # have side effects, making this problem rather academic, but it is there. .if ${MOD_COUNT} != "1 1" . error .endif #.MAKEFLAGS: -d0 diff --git a/contrib/bmake/unit-tests/cond-token-string.exp b/contrib/bmake/unit-tests/cond-token-string.exp index f970a21bf69f..e48244a457e9 100644 --- a/contrib/bmake/unit-tests/cond-token-string.exp +++ b/contrib/bmake/unit-tests/cond-token-string.exp @@ -1,21 +1,21 @@ -make: "cond-token-string.mk" line 15: while evaluating "${:Uvalue:Z}"" with value "value": Unknown modifier "Z" -make: "cond-token-string.mk" line 15: Malformed conditional ("" != "${:Uvalue:Z}") -make: "cond-token-string.mk" line 25: xvalue is not defined. -make: "cond-token-string.mk" line 32: Malformed conditional (x${:Uvalue} == "") -make: "cond-token-string.mk" line 42: Expected. +make: "cond-token-string.mk" line 14: Unknown modifier "Z" + while evaluating "${:Uvalue:Z}"" with value "value" +make: "cond-token-string.mk" line 24: xvalue is not defined. +make: "cond-token-string.mk" line 31: Malformed conditional 'x${:Uvalue} == ""' +make: "cond-token-string.mk" line 41: Expected. CondParser_Eval: "UNDEF" -make: "cond-token-string.mk" line 52: The string literal "UNDEF" is not empty. +make: "cond-token-string.mk" line 51: The string literal "UNDEF" is not empty. CondParser_Eval: " " -make: "cond-token-string.mk" line 61: The string literal " " is not empty, even though it consists of whitespace only. +make: "cond-token-string.mk" line 60: The string literal " " is not empty, even though it consists of whitespace only. CondParser_Eval: "${UNDEF}" -make: "cond-token-string.mk" line 71: An undefined variable in quotes expands to an empty string, which then evaluates to false. +make: "cond-token-string.mk" line 70: An undefined variable in quotes expands to an empty string, which then evaluates to false. CondParser_Eval: "${:Uvalue}" -make: "cond-token-string.mk" line 77: A nonempty expression evaluates to true. +make: "cond-token-string.mk" line 76: A nonempty expression evaluates to true. CondParser_Eval: "${:U}" -make: "cond-token-string.mk" line 86: An empty variable evaluates to false. +make: "cond-token-string.mk" line 85: An empty variable evaluates to false. CondParser_Eval: ("${VAR}") CondParser_Eval: "quoted" == quoted Comparing "quoted" == "quoted" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-token-string.mk b/contrib/bmake/unit-tests/cond-token-string.mk index d24645748233..45e9cf8a8469 100644 --- a/contrib/bmake/unit-tests/cond-token-string.mk +++ b/contrib/bmake/unit-tests/cond-token-string.mk @@ -1,109 +1,108 @@ -# $NetBSD: cond-token-string.mk,v 1.11 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: cond-token-string.mk,v 1.15 2025/01/11 20:54:45 rillig Exp $ # # Tests for quoted string literals in .if conditions. # # See also: # cond-token-plain.mk # Covers string literals without quotes (called "bare words"). # TODO: Implementation # Cover the code in CondParser_String that frees the memory after parsing # an expression based on an undefined variable. -# expect+2: Malformed conditional ("" != "${:Uvalue:Z}") -# expect+1: while evaluating "${:Uvalue:Z}"" with value "value": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" .if "" != "${:Uvalue:Z}" . error .else . error .endif .if x${:Uvalue} . error .else # expect+1: xvalue is not defined. . info xvalue is not defined. .endif # The 'x' produces a "Malformed conditional" since the left-hand side of a # comparison in an .if directive must be either an expression, a # quoted string literal or a number that starts with a digit. -# expect+1: Malformed conditional (x${:Uvalue} == "") +# expect+1: Malformed conditional 'x${:Uvalue} == ""' .if x${:Uvalue} == "" . error .else . error .endif # In plain words, a '\' can be used to escape any character, just as in # double-quoted string literals. See CondParser_String. .if \x${:Uvalue} == "xvalue" # expect+1: Expected. . info Expected. .else . error .endif .MAKEFLAGS: -dc # A string in quotes is checked whether it is not empty. .if "UNDEF" # expect+1: The string literal "UNDEF" is not empty. . info The string literal "UNDEF" is not empty. .else . error .endif # A space is not empty as well. # This differs from many other places where whitespace is trimmed. .if " " # expect+1: The string literal " " is not empty, even though it consists of whitespace only. . info The string literal " " is not empty, even though it consists of $\ whitespace only. .else . error .endif .if "${UNDEF}" . error .else # expect+1: An undefined variable in quotes expands to an empty string, which then evaluates to false. . info An undefined variable in quotes expands to an empty string, which $\ then evaluates to false. .endif .if "${:Uvalue}" # expect+1: A nonempty expression evaluates to true. . info A nonempty expression evaluates to true. .else . error .endif .if "${:U}" . error .else # expect+1: An empty variable evaluates to false. . info An empty variable evaluates to false. .endif # A non-empty string evaluates to true, no matter if it's a literal string or # if it contains expressions. The parentheses are not necessary for # the parser, in this case their only purpose is to make the code harder to # read for humans. VAR= value .if ("${VAR}") .else . error .endif # In the conditions in .if directives, the left-hand side of a comparison must # be enclosed in quotes. The right-hand side does not need to be enclosed in # quotes. .if "quoted" == quoted .else . error .endif .MAKEFLAGS: -d0 all: .PHONY diff --git a/contrib/bmake/unit-tests/cond-token-var.exp b/contrib/bmake/unit-tests/cond-token-var.exp index d84ca56e981b..a7e5c8ab5531 100644 --- a/contrib/bmake/unit-tests/cond-token-var.exp +++ b/contrib/bmake/unit-tests/cond-token-var.exp @@ -1,7 +1,21 @@ -make: "cond-token-var.mk" line 21: ok -make: "cond-token-var.mk" line 28: Malformed conditional (${UNDEF} == ${DEF}) -make: "cond-token-var.mk" line 34: Malformed conditional (${DEF} == ${UNDEF}) -make: "cond-token-var.mk" line 44: Malformed conditional (${UNDEF}) +make: "cond-token-var.mk" line 23: ok +make: "cond-token-var.mk" line 30: Variable "UNDEF" is undefined +make: "cond-token-var.mk" line 36: Variable "UNDEF" is undefined +make: "cond-token-var.mk" line 46: Variable "UNDEF" is undefined +make: "cond-token-var.mk" line 64: Variable "U" is undefined +make: "cond-token-var.mk" line 69: Variable "U" is undefined +make: "cond-token-var.mk" line 78: Variable "U" is undefined +Var_Parse: ${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3} (eval) +make: "cond-token-var.mk" line 106: Malformed conditional 'x${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3}' +Var_Parse: ${DEF}y == "${UNDEF2}" || 0x${UNDEF3} (eval) +make: "cond-token-var.mk" line 111: Malformed conditional 'x${DEF}y == "${UNDEF2}" || 0x${UNDEF3}' +Var_Parse: ${DEF}y == "${DEF}" || 0x${UNDEF3} (eval) +make: "cond-token-var.mk" line 116: Malformed conditional 'x${DEF}y == "${DEF}" || 0x${UNDEF3}' +Global: VAR.param = value of VAR.param +Var_Parse: ${VAR.param$U} (eval-defined-loud) +Var_Parse: $U} (eval) +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-token-var.mk b/contrib/bmake/unit-tests/cond-token-var.mk index c6471756a3dd..972a6b3c3d4b 100644 --- a/contrib/bmake/unit-tests/cond-token-var.mk +++ b/contrib/bmake/unit-tests/cond-token-var.mk @@ -1,71 +1,128 @@ -# $NetBSD: cond-token-var.mk,v 1.8 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.11 2025/01/11 21:21:33 rillig Exp $ # # Tests for expressions in .if conditions. # # Note the fine distinction between a variable and an expression. # A variable has a name and a value. To access the value, one writes an # expression of the form ${VAR}. This is a simple # expression. Expressions can get more complicated by adding # variable modifiers such as in ${VAR:Mpattern}. # # XXX: Strictly speaking, variable modifiers should be called expression # modifiers instead since they only modify the expression, not the variable. # Well, except for the assignment modifiers, these do indeed change the value # of the variable. +D= defined DEF= defined + # A defined variable may appear on either side of the comparison. .if ${DEF} == ${DEF} # expect+1: ok . info ok .else . error .endif # A variable that appears on the left-hand side must be defined. -# expect+1: Malformed conditional (${UNDEF} == ${DEF}) +# expect+1: Variable "UNDEF" is undefined .if ${UNDEF} == ${DEF} . error .endif # A variable that appears on the right-hand side must be defined. -# expect+1: Malformed conditional (${DEF} == ${UNDEF}) +# expect+1: Variable "UNDEF" is undefined .if ${DEF} == ${UNDEF} . error .endif # A defined variable may appear as an expression of its own. .if ${DEF} .endif # An undefined variable on its own generates a parse error. -# expect+1: Malformed conditional (${UNDEF}) +# expect+1: Variable "UNDEF" is undefined .if ${UNDEF} .endif # The :U modifier turns an undefined expression into a defined expression. # Since the expression is defined now, it doesn't generate any parse error. .if ${UNDEF:U} .endif + +# The same as above, for single-letter variables without braces or +# parentheses. + +# A defined variable may appear on either side of the comparison. +.if $D == $D +.endif + +# A variable on the left-hand side must be defined. +# expect+1: Variable "U" is undefined +.if $U == $D +.endif + +# A variable on the right-hand side must be defined. +# expect+1: Variable "U" is undefined +.if $D == $U +.endif + +# A defined variable may appear as an expression of its own. +.if $D +.endif + +# An undefined variable without a comparison operator generates a parse error. +# expect+1: Variable "U" is undefined +.if $U +.endif + + # If the value of the expression is a number, it is compared against # zero. .if ${:U0} . error .endif .if !${:U1} . error .endif # If the value of the expression is not a number, any non-empty # value evaluates to true, even if there is only whitespace. .if ${:U} . error .endif .if !${:U } . error .endif .if !${:Uanything} . error .endif + +.MAKEFLAGS: -dv +# The left-hand side of a comparison must not be an unquoted word. +# expect+1: Malformed conditional 'x${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3}' +.if x${UNDEF1}y == "${UNDEF2}" || 0x${UNDEF3} +.endif + +# The left-hand side of a comparison must not be an unquoted word. +# expect+1: Malformed conditional 'x${DEF}y == "${UNDEF2}" || 0x${UNDEF3}' +.if x${DEF}y == "${UNDEF2}" || 0x${UNDEF3} +.endif + +# The left-hand side of a comparison must not be an unquoted word. +# expect+1: Malformed conditional 'x${DEF}y == "${DEF}" || 0x${UNDEF3}' +.if x${DEF}y == "${DEF}" || 0x${UNDEF3} +.endif + +# An expression in a condition must not be based on an undefined variable, +# but undefined variables may occur in the variable name or in modifiers. +# +# expect: Var_Parse: ${VAR.param$U} (eval-defined-loud) +# expect: Var_Parse: $U} (eval) +VAR.param= value of VAR.param +.if ${VAR.param$U} +.endif + +.MAKEFLAGS: -d0 diff --git a/contrib/bmake/unit-tests/cond-undef-lint.exp b/contrib/bmake/unit-tests/cond-undef-lint.exp index efab46113f67..ce619293a35b 100755 --- a/contrib/bmake/unit-tests/cond-undef-lint.exp +++ b/contrib/bmake/unit-tests/cond-undef-lint.exp @@ -1,10 +1,6 @@ -make: "cond-undef-lint.mk" line 25: Variable "UNDEF" is undefined -make: "cond-undef-lint.mk" line 25: Malformed conditional (${UNDEF}) -make: "cond-undef-lint.mk" line 43: Variable "UNDEF" is undefined -make: "cond-undef-lint.mk" line 43: Variable "VAR." is undefined -make: "cond-undef-lint.mk" line 43: Malformed conditional (${VAR.${UNDEF}}) -make: "cond-undef-lint.mk" line 56: Variable "VAR.defined" is undefined -make: "cond-undef-lint.mk" line 56: Malformed conditional (${VAR.${DEF}}) +make: "cond-undef-lint.mk" line 24: Variable "UNDEF" is undefined +make: "cond-undef-lint.mk" line 35: Variable "VAR." is undefined +make: "cond-undef-lint.mk" line 45: Variable "VAR.defined" is undefined make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/cond-undef-lint.mk b/contrib/bmake/unit-tests/cond-undef-lint.mk index 6fd353dc60e2..1b4d19636c41 100755 --- a/contrib/bmake/unit-tests/cond-undef-lint.mk +++ b/contrib/bmake/unit-tests/cond-undef-lint.mk @@ -1,80 +1,69 @@ -# $NetBSD: cond-undef-lint.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-undef-lint.mk,v 1.8 2025/01/11 21:21:33 rillig Exp $ # # Tests for defined and undefined variables in .if conditions, in lint mode. # # As of 2020-09-14, lint mode contains experimental code for printing # accurate error messages in case of undefined variables, instead of the # wrong "Malformed condition". # # See also: # opt-debug-lint.mk .MAKEFLAGS: -dL # DEF is defined, UNDEF is not. DEF= defined # An expression based on a defined variable is fine. .if !${DEF} . error .endif # Since the condition fails to evaluate, neither of the branches is taken. -# expect+2: Malformed conditional (${UNDEF}) # expect+1: Variable "UNDEF" is undefined .if ${UNDEF} . error .else . error .endif # The variable name depends on the undefined variable, which is probably a # mistake. The variable UNDEF, as used here, can be easily turned into # an expression that is always defined, using the :U modifier. # -# The outer expression does not generate an error message since there was -# already an error evaluating this variable's name. -# -# TODO: Suppress the error message "Variable VAR. is undefined". That part -# of the expression must not be evaluated at all. -# expect+3: Variable "UNDEF" is undefined -# expect+2: Variable "VAR." is undefined -# expect+1: Malformed conditional (${VAR.${UNDEF}}) +# expect+1: Variable "VAR." is undefined .if ${VAR.${UNDEF}} . error .else . error .endif -# The variable VAR.defined is not defined and thus generates an error message. +# The inner variable DEF is defined, but the resulting name VAR.defined +# refers to an undefined variable, thus an error message. # -# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least -# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect -# expression, as in the next example? -# expect+2: Variable "VAR.defined" is undefined -# expect+1: Malformed conditional (${VAR.${DEF}}) +# expect+1: Variable "VAR.defined" is undefined .if ${VAR.${DEF}} . error .else . error .endif # Variables that are referenced indirectly may be undefined in a condition. # # A practical example for this is CFLAGS, which consists of CWARNS, COPTS # and a few others. Just because these nested variables are not defined, # this does not make the condition invalid. # # The crucial point is that at the point where the variable appears in the # condition, there is no way to influence the definedness of the nested # variables. In particular, there is no modifier that would turn undefined # nested variables into empty strings, as an equivalent to the :U modifier. INDIRECT= ${NESTED_UNDEF} ${NESTED_DEF} NESTED_DEF= nested-defined # Since NESTED_UNDEF is not controllable at this point, it must not generate # an error message, and it doesn't do so, since 2020-09-14. .if !${INDIRECT} . error .endif diff --git a/contrib/bmake/unit-tests/dep-var.exp b/contrib/bmake/unit-tests/dep-var.exp index 194c723fc1a8..4a570206b300 100755 --- a/contrib/bmake/unit-tests/dep-var.exp +++ b/contrib/bmake/unit-tests/dep-var.exp @@ -1,30 +1,30 @@ -Var_Parse: ${UNDEF1} (eval-defined) +Var_Parse: ${UNDEF1} (eval) Global: .ALLTARGETS = all Global: .ALLTARGETS = all ${DEF2} Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 Global: INDIRECT_1 = 2-$${INDIRECT_2}-2 Global: INDIRECT_2 = 3-$${INDIRECT_3}-3 Global: INDIRECT_3 = indirect Global: UNDEF1 = undef1 Global: DEF2 = def2 Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) -Var_Parse: ${:U\$)}: (eval-defined) -Evaluating modifier ${:U...} on value "" (eval-defined, undefined) -Result of ${:U\$)} is "$)" (eval-defined, defined) +Var_Parse: ${:U\$)}: (eval) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:U\$)} is "$)" (eval, defined) Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b Var_Parse: $INDIRECT_2-2-1 $): (parse) Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1 Var_Parse: $): (parse) Global: .ALLTARGETS = all ${DEF2} a-${DEF2}-b ${UNDEF3} 1-${INDIRECT_1}-1 $$) undef1 def2 a-def2-b 1-2-$INDIRECT_2-2-1 $) Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 make: Malformed expression at "$)" def2 a-def2-b 1-2-NDIRECT_2-2-1 ) exit status 2 diff --git a/contrib/bmake/unit-tests/dep-var.mk b/contrib/bmake/unit-tests/dep-var.mk index fa720c3af610..ed8549583b82 100755 --- a/contrib/bmake/unit-tests/dep-var.mk +++ b/contrib/bmake/unit-tests/dep-var.mk @@ -1,96 +1,86 @@ -# $NetBSD: dep-var.mk,v 1.12 2024/06/02 15:31:26 rillig Exp $ +# $NetBSD: dep-var.mk,v 1.13 2025/01/14 21:23:17 rillig Exp $ # # Tests for variable references in dependency declarations. # # Uh oh, this feels so strange that probably nobody uses it. But it seems to # be the only way to reach the lower half of SuffExpandChildren. .MAKEFLAGS: -dv -# expect: Var_Parse: ${UNDEF1} (eval-defined) -# Even though undefined expressions should lead to errors, no error message is -# generated for this line. The expression ${UNDEF1} simply expands -# to an empty string. +# In a dependency line, an undefined expressions expands to an empty string. +# expect: Var_Parse: ${UNDEF1} (eval) all: ${UNDEF1} # Using a double dollar in order to circumvent immediate expression expansion # feels like unintended behavior. At least the manual page says nothing at # all about defined or undefined variables in dependency lines. # # At the point where the expression ${DEF2} is expanded, the variable DEF2 # is defined, so everything's fine. all: $${DEF2} a-$${DEF2}-b -# This variable is not defined at all. -# XXX: The -dv log says later when expanding the sources of 'all': -# Var_Parse: ${UNDEF3} (eval-defined) -# but no error message is generated for this line, just like for UNDEF1. -# The expression ${UNDEF3} simply expands to an empty string. +# This variable is neither defined now nor later. all: $${UNDEF3} # Try out how many levels of indirection are really expanded in dependency # lines. # # The first level of indirection is the $$ in the dependency line. # When the dependency line is parsed, it is resolved to the string # "${INDIRECT_1}". At this point, the dollar is just an ordinary character, # waiting to be expanded at some later point. # # Later, in SuffExpandChildren, that expression is expanded again by calling # Var_Parse, and this time, the result is the string "1-2-${INDIRECT_2}-2-1". # # This string is not expanded anymore by Var_Parse. But there is another # effect. Now DirExpandCurly comes into play and expands the curly braces # in this filename pattern, resulting in the string "1-2-$INDIRECT_2-2-1". # As of 2020-09-03, the test dir.mk contains further details on this topic. # # Finally, this string is assigned to the local ${.TARGET} variable. This # variable is expanded when the shell command is generated. At that point, # the $I is expanded. Since the variable I is not defined, it expands to # the empty string. This way, the final output is the string # "1-2-NDIRECT_2-2-1", which differs from the actual name of the target. # For exactly this reason, it is not recommended to use dollar signs in # target names. # # The number of actual expansions is way more than one might expect, # therefore this feature is probably not widely used. # all: 1-$${INDIRECT_1}-1 INDIRECT_1= 2-$${INDIRECT_2}-2 INDIRECT_2= 3-$${INDIRECT_3}-3 INDIRECT_3= indirect UNDEF1= undef1 DEF2= def2 # Cover the code in SuffExpandChildren that deals with malformed # expressions. # # This seems to be an edge case that never happens in practice, and it would # probably be appropriate to just error out in such a case. # # To trigger this piece of code, the variable name must contain "$)" or "$:" # or "$)" or "$$". Using "$:" does not work since the dependency line is # fully expanded before parsing, therefore any ':' in a target or source name # would be interpreted as a dependency operator instead. all: $$$$) # The $$INDIRECT in the following line is treated like the dependency of the # "all" target, that is, the "$$I" is first expanded to "$I", and in a second # round of expansion, the "$I" expands to nothing since the variable "I" is # undefined. # # Since 2020-09-13, this generates a parse error in lint mode (-dL), but not # in normal mode since ParseDependency does not handle any errors after # calling Var_Parse. -# expect: Var_Parse: ${:U\$)}: (eval-defined) +# expect: Var_Parse: ${:U\$)}: (eval) # expect: Var_Parse: $INDIRECT_2-2-1 $): (parse) # expect: Var_Parse: $): (parse) undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}: @echo ${.TARGET:Q} .MAKEFLAGS: -d0 - -# XXX: The exit status is still 0, even though Parse_Error is called with -# PARSE_FATAL in SuffExpandChildren. The exit status is only affected by -# parse errors when they occur in the parsing phase, see Parse_File. diff --git a/contrib/bmake/unit-tests/deptgt-makeflags.exp b/contrib/bmake/unit-tests/deptgt-makeflags.exp index ea29f76ad464..099ce8e15ed5 100644 --- a/contrib/bmake/unit-tests/deptgt-makeflags.exp +++ b/contrib/bmake/unit-tests/deptgt-makeflags.exp @@ -1,10 +1,10 @@ Global: ignoring delete 'DOLLAR' as it is not found Command: DOLLAR = $$$$ Global: .MAKEOVERRIDES = VAR DOLLAR CondParser_Eval: ${DOLLAR} != "\$\$" -Var_Parse: ${DOLLAR} != "\$\$" (eval-defined) +Var_Parse: ${DOLLAR} != "\$\$" (eval-defined-loud) Comparing "$$" != "$$" Global: .MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d Global: .MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0 make: Unterminated quoted string [make VAR=initial UNBALANCED='] exit status 0 diff --git a/contrib/bmake/unit-tests/deptgt-order.exp b/contrib/bmake/unit-tests/deptgt-order.exp index ecbf03fcc572..c5cdaa4cbe2b 100644 --- a/contrib/bmake/unit-tests/deptgt-order.exp +++ b/contrib/bmake/unit-tests/deptgt-order.exp @@ -1,11 +1,11 @@ -Parsing line 15: .ORDER: three one +Parsing deptgt-order.mk:15: .ORDER: three one ParseDependency(.ORDER: three one) # .ORDER forces 'three' to be made before 'one' # three, unmade, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS, flags none # one, unmade, type OP_DEPENDS|OP_PHONY, flags none -Parsing line 16: .MAKEFLAGS: -d0 +Parsing deptgt-order.mk:16: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) : 'Making two out of one.' : 'Making three out of two.' : 'Making all out of three.' exit status 0 diff --git a/contrib/bmake/unit-tests/deptgt.exp b/contrib/bmake/unit-tests/deptgt.exp index ae7315716d8c..8a70e2495038 100644 --- a/contrib/bmake/unit-tests/deptgt.exp +++ b/contrib/bmake/unit-tests/deptgt.exp @@ -1,18 +1,26 @@ make: "deptgt.mk" line 11: warning: Extra target '.PHONY' ignored make: "deptgt.mk" line 30: Unassociated shell command ": command3 # parse error, since targets == NULL" -Parsing line 36: ${:U}: empty-source +Parsing deptgt.mk:36: ${:U}: empty-source ParseDependency(: empty-source) -Parsing line 37: : command for empty targets list -Parsing line 38: : empty-source +Parsing deptgt.mk:37: : command for empty targets list +Parsing deptgt.mk:38: : empty-source ParseDependency(: empty-source) -Parsing line 39: : command for empty targets list -Parsing line 40: .MAKEFLAGS: -d0 +Parsing deptgt.mk:39: : command for empty targets list +Parsing deptgt.mk:40: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) -make: "deptgt.mk" line 45: while evaluating "${:U:Z}:" with value "": Unknown modifier "Z" -make: "deptgt.mk" line 49: while parsing "${:U:Z}:": Unknown modifier "Z" -make: "deptgt.mk" line 52: warning: Extra target 'ordinary' ignored -make: "deptgt.mk" line 55: warning: Extra target (ordinary) ignored -make: "deptgt.mk" line 58: warning: Special and mundane targets don't mix. Mundane ones ignored +Var_Parse: ${UNDEF}: depsrc-${UNDEF} (eval) +Var_Parse: ${UNDEF} (eval) +Global: .ALLTARGETS = target1 target2 sources empty-source deptgt- +Global: .ALLTARGETS = target1 target2 sources empty-source deptgt- depsrc- +Global: .MAKEFLAGS = -r -k -d p -d 0 -d v -d +Global: .MAKEFLAGS = -r -k -d p -d 0 -d v -d 0 +make: "deptgt.mk" line 51: Unknown modifier "Z" + while evaluating "${:U:Z}:" with value "" +make: "deptgt.mk" line 55: Unknown modifier "Z" + while parsing "${:U:Z}:" +make: "deptgt.mk" line 58: warning: Extra target 'ordinary' ignored +make: "deptgt.mk" line 61: warning: Extra target (ordinary) ignored +make: "deptgt.mk" line 64: warning: Special and mundane targets don't mix. Mundane ones ignored make: Fatal errors encountered -- cannot continue make: stopped making "target1" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/deptgt.mk b/contrib/bmake/unit-tests/deptgt.mk index 2e6845b88038..f841f24e8984 100644 --- a/contrib/bmake/unit-tests/deptgt.mk +++ b/contrib/bmake/unit-tests/deptgt.mk @@ -1,58 +1,64 @@ -# $NetBSD: deptgt.mk,v 1.20 2024/07/06 10:14:35 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.22 2025/01/10 23:00:38 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. # TODO: Implementation # Just in case anyone tries to compile several special targets in a single # dependency line: That doesn't work, and make immediately rejects it. # expect+1: warning: Extra target '.PHONY' ignored .SUFFIXES .PHONY: .c.o # The following lines demonstrate how 'targets' is set and reset during # parsing of dependencies. To see it in action, set breakpoints in: # # ParseDependency at the beginning # FinishDependencyGroup at "targets = NULL" # Parse_File at "Lst_Free(targets)" # Parse_File at "targets = Lst_New()" # ParseLine_ShellCommand at "targets == NULL" # # Keywords: # parse.c:targets target1 target2: sources # targets := [target1, target2] : command1 # targets == [target1, target2] : command2 # targets == [target1, target2] VAR=value # targets := NULL # expect+1: Unassociated shell command ": command3 # parse error, since targets == NULL" : command3 # parse error, since targets == NULL # In a dependency declaration, the list of targets can be empty. # It doesn't matter whether the empty string is generated by an # expression or whether it is just omitted. .MAKEFLAGS: -dp ${:U}: empty-source : command for empty targets list : empty-source : command for empty targets list .MAKEFLAGS: -d0 +# An expression based on an undefined variable is allowed on both sides of +# the dependency declaration. +.MAKEFLAGS: -dv +deptgt-${UNDEF}: depsrc-${UNDEF} +.MAKEFLAGS: -d0 + # In a dependency declaration, the whole line is expanded before interpreting # the line. -# expect+1: while evaluating "${:U:Z}:" with value "": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" ${:U:Z}: # After expanding the line as a whole, each target is parsed but not # evaluated, separately, in ParseDependencyTargetWord. -# expect+1: while parsing "${:U:Z}:": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" $${:U:Z}: # expect+1: warning: Extra target 'ordinary' ignored .END ordinary: # expect+1: warning: Extra target (ordinary) ignored .PATH ordinary: # expect+1: warning: Special and mundane targets don't mix. Mundane ones ignored ordinary .PATH: diff --git a/contrib/bmake/unit-tests/directive-export-impl.exp b/contrib/bmake/unit-tests/directive-export-impl.exp index 7daf1f45b0cb..ff5f1663e160 100644 --- a/contrib/bmake/unit-tests/directive-export-impl.exp +++ b/contrib/bmake/unit-tests/directive-export-impl.exp @@ -1,60 +1,60 @@ -Parsing line 21: UT_VAR= <${REF}> +Parsing directive-export-impl.mk:21: UT_VAR= <${REF}> Global: UT_VAR = <${REF}> -Parsing line 28: .export UT_VAR +Parsing directive-export-impl.mk:28: .export UT_VAR Global: .MAKE.EXPORTED = UT_VAR -Parsing line 32: : ${UT_VAR:N*} -Var_Parse: ${UT_VAR:N*} (eval-defined) -Var_Parse: ${REF}> (eval-defined) +Parsing directive-export-impl.mk:32: : ${UT_VAR:N*} +Var_Parse: ${UT_VAR:N*} (eval) +Var_Parse: ${REF}> (eval) Evaluating modifier ${UT_VAR:N...} on value "<>" Pattern for ':N' is "*" ModifyWords: split "<>" into 1 word Result of ${UT_VAR:N*} is "" ParseDependency(: ) -Parsing line 42: .if ${:!echo "\$UT_VAR"!} != "<>" +Parsing directive-export-impl.mk:42: .if ${:!echo "\$UT_VAR"!} != "<>" CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>" -Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined) -Evaluating modifier ${:!...} on value "" (eval-defined, undefined) +Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined-loud) +Evaluating modifier ${:!...} on value "" (eval, undefined) Modifier part: "echo "$UT_VAR"" Capturing the output of command "echo "$UT_VAR"" Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" Result of ${.MAKE.EXPORTED:O} is "UT_VAR" Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR" Result of ${.MAKE.EXPORTED:u} is "UT_VAR" Var_Parse: ${UT_VAR} (eval) Var_Parse: ${REF}> (eval) -Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, defined) +Result of ${:!echo "\$UT_VAR"!} is "<>" (eval, defined) Comparing "<>" != "<>" -Parsing line 50: : ${UT_VAR:N*} -Var_Parse: ${UT_VAR:N*} (eval-defined) -Var_Parse: ${REF}> (eval-defined) +Parsing directive-export-impl.mk:50: : ${UT_VAR:N*} +Var_Parse: ${UT_VAR:N*} (eval) +Var_Parse: ${REF}> (eval) Evaluating modifier ${UT_VAR:N...} on value "<>" Pattern for ':N' is "*" ModifyWords: split "<>" into 1 word Result of ${UT_VAR:N*} is "" ParseDependency(: ) -Parsing line 54: REF= defined +Parsing directive-export-impl.mk:54: REF= defined Global: REF = defined -Parsing line 58: .if ${:!echo "\$UT_VAR"!} != "" +Parsing directive-export-impl.mk:58: .if ${:!echo "\$UT_VAR"!} != "" CondParser_Eval: ${:!echo "\$UT_VAR"!} != "" -Var_Parse: ${:!echo "\$UT_VAR"!} != "" (eval-defined) -Evaluating modifier ${:!...} on value "" (eval-defined, undefined) +Var_Parse: ${:!echo "\$UT_VAR"!} != "" (eval-defined-loud) +Evaluating modifier ${:!...} on value "" (eval, undefined) Modifier part: "echo "$UT_VAR"" Capturing the output of command "echo "$UT_VAR"" Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) Evaluating modifier ${.MAKE.EXPORTED:O} on value "UT_VAR" Result of ${.MAKE.EXPORTED:O} is "UT_VAR" Evaluating modifier ${.MAKE.EXPORTED:u} on value "UT_VAR" Result of ${.MAKE.EXPORTED:u} is "UT_VAR" Var_Parse: ${UT_VAR} (eval) Var_Parse: ${REF}> (eval) -Result of ${:!echo "\$UT_VAR"!} is "" (eval-defined, defined) +Result of ${:!echo "\$UT_VAR"!} is "" (eval, defined) Comparing "" != "" -Parsing line 62: all: +Parsing directive-export-impl.mk:62: all: ParseDependency(all:) Global: .ALLTARGETS = all -Parsing line 63: .MAKEFLAGS: -d0 +Parsing directive-export-impl.mk:63: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d cpv -d Global: .MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/directive-for-errors.exp b/contrib/bmake/unit-tests/directive-for-errors.exp index 463f5a376997..e5d5cd158e8e 100644 --- a/contrib/bmake/unit-tests/directive-for-errors.exp +++ b/contrib/bmake/unit-tests/directive-for-errors.exp @@ -1,17 +1,18 @@ make: "directive-for-errors.mk" line 9: Unknown directive "fori" make: "directive-for-errors.mk" line 11: warning: <> make: "directive-for-errors.mk" line 13: for-less endfor make: "directive-for-errors.mk" line 25: Unknown directive "for" make: "directive-for-errors.mk" line 27: warning: <> make: "directive-for-errors.mk" line 29: for-less endfor make: "directive-for-errors.mk" line 44: invalid character '$' in .for loop variable name make: "directive-for-errors.mk" line 52: no iteration variables in for make: "directive-for-errors.mk" line 64: Wrong number of words (5) in .for substitution list with 3 variables make: "directive-for-errors.mk" line 78: missing `in' in for -make: "directive-for-errors.mk" line 89: while evaluating "${:U3:Z} 4" with value "3": Unknown modifier "Z" +make: "directive-for-errors.mk" line 89: Unknown modifier "Z" + while evaluating "${:U3:Z} 4" with value "3" make: "directive-for-errors.mk" line 93: warning: Should not be reached. make: "directive-for-errors.mk" line 93: warning: Should not be reached. make: "directive-for-errors.mk" line 93: warning: Should not be reached. make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-for-errors.mk b/contrib/bmake/unit-tests/directive-for-errors.mk index 3cbf457622e8..0fb556ec8ec3 100644 --- a/contrib/bmake/unit-tests/directive-for-errors.mk +++ b/contrib/bmake/unit-tests/directive-for-errors.mk @@ -1,94 +1,94 @@ -# $NetBSD: directive-for-errors.mk,v 1.13 2024/07/06 10:14:35 rillig Exp $ +# $NetBSD: directive-for-errors.mk,v 1.14 2024/08/29 20:20:36 rillig Exp $ # # Tests for error handling in .for loops. # A .for directive must be followed by whitespace, everything else results # in a parse error. # expect+1: Unknown directive "fori" .fori in 1 2 3 # expect+1: warning: <> . warning <${i}> # expect+1: for-less endfor .endfor # A slash is not whitespace, therefore this is not parsed as a .for loop. # # XXX: The error message is misleading though. As of 2020-12-31, it says # 'Unknown directive "for"', but that directive is actually known. This is # because ForEval does not detect the .for loop as such, so parsing # continues in ParseLine > ParseDependencyLine > ParseDependency > # ParseDependencyTargets > ParseErrorNoDependency, and there the directive # name is parsed a bit differently. # expect+1: Unknown directive "for" .for/i in 1 2 3 # expect+1: warning: <> . warning <${i}> # expect+1: for-less endfor .endfor # Before for.c 1.173 from 2023-05-08, the variable name could be an arbitrary # word, it only needed to be separated by whitespace. Even '$' and '\' were # valid variable names, which was not useful in practice. # # The '$$' was not replaced with the values '1' or '3' from the .for loop, # instead it was kept as-is, and when the .info directive expanded its # argument, each '$$' got replaced with a single '$'. The "long # expression" ${$} got replaced though, even though this would be a parse # error everywhere outside a .for loop. ${:U\$}= dollar # see whether the "variable" '$' is local ${:U\\}= backslash # see whether the "variable" '\' is local # expect+1: invalid character '$' in .for loop variable name .for a b $ \ in 1 2 3 4 . info Dollar $$ ${$} $($) and backslash $\ ${\} $(\). .endfor # If there are no variables, there is no point in expanding the .for loop # since this would end up in an endless loop, consuming 0 of the 3 values in # each iteration. # expect+1: no iteration variables in for .for in 1 2 3 # XXX: This should not be reached. It should be skipped, as already done # when the number of values is not a multiple of the number of variables, # see below. . warning Should not be reached. .endfor # There are 3 variables and 5 values. These 5 values cannot be split evenly # among the variables, therefore the loop is not expanded at all, it is # skipped instead. # expect+1: Wrong number of words (5) in .for substitution list with 3 variables .for a b c in 1 2 3 4 5 . warning Should not be reached. .endfor # The list of values after the 'in' may be empty, no matter if this emptiness # comes from an expanded expression or from a syntactically empty line. .for i in . info Would be reached if there were items to loop over. .endfor # A missing 'in' should parse the .for loop but skip the body. # expect+1: missing `in' in for .for i over k # XXX: As of 2020-12-31, this line is reached once. . warning Should not be reached. .endfor # A malformed modifier should be detected and skip the body of the loop. # # XXX: As of 2020-12-31, Var_Subst doesn't report any errors, therefore # the loop body is expanded as if no error had happened. -# expect+1: while evaluating "${:U3:Z} 4" with value "3": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" .for i in 1 2 ${:U3:Z} 4 # expect+3: warning: Should not be reached. # expect+2: warning: Should not be reached. # expect+1: warning: Should not be reached. . warning Should not be reached. .endfor diff --git a/contrib/bmake/unit-tests/directive-for-escape.exp b/contrib/bmake/unit-tests/directive-for-escape.exp index 6dfea5e79bb6..be6c2b2a41bf 100644 --- a/contrib/bmake/unit-tests/directive-for-escape.exp +++ b/contrib/bmake/unit-tests/directive-for-escape.exp @@ -1,168 +1,170 @@ For: end for 1 For: loop body with chars = !"#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~: . info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: "directive-for-escape.mk" line 21: while evaluating "${:U!"" with value "!"": Unclosed expression, expecting '}' for modifier "U!"" +make: "directive-for-escape.mk" line 21: Unclosed expression, expecting '}' for modifier "U!"" + while evaluating "${:U!"" with value "!"" in .for loop from directive-for-escape.mk:20 with chars = !"#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ make: "directive-for-escape.mk" line 21: !" For: end for 1 For: loop body with chars = !"\\#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~: . info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: "directive-for-escape.mk" line 33: while evaluating "${:U!"\\\\" with value "!"\\": Unclosed expression, expecting '}' for modifier "U!"\\\\" +make: "directive-for-escape.mk" line 33: Unclosed expression, expecting '}' for modifier "U!"\\\\" + while evaluating "${:U!"\\\\" with value "!"\\" in .for loop from directive-for-escape.mk:32 with chars = !"\\#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ make: "directive-for-escape.mk" line 33: !"\\ For: end for 1 For: loop body with i = $: . info ${:U\$} make: "directive-for-escape.mk" line 57: $ For: loop body with i = ${V}: . info ${:U${V}} make: "directive-for-escape.mk" line 57: value For: loop body with i = ${V:=-with-modifier}: . info ${:U${V:=-with-modifier}} make: "directive-for-escape.mk" line 57: value-with-modifier For: loop body with i = $(V): . info ${:U$(V)} make: "directive-for-escape.mk" line 57: value For: loop body with i = $(V:=-with-modifier): . info ${:U$(V:=-with-modifier)} make: "directive-for-escape.mk" line 57: value-with-modifier For: end for 1 For: loop body with i = $: . info ${:U\$} make: "directive-for-escape.mk" line 69: $ For: loop body with i = ${V}: . info ${:U${V}} make: "directive-for-escape.mk" line 69: value For: loop body with i = ${V:=-with-modifier}: . info ${:U${V:=-with-modifier}} make: "directive-for-escape.mk" line 69: value-with-modifier For: loop body with i = $(V): . info ${:U$(V)} make: "directive-for-escape.mk" line 69: value For: loop body with i = $(V:=-with-modifier): . info ${:U$(V:=-with-modifier)} make: "directive-for-escape.mk" line 69: value-with-modifier For: end for 1 For: loop body with i = ${UNDEF:U\$\$: # ${:U\${UNDEF\:U\\$\\$} For: loop body with i = {{}}: # ${:U{{\}\}} For: loop body with i = end}: # ${:Uend\}} For: end for 1 For: loop body with i = ${UNDEF:U\$\$: . info ${:U\${UNDEF\:U\\$\\$} make: "directive-for-escape.mk" line 120: ${UNDEF:U\backslash$ For: loop body with i = {{}}: . info ${:U{{\}\}} make: "directive-for-escape.mk" line 120: {{}} For: loop body with i = end}: . info ${:Uend\}} make: "directive-for-escape.mk" line 120: end} For: end for 1 For: loop body with i = begin<${UNDEF:Ufallback:N{{{}}}}>end: . info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end} make: "directive-for-escape.mk" line 138: beginend For: end for 1 For: loop body with i = $: . info ${:U\$} make: "directive-for-escape.mk" line 147: $ make: "directive-for-escape.mk" line 155: invalid character ':' in .for loop variable name For: end for 1 make: "directive-for-escape.mk" line 165: invalid character '}' in .for loop variable name For: end for 1 For: end for 1 For: loop body with i = inner: . info ${:Uinner} ${:Uinner} ${:Uinner:M*} $(:Uinner) $(:Uinner:M*) make: "directive-for-escape.mk" line 175: inner inner inner inner inner For: end for 1 For: loop body with i = inner: . info ${i${:U}} make: "directive-for-escape.mk" line 179: outer For: end for 1 For: loop body with i = inner: . info ${:Uinner\}} # XXX: unclear why ForLoop_SubstVarLong needs this make: "directive-for-escape.mk" line 183: inner} For: end for 1 For: loop body with i = inner: . info ${i2} ${i,} ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner} make: "directive-for-escape.mk" line 187: two comma innerinnerinnerinner make: "directive-for-escape.mk" line 196: invalid character '$' in .for loop variable name For: end for 1 make: "directive-for-escape.mk" line 208: eight and no cents. For: end for 1 make: "directive-for-escape.mk" line 222: newline in .for value in .for loop from directive-for-escape.mk:222 with i = " " make: "directive-for-escape.mk" line 222: newline in .for value in .for loop from directive-for-escape.mk:222 with i = " " For: loop body with i = " ": . info short: ${:U" "}, long: ${:U" "} make: "directive-for-escape.mk" line 223: short: " ", long: " " For: end for 1 For: loop body with i = " ": -Parsing line 236: .for i in "${.newline}" +Parsing directive-for-escape.mk:236: .for i in "${.newline}" For: end for 1 Parse_PushInput: .for loop in directive-for-escape.mk, line 236 make: "directive-for-escape.mk" line 236: newline in .for value in .for loop from directive-for-escape.mk:236 with i = " " For: loop body with i = " ": : ${:U" "} SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `directive-for-escape.mk' -Parsing line 237: : ${:U" "} +Parsing directive-for-escape.mk:237: : ${:U" "} ParseDependency(: " ") ParseEOF: returning to file directive-for-escape.mk, line 239 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `directive-for-escape.mk' -Parsing line 239: .MAKEFLAGS: -d0 +Parsing directive-for-escape.mk:239: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) For: end for 1 For: loop body with i = #: # ${:U#} For: loop body with i = \\#: # ${:U\\\\#} For: end for 1 For: loop body with i = $: # ${:U\$} For: loop body with i = $i: # ${:U$i} For: loop body with i = $(i): # ${:U$(i)} For: loop body with i = ${i}: # ${:U${i}} For: loop body with i = $$: # ${:U$$} For: loop body with i = $$$$: # ${:U$$$$} For: loop body with i = ${:U\$\$}: # ${:U${:U\$\$}} For: end for 1 For: loop body with i = ${.TARGET}: # ${:U${.TARGET}} For: loop body with i = ${.TARGET}: # ${:U${.TARGET}} For: loop body with i = $${.TARGET}: # ${:U$${.TARGET\}} For: loop body with i = $${.TARGET}: # ${:U$${.TARGET\}} For: end for 1 For: loop body with i = (((: # ${:U(((} For: loop body with i = {{{: # ${:U{{{} For: loop body with i = ))): # ${:U)))} For: loop body with i = }}}: # ${:U\}\}\}} For: end for 1 For: loop body with , = 1: # $$i $i # VAR= $$i $i ${a:S,from${:U1}to,} VAR= $$i $i ${a:S,from${:U1}to,} make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-for-escape.mk b/contrib/bmake/unit-tests/directive-for-escape.mk index dcd13e1392c0..e688ce98f258 100644 --- a/contrib/bmake/unit-tests/directive-for-escape.mk +++ b/contrib/bmake/unit-tests/directive-for-escape.mk @@ -1,285 +1,285 @@ -# $NetBSD: directive-for-escape.mk,v 1.28 2024/07/07 11:20:10 rillig Exp $ +# $NetBSD: directive-for-escape.mk,v 1.29 2024/08/29 20:20:36 rillig Exp $ # # Test escaping of special characters in the iteration values of a .for loop. # These values get expanded later using the :U variable modifier, and this # escaping and unescaping must pass all characters and strings unmodified. .MAKEFLAGS: -df # Even though the .for loops take quotes into account when splitting the # string into words, the quotes don't need to be balanced, as of 2020-12-31. # This could be considered a bug. ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ # XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of # the loop. Not only would it need the escaping for the variable modifier # ':U' but also the escaping for the line-end comment. -# expect+3: while evaluating "${:U!"" with value "!"": Unclosed expression, expecting '}' for modifier "U!"" +# expect+3: Unclosed expression, expecting '}' for modifier "U!"" # expect+2: !" .for chars in ${ASCII} . info ${chars} .endfor # As of 2020-12-31, using 2 backslashes before be '#' would treat the '#' # as comment character. Using 3 backslashes doesn't help either since # then the situation is essentially the same as with 1 backslash. # This means that a '#' sign cannot be passed in the value of a .for loop # at all. ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ -# expect+3: while evaluating "${:U!"\\\\" with value "!"\\": Unclosed expression, expecting '}' for modifier "U!"\\\\" +# expect+3: Unclosed expression, expecting '}' for modifier "U!"\\\\" # expect+2: !"\\ .for chars in ${ASCII.2020-12-31} . info ${chars} .endfor # Cover the code in ExprLen. # # XXX: It is unexpected that the variable V gets expanded in the loop body. # The double '$$' should intuitively prevent exactly this. Probably nobody # was adventurous enough to use literal dollar signs in the values of a .for # loop, allowing this edge case to go unnoticed for years. # # See for.c, function ExprLen. V= value VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) # expect: . info ${:U\$} # expect+10: $ # expect: . info ${:U${V}} # expect+8: value # expect: . info ${:U${V:=-with-modifier}} # expect+6: value-with-modifier # expect: . info ${:U$(V)} # expect+4: value # expect: . info ${:U$(V:=-with-modifier)} # expect+2: value-with-modifier .for i in ${VALUES} . info $i .endfor # # Providing the loop items directly has the same effect. # expect: . info ${:U\$} # expect+7: $ # expect: . info ${:U${V}} # expect+5: value # expect+4: value-with-modifier # expect+3: value # expect+2: value-with-modifier .for i in $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) . info $i .endfor # Try to cover the code for nested '{}' in ExprLen, without success. # # The value of the variable VALUES is not meant to be an expression. # Instead, it is meant to represent literal text, the only escaping mechanism # being that each '$' is written as '$$'. VALUES= $${UNDEF:U\$$\$$ {{}} end} # # The .for loop splits ${VALUES} into 3 words, at the space characters, since # the '$$' is an ordinary character and the spaces are not escaped. # Word 1 is '${UNDEF:U\$\$' # Word 2 is '{{}}' # Word 3 is 'end}' # # Each of these words is now inserted in the body of the .for loop. .for i in ${VALUES} # $i .endfor # # When these words are injected into the body of the .for loop, each inside a # '${:U...}' expression, the result is: # # expect: For: loop body with i = ${UNDEF:U\$\$: # expect: # ${:U\${UNDEF\:U\\$\\$} # expect: For: loop body with i = {{}}: # expect: # ${:U{{\}\}} # expect: For: loop body with i = end}: # expect: # ${:Uend\}} # expect: For: end for 1 # # The first of these expressions is the most interesting one, due to its many # special characters. This expression is properly balanced: # # Text Meaning Explanation # \$ $ escaped # { { ordinary text # UNDEF UNDEF ordinary text # \: : escaped # U U ordinary text # \\ \ escaped # $\ (expr) an expression, the variable name is '\' # \$ $ escaped # # To make the expression '$\' visible, define it to an actual word: ${:U\\}= backslash # expect+4: ${UNDEF:U\backslash$ # expect+3: {{}} # expect+2: end} .for i in ${VALUES} . info $i .endfor # # FIXME: There was no expression '$\' in the original text of the variable # 'VALUES', that's a surprise in the parser. # The second attempt to cover the code for nested '{}' in ExprLen. # # XXX: It is not the job of ExprLen to parse an expression, it is naive to # expect ExprLen to get all the details right in just a few lines of code. # Each variable modifier has its own inconsistent way of parsing nested # expressions, braces and parentheses. (Compare ':M', ':S', and # ':D' for details.) The only sensible thing to do is therefore to let # Var_Parse do all the parsing work. VALUES= begin<$${UNDEF:Ufallback:N{{{}}}}>end # expect+2: beginend .for i in ${VALUES} . info $i .endfor # A single trailing dollar doesn't happen in practice. # The dollar sign is correctly passed through to the body of the .for loop. # There, it is expanded by the .info directive, but even there a trailing # dollar sign is kept as-is. # expect+2: $ .for i in ${:U\$} . info ${i} .endfor # Before for.c 1.173 from 2023-05-08, the name of the iteration variable # could contain colons, which affected expressions having this exact # modifier. This possibility was neither intended nor documented. NUMBERS= one two three # expect+1: invalid character ':' in .for loop variable name .for NUMBERS:M*e in replaced . info ${NUMBERS} ${NUMBERS:M*e} .endfor # Before for.c 1.173 from 2023-05-08, the name of the iteration variable # could contain braces, which allowed to replace sequences of # expressions. This possibility was neither intended nor documented. BASENAME= one EXT= .c # expect+1: invalid character '}' in .for loop variable name .for BASENAME}${EXT in replaced . info ${BASENAME}${EXT} .endfor # Demonstrate the various ways to refer to the iteration variable. i= outer i2= two i,= comma # expect+2: inner inner inner inner inner .for i in inner . info $i ${i} ${i:M*} $(i) $(i:M*) .endfor # expect+2: outer .for i in inner . info ${i${:U}} .endfor # expect+2: inner} .for i in inner . info ${i\}} # XXX: unclear why ForLoop_SubstVarLong needs this .endfor # expect+2: two comma innerinnerinnerinner .for i in inner . info ${i2} ${i,} $i${i}${i:M*}$i .endfor # Before for.c 1.173 from 2023-05-08, the variable name could be a single '$' # since there was no check on valid variable names. ForLoop_SubstVarShort # skipped "stupid" variable names though, but ForLoop_SubstVarLong naively # parsed the body of the loop, substituting each '${$}' with an actual # '${:Udollar}'. # expect+1: invalid character '$' in .for loop variable name .for $ in dollar . info eight $$$$$$$$ and no cents. . info eight ${$}${$}${$}${$} and no cents. .endfor # Outside a .for loop, '${$}' is interpreted differently. The outer '$' starts # an expression. The inner '$' is followed by a '}' and is thus a # silent syntax error, the '$' is skipped. The variable name is thus '', and # since since there is never a variable named '', the whole expression '${$}' # evaluates to an empty string. closing-brace= } # guard against an ${closing-brace}= # alternative interpretation # expect+1: eight and no cents. .info eight ${$}${$}${$}${$} and no cents. # What happens if the values from the .for loop contain a literal newline? # Before for.c 1.144 from 2021-06-25, the newline was passed verbatim to the # body of the .for loop, where it was then interpreted as a literal newline, # leading to syntax errors such as "Unclosed variable expression" in the upper # line and "Invalid line type" in the lower line. # # The error message occurs in the line of the .for loop since that's the place # where the body of the .for loop is constructed, and at this point the # newline character gets replaced with a plain space. # expect+3: newline in .for value # expect+2: newline in .for value # expect+2: short: " ", long: " " .for i in "${.newline}" . info short: $i, long: ${i} .endfor # No error since the newline character is not actually used in the body. .for i in "${.newline}" .endfor # Between for.c 1.161 from 2022-01-08 and before for.c 1.163 from 2022-01-09, # a newline character in a .for loop led to a crash since at the point where # the error message including the stack trace is printed, the body of the .for # loop is assembled, and at that point, ForLoop.nextItem had already been # advanced. .MAKEFLAGS: -dp # expect+1: newline in .for value .for i in "${.newline}" : $i .endfor .MAKEFLAGS: -d0 .MAKEFLAGS: -df .for i in \# \\\# # $i .endfor .for i in $$ $$i $$(i) $${i} $$$$ $$$$$$$$ $${:U\$$\$$} # $i .endfor # The expression '${.TARGET}' must be preserved as it is one of the 7 built-in # target-local variables. See for.c 1.45 from 2009-01-14. .for i in ${.TARGET} $${.TARGET} $$${.TARGET} $$$${.TARGET} # $i .endfor # expect: # ${:U${.TARGET}} # XXX: Why does '$' result in the same text as '$$'? # expect: # ${:U${.TARGET}} # XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'? # expect: # ${:U$${.TARGET\}} # XXX: Why does '$' result in the same text as '$$'? # XXX: Why does the '$$' before the '${.TARGET}' lead to an escaped '}'? # expect: # ${:U$${.TARGET\}} .for i in ((( {{{ ))) }}} # $i .endfor # When generating the body of a .for loop, recognizing the expressions is done # using simple heuristics. These can go wrong in ambiguous cases like this. # The variable name ',' is unusual as it is not a pronounceable name, but the # same principle applies for other names as well. In this case, the text '$,' # is replaced with the expression '${:U1}', even though the text does not # represent an expression. .for , in 1 # $$i $i # VAR= $$i $i ${a:S,from$,to,} VAR= $$i $i ${a:S,from$,to,} .endfor # expect: # $$i $i # expect: # VAR= $$i $i ${a:S,from${:U1}to,} # expect: VAR= $$i $i ${a:S,from${:U1}to,} # # When the above variable is evaluated, make will complain about the # unfinished modifier ':S', as it is missing a comma. diff --git a/contrib/bmake/unit-tests/directive-for.exp b/contrib/bmake/unit-tests/directive-for.exp index 62b51aa406bf..b76a62bc81a2 100755 --- a/contrib/bmake/unit-tests/directive-for.exp +++ b/contrib/bmake/unit-tests/directive-for.exp @@ -1,44 +1,45 @@ make: "directive-for.mk" line 117: outer make: "directive-for.mk" line 138: a:\ a:\file.txt make: "directive-for.mk" line 138: d:\\ make: "directive-for.mk" line 138: d:\\file.txt make: "directive-for.mk" line 158: ( ( ( make: "directive-for.mk" line 158: [ [ [ make: "directive-for.mk" line 158: { { { make: "directive-for.mk" line 158: ) ) ) make: "directive-for.mk" line 158: ] ] ] make: "directive-for.mk" line 158: } } } make: "directive-for.mk" line 158: (()) (()) (()) make: "directive-for.mk" line 158: [[]] [[]] [[]] make: "directive-for.mk" line 158: {{}} {{}} {{}} make: "directive-for.mk" line 158: )( )( )( make: "directive-for.mk" line 158: ][ ][ ][ make: "directive-for.mk" line 158: }{ }{ }{ make: "directive-for.mk" line 166: invalid character ':' in .for loop variable name make: "directive-for.mk" line 173: invalid character '$' in .for loop variable name make: "directive-for.mk" line 185: invalid character '$' in .for loop variable name -make: "directive-for.mk" line 210: while evaluating "${:Uword2:Z}-after word3" with value "word2": Unknown modifier "Z" +make: "directive-for.mk" line 210: Unknown modifier "Z" + while evaluating "${:Uword2:Z}-after word3" with value "word2" make: "directive-for.mk" line 214: XXX: Should not reach word1 make: "directive-for.mk" line 214: XXX: Should not reach before--after make: "directive-for.mk" line 214: XXX: Should not reach word3 make: "directive-for.mk" line 219: no iteration variables in for make: "directive-for.mk" line 245: 1 open conditional in .for loop from directive-for.mk:243 with var = value make: "directive-for.mk" line 261: for-less endfor make: "directive-for.mk" line 262: if-less endif make: "directive-for.mk" line 270: if-less endif in .for loop from directive-for.mk:269 with var = value For: new loop 2 For: end for 2 For: end for 1 For: loop body with outer = o: .\ for inner in i .\ endfor For: end for 1 For: loop body with inner = i: make: "directive-for.mk" line 319: newline-item=(a) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-for.mk b/contrib/bmake/unit-tests/directive-for.mk index 96c1a492a5f7..cf24e282f730 100755 --- a/contrib/bmake/unit-tests/directive-for.mk +++ b/contrib/bmake/unit-tests/directive-for.mk @@ -1,320 +1,320 @@ -# $NetBSD: directive-for.mk,v 1.27 2024/07/06 10:14:35 rillig Exp $ +# $NetBSD: directive-for.mk,v 1.28 2024/08/29 20:20:36 rillig Exp $ # # Tests for the .for directive. # # TODO: Describe naming conventions for the loop variables. # .for f in values # .for file in values # .for _FILE_ in values # .for .FILE. in values # .for _f_ in values # # See also: # varmod-loop.mk The ':@var@...@' modifier # A typical use case for a .for loop is to populate a variable with a list of # values depending on other variables. In simple cases, the same effect can # be achieved using the ':@var@${var}@' modifier. .undef NUMBERS .for num in 1 2 3 NUMBERS+= ${num} .endfor .if ${NUMBERS} != "1 2 3" . error .endif # The .for loop also works for multiple iteration variables. # This is something that the modifier :@ cannot do as easily. .for name value in VARNAME value NAME2 value2 ${name}= ${value} .endfor .if ${VARNAME} != "value" || ${NAME2} != "value2" . error .endif # The .for loop splits the items at whitespace, taking quotes into account, # just like the :M or :S modifiers. # # Until 2012-06-03, the .for loop had split the items exactly at whitespace, # without taking the quotes into account. This had resulted in 10 words. .undef WORDS .for var in one t\ w\ o "three three" 'four four' `five six` WORDS+= counted .endfor .if ${WORDS:[#]} != 6 . error .endif # In the body of the .for loop, the iteration variables can be accessed # like normal variables, even though they are not really variables. # # Instead, before interpreting the body of the .for loop, the body is # generated by replacing each expression ${var} with ${:U1}, ${:U2} and so # on. # # A noticeable effect of this implementation technique is that the .for # iteration variables and the normal global variables live in separate # namespaces and do not influence each other. The "scope" of the .for loop # variables is restricted to the current makefile, it does not reach over to # any included makefiles. var= value before var2= value before .for var var2 in 1 2 3 4 .endfor .if ${var} != "value before" . warning After the .for loop, var must still have its original value. .endif .if ${var2} != "value before" . warning After the .for loop, var2 must still have its original value. .endif # Everything from the paragraph above also applies if the loop body is # empty. In this particular example, the items to be iterated are empty as # well. var= value before var2= value before .for var var2 in ${:U} .endfor .if ${var} != "value before" . warning After the .for loop, var must still have its original value. .endif .if ${var2} != "value before" . warning After the .for loop, var2 must still have its original value. .endif # Before for.c 1.39 from 2008-12-21, the values of the iteration variables # were simply inserted as plain text and then parsed as usual, which made it # possible to achieve all kinds of strange effects, such as generating '.if' # directives or inserting '$' characters in random places, thereby changing # how following '$' are interpreted. # # Before that date, the .for loop below expanded to: # EXPANSION+= value # Since that date, the .for loop below expands to: # EXPANSION${:U+}= value # EXPANSION= before EXPANSION+ = before .for plus in + EXPANSION${plus}= value .endfor .if ${EXPANSION} != "before" . error This must be a make from before 2009. .endif .if ${EXPANSION+} != "value" . error This must be a make from before 2009. .endif # When the outer .for loop is expanded, it sees the expression ${i} and # expands it. The inner loop then only sees the expression ${:Uouter} and # has nothing more to expand. .for i in outer . for i in inner # expect+1: outer . info ${i} . endfor .endfor # From https://gnats.netbsd.org/29985. # # Until 2008-12-21, the .for loop was expanded by replacing the variable # value literally in the body. This could lead to situations where the # characters from the variable value were interpreted as markup rather than # plain text. # # Until 2012-06-03, the .for loop had split the words at whitespace, without # taking quotes into account. This made it possible to have variable values # like "a:\ a:\file.txt" that ended in a single backslash. Since then, the # variable values have been replaced with expressions of the form ${:U...}, # which are not interpreted as code anymore. .for path in a:\ a:\file.txt d:\\ d:\\file.txt # expect+3: a:\ a:\file.txt # expect+2: d:\\ # expect+1: d:\\file.txt . info ${path} .endfor # Ensure that braces and parentheses are properly escaped by the .for loop. # Each line must print the same word 3 times. # See ForLoop_SubstBody. .for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{ # expect+12: ( ( ( # expect+11: [ [ [ # expect+10: { { { # expect+9: ) ) ) # expect+8: ] ] ] # expect+7: } } } # expect+6: (()) (()) (()) # expect+5: [[]] [[]] [[]] # expect+4: {{}} {{}} {{}} # expect+3: )( )( )( # expect+2: ][ ][ ][ # expect+1: }{ }{ }{ . info $v ${v} $(v) .endfor # Before 2023-05-09, the variable names could contain arbitrary characters, # except for whitespace, allowing for creative side effects, as usual for # arbitrary code injection. var= outer # expect+1: invalid character ':' in .for loop variable name .for var:Q in value "quoted" . info <${var}> <${var:Q}> <${var:Q:Q}> .endfor # Before 2023-05-09, when variable names could contain '$', the short # expression '$$' was preserved, the long expressions were substituted. # expect+1: invalid character '$' in .for loop variable name .for $ in value . info <$$> <${$}> <$($)> .endfor # https://gnats.netbsd.org/53146 mentions the idea of using a dynamic # variable name in .for loops, based on some other variable. The .for loops # are already tricky enough to understand in detail, even without this # possibility, therefore the variable names are restricted to using harmless # characters only. INDIRECT= direct # expect+1: invalid character '$' in .for loop variable name .for $(INDIRECT) in value # If the variable name could be chosen dynamically, the iteration variable # might have been 'direct', thereby expanding the expression '${direct}'. . info <$(INDIRECT)> <$(direct)> <$($(INDIRECT))> .endfor # Regular global variables and the "variables" from the .for loop don't # interfere with each other. In the following snippet, the variable 'DIRECT' # is used both as a global variable, as well as an iteration variable in the # .for loop. The expression '${INDIRECT}' refers to the global variable, not # to the one from the .for loop. DIRECT= global INDIRECT= ${DIRECT} .for DIRECT in iteration . if "${DIRECT} ${INDIRECT}" != "iteration global" . error . endif .endfor # XXX: A parse error or evaluation error in the items of the .for loop # should skip the whole loop. As of 2023-05-09, the loop is expanded as # usual. -# expect+1: while evaluating "${:Uword2:Z}-after word3" with value "word2": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" .for var in word1 before-${:Uword2:Z}-after word3 # expect+3: XXX: Should not reach word1 # expect+2: XXX: Should not reach before--after # expect+1: XXX: Should not reach word3 . info XXX: Should not reach ${var} .endfor # An empty list of variables to the left of the 'in' is a parse error. .for in value # expect+0: no iteration variables in for . error .endfor # An empty list of iteration values to the right of the 'in' is accepted. # Unlike in the shell, it is not a parse error. .for var in . error .endfor # If the iteration values become empty after expanding the expressions, the # body of the loop is not evaluated. It is not a parse error. .for var in ${:U} . error .endfor # The loop body can be empty. .for var in 1 2 3 .endfor # A mismatched .if inside a .for loop is detected each time when the loop body # is processed. .for var in value . if 0 .endfor # expect+0: 1 open conditional # If there are no iteration values, the loop body is not processed, and the # check for mismatched conditionals is not performed. .for var in ${:U} . if 0 .endfor # When a .for without the corresponding .endfor occurs in an inactive branch # of an .if, the .for directive is just skipped, it does not even need a # corresponding .endfor. In other words, the behavior of the parser depends # on the actual values of the conditions in the .if clauses. .if 0 . for var in value # does not need a corresponding .endfor .endif .endfor # expect+0: for-less endfor .endif # expect+0: if-less endif # When a .for without the corresponding .endfor occurs in an active branch of # an .if, the parser just counts the number of .for and .endfor directives, # without looking at any other directives. .if 1 . for var in value . endif # expect+0: if-less endif . endfor # no 'for-less endfor' .endif # no 'if-less endif' # Before for.c 1.172 from 2023-05-08, when make parsed a .for loop, it # assumed that there was no line continuation between the '.' and the 'for' # or 'endfor', as there is no practical reason to break the line at this # point. # # When make scanned the outer .for loop, it did not recognize the inner .for # loop as such and instead treated it as an unknown directive. The body of # the outer .for loop thus ended above the '.endfor'. # # When make scanned the inner .for loop, it did not recognize the inner # .endfor as such, which led to a parse error 'Unexpected end of file in .for # loop' from the '.endfor' line, followed by a second parse error 'for-less # .endfor' from the '.\\n endfor' line. .MAKEFLAGS: -df .for outer in o .\ for inner in i .\ endfor .endfor .MAKEFLAGS: -d0 # When there is a variable definition 'scope=cmdline' from the command line # (which has higher precedence than global variables) and a .for loop iterates # over a variable of the same name, the expression '${scope}' expands to the # value from the .for loop. This is because when the body of the .for loop is # expanded, the expression '${scope}' is textually replaced with ${:Uloop}', # without resolving any other variable names (ForLoop_SubstBody). Later, when # the body of the .for loop is actually interpreted, the body text doesn't # contain the word 'scope' anymore. .MAKEFLAGS: scope=cmdline .for scope in loop . if ${scope} != "loop" . error . endif .endfor # Since at least 1993, iteration stops at the first newline. # Back then, the .newline variable didn't exist, therefore it was unlikely # that a newline ever occurred. .for var in a${.newline}b${.newline}c # expect+1: newline-item=(a) . info newline-item=(${var}) .endfor diff --git a/contrib/bmake/unit-tests/directive-if-nested.mk b/contrib/bmake/unit-tests/directive-if-nested.mk index 19c8e9452660..93fce90b5d52 100644 --- a/contrib/bmake/unit-tests/directive-if-nested.mk +++ b/contrib/bmake/unit-tests/directive-if-nested.mk @@ -1,25 +1,25 @@ -# $NetBSD: directive-if-nested.mk,v 1.1 2020/11/10 22:23:37 rillig Exp $ +# $NetBSD: directive-if-nested.mk,v 1.2 2025/01/03 05:00:37 rillig Exp $ # # Tests for deeply nested .if directives. By default, memory for 128 nested # .if directives is pre-allocated, any deeper nesting is reallocated. # # See also: # Cond_EvalLine GEN= directive-if-nested.inc all: set-up test tear-down set-up: .PHONY @{ printf '.if %s\n' ${:U:range=1000}; \ printf '.info deeply nested .if directives\n'; \ - printf '.endif # %s\n' ${:U:range=1000}; \ + printf '.endif # %s\n' ${:U:range=1000:[-1..1]}; \ printf '\n'; \ printf 'all:\n'; \ } > ${GEN} test: .PHONY @${MAKE} -f ${GEN} tear-down: .PHONY @rm -f ${GEN} diff --git a/contrib/bmake/unit-tests/directive-if.exp b/contrib/bmake/unit-tests/directive-if.exp index 34ba63d034dd..56c8dfda5e30 100644 --- a/contrib/bmake/unit-tests/directive-if.exp +++ b/contrib/bmake/unit-tests/directive-if.exp @@ -1,18 +1,18 @@ make: "directive-if.mk" line 14: 0 evaluates to false. make: "directive-if.mk" line 19: 1 evaluates to true. make: "directive-if.mk" line 43: Unknown directive "ifx" make: "directive-if.mk" line 45: This is not conditional. make: "directive-if.mk" line 47: if-less else make: "directive-if.mk" line 49: This is not conditional. make: "directive-if.mk" line 51: if-less endif -make: "directive-if.mk" line 55: Malformed conditional () +make: "directive-if.mk" line 55: Malformed conditional '' make: "directive-if.mk" line 66: Quotes in plain words are probably a mistake. make: "directive-if.mk" line 76: Don't do this, always put a space after a directive. make: "directive-if.mk" line 81: Don't do this, always put a space after a directive. make: "directive-if.mk" line 88: Don't do this, always put a space around comparison operators. make: "directive-if.mk" line 95: Don't do this, always put a space after a directive. make: "directive-if.mk" line 100: Don't do this, always put a space after a directive. make: "directive-if.mk" line 108: Unknown directive "ifn" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-if.mk b/contrib/bmake/unit-tests/directive-if.mk index 7a68c0041415..7ff04da0755b 100644 --- a/contrib/bmake/unit-tests/directive-if.mk +++ b/contrib/bmake/unit-tests/directive-if.mk @@ -1,108 +1,108 @@ -# $NetBSD: directive-if.mk,v 1.12 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-if.mk,v 1.13 2024/08/06 18:00:17 rillig Exp $ # # Tests for the .if directive. # # See also: # cond-*.mk # TODO: Implementation .if 0 . error .else # expect+1: 0 evaluates to false. . info 0 evaluates to false. .endif .if 1 # expect+1: 1 evaluates to true. . info 1 evaluates to true. .else . error .endif # There is no '.ifx'. # # The commit from 2005-05-01 intended to detect this situation, but it failed # to do this since the call to is_token had its arguments switched. They were # expected as (str, token, token_len) but were actually passed as (token, str, # token_len). This made is_token return true even if the directive was # directly followed by alphanumerical characters, which was wrong. The # typical cases produced an error message such as "Malformed conditional # (x 123)", while the intended error message was "Unknown directive". # # Back at that time, the commits only modified the main code but did not add # the corresponding unit tests. This allowed the bug to hide for more than # 15 years. # # Since 2020-11-10, the correct error message is produced. The '.ifx' is no # longer interpreted as a variant of '.if', therefore the '.error' and '.else' # are interpreted as ordinary directives, producing the error messages # "if-less else" and "if-less endif". # expect+1: Unknown directive "ifx" .ifx 123 # expect+1: This is not conditional. .info This is not conditional. # expect+1: if-less else .else # expect+1: This is not conditional. .info This is not conditional. # expect+1: if-less endif .endif # Missing condition. -# expect+1: Malformed conditional () +# expect+1: Malformed conditional '' .if . error .else . error .endif # A plain word must not start with a '"'. It may contain a embedded quotes # though, which are kept. The quotes need not be balanced. The next space # ends the word, and the remaining " || 1" is parsed as "or true". .if ${:Uplain"""""} == plain""""" || 1 # expect+1: Quotes in plain words are probably a mistake. . info Quotes in plain words are probably a mistake. # XXX: Accepting quotes in plain words is probably a mistake as well. .else . error .endif .if0 . error .else # expect+1: Don't do this, always put a space after a directive. . info Don't do this, always put a space after a directive. .endif .if${:U-3} # expect+1: Don't do this, always put a space after a directive. . info Don't do this, always put a space after a directive. .else . error .endif .if${:U-3}>-4 # expect+1: Don't do this, always put a space around comparison operators. . info Don't do this, always put a space around comparison operators. .else . error .endif .if(1) # expect+1: Don't do this, always put a space after a directive. . info Don't do this, always put a space after a directive. .endif .if!0 # expect+1: Don't do this, always put a space after a directive. . info Don't do this, always put a space after a directive. .endif # The directives '.ifdef' and '.ifmake' can be negated by inserting an 'n'. # This doesn't work for a plain '.if' though. # # expect+1: Unknown directive "ifn" .ifn 0 diff --git a/contrib/bmake/unit-tests/directive-include-fatal.exp b/contrib/bmake/unit-tests/directive-include-fatal.exp index edb32df1a59b..35706336c1dd 100755 --- a/contrib/bmake/unit-tests/directive-include-fatal.exp +++ b/contrib/bmake/unit-tests/directive-include-fatal.exp @@ -1,4 +1,4 @@ -make: "directive-include-fatal.mk" line 14: Malformed conditional (${UNDEF}) +make: "directive-include-fatal.mk" line 14: Variable "UNDEF" is undefined make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-include-fatal.mk b/contrib/bmake/unit-tests/directive-include-fatal.mk index d4ed26f2a4aa..ecda6f1982c4 100755 --- a/contrib/bmake/unit-tests/directive-include-fatal.mk +++ b/contrib/bmake/unit-tests/directive-include-fatal.mk @@ -1,28 +1,28 @@ -# $NetBSD: directive-include-fatal.mk,v 1.4 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-include-fatal.mk,v 1.6 2025/01/11 21:21:33 rillig Exp $ # # Test for the .include directive combined with fatal errors. # # At 2020-09-13, the code in Parse_File that sets "fatals = 0" looked # suspicious, as if it were possible to suppress fatal errors by including # another file. It was a false alarm though, since Parse_File only handles # the top-level makefiles from the command line. Any included files are # handled by IncludeFile instead, and that function does not reset # the "fatals" counter. # Using an undefined variable in a condition generates a fatal error. -# expect+1: Malformed conditional (${UNDEF}) +# expect+1: Variable "UNDEF" is undefined .if ${UNDEF} .endif # Including another file does not reset the global variable "fatals". # The exit status will be 1. .include "/dev/null" # Adding another file to be included has no effect either. # When the command line is parsed, the additional file is only enqueued # in the global "makefiles" variable, but not immediately run through # Parse_File. .MAKEFLAGS: -f "/dev/null" all: @:; diff --git a/contrib/bmake/unit-tests/directive-include-guard.exp b/contrib/bmake/unit-tests/directive-include-guard.exp index 70d23a19fe7c..d7d2789681af 100644 --- a/contrib/bmake/unit-tests/directive-include-guard.exp +++ b/contrib/bmake/unit-tests/directive-include-guard.exp @@ -1,104 +1,106 @@ Parse_PushInput: file variable-ifndef.tmp, line 1 Skipping 'variable-ifndef.tmp' because 'VARIABLE_IFNDEF' is defined Parse_PushInput: file variable-ifndef-reuse.tmp, line 1 Skipping 'variable-ifndef-reuse.tmp' because 'VARIABLE_IFNDEF' is defined Parse_PushInput: file variable-ifndef-zero.tmp, line 1 Parse_PushInput: file variable-ifndef-zero.tmp, line 1 Parse_PushInput: file variable-ifndef-one.tmp, line 1 Parse_PushInput: file variable-ifndef-one.tmp, line 1 Parse_PushInput: file comments.tmp, line 1 Skipping 'comments.tmp' because 'COMMENTS' is defined Parse_PushInput: file variable-if.tmp, line 1 Skipping 'variable-if.tmp' because 'VARIABLE_IF' is defined Parse_PushInput: file variable-if-reuse.tmp, line 1 Skipping 'variable-if-reuse.tmp' because 'VARIABLE_IF' is defined Parse_PushInput: file variable-if-triple-negation.tmp, line 1 Parse_PushInput: file variable-if-triple-negation.tmp, line 1 Parse_PushInput: file variable-if-spaced.tmp, line 1 Parse_PushInput: file variable-if-spaced.tmp, line 1 Parse_PushInput: file variable-if-parenthesized.tmp, line 1 Parse_PushInput: file variable-if-parenthesized.tmp, line 1 Parse_PushInput: file variable-ifdef-negated.tmp, line 1 Parse_PushInput: file variable-ifdef-negated.tmp, line 1 Parse_PushInput: file variable-name-mismatch.tmp, line 1 Parse_PushInput: file variable-name-mismatch.tmp, line 1 Parse_PushInput: file variable-ifndef-parenthesized.tmp, line 1 Parse_PushInput: file variable-ifndef-parenthesized.tmp, line 1 Parse_PushInput: file variable-name-exclamation.tmp, line 1 Parse_PushInput: file variable-name-exclamation.tmp, line 1 Parse_PushInput: file variable-name-exclamation-middle.tmp, line 1 Parse_PushInput: file variable-name-exclamation-middle.tmp, line 1 Parse_PushInput: file variable-name-parentheses.tmp, line 1 Parse_PushInput: file variable-name-parentheses.tmp, line 1 Parse_PushInput: file variable-ifndef-plus.tmp, line 1 Parse_PushInput: file variable-ifndef-plus.tmp, line 1 Parse_PushInput: file variable-if-plus.tmp, line 1 Parse_PushInput: file variable-if-plus.tmp, line 1 Parse_PushInput: file variable-ifndef-indirect.tmp, line 1 Parse_PushInput: file variable-ifndef-indirect.tmp, line 1 Parse_PushInput: file variable-if-indirect.tmp, line 1 Parse_PushInput: file variable-if-indirect.tmp, line 1 Parse_PushInput: file variable-assign-indirect.tmp, line 1 Skipping 'variable-assign-indirect.tmp' because 'VARIABLE_ASSIGN_INDIRECT' is defined Parse_PushInput: file variable-assign-late.tmp, line 1 Skipping 'variable-assign-late.tmp' because 'VARIABLE_ASSIGN_LATE' is defined Parse_PushInput: file variable-assign-nested.tmp, line 1 Parse_PushInput: .for loop in variable-assign-nested.tmp, line 3 Skipping 'variable-assign-nested.tmp' because 'VARIABLE_ASSIGN_NESTED' is defined Parse_PushInput: file variable-already-defined.tmp, line 1 Skipping 'variable-already-defined.tmp' because 'VARIABLE_ALREADY_DEFINED' is defined Parse_PushInput: file variable-defined-then-undefined.tmp, line 1 Parse_PushInput: file variable-defined-then-undefined.tmp, line 1 Parse_PushInput: file variable-two-times.tmp, line 1 Parse_PushInput: file variable-two-times.tmp, line 1 Parse_PushInput: file variable-clash.tmp, line 1 Skipping 'variable-clash.tmp' because 'VARIABLE_IF' is defined Parse_PushInput: file variable-swapped.tmp, line 1 Parse_PushInput: file variable-swapped.tmp, line 1 Parse_PushInput: file variable-undef-between.tmp, line 1 Parse_PushInput: file variable-undef-between.tmp, line 1 Parse_PushInput: file variable-undef-inside.tmp, line 1 Parse_PushInput: file variable-undef-inside.tmp, line 1 Parse_PushInput: file variable-not-defined.tmp, line 1 Parse_PushInput: file variable-not-defined.tmp, line 1 Parse_PushInput: file elif.tmp, line 1 Parse_PushInput: file elif.tmp, line 1 Parse_PushInput: file elif-reuse.tmp, line 1 Parse_PushInput: file elif-reuse.tmp, line 1 Parse_PushInput: file else.tmp, line 1 Parse_PushInput: file else.tmp, line 1 Parse_PushInput: file else-reuse.tmp, line 1 Parse_PushInput: file else-reuse.tmp, line 1 Parse_PushInput: file inner-if-elif-else.tmp, line 1 Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined Parse_PushInput: file target.tmp, line 1 Skipping 'target.tmp' because '__target.tmp__' is defined Parse_PushInput: file target-sys.tmp, line 1 Skipping 'target-sys.tmp' because '____' is defined Parse_PushInput: file target-indirect.tmp, line 1 Skipping 'target-indirect.tmp' because 'target-indirect.tmp' is defined Parse_PushInput: file target-indirect-PARSEFILE.tmp, line 1 Skipping 'target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined Parse_PushInput: file target-indirect-PARSEFILE2.tmp, line 1 Skipping 'target-indirect-PARSEFILE2.tmp' because '__target-indirect-PARSEFILE2.tmp__' is defined Parse_PushInput: file subdir/target-indirect-PARSEFILE.tmp, line 1 Skipping 'subdir/target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined Parse_PushInput: file target-indirect-PARSEDIR-PARSEFILE.tmp, line 1 Skipping 'target-indirect-PARSEDIR-PARSEFILE.tmp' because '__target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined Parse_PushInput: file subdir/target-indirect-PARSEDIR-PARSEFILE.tmp, line 1 Skipping 'subdir/target-indirect-PARSEDIR-PARSEFILE.tmp' because '__subdir/target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined Parse_PushInput: file target-unguarded.tmp, line 1 Parse_PushInput: file target-unguarded.tmp, line 1 Parse_PushInput: file target-plus.tmp, line 1 Parse_PushInput: file target-plus.tmp, line 1 Parse_PushInput: file target-already-defined.tmp, line 1 Skipping 'target-already-defined.tmp' because 'target-already-defined' is defined Parse_PushInput: file target-name-exclamation.tmp, line 1 Parse_PushInput: file target-name-exclamation.tmp, line 1 -Parse_PushInput: file target-name-parenthesized.tmp, line 1 -Parse_PushInput: file target-name-parenthesized.tmp, line 1 +Parse_PushInput: file target-name-leading-space.tmp, line 1 +Parse_PushInput: file target-name-leading-space.tmp, line 1 +Parse_PushInput: file target-name-trailing-space.tmp, line 1 +Parse_PushInput: file target-name-trailing-space.tmp, line 1 Parse_PushInput: file target-call-parenthesized.tmp, line 1 Parse_PushInput: file target-call-parenthesized.tmp, line 1 Parse_PushInput: file multiline.tmp, line 1 Skipping 'multiline.tmp' because 'MULTILINE' is defined exit status 0 diff --git a/contrib/bmake/unit-tests/directive-include-guard.mk b/contrib/bmake/unit-tests/directive-include-guard.mk index 85c0242c2009..4c4194be4c36 100644 --- a/contrib/bmake/unit-tests/directive-include-guard.mk +++ b/contrib/bmake/unit-tests/directive-include-guard.mk @@ -1,638 +1,648 @@ -# $NetBSD: directive-include-guard.mk,v 1.16 2023/12/17 14:07:22 rillig Exp $ +# $NetBSD: directive-include-guard.mk,v 1.18 2024/08/07 05:48:45 rillig Exp $ # # Tests for multiple-inclusion guards in makefiles. # # A file that is guarded by a multiple-inclusion guard has one of the # following forms: # # .ifndef GUARD_VARIABLE # .endif # # .if !defined(GUARD_VARIABLE) # .endif # # .if !target(guard-target) # .endif # # When such a file is included for the second or later time, and the guard # variable or the guard target is defined, the file is skipped completely, as # including it would not have any effect, not even on the special variable # '.MAKE.MAKEFILES', as that variable skips duplicate pathnames. # # See also: # https://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html # Each of the following test cases creates a temporary file named after the # test case and writes some lines of text to that file. That file is then # included twice, to see whether the second '.include' is skipped. # This is the canonical form of a variable-based multiple-inclusion guard. CASES+= variable-ifndef LINES.variable-ifndef= \ '.ifndef VARIABLE_IFNDEF' \ 'VARIABLE_IFNDEF=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef.tmp, line 1 # expect: Skipping 'variable-ifndef.tmp' because 'VARIABLE_IFNDEF' is defined # A file that reuses a guard from a previous file (or whose guard is defined # for any other reason) is only processed once, to see whether it is guarded. # Its content is skipped, therefore the syntax error is not detected. CASES+= variable-ifndef-reuse LINES.variable-ifndef-reuse= \ '.ifndef VARIABLE_IFNDEF' \ 'syntax error' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-reuse.tmp, line 1 # expect: Skipping 'variable-ifndef-reuse.tmp' because 'VARIABLE_IFNDEF' is defined # The guard variable cannot be a number, as numbers are interpreted # differently from bare words. CASES+= variable-ifndef-zero LINES.variable-ifndef-zero= \ '.ifndef 0e0' \ 'syntax error' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-zero.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-zero.tmp, line 1 # The guard variable cannot be a number, as numbers are interpreted # differently from bare words. CASES+= variable-ifndef-one LINES.variable-ifndef-one= \ '.ifndef 1' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-one.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-one.tmp, line 1 # Comments and empty lines do not affect the multiple-inclusion guard. CASES+= comments LINES.comments= \ '\# comment' \ '' \ '.ifndef COMMENTS' \ '\# comment' \ 'COMMENTS=\#comment' \ '.endif' \ '\# comment' # expect: Parse_PushInput: file comments.tmp, line 1 # expect: Skipping 'comments.tmp' because 'COMMENTS' is defined # An alternative form uses the 'defined' function. It is more verbose than # the canonical form but avoids the '.ifndef' directive, as that directive is # not commonly used. CASES+= variable-if LINES.variable-if= \ '.if !defined(VARIABLE_IF)' \ 'VARIABLE_IF=' \ '.endif' # expect: Parse_PushInput: file variable-if.tmp, line 1 # expect: Skipping 'variable-if.tmp' because 'VARIABLE_IF' is defined # A file that reuses a guard from a previous file (or whose guard is defined # for any other reason) is only processed once, to see whether it is guarded. # Its content is skipped, therefore the syntax error is not detected. CASES+= variable-if-reuse LINES.variable-if-reuse= \ '.if !defined(VARIABLE_IF)' \ 'syntax error' \ '.endif' # expect: Parse_PushInput: file variable-if-reuse.tmp, line 1 # expect: Skipping 'variable-if-reuse.tmp' because 'VARIABLE_IF' is defined # Triple negation is so uncommon that it's not recognized, even though it has # the same effect as a single negation. CASES+= variable-if-triple-negation LINES.variable-if-triple-negation= \ '.if !!!defined(VARIABLE_IF_TRIPLE_NEGATION)' \ 'VARIABLE_IF_TRIPLE_NEGATION=' \ '.endif' # expect: Parse_PushInput: file variable-if-triple-negation.tmp, line 1 # expect: Parse_PushInput: file variable-if-triple-negation.tmp, line 1 # If the guard variable is enclosed in spaces, it does not have an effect, as # that form is not common in practice. CASES+= variable-if-spaced LINES.variable-if-spaced= \ '.if !defined( VARIABLE_IF_SPACED )' \ 'VARIABLE_IF_SPACED=' \ '.endif' # expect: Parse_PushInput: file variable-if-spaced.tmp, line 1 # expect: Parse_PushInput: file variable-if-spaced.tmp, line 1 # If the guard variable condition is enclosed in parentheses, it does not have # an effect, as that form is not common in practice. CASES+= variable-if-parenthesized LINES.variable-if-parenthesized= \ '.if (!defined(VARIABLE_IF_PARENTHESIZED))' \ 'VARIABLE_IF_PARENTHESIZED=' \ '.endif' # expect: Parse_PushInput: file variable-if-parenthesized.tmp, line 1 # expect: Parse_PushInput: file variable-if-parenthesized.tmp, line 1 # A conditional other than '.if' or '.ifndef' does not guard the file, even if # it is otherwise equivalent to the above accepted forms. CASES+= variable-ifdef-negated LINES.variable-ifdef-negated= \ '.ifdef !VARIABLE_IFDEF_NEGATED' \ 'VARIABLE_IFDEF_NEGATED=' \ '.endif' # expect: Parse_PushInput: file variable-ifdef-negated.tmp, line 1 # expect: Parse_PushInput: file variable-ifdef-negated.tmp, line 1 # The variable names in the '.if' and the assignment must be the same. CASES+= variable-name-mismatch LINES.variable-name-mismatch= \ '.ifndef VARIABLE_NAME_MISMATCH' \ 'VARIABLE_NAME_DIFFERENT=' \ '.endif' # expect: Parse_PushInput: file variable-name-mismatch.tmp, line 1 # expect: Parse_PushInput: file variable-name-mismatch.tmp, line 1 # If the guard variable condition is enclosed in parentheses, it does not have # an effect, as that form is not common in practice. CASES+= variable-ifndef-parenthesized LINES.variable-ifndef-parenthesized= \ '.ifndef (VARIABLE_IFNDEF_PARENTHESIZED)' \ 'VARIABLE_IFNDEF_PARENTHESIZED=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-parenthesized.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-parenthesized.tmp, line 1 # The variable name '!VARNAME' cannot be used in an '.ifndef' directive, as # the '!' would be a negation. It is syntactically valid in a '.if !defined' # condition, but this case is so uncommon that the guard mechanism doesn't # accept '!' in the guard variable name. Furthermore, when defining the # variable, the character '!' has to be escaped, to prevent it from being # interpreted as the '!' dependency operator. CASES+= variable-name-exclamation LINES.variable-name-exclamation= \ '.if !defined(!VARIABLE_NAME_EXCLAMATION)' \ '${:U!}VARIABLE_NAME_EXCLAMATION=' \ '.endif' # expect: Parse_PushInput: file variable-name-exclamation.tmp, line 1 # expect: Parse_PushInput: file variable-name-exclamation.tmp, line 1 # In general, a variable name can contain a '!' in the middle, as that # character is interpreted as an ordinary character in conditions as well as # on the left side of a variable assignment. For guard variable names, the # '!' is not supported in any place, though. CASES+= variable-name-exclamation-middle LINES.variable-name-exclamation-middle= \ '.ifndef VARIABLE_NAME!MIDDLE' \ 'VARIABLE_NAME!MIDDLE=' \ '.endif' # expect: Parse_PushInput: file variable-name-exclamation-middle.tmp, line 1 # expect: Parse_PushInput: file variable-name-exclamation-middle.tmp, line 1 # A variable name can contain balanced parentheses, at least in conditions and # on the left side of a variable assignment. There are enough places in make # where parentheses or braces are handled inconsistently to make this naming # choice a bad idea, therefore these characters are not allowed in guard # variable names. CASES+= variable-name-parentheses LINES.variable-name-parentheses= \ '.ifndef VARIABLE_NAME(&)PARENTHESES' \ 'VARIABLE_NAME(&)PARENTHESES=' \ '.endif' # expect: Parse_PushInput: file variable-name-parentheses.tmp, line 1 # expect: Parse_PushInput: file variable-name-parentheses.tmp, line 1 # The guard condition must consist of only the guard variable, nothing else. CASES+= variable-ifndef-plus LINES.variable-ifndef-plus= \ '.ifndef VARIABLE_IFNDEF_PLUS && VARIABLE_IFNDEF_SECOND' \ 'VARIABLE_IFNDEF_PLUS=' \ 'VARIABLE_IFNDEF_SECOND=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-plus.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-plus.tmp, line 1 # The guard condition must consist of only the guard variable, nothing else. CASES+= variable-if-plus LINES.variable-if-plus= \ '.if !defined(VARIABLE_IF_PLUS) && !defined(VARIABLE_IF_SECOND)' \ 'VARIABLE_IF_PLUS=' \ 'VARIABLE_IF_SECOND=' \ '.endif' # expect: Parse_PushInput: file variable-if-plus.tmp, line 1 # expect: Parse_PushInput: file variable-if-plus.tmp, line 1 # The variable name in an '.ifndef' guard must be given directly, it must not # contain any '$' expression. CASES+= variable-ifndef-indirect LINES.variable-ifndef-indirect= \ '.ifndef $${VARIABLE_IFNDEF_INDIRECT:L}' \ 'VARIABLE_IFNDEF_INDIRECT=' \ '.endif' # expect: Parse_PushInput: file variable-ifndef-indirect.tmp, line 1 # expect: Parse_PushInput: file variable-ifndef-indirect.tmp, line 1 # The variable name in an '.if' guard must be given directly, it must not # contain any '$' expression. CASES+= variable-if-indirect LINES.variable-if-indirect= \ '.if !defined($${VARIABLE_IF_INDIRECT:L})' \ 'VARIABLE_IF_INDIRECT=' \ '.endif' # expect: Parse_PushInput: file variable-if-indirect.tmp, line 1 # expect: Parse_PushInput: file variable-if-indirect.tmp, line 1 # The variable name in the guard condition must only contain alphanumeric # characters and underscores. The place where the guard variable is defined # is more flexible, as long as the variable is defined at the point where the # file is included the next time. CASES+= variable-assign-indirect LINES.variable-assign-indirect= \ '.ifndef VARIABLE_ASSIGN_INDIRECT' \ '$${VARIABLE_ASSIGN_INDIRECT:L}=' \ '.endif' # expect: Parse_PushInput: file variable-assign-indirect.tmp, line 1 # expect: Skipping 'variable-assign-indirect.tmp' because 'VARIABLE_ASSIGN_INDIRECT' is defined # The time at which the guard variable is defined doesn't matter, as long as # it is defined at the point where the file is included the next time. CASES+= variable-assign-late LINES.variable-assign-late= \ '.ifndef VARIABLE_ASSIGN_LATE' \ 'VARIABLE_ASSIGN_LATE_OTHER=' \ 'VARIABLE_ASSIGN_LATE=' \ '.endif' # expect: Parse_PushInput: file variable-assign-late.tmp, line 1 # expect: Skipping 'variable-assign-late.tmp' because 'VARIABLE_ASSIGN_LATE' is defined # The time at which the guard variable is defined doesn't matter, as long as # it is defined at the point where the file is included the next time. CASES+= variable-assign-nested LINES.variable-assign-nested= \ '.ifndef VARIABLE_ASSIGN_NESTED' \ '. if 1' \ '. for i in once' \ 'VARIABLE_ASSIGN_NESTED=' \ '. endfor' \ '. endif' \ '.endif' # expect: Parse_PushInput: file variable-assign-nested.tmp, line 1 # expect: Skipping 'variable-assign-nested.tmp' because 'VARIABLE_ASSIGN_NESTED' is defined # If the guard variable is defined before the file is included for the first # time, the file is considered guarded as well. In such a case, the parser # skips almost all lines, as they are irrelevant, but the structure of the # top-level '.if/.endif' conditional can be determined reliably enough to # decide whether the file is guarded. CASES+= variable-already-defined LINES.variable-already-defined= \ '.ifndef VARIABLE_ALREADY_DEFINED' \ 'VARIABLE_ALREADY_DEFINED=' \ '.endif' VARIABLE_ALREADY_DEFINED= # expect: Parse_PushInput: file variable-already-defined.tmp, line 1 # expect: Skipping 'variable-already-defined.tmp' because 'VARIABLE_ALREADY_DEFINED' is defined # If the guard variable is defined before the file is included the first time, # the file is processed but its content is skipped. If that same guard # variable is undefined when the file is included the second time, the file is # processed as usual. CASES+= variable-defined-then-undefined LINES.variable-defined-then-undefined= \ '.ifndef VARIABLE_DEFINED_THEN_UNDEFINED' \ '.endif' VARIABLE_DEFINED_THEN_UNDEFINED= UNDEF_BETWEEN.variable-defined-then-undefined= \ VARIABLE_DEFINED_THEN_UNDEFINED # expect: Parse_PushInput: file variable-defined-then-undefined.tmp, line 1 # expect: Parse_PushInput: file variable-defined-then-undefined.tmp, line 1 # The whole file content must be guarded by a single '.if' conditional, not by # several, as each of these conditionals would require its separate guard. # This case is not expected to occur in practice, as the two parts would # rather be split into separate files. CASES+= variable-two-times LINES.variable-two-times= \ '.ifndef VARIABLE_TWO_TIMES_1' \ 'VARIABLE_TWO_TIMES_1=' \ '.endif' \ '.ifndef VARIABLE_TWO_TIMES_2' \ 'VARIABLE_TWO_TIMES_2=' \ '.endif' # expect: Parse_PushInput: file variable-two-times.tmp, line 1 # expect: Parse_PushInput: file variable-two-times.tmp, line 1 # When multiple files use the same guard variable name, the optimization of # skipping the file affects each of these files. # # Choosing unique guard names is the responsibility of the makefile authors. # A typical pattern of guard variable names is '${PROJECT}_${DIR}_${FILE}_MK'. # System-provided files typically start the guard names with '_'. CASES+= variable-clash LINES.variable-clash= \ ${LINES.variable-if} # expect: Parse_PushInput: file variable-clash.tmp, line 1 # expect: Skipping 'variable-clash.tmp' because 'VARIABLE_IF' is defined # The conditional must come before the assignment, otherwise the conditional # is useless, as it always evaluates to false. CASES+= variable-swapped LINES.variable-swapped= \ 'SWAPPED=' \ '.ifndef SWAPPED' \ '. error' \ '.endif' # expect: Parse_PushInput: file variable-swapped.tmp, line 1 # expect: Parse_PushInput: file variable-swapped.tmp, line 1 # If the guard variable is undefined between the first and the second time the # file is included, the guarded file is included again. CASES+= variable-undef-between LINES.variable-undef-between= \ '.ifndef VARIABLE_UNDEF_BETWEEN' \ 'VARIABLE_UNDEF_BETWEEN=' \ '.endif' UNDEF_BETWEEN.variable-undef-between= \ VARIABLE_UNDEF_BETWEEN # expect: Parse_PushInput: file variable-undef-between.tmp, line 1 # expect: Parse_PushInput: file variable-undef-between.tmp, line 1 # If the guard variable is undefined while the file is included the first # time, the guard does not have an effect, and the file is included again. CASES+= variable-undef-inside LINES.variable-undef-inside= \ '.ifndef VARIABLE_UNDEF_INSIDE' \ 'VARIABLE_UNDEF_INSIDE=' \ '.undef VARIABLE_UNDEF_INSIDE' \ '.endif' # expect: Parse_PushInput: file variable-undef-inside.tmp, line 1 # expect: Parse_PushInput: file variable-undef-inside.tmp, line 1 # If the file does not define the guard variable, the guard does not have an # effect, and the file is included again. CASES+= variable-not-defined LINES.variable-not-defined= \ '.ifndef VARIABLE_NOT_DEFINED' \ '.endif' # expect: Parse_PushInput: file variable-not-defined.tmp, line 1 # expect: Parse_PushInput: file variable-not-defined.tmp, line 1 # The outermost '.if' must not have an '.elif' branch. CASES+= elif LINES.elif= \ '.ifndef ELIF' \ 'ELIF=' \ '.elif 1' \ '.endif' # expect: Parse_PushInput: file elif.tmp, line 1 # expect: Parse_PushInput: file elif.tmp, line 1 # When a file with an '.if/.elif/.endif' conditional at the top level is # included, it is never optimized, as one of its branches is taken. CASES+= elif-reuse LINES.elif-reuse= \ '.ifndef ELIF' \ 'syntax error' \ '.elif 1' \ '.endif' # expect: Parse_PushInput: file elif-reuse.tmp, line 1 # expect: Parse_PushInput: file elif-reuse.tmp, line 1 # The outermost '.if' must not have an '.else' branch. CASES+= else LINES.else= \ '.ifndef ELSE' \ 'ELSE=' \ '.else' \ '.endif' # expect: Parse_PushInput: file else.tmp, line 1 # expect: Parse_PushInput: file else.tmp, line 1 # When a file with an '.if/.else/.endif' conditional at the top level is # included, it is never optimized, as one of its branches is taken. CASES+= else-reuse LINES.else-reuse= \ '.ifndef ELSE' \ 'syntax error' \ '.else' \ '.endif' # expect: Parse_PushInput: file else-reuse.tmp, line 1 # expect: Parse_PushInput: file else-reuse.tmp, line 1 # The inner '.if' directives may have an '.elif' or '.else', and it doesn't # matter which of their branches are taken. CASES+= inner-if-elif-else LINES.inner-if-elif-else= \ '.ifndef INNER_IF_ELIF_ELSE' \ 'INNER_IF_ELIF_ELSE=' \ '. if 0' \ '. elif 0' \ '. else' \ '. endif' \ '. if 0' \ '. elif 1' \ '. else' \ '. endif' \ '. if 1' \ '. elif 1' \ '. else' \ '. endif' \ '.endif' # expect: Parse_PushInput: file inner-if-elif-else.tmp, line 1 # expect: Skipping 'inner-if-elif-else.tmp' because 'INNER_IF_ELIF_ELSE' is defined # The guard can also be a target instead of a variable. Using a target as a # guard has the benefit that a target cannot be undefined once it is defined. # The target should be declared '.NOTMAIN'. Since the target names are # usually chosen according to a pattern that doesn't interfere with real # target names, they don't need to be declared '.PHONY' as they don't generate # filesystem operations. CASES+= target LINES.target= \ '.if !target(__target.tmp__)' \ '__target.tmp__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target.tmp, line 1 # expect: Skipping 'target.tmp' because '__target.tmp__' is defined # When used for system files, the target name may include '<' and '>', for # symmetry with the '.include ' directive. The characters '<' and '>' # are ordinary characters. CASES+= target-sys LINES.target-sys= \ '.if !target(____)' \ '____: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-sys.tmp, line 1 # expect: Skipping 'target-sys.tmp' because '____' is defined # The target name may include variable references. These references are # expanded as usual. Due to the current implementation, the expressions are # evaluated twice: Once for checking whether the condition evaluates to true, # and once for determining the guard name. This double evaluation should not # matter in practice, as guard expressions are expected to be simple, # deterministic and without side effects. CASES+= target-indirect LINES.target-indirect= \ '.if !target($${target-indirect.tmp:L})' \ 'target-indirect.tmp: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect.tmp, line 1 # expect: Skipping 'target-indirect.tmp' because 'target-indirect.tmp' is defined # A common form of guard target is __${.PARSEFILE}__. This form can only be # used if all files using this form have unique basenames. To get a robust # pattern based on the same idea, use __${.PARSEDIR}/${.PARSEFILE}__ instead. # This form does not work when the basename contains whitespace characters, as # it is not possible to define a target with whitespace, not even by cheating. CASES+= target-indirect-PARSEFILE LINES.target-indirect-PARSEFILE= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect-PARSEFILE.tmp, line 1 # expect: Skipping 'target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined # Two files with different basenames can both use the same syntactic pattern # for the target guard name, as the expressions expand to different strings. CASES+= target-indirect-PARSEFILE2 LINES.target-indirect-PARSEFILE2= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect-PARSEFILE2.tmp, line 1 # expect: Skipping 'target-indirect-PARSEFILE2.tmp' because '__target-indirect-PARSEFILE2.tmp__' is defined # Using plain .PARSEFILE without .PARSEDIR leads to name clashes. The include # guard is the same as in the test case 'target-indirect-PARSEFILE', as the # guard name only contains the basename but not the directory name. So even # without defining the guard target, the file is considered guarded. CASES+= subdir/target-indirect-PARSEFILE LINES.subdir/target-indirect-PARSEFILE= \ '.if !target(__$${.PARSEFILE}__)' \ '.endif' # expect: Parse_PushInput: file subdir/target-indirect-PARSEFILE.tmp, line 1 # expect: Skipping 'subdir/target-indirect-PARSEFILE.tmp' because '__target-indirect-PARSEFILE.tmp__' is defined # Another common form of guard target is __${.PARSEDIR}/${.PARSEFILE}__ # or __${.PARSEDIR:tA}/${.PARSEFILE}__ to be truly unique. CASES+= target-indirect-PARSEDIR-PARSEFILE LINES.target-indirect-PARSEDIR-PARSEFILE= \ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-indirect-PARSEDIR-PARSEFILE.tmp, line 1 # expect: Skipping 'target-indirect-PARSEDIR-PARSEFILE.tmp' because '__target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined # The actual target starts with '__${.OBJDIR}/', see the .rawout file, but the # string '${.OBJDIR}/' gets stripped in post processing. # Using the combination of '.PARSEDIR' and '.PARSEFILE', a file in a # subdirectory gets a different guard target name than the previous one. CASES+= subdir/target-indirect-PARSEDIR-PARSEFILE LINES.subdir/target-indirect-PARSEDIR-PARSEFILE= \ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file subdir/target-indirect-PARSEDIR-PARSEFILE.tmp, line 1 # expect: Skipping 'subdir/target-indirect-PARSEDIR-PARSEFILE.tmp' because '__subdir/target-indirect-PARSEDIR-PARSEFILE.tmp__' is defined # The actual target starts with '__${.OBJDIR}/', see the .rawout file, but the # string '${.OBJDIR}/' gets stripped in post processing. # If the guard target is not defined when including the file the next time, # the file is processed again. CASES+= target-unguarded LINES.target-unguarded= \ '.if !target(target-unguarded)' \ '.endif' # expect: Parse_PushInput: file target-unguarded.tmp, line 1 # expect: Parse_PushInput: file target-unguarded.tmp, line 1 # The guard condition must consist of only the guard target, nothing else. CASES+= target-plus LINES.target-plus= \ '.if !target(target-plus) && 1' \ 'target-plus: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-plus.tmp, line 1 # expect: Parse_PushInput: file target-plus.tmp, line 1 # If the guard target is defined before the file is included the first time, # the file is read once and then considered guarded. CASES+= target-already-defined LINES.target-already-defined= \ '.if !target(target-already-defined)' \ 'target-already-defined: .NOTMAIN' \ '.endif' target-already-defined: .NOTMAIN # expect: Parse_PushInput: file target-already-defined.tmp, line 1 # expect: Skipping 'target-already-defined.tmp' because 'target-already-defined' is defined # A target name cannot contain the character '!'. In the condition, the '!' # is syntactically valid, but in the dependency declaration line, the '!' is # interpreted as the '!' dependency operator, no matter whether it occurs at # the beginning or in the middle of a target name. Escaping it as '${:U!}' # doesn't work, as the whole line is first expanded and then scanned for the # dependency operator. Escaping it as '\!' doesn't work either, even though # the '\' escapes the '!' from being a dependency operator, but when reading # the target name, the '\' is kept, resulting in the target name # '\!target-name-exclamation' instead of '!target-name-exclamation'. CASES+= target-name-exclamation LINES.target-name-exclamation= \ '.if !target(!target-name-exclamation)' \ '\!target-name-exclamation: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-name-exclamation.tmp, line 1 # expect: Parse_PushInput: file target-name-exclamation.tmp, line 1 -# If the guard target name is enclosed in spaces, it does not have an effect, +# If the guard target name has leading spaces, it does not have an effect, # as that form is not common in practice. -CASES+= target-name-parenthesized -LINES.target-name-parenthesized= \ - '.if !target( target-name-parenthesized )' \ - 'target-name-parenthesized: .NOTMAIN' \ +CASES+= target-name-leading-space +LINES.target-name-leading-space= \ + '.if !target( target-name-leading-space)' \ + 'target-name-leading-space: .NOTMAIN' \ '.endif' -# expect: Parse_PushInput: file target-name-parenthesized.tmp, line 1 -# expect: Parse_PushInput: file target-name-parenthesized.tmp, line 1 +# expect: Parse_PushInput: file target-name-leading-space.tmp, line 1 +# expect: Parse_PushInput: file target-name-leading-space.tmp, line 1 + +# If the guard target name has trailing spaces, it does not have an effect, +# as that form is not common in practice. +CASES+= target-name-trailing-space +LINES.target-name-trailing-space= \ + '.if !target(target-name-trailing-space )' \ + 'target-name-trailing-space: .NOTMAIN' \ + '.endif' +# expect: Parse_PushInput: file target-name-trailing-space.tmp, line 1 +# expect: Parse_PushInput: file target-name-trailing-space.tmp, line 1 # If the guard target condition is enclosed in parentheses, it does not have # an effect, as that form is not common in practice. CASES+= target-call-parenthesized LINES.target-call-parenthesized= \ '.if (!target(target-call-parenthesized))' \ 'target-call-parenthesized: .NOTMAIN' \ '.endif' # expect: Parse_PushInput: file target-call-parenthesized.tmp, line 1 # expect: Parse_PushInput: file target-call-parenthesized.tmp, line 1 # If the '.if' or '.ifndef' directive spans more than a single line, it is # still recognized as a guard condition. This case is entirely uncommon, but # at the point where the guard condition is checked, line continuations have # already been converted to spaces. CASES+= multiline LINES.multiline= \ '.\' \ ' ifndef \' \ ' MULTILINE' \ 'MULTILINE=' \ '.endif' # expect: Parse_PushInput: file multiline.tmp, line 1 # expect: Skipping 'multiline.tmp' because 'MULTILINE' is defined # Now run all test cases by including each of the files twice and looking at # the debug output. The files that properly guard against multiple inclusion # generate a 'Skipping' line, the others repeat the 'Parse_PushInput' line. # # Some debug output lines are suppressed in the .exp file, see ./Makefile. .for i in ${CASES} . for fname in $i.tmp _:= ${fname:H:N.:@dir@${:!mkdir -p ${dir}!}@} _!= printf '%s\n' ${LINES.$i} > ${fname} .MAKEFLAGS: -dp .include "${.CURDIR}/${fname}" .undef ${UNDEF_BETWEEN.$i:U} .include "${.CURDIR}/${fname}" .MAKEFLAGS: -d0 _!= rm ${fname} _:= ${fname:H:N.:@dir@${:!rmdir ${dir}!}@} . endfor .endfor all: diff --git a/contrib/bmake/unit-tests/directive-include.exp b/contrib/bmake/unit-tests/directive-include.exp index 4c395d7532aa..11a6ab8bc88a 100755 --- a/contrib/bmake/unit-tests/directive-include.exp +++ b/contrib/bmake/unit-tests/directive-include.exp @@ -1,13 +1,14 @@ CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" Comparing "directive-include.mk null" != "directive-include.mk null" CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" Comparing "directive-include.mk null" != "directive-include.mk null" make: "directive-include.mk" line 26: Could not find nonexistent.mk make: "directive-include.mk" line 49: Could not find " -make: "directive-include.mk" line 56: while evaluating "${:U123:Z}.mk" with value "123": Unknown modifier "Z" +make: "directive-include.mk" line 56: Unknown modifier "Z" + while evaluating "${:U123:Z}.mk" with value "123" make: "directive-include.mk" line 56: Could not find nonexistent.mk make: "directive-include.mk" line 61: Cannot open /nonexistent make: "directive-include.mk" line 66: Invalid line 'include' make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-include.mk b/contrib/bmake/unit-tests/directive-include.mk index e2ef8dc55e3c..bf3dd158b2ad 100755 --- a/contrib/bmake/unit-tests/directive-include.mk +++ b/contrib/bmake/unit-tests/directive-include.mk @@ -1,89 +1,89 @@ -# $NetBSD: directive-include.mk,v 1.15 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: directive-include.mk,v 1.17 2025/01/11 20:16:40 rillig Exp $ # # Tests for the .include directive, which includes another file. # TODO: Implementation .MAKEFLAGS: -dc # All included files are recorded in the variable .MAKE.MAKEFILES. # In this test, only the basenames of the files are compared since # the directories can differ. .include "/dev/null" .if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" . error .endif # Each file is recorded only once in the variable .MAKE.MAKEFILES. # Between 2015-11-26 and 2020-10-31, the very last file could be repeated, # due to an off-by-one bug in ParseTrackInput. .include "/dev/null" .if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" . error .endif # expect+1: Could not find nonexistent.mk .include "nonexistent.mk" .include "/dev/null" # size 0 # including a directory technically succeeds, but shouldn't. #.include "." # directory # As of 2020-11-21, anything after the delimiter '"' is ignored. .include "/dev/null" and ignore anything in the rest of the line. # The filename to be included can contain expressions. DEV= null .include "/dev/${DEV}" # Expressions in double quotes or angle quotes are first parsed naively, to # find the closing '"'. In a second step, the expressions are expanded. This # means that the expressions cannot include the characters '"' or '>'. This # restriction is not practically relevant since the expressions inside # '.include' directives are typically kept as simple as possible. # # If the whole line were expanded before parsing, the filename to be included # would be empty, and the closing '"' would be in the trailing part of the # line, which is ignored as of 2021-12-03. DQUOT= " # expect+1: Could not find " .include "${DQUOT}" # When the expression in a filename cannot be evaluated, the failing # expression is skipped and the file is included nevertheless. # FIXME: Add proper error handling, no file must be included here. -# expect+2: Could not find nonexistent.mk -# expect+1: while evaluating "${:U123:Z}.mk" with value "123": Unknown modifier "Z" +# expect+2: Unknown modifier "Z" +# expect+1: Could not find nonexistent.mk .include "nonexistent${:U123:Z}.mk" # The traditional include directive is seldom used. include /dev/null # comment # expect+1: Cannot open /nonexistent include /nonexistent # comment sinclude /nonexistent # comment include ${:U/dev/null} # comment include /dev/null /dev/null # expect+1: Invalid line 'include' include # XXX: trailing whitespace in diagnostic, missing quotes around filename ### TODO: expect+1: Could not find # The following include directive behaves differently, depending on whether # the current file has a slash or is a relative filename. In the first case, # make opens the directory of the current file and tries to read from it, # resulting in the error message """ line 1: Zero byte read from file". # In the second case, the error message is "Could not find ", without quotes # or any other indicator for the empty filename at the end of the line. #include ${:U} # Since parse.c 1.612 from 2022-01-01 and before parse.c 1.620 from # 2022-01-07, including an empty regular file called bmake_malloc(0), which # may return a null pointer. On OpenBSD, this led to a segmentation fault in # Buf_InitSize, which assumes that bmake_malloc never returns NULL, just like # all other places in the code. _!= > directive-include-empty .include "${.CURDIR}/directive-include-empty" _!= rm directive-include-empty all: diff --git a/contrib/bmake/unit-tests/directive-undef.exp b/contrib/bmake/unit-tests/directive-undef.exp index c3d01fc480c6..c62452abe2ba 100644 --- a/contrib/bmake/unit-tests/directive-undef.exp +++ b/contrib/bmake/unit-tests/directive-undef.exp @@ -1,6 +1,7 @@ make: "directive-undef.mk" line 30: The .undef directive requires an argument -make: "directive-undef.mk" line 88: while evaluating variable "VARNAMES" with value "VARNAMES": Unknown modifier "Z" +make: "directive-undef.mk" line 88: Unknown modifier "Z" + while evaluating variable "VARNAMES" with value "VARNAMES" make: "directive-undef.mk" line 105: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/directive-undef.mk b/contrib/bmake/unit-tests/directive-undef.mk index 43938e3170ea..167bb79ad07f 100644 --- a/contrib/bmake/unit-tests/directive-undef.mk +++ b/contrib/bmake/unit-tests/directive-undef.mk @@ -1,148 +1,148 @@ -# $NetBSD: directive-undef.mk,v 1.15 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: directive-undef.mk,v 1.16 2024/08/29 20:20:36 rillig Exp $ # # Tests for the .undef directive. # # See also: # directive-misspellings.mk # Before var.c 1.737 from 2020-12-19, .undef only undefined the first # variable, silently skipping all further variable names. # # Before var.c 1.761 from 2020-12-22, .undef complained about too many # arguments. # # Since var.c 1.761 from 2020-12-22, .undef handles multiple variable names # just like the .export directive. 1= 1 2= 2 3= 3 .undef 1 2 3 .if ${1:U_}${2:U_}${3:U_} != ___ . warning $1$2$3 .endif # Without any arguments, until var.c 1.736 from 2020-12-19, .undef tried # to delete the variable with the empty name, which never exists; see # varname-empty.mk. Since var.c 1.737 from 2020-12-19, .undef complains # about a missing argument. # expect+1: The .undef directive requires an argument .undef # Trying to delete the variable with the empty name is ok, it just won't # ever do anything since that variable is never defined. .undef ${:U} # The argument of .undef is first expanded exactly once and then split into # words, just like everywhere else. This prevents variables whose names # contain spaces or unbalanced 'single' or "double" quotes from being # undefined, but these characters do not appear in variables names anyway. 1= 1 2= 2 3= 3 ${:U1 2 3}= one two three VARNAMES= 1 2 3 .undef ${VARNAMES} # undefines the variables "1", "2" and "3" .if ${${:U1 2 3}} != "one two three" # still there . error .endif .if ${1:U_}${2:U_}${3:U_} != "___" # these have been undefined . error .endif # A variable named " " cannot be undefined. There's no practical use case # for such variables anyway. SPACE= ${:U } ${SPACE}= space .if !defined(${SPACE}) . error .endif .undef ${SPACE} .if !defined(${SPACE}) . error .endif # A variable named "$" can be undefined since the argument to .undef is # expanded exactly once, before being split into words. DOLLAR= $$ ${DOLLAR}= dollar .if !defined(${DOLLAR}) . error .endif .undef ${DOLLAR} .if defined(${DOLLAR}) . error .endif # Since var.c 1.762 from 2020-12-22, parse errors in the argument should be # properly detected and should stop the .undef directive from doing any work. # # As of var.c 1.762, this doesn't happen though because the error handling # in Var_Parse and Var_Subst is not done properly. -# expect+1: while evaluating variable "VARNAMES" with value "VARNAMES": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" .undef ${VARNAMES:L:Z} UT_EXPORTED= exported-value .export UT_EXPORTED .if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "exported-value" . error .endif .if !${.MAKE.EXPORTED:MUT_EXPORTED} . error .endif .undef UT_EXPORTED # XXX: does not update .MAKE.EXPORTED .if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "not-exported" . error .endif .if ${.MAKE.EXPORTED:MUT_EXPORTED} # expect+1: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. . warning UT_EXPORTED is still listed in .MAKE.EXPORTED even though $\ it is not exported anymore. .endif # When an exported variable is undefined, the variable is removed both from # the global scope as well as from the environment. DIRECT= direct INDIRECT= in-${DIRECT} .export DIRECT INDIRECT .if ${DIRECT} != "direct" . error .endif .if ${INDIRECT} != "in-direct" . error .endif # Deletes the variables from the global scope and also from the environment. # This applies to both variables, even though 'INDIRECT' is not actually # exported yet since it refers to another variable. .undef DIRECT # Separate '.undef' directives, .undef INDIRECT # for backwards compatibility. .if ${DIRECT:Uundefined} != "undefined" . error .endif .if ${INDIRECT:Uundefined} != "undefined" . error .endif # Since var.c 1.570 from 2020-10-06 and before var.c 1.1014 from 2022-03-26, # make ran into an assertion failure when trying to undefine a variable that # was based on an environment variable. .if ${ENV_VAR} != "env-value" # see ./Makefile, ENV.directive-undef . error .endif ENV_VAR+= appended # moves the short-lived variable to the # global scope .undef ENV_VAR # removes the variable from both the global # scope and from the environment all: diff --git a/contrib/bmake/unit-tests/include-main.exp b/contrib/bmake/unit-tests/include-main.exp index fcc38247109f..f2b4263fccc4 100644 --- a/contrib/bmake/unit-tests/include-main.exp +++ b/contrib/bmake/unit-tests/include-main.exp @@ -1,17 +1,17 @@ make: "include-main.mk" line 15: main-before-ok make: "include-main.mk" line 23: main-before-for-ok make: "include-sub.inc" line 4: sub-before-ok make: "include-sub.inc" line 14: sub-before-for-ok -Parsing line 5: . info subsub-ok +Parsing include-subsub.inc:5: . info subsub-ok make: "include-subsub.inc" line 5: subsub-ok in .for loop from include-sub.inc:31 with i = include in .for loop from include-sub.inc:30 with i = nested in .for loop from include-sub.inc:29 with i = deeply in include-main.mk:29 -Parsing line 6: .MAKEFLAGS: -d0 +Parsing include-subsub.inc:6: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) make: "include-sub.inc" line 38: sub-after-ok make: "include-sub.inc" line 45: sub-after-for-ok make: "include-main.mk" line 33: main-after-ok make: "include-main.mk" line 41: main-after-for-ok exit status 0 diff --git a/contrib/bmake/unit-tests/lint.exp b/contrib/bmake/unit-tests/lint.exp index 714093ca2b7d..ded93d192b14 100755 --- a/contrib/bmake/unit-tests/lint.exp +++ b/contrib/bmake/unit-tests/lint.exp @@ -1,4 +1,4 @@ -make: in target "mod-loop-varname": while evaluating variable "VAR" with value "value": In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar -y@:Q} -xvaluey +make: In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar + while evaluating variable "VAR" with value "value" + in target "mod-loop-varname" exit status 2 diff --git a/contrib/bmake/unit-tests/moderrs.exp b/contrib/bmake/unit-tests/moderrs.exp index 05a5108935cd..06c305c83e4f 100644 --- a/contrib/bmake/unit-tests/moderrs.exp +++ b/contrib/bmake/unit-tests/moderrs.exp @@ -1,115 +1,140 @@ -make: in target "mod-unknown-direct": while evaluating variable "VAR" with value "TheVariable": Unknown modifier "Z" -VAR:Z=before--after - -make: in target "mod-unknown-indirect": while evaluating variable "VAR" with value "TheVariable": Unknown modifier "Z" -VAR:Z=before-inner}-after - -unclosed-direct: -make: in target "unclosed-direct": while evaluating variable "VAR" with value "Thevariable": Unclosed expression, expecting '}' for modifier "S,V,v," -VAR:S,V,v,=Thevariable - -unclosed-indirect: -make: in target "unclosed-indirect": while evaluating variable "VAR" with value "Thevariable": Unclosed expression after indirect modifier, expecting '}' -VAR:S,V,v,=Thevariable - -make: in target "unfinished-indirect": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -VAR:S,V,v= - -make: in target "unfinished-loop": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier ('@' missing) - -make: in target "unfinished-loop": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier ('@' missing) - +make: Unknown modifier "Z" + while evaluating variable "VAR" with value "TheVariable" + in target "mod-unknown-direct" +make: Unknown modifier "Z" + while evaluating variable "VAR" with value "TheVariable" + in target "mod-unknown-indirect" +make: Unclosed expression, expecting '}' for modifier "S,V,v," + while evaluating variable "VAR" with value "Thevariable" + in target "unclosed-direct" +make: Unclosed expression after indirect modifier, expecting '}' + while evaluating variable "VAR" with value "Thevariable" + in target "unclosed-indirect" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "unfinished-indirect" +make: Unfinished modifier ('@' missing) + while evaluating variable "UNDEF" with value "1 2 3" + in target "unfinished-loop-1" +make: Unfinished modifier ('@' missing) + while evaluating variable "UNDEF" with value "1 2 3" + in target "unfinished-loop-2" 1 2 3 - -loop-close: -make: in target "loop-close": while evaluating variable "UNDEF" with value "1}... 2}... 3}...": Unclosed expression, expecting '}' for modifier "@var@${var}}...@" +make: Unclosed expression, expecting '}' for modifier "@var@${var}}...@" + while evaluating variable "UNDEF" with value "1}... 2}... 3}..." + in target "loop-close-1" 1}... 2}... 3}... -1}... 2}... 3}... - -make: in target "words": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier (']' missing) - -make: in target "words": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier (']' missing) - +make: Unfinished modifier (']' missing) + while evaluating variable "UNDEF" with value "1 2 3" + in target "words-1" +make: Unfinished modifier (']' missing) + while evaluating variable "UNDEF" with value "1 2 3" + in target "words-2" 13= -make: in target "words": while evaluating variable "UNDEF" with value "1 2 3": Bad modifier ":[123451234512345123451234512345]" -12345=S,^ok,:S,^3ok,} - -make: in target "exclam": while evaluating variable "VARNAME" with value "": Unfinished modifier ('!' missing) - -make: in target "exclam": while evaluating variable "!" with value "!": Unfinished modifier ('!' missing) - - -make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Missing delimiter for modifier ':S' -1: -make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -2: -make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -3: -make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -4: -make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -5: -make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unclosed expression, expecting '}' for modifier "S,from,to," -6: TheVariable +make: Bad modifier ":[123451234512345123451234512345]" + while evaluating variable "UNDEF" with value "1 2 3" + in target "words-3" +make: Unfinished modifier ('!' missing) + while evaluating variable "VARNAME" with value "" + in target "exclam-1" +make: Unfinished modifier ('!' missing) + while evaluating variable "!" with value "!" + in target "exclam-2" +make: Missing delimiter for modifier ':S' + while evaluating variable "VAR" with value "TheVariable" + in target "mod-subst-delimiter-1" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-subst-delimiter-2" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-subst-delimiter-3" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-subst-delimiter-4" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-subst-delimiter-5" +make: Unclosed expression, expecting '}' for modifier "S,from,to," + while evaluating variable "VAR" with value "TheVariable" + in target "mod-subst-delimiter-6" 7: TheVariable - -make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Missing delimiter for modifier ':C' -1: -make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -2: -make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -3: -make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -4: -make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) -5: -make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unclosed expression, expecting '}' for modifier "C,from,to," -6: TheVariable +make: Missing delimiter for modifier ':C' + while evaluating variable "VAR" with value "TheVariable" + in target "mod-regex-delimiter-1" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-regex-delimiter-2" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-regex-delimiter-3" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-regex-delimiter-4" +make: Unfinished modifier (',' missing) + while evaluating variable "VAR" with value "TheVariable" + in target "mod-regex-delimiter-5" +make: Unclosed expression, expecting '}' for modifier "C,from,to," + while evaluating variable "VAR" with value "TheVariable" + in target "mod-regex-delimiter-6" 7: TheVariable - -mod-ts-parse: 112358132134 15152535558513521534 -make: in target "mod-ts-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":ts\65oct" -65oct} -make: in target "mod-ts-parse": while evaluating "${:U${FIB}:ts\65oct} # bad modifier, variable name is """ with value "1 1 2 3 5 8 13 21 34": Bad modifier ":ts\65oct" -65oct} -make: in target "mod-ts-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":tsxy" -xy} - -mod-t-parse: -make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":t" - -make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":txy" -y} -make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":t" - -make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":t" -M*} - -make: in target "mod-ifelse-parse": while evaluating then-branch of condition "FIB": Unfinished modifier (':' missing) - -make: in target "mod-ifelse-parse": while evaluating then-branch of condition "FIB": Unfinished modifier (':' missing) - -make: in target "mod-ifelse-parse": while evaluating else-branch of condition "FIB": Unfinished modifier ('}' missing) - -make: in target "mod-ifelse-parse": while evaluating else-branch of condition "FIB": Unfinished modifier ('}' missing) - +make: Bad modifier ":ts\65oct" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-ts-parse-3" +make: Bad modifier ":ts\65oct" + while evaluating "${:U${FIB}:ts\65oct} # bad modifier, variable name is """ with value "1 1 2 3 5 8 13 21 34" + in target "mod-ts-parse-4" +make: Bad modifier ":tsxy" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-ts-parse-5" +make: Bad modifier ":t" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-t-parse-1" +make: Bad modifier ":txy" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-t-parse-2" +make: Bad modifier ":t" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-t-parse-3" +make: Bad modifier ":t" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-t-parse-4" +make: Unfinished modifier (':' missing) + while evaluating then-branch of condition "FIB" + in target "mod-ifelse-parse-1" +make: Unfinished modifier (':' missing) + while evaluating then-branch of condition "FIB" + in target "mod-ifelse-parse-2" +make: Unfinished modifier ('}' missing) + while evaluating else-branch of condition "FIB" + in target "mod-ifelse-parse-3" +make: Unfinished modifier ('}' missing) + while evaluating else-branch of condition "FIB" + in target "mod-ifelse-parse-4" then - 1 1 2 3 5 8 13 21 34 -make: in target "mod-remember-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "__" - - -make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "3" -make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "": Unclosed expression, expecting '}' for modifier "3" - -make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "3=" -make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "": Unclosed expression, expecting '}' for modifier "3=" - -make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "3=x3" -make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "": Unclosed expression, expecting '}' for modifier "3=x3" - +make: Unknown modifier "__" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-remember-parse" +make: Unknown modifier "3" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-sysv-parse-1" +make: Unclosed expression, expecting '}' for modifier "3" + while evaluating variable "FIB" with value "" + in target "mod-sysv-parse-1" +make: Unknown modifier "3=" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-sysv-parse-2" +make: Unclosed expression, expecting '}' for modifier "3=" + while evaluating variable "FIB" with value "" + in target "mod-sysv-parse-2" +make: Unknown modifier "3=x3" + while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34" + in target "mod-sysv-parse-3" +make: Unclosed expression, expecting '}' for modifier "3=x3" + while evaluating variable "FIB" with value "" + in target "mod-sysv-parse-3" 1 1 2 x3 5 8 1x3 21 34 - exit status 2 diff --git a/contrib/bmake/unit-tests/moderrs.mk b/contrib/bmake/unit-tests/moderrs.mk index 66af52c14a65..ad9333dcfb1d 100644 --- a/contrib/bmake/unit-tests/moderrs.mk +++ b/contrib/bmake/unit-tests/moderrs.mk @@ -1,184 +1,211 @@ -# $NetBSD: moderrs.mk,v 1.38 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: moderrs.mk,v 1.41 2024/08/29 20:20:36 rillig Exp $ # # various modifier error tests VAR= TheVariable # in case we have to change it ;-) MOD_UNKN= Z MOD_TERM= S,V,v MOD_S:= ${MOD_TERM}, FIB= 1 1 2 3 5 8 13 21 34 all: mod-unknown-direct mod-unknown-indirect all: unclosed-direct unclosed-indirect -all: unfinished-indirect unfinished-loop -all: loop-close -all: words -all: exclam -all: mod-subst-delimiter -all: mod-regex-delimiter -all: mod-ts-parse -all: mod-t-parse -all: mod-ifelse-parse +all: unfinished-indirect unfinished-loop-{1,2,3} +all: loop-close-{1,2} +all: words-{1,2,3} +all: exclam-{1,2} +all: mod-subst-delimiter-{1,2,3,4,5,6,7} +all: mod-regex-delimiter-{1,2,3,4,5,6,7} +all: mod-ts-parse-{1,2,3,4,5} +all: mod-t-parse-{1,2,3,4} +all: mod-ifelse-parse-{1,2,3,4,5} all: mod-remember-parse -all: mod-sysv-parse +all: mod-sysv-parse-{1,2,3,4} -mod-unknown-direct: print-footer -# expect: make: in target "mod-unknown-direct": while evaluating variable "VAR" with value "TheVariable": Unknown modifier "Z" +mod-unknown-direct: +# expect: make: Unknown modifier "Z" @echo 'VAR:Z=before-${VAR:Z}-after' -mod-unknown-indirect: print-footer -# expect: make: in target "mod-unknown-indirect": while evaluating variable "VAR" with value "TheVariable": Unknown modifier "Z" +mod-unknown-indirect: +# expect: make: Unknown modifier "Z" @echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after' -unclosed-direct: print-header print-footer -# expect: make: in target "unclosed-direct": while evaluating variable "VAR" with value "Thevariable": Unclosed expression, expecting '}' for modifier "S,V,v," +unclosed-direct: +# expect: make: Unclosed expression, expecting '}' for modifier "S,V,v," @echo VAR:S,V,v,=${VAR:S,V,v, -unclosed-indirect: print-header print-footer -# expect: make: in target "unclosed-indirect": while evaluating variable "VAR" with value "Thevariable": Unclosed expression after indirect modifier, expecting '}' +unclosed-indirect: +# expect: make: Unclosed expression after indirect modifier, expecting '}' @echo VAR:${MOD_TERM},=${VAR:${MOD_S} -unfinished-indirect: print-footer -# expect: make: in target "unfinished-indirect": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +unfinished-indirect: +# expect: make: Unfinished modifier (',' missing) -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" -unfinished-loop: print-footer -# expect: make: in target "unfinished-loop": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier ('@' missing) +unfinished-loop-1: +# expect: make: Unfinished modifier ('@' missing) @echo ${UNDEF:U1 2 3:@var} -# expect: make: in target "unfinished-loop": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier ('@' missing) +unfinished-loop-2: +# expect: make: Unfinished modifier ('@' missing) @echo ${UNDEF:U1 2 3:@var@...} +unfinished-loop-3: @echo ${UNDEF:U1 2 3:@var@${var}@} # The closing brace after the ${var} is part of the replacement string. # In ParseModifierPart, braces and parentheses don't have to be balanced. # This is contrary to the :M, :N modifiers, where both parentheses and # braces must be balanced. # This is also contrary to the SysV modifier, where only the actually # used delimiter (either braces or parentheses) must be balanced. -loop-close: print-header print-footer -# expect: make: in target "loop-close": while evaluating variable "UNDEF" with value "1}... 2}... 3}...": Unclosed expression, expecting '}' for modifier "@var@${var}}...@" +loop-close-1: +# expect: make: Unclosed expression, expecting '}' for modifier "@var@${var}}...@" @echo ${UNDEF:U1 2 3:@var@${var}}...@ +loop-close-2: @echo ${UNDEF:U1 2 3:@var@${var}}...@} -words: print-footer -# expect: make: in target "words": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier (']' missing) +words-1: +# expect: make: Unfinished modifier (']' missing) @echo ${UNDEF:U1 2 3:[} -# expect: make: in target "words": while evaluating variable "UNDEF" with value "1 2 3": Unfinished modifier (']' missing) +words-2: +# expect: make: Unfinished modifier (']' missing) @echo ${UNDEF:U1 2 3:[#} +words-3: # out of bounds => empty @echo 13=${UNDEF:U1 2 3:[13]} # Word index out of bounds. # # Until 2020-11-01, the behavior in this case depended upon the size # of unsigned long. # # On LP64I32, strtol returns LONG_MAX, which was then truncated to # int (undefined behavior), typically resulting in -1. This -1 was # interpreted as "the last word". # # On ILP32, strtol returns LONG_MAX, which is a large number. This # resulted in a range from LONG_MAX - 1 to 3, which was empty. # # Since 2020-11-01, the numeric overflow is detected and generates an # error. In the remainder of the text, the '$,' is no longer parsed # as part of a variable modifier, where it would have been interpreted # as an anchor to the :S modifier, but as a normal variable named ','. # That variable is undefined, resulting in an empty string. @echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,} -exclam: print-footer -# expect: make: in target "exclam": while evaluating variable "VARNAME" with value "": Unfinished modifier ('!' missing) +exclam-1: +# expect: make: Unfinished modifier ('!' missing) @echo ${VARNAME:!echo} # When the final exclamation mark is missing, there is no # fallback to the SysV substitution modifier. # If there were a fallback, the output would be "exclam", # and the above would have produced an "Unknown modifier '!'". -# expect: make: in target "exclam": while evaluating variable "!" with value "!": Unfinished modifier ('!' missing) +exclam-2: +# expect: make: Unfinished modifier ('!' missing) @echo ${!:L:!=exclam} -mod-subst-delimiter: print-footer -# expect: make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Missing delimiter for modifier ':S' +mod-subst-delimiter-1: +# expect: make: Missing delimiter for modifier ':S' @echo 1: ${VAR:S -# expect: make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-subst-delimiter-2: +# expect: make: Unfinished modifier (',' missing) @echo 2: ${VAR:S, -# expect: make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-subst-delimiter-3: +# expect: make: Unfinished modifier (',' missing) @echo 3: ${VAR:S,from -# expect: make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-subst-delimiter-4: +# expect: make: Unfinished modifier (',' missing) @echo 4: ${VAR:S,from, -# expect: make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-subst-delimiter-5: +# expect: make: Unfinished modifier (',' missing) @echo 5: ${VAR:S,from,to -# expect: make: in target "mod-subst-delimiter": while evaluating variable "VAR" with value "TheVariable": Unclosed expression, expecting '}' for modifier "S,from,to," +mod-subst-delimiter-6: +# expect: make: Unclosed expression, expecting '}' for modifier "S,from,to," @echo 6: ${VAR:S,from,to, +mod-subst-delimiter-7: @echo 7: ${VAR:S,from,to,} -mod-regex-delimiter: print-footer -# expect: make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Missing delimiter for modifier ':C' +mod-regex-delimiter-1: +# expect: make: Missing delimiter for modifier ':C' @echo 1: ${VAR:C -# expect: make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-regex-delimiter-2: +# expect: make: Unfinished modifier (',' missing) @echo 2: ${VAR:C, -# expect: make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-regex-delimiter-3: +# expect: make: Unfinished modifier (',' missing) @echo 3: ${VAR:C,from -# expect: make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-regex-delimiter-4: +# expect: make: Unfinished modifier (',' missing) @echo 4: ${VAR:C,from, -# expect: make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unfinished modifier (',' missing) +mod-regex-delimiter-5: +# expect: make: Unfinished modifier (',' missing) @echo 5: ${VAR:C,from,to -# expect: make: in target "mod-regex-delimiter": while evaluating variable "VAR" with value "TheVariable": Unclosed expression, expecting '}' for modifier "C,from,to," +mod-regex-delimiter-6: +# expect: make: Unclosed expression, expecting '}' for modifier "C,from,to," @echo 6: ${VAR:C,from,to, +mod-regex-delimiter-7: @echo 7: ${VAR:C,from,to,} -mod-ts-parse: print-header print-footer +mod-ts-parse-1: @echo ${FIB:ts} +mod-ts-parse-2: @echo ${FIB:ts\65} # octal 065 == U+0035 == '5' -# expect: make: in target "mod-ts-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":ts\65oct" +mod-ts-parse-3: +# expect: make: Bad modifier ":ts\65oct" @echo ${FIB:ts\65oct} # bad modifier -# expect: make: in target "mod-ts-parse": while evaluating "${:U${FIB}:ts\65oct} # bad modifier, variable name is """ with value "1 1 2 3 5 8 13 21 34": Bad modifier ":ts\65oct" +mod-ts-parse-4: +# expect: make: Bad modifier ":ts\65oct" @echo ${:U${FIB}:ts\65oct} # bad modifier, variable name is "" -# expect: make: in target "mod-ts-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":tsxy" +mod-ts-parse-5: +# expect: make: Bad modifier ":tsxy" @echo ${FIB:tsxy} # modifier too long -mod-t-parse: print-header print-footer -# expect: make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":t" +mod-t-parse-1: +# expect: make: Bad modifier ":t" @echo ${FIB:t -# expect: make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":txy" +mod-t-parse-2: +# expect: make: Bad modifier ":txy" @echo ${FIB:txy} -# expect: make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":t" +mod-t-parse-3: +# expect: make: Bad modifier ":t" @echo ${FIB:t} -# expect: make: in target "mod-t-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Bad modifier ":t" +mod-t-parse-4: +# expect: make: Bad modifier ":t" @echo ${FIB:t:M*} -mod-ifelse-parse: print-footer -# expect: make: in target "mod-ifelse-parse": while evaluating then-branch of condition "FIB": Unfinished modifier (':' missing) +mod-ifelse-parse-1: +# expect: make: Unfinished modifier (':' missing) @echo ${FIB:? -# expect: make: in target "mod-ifelse-parse": while evaluating then-branch of condition "FIB": Unfinished modifier (':' missing) +mod-ifelse-parse-2: +# expect: make: Unfinished modifier (':' missing) @echo ${FIB:?then -# expect: make: in target "mod-ifelse-parse": while evaluating else-branch of condition "FIB": Unfinished modifier ('}' missing) +mod-ifelse-parse-3: +# expect: make: Unfinished modifier ('}' missing) @echo ${FIB:?then: -# expect: make: in target "mod-ifelse-parse": while evaluating else-branch of condition "FIB": Unfinished modifier ('}' missing) +mod-ifelse-parse-4: +# expect: make: Unfinished modifier ('}' missing) @echo ${FIB:?then:else +mod-ifelse-parse-5: @echo ${FIB:?then:else} -mod-remember-parse: print-footer +mod-remember-parse: @echo ${FIB:_} # ok -# expect: make: in target "mod-remember-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "__" +# expect: make: Unknown modifier "__" @echo ${FIB:__} # modifier name too long -mod-sysv-parse: print-footer -# expect: make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "3" -# expect: make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "": Unclosed expression, expecting '}' for modifier "3" +mod-sysv-parse-1: +# expect: make: Unknown modifier "3" +# expect: make: Unclosed expression, expecting '}' for modifier "3" @echo ${FIB:3 -# expect: make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "3=" -# expect: make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "": Unclosed expression, expecting '}' for modifier "3=" +mod-sysv-parse-2: +# expect: make: Unknown modifier "3=" +# expect: make: Unclosed expression, expecting '}' for modifier "3=" @echo ${FIB:3= -# expect: make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "1 1 2 3 5 8 13 21 34": Unknown modifier "3=x3" -# expect: make: in target "mod-sysv-parse": while evaluating variable "FIB" with value "": Unclosed expression, expecting '}' for modifier "3=x3" +mod-sysv-parse-3: +# expect: make: Unknown modifier "3=x3" +# expect: make: Unclosed expression, expecting '}' for modifier "3=x3" @echo ${FIB:3=x3 +mod-sysv-parse-4: @echo ${FIB:3=x3} # ok - -print-header: .USEBEFORE - @echo $@: -print-footer: .USE - @echo diff --git a/contrib/bmake/unit-tests/opt-debug-lint.exp b/contrib/bmake/unit-tests/opt-debug-lint.exp index eea6c8db5a49..7274a06333ee 100644 --- a/contrib/bmake/unit-tests/opt-debug-lint.exp +++ b/contrib/bmake/unit-tests/opt-debug-lint.exp @@ -1,10 +1,11 @@ -make: "opt-debug-lint.mk" line 21: Variable "X" is undefined -make: "opt-debug-lint.mk" line 21: Malformed conditional ($X) -make: "opt-debug-lint.mk" line 45: Variable "UNDEF" is undefined -make: "opt-debug-lint.mk" line 45: Malformed conditional (${UNDEF}) -make: "opt-debug-lint.mk" line 67: while evaluating variable "value" with value "value": Missing delimiter ':' after modifier "L" -make: "opt-debug-lint.mk" line 67: while evaluating variable "value" with value "value": Missing delimiter ':' after modifier "P" -make: "opt-debug-lint.mk" line 76: while evaluating variable "value" with value "": Unknown modifier "${" +make: "opt-debug-lint.mk" line 20: Variable "X" is undefined +make: "opt-debug-lint.mk" line 43: Variable "UNDEF" is undefined +make: "opt-debug-lint.mk" line 65: Missing delimiter ':' after modifier "L" + while evaluating variable "value" with value "value" +make: "opt-debug-lint.mk" line 65: Missing delimiter ':' after modifier "P" + while evaluating variable "value" with value "value" +make: "opt-debug-lint.mk" line 74: Unknown modifier "${" + while evaluating variable "value" with value "" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/opt-debug-lint.mk b/contrib/bmake/unit-tests/opt-debug-lint.mk index cc20694e268c..7918490b3f1a 100644 --- a/contrib/bmake/unit-tests/opt-debug-lint.mk +++ b/contrib/bmake/unit-tests/opt-debug-lint.mk @@ -1,102 +1,100 @@ -# $NetBSD: opt-debug-lint.mk,v 1.18 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: opt-debug-lint.mk,v 1.22 2025/01/11 20:54:45 rillig Exp $ # # Tests for the -dL command line option, which runs additional checks # to catch common mistakes, such as unclosed expressions. .MAKEFLAGS: -dL # Since 2020-09-13, undefined variables that are used on the left-hand side # of a condition at parse time get a proper error message. Before, the # error message was "Malformed conditional" only, which was wrong and # misleading. The form of the condition is totally fine, it's the evaluation # that fails. # # Since 2020-09-13, the "Malformed conditional" error message is not printed # anymore. # # See also: # cond-undef-lint.mk -# expect+2: Malformed conditional ($X) # expect+1: Variable "X" is undefined .if $X . error .endif # The dynamic variables like .TARGET are treated specially. It does not make # sense to expand them in the global scope since they will never be defined # there under normal circumstances. Therefore they expand to a string that # will later be expanded correctly, when the variable is evaluated again in # the scope of an actual target. # # Even though the "@" variable is not defined at this point, this is not an # error. In all practical cases, this is no problem. This particular test # case is made up and unrealistic. .if $@ != "\$(.TARGET)" . error .endif # Since 2020-09-13, Var_Parse properly reports errors for undefined variables, # but only in lint mode. Before, it had only silently returned var_Error, # hoping for the caller to print an error message. This resulted in the # well-known "Malformed conditional" error message, even though the # conditional was well-formed and the only error was an undefined variable. -# expect+2: Malformed conditional (${UNDEF}) # expect+1: Variable "UNDEF" is undefined .if ${UNDEF} . error .endif # Since 2020-09-14, dependency lines may contain undefined variables. # Before, undefined variables were forbidden, but this distinction was not # observable from the outside of the function Var_Parse. ${UNDEF}: ${UNDEF} # In a condition that has a defined(UNDEF) guard, all guarded conditions # may assume that the variable is defined since they will only be evaluated # if the variable is indeed defined. Otherwise they are only parsed, and # for parsing it doesn't make a difference whether the variable is defined # or not. .if defined(UNDEF) && exists(${UNDEF}) . error .endif # Since 2020-10-03, in lint mode the variable modifier must be separated # by colons. See varparse-mod.mk. -# expect+2: while evaluating variable "value" with value "value": Missing delimiter ':' after modifier "L" -# expect+1: while evaluating variable "value" with value "value": Missing delimiter ':' after modifier "P" +# expect+2: Missing delimiter ':' after modifier "L" +# expect+1: Missing delimiter ':' after modifier "P" .if ${value:LPL} != "value" . error .endif # Between 2020-10-03 and var.c 1.752 from 2020-12-20, in lint mode the # variable modifier had to be separated by colons. This was wrong though # since make always fell back trying to parse the indirect modifier as a # SysV modifier. -# expect+1: while evaluating variable "value" with value "": Unknown modifier "${" +# expect+1: Unknown modifier "${" .if ${value:${:UL}PL} != "LPL}" # FIXME: "LPL}" is unexpected here. . error ${value:${:UL}PL} .endif # Typically, an indirect modifier is followed by a colon or the closing # brace. This one isn't, therefore make falls back to parsing it as the SysV # modifier ":lue=lid". .if ${value:L:${:Ulue}=${:Ulid}} != "valid" . error .endif # In lint mode, the whole variable text is evaluated to check for unclosed # expressions and unknown operators. During this check, the subexpression # '${:U2}' is not expanded, instead it is copied verbatim into the regular # expression, leading to '.*=.{1,${:U2}}$'. # # Before var.c 1.856 from 2021-03-14, this regular expression was then # compiled even though that was not necessary for checking the syntax at the # level of expressions. The unexpanded '$' then resulted in a wrong # error message. # # This only happened in lint mode since in default mode the early check for # unclosed expressions and unknown modifiers is skipped. # # See VarCheckSyntax, ApplyModifier_Regex. # VARMOD_REGEX= ${:UA=111 B=222 C=33:C/.*=.{1,${:U2}}$//g} diff --git a/contrib/bmake/unit-tests/opt-debug-parse.exp b/contrib/bmake/unit-tests/opt-debug-parse.exp index a19a58bcc965..f0b57f2a703b 100644 --- a/contrib/bmake/unit-tests/opt-debug-parse.exp +++ b/contrib/bmake/unit-tests/opt-debug-parse.exp @@ -1,28 +1,28 @@ -Parsing line 16: .for var in value +Parsing opt-debug-parse.mk:16: .for var in value Parse_PushInput: .for loop in opt-debug-parse.mk, line 16 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' -Parsing line 21: .info trace with multi-line .for loop head +Parsing opt-debug-parse.mk:21: .info trace with multi-line .for loop head make: "opt-debug-parse.mk" line 21: trace with multi-line .for loop head in .for loop from opt-debug-parse.mk:16 with var = value ParseEOF: returning to file opt-debug-parse.mk, line 23 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' -Parsing line 26: .include "/dev/null" +Parsing opt-debug-parse.mk:26: .include "/dev/null" Parse_PushInput: file /dev/null, line 1 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `null' SetFilenameVars: ${.INCLUDEDFROMDIR} = ${.INCLUDEDFROMFILE} = `opt-debug-parse.mk' ParseEOF: returning to file opt-debug-parse.mk, line 27 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' -Parsing line 31: .for a b c in 1 2 3 ${:U4 5 6} +Parsing opt-debug-parse.mk:31: .for a b c in 1 2 3 ${:U4 5 6} Parse_PushInput: .for loop in opt-debug-parse.mk, line 31 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' -Parsing line 34: .info trace +Parsing opt-debug-parse.mk:34: .info trace make: "opt-debug-parse.mk" line 34: trace in .for loop from opt-debug-parse.mk:31 with a = 1, b = 2, c = 3 -Parsing line 34: .info trace +Parsing opt-debug-parse.mk:34: .info trace make: "opt-debug-parse.mk" line 34: trace in .for loop from opt-debug-parse.mk:31 with a = 4, b = 5, c = 6 ParseEOF: returning to file opt-debug-parse.mk, line 36 SetFilenameVars: ${.PARSEDIR} = ${.PARSEFILE} = `opt-debug-parse.mk' -Parsing line 38: .MAKEFLAGS: -d0 +Parsing opt-debug-parse.mk:38: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) exit status 0 diff --git a/contrib/bmake/unit-tests/opt-debug-var.exp b/contrib/bmake/unit-tests/opt-debug-var.exp index 5e9d10c671f1..e113e66f26fe 100644 --- a/contrib/bmake/unit-tests/opt-debug-var.exp +++ b/contrib/bmake/unit-tests/opt-debug-var.exp @@ -1,7 +1,13 @@ Global: ASSIGNED = value Global: SUBST = # (empty) Global: SUBST = value Var_Parse: y(ASSIGNED) (eval) +Var_Parse: $U (eval-defined-loud) +make: "opt-debug-var.mk" line 34: Variable "U" is undefined +Var_Parse: $< (eval-defined-loud) +make: "opt-debug-var.mk" line 40: Variable "<" is undefined Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 -exit status 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/contrib/bmake/unit-tests/opt-debug-var.mk b/contrib/bmake/unit-tests/opt-debug-var.mk index 9017f18e81d3..7a3a80f11432 100644 --- a/contrib/bmake/unit-tests/opt-debug-var.mk +++ b/contrib/bmake/unit-tests/opt-debug-var.mk @@ -1,31 +1,44 @@ -# $NetBSD: opt-debug-var.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: opt-debug-var.mk,v 1.5 2025/01/11 21:21:33 rillig Exp $ # # Tests for the -dv command line option, which adds debug logging about # variable assignment and evaluation. .MAKEFLAGS: -dv # expect: Global: ASSIGNED = value ASSIGNED= value # TODO: Explain why the empty assignment "Global: SUBST = " is needed. +# expect: Global: SUBST = # (empty) # expect: Global: SUBST = value SUBST:= value .if defined(ASSIGNED) .endif # The usual form of expressions is ${VAR}. The form $(VAR) is used # less often as it can be visually confused with the shell construct for # capturing the output of a subshell, which looks the same. # # In conditions, a call to the function 'empty' is syntactically similar to # the form $(VAR), only that the initial '$' is the 'y' of 'empty'. # # expect: Var_Parse: y(ASSIGNED) (eval) .if !empty(ASSIGNED) .endif -.MAKEFLAGS: -d0 -all: .PHONY +# An expression for a variable with a single-character ordinary name. +# expect: Var_Parse: $U (eval-defined-loud) +# expect+1: Variable "U" is undefined +.if $U +.endif + +# An expression for a target-specific variable with a single-character name. +# expect: Var_Parse: $< (eval-defined-loud) +# expect+1: Variable "<" is undefined +.if $< +.endif + + +.MAKEFLAGS: -d0 diff --git a/contrib/bmake/unit-tests/suff-incomplete.exp b/contrib/bmake/unit-tests/suff-incomplete.exp index a0603de34f11..e5e4a770b26d 100644 --- a/contrib/bmake/unit-tests/suff-incomplete.exp +++ b/contrib/bmake/unit-tests/suff-incomplete.exp @@ -1,42 +1,42 @@ -Parsing line 9: .SUFFIXES: +Parsing suff-incomplete.mk:9: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -Parsing line 11: .SUFFIXES: .a .b .c +Parsing suff-incomplete.mk:11: .SUFFIXES: .a .b .c ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" -Parsing line 17: .a.b: +Parsing suff-incomplete.mk:17: .a.b: ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list -Parsing line 21: .a.c: ${.PREFIX}.dependency +Parsing suff-incomplete.mk:21: .a.c: ${.PREFIX}.dependency deleting incomplete transformation from `.a' to `.b' ParseDependency(.a.c: ${.PREFIX}.dependency) defining transformation from `.a' to `.c' inserting ".a" (1) at end of list inserting ".c" (3) at end of list Target ".a.c" depends on "${.PREFIX}.dependency" # .a.c, unmade, type OP_DEPENDS|OP_TRANSFORM, flags none # ${.PREFIX}.dependency, unmade, type none, flags none -Parsing line 23: .DEFAULT: +Parsing suff-incomplete.mk:23: .DEFAULT: transformation .a.c complete ParseDependency(.DEFAULT:) -Parsing line 24: : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default. +Parsing suff-incomplete.mk:24: : Making ${.TARGET} from ${.IMPSRC} all ${.ALLSRC} by default. transformation .DEFAULT complete Wildcard expanding "all"... SuffFindDeps "all" No known suffix on all. Using .NULL suffix adding suffix rules Wildcard expanding "suff-incomplete.c"...suffix is ".c"... SuffFindDeps "suff-incomplete.c" trying suff-incomplete.a...not there Wildcard expanding "suff-incomplete.c"...suffix is ".c"... : Making suff-incomplete.c from suff-incomplete.c all by default. Wildcard expanding "all"... SuffFindDeps ".END" No known suffix on .END. Using .NULL suffix adding suffix rules Wildcard expanding ".END"... exit status 0 diff --git a/contrib/bmake/unit-tests/suff-main-several.exp b/contrib/bmake/unit-tests/suff-main-several.exp index 99585102029e..c06fb0cf88b9 100644 --- a/contrib/bmake/unit-tests/suff-main-several.exp +++ b/contrib/bmake/unit-tests/suff-main-several.exp @@ -1,141 +1,141 @@ -Parsing line 8: .1.2 .1.3 .1.4: +Parsing suff-main-several.mk:8: .1.2 .1.3 .1.4: ParseDependency(.1.2 .1.3 .1.4:) Setting main node to ".1.2" -Parsing line 9: : Making ${.TARGET} from ${.IMPSRC}. -Parsing line 14: next-main: +Parsing suff-main-several.mk:9: : Making ${.TARGET} from ${.IMPSRC}. +Parsing suff-main-several.mk:14: next-main: ParseDependency(next-main:) -Parsing line 15: : Making ${.TARGET} -Parsing line 19: .SUFFIXES: .1 .2 .3 .4 +Parsing suff-main-several.mk:15: : Making ${.TARGET} +Parsing suff-main-several.mk:19: .SUFFIXES: .1 .2 .3 .4 ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Setting main node from ".1.2" back to null defining transformation from `.1' to `.2' inserting ".1" (1) at end of list inserting ".2" (2) at end of list Setting main node to ".1.3" Adding suffix ".3" Setting main node from ".1.3" back to null defining transformation from `.1' to `.3' inserting ".1" (1) at end of list inserting ".3" (3) at end of list Setting main node to ".1.4" Adding suffix ".4" Setting main node from ".1.4" back to null defining transformation from `.1' to `.4' inserting ".1" (1) at end of list inserting ".4" (4) at end of list Setting main node to "next-main" -Parsing line 24: .SUFFIXES: +Parsing suff-main-several.mk:24: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -Parsing line 32: .SUFFIXES: .4 .3 .2 .1 +Parsing suff-main-several.mk:32: .SUFFIXES: .4 .3 .2 .1 ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" -Parsing line 33: .SUFFIXES: +Parsing suff-main-several.mk:33: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -Parsing line 34: .SUFFIXES: .1 .2 .3 .4 +Parsing suff-main-several.mk:34: .SUFFIXES: .1 .2 .3 .4 ParseDependency(.SUFFIXES: .1 .2 .3 .4) Adding suffix ".1" Adding suffix ".2" Adding suffix ".3" Adding suffix ".4" -Parsing line 35: .SUFFIXES: +Parsing suff-main-several.mk:35: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -Parsing line 36: .SUFFIXES: .4 .3 .2 .1 +Parsing suff-main-several.mk:36: .SUFFIXES: .4 .3 .2 .1 ParseDependency(.SUFFIXES: .4 .3 .2 .1) Adding suffix ".4" Adding suffix ".3" Adding suffix ".2" Adding suffix ".1" -Parsing line 38: suff-main-several.1: +Parsing suff-main-several.mk:38: suff-main-several.1: ParseDependency(suff-main-several.1:) -Parsing line 39: : Making ${.TARGET} out of nothing. -Parsing line 40: next-main: suff-main-several.{2,3,4} +Parsing suff-main-several.mk:39: : Making ${.TARGET} out of nothing. +Parsing suff-main-several.mk:40: next-main: suff-main-several.{2,3,4} ParseDependency(next-main: suff-main-several.{2,3,4}) Target "next-main" depends on "suff-main-several.{2,3,4}" # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none -Parsing line 42: .MAKEFLAGS: -d0 -dg1 +Parsing suff-main-several.mk:42: .MAKEFLAGS: -d0 -dg1 ParseDependency(.MAKEFLAGS: -d0 -dg1) #*** Input graph: # .1.2, unmade, type OP_TRANSFORM, flags none # .1.3, unmade, type OP_TRANSFORM, flags none # .1.4, unmade, type OP_TRANSFORM, flags none # next-main, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.1, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none # suff-main-several.{2,3,4}, unmade, type none, flags none # # Files that are only sources: # .1.2 [.1.2] # .1.3 [.1.3] # .1.4 [.1.4] # suff-main-several.{2,3,4} [suff-main-several.{2,3,4}] #*** Global Variables: .ALLTARGETS = .1.2 .1.3 .1.4 next-main suff-main-several.1 suff-main-several.{2,3,4} .CURDIR = .INCLUDES = # (empty) .LIBS = # (empty) .MAKE =
.MAKE.DEPENDFILE =
.MAKE.GID =
.MAKE.JOBS.C =
.MAKE.LEVEL =
.MAKE.LEVEL.ENV = MAKELEVEL .MAKE.MAKEFILES =
.MAKE.MAKEFILE_PREFERENCE =
.MAKE.OS =
.MAKE.PID =
.MAKE.PPID =
.MAKE.UID =
.MAKEFLAGS = -r -k -d mps -d 0 -d g1 .MAKEOVERRIDES = # (empty) .OBJDIR = .PATH = . .TARGETS = # (empty) .newline = # (ends with space) MACHINE =
MACHINE_ARCH =
MAKE =
MFLAGS = -r -k -d mps -d 0 -d g1 #*** Command-line Variables: #*** Directory Cache: # Stats: 0 hits 2 misses 0 near misses 0 losers (0%) # refs hits directory # 1 0 #*** Suffixes: # ".4" (num 1, ref 1) # To: # From: # Search Path: # ".3" (num 2, ref 1) # To: # From: # Search Path: # ".2" (num 3, ref 1) # To: # From: # Search Path: # ".1" (num 4, ref 1) # To: # From: # Search Path: #*** Transformations: make: don't know how to make suff-main-several.2 (continuing) make: don't know how to make suff-main-several.3 (continuing) make: don't know how to make suff-main-several.4 (continuing) `next-main' not remade because of errors. Stop. make: stopped making "next-main" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/suff-rebuild.exp b/contrib/bmake/unit-tests/suff-rebuild.exp index 8c0979537524..e44fa0fe9893 100644 --- a/contrib/bmake/unit-tests/suff-rebuild.exp +++ b/contrib/bmake/unit-tests/suff-rebuild.exp @@ -1,73 +1,73 @@ -Parsing line 10: .SUFFIXES: +Parsing suff-rebuild.mk:10: .SUFFIXES: ParseDependency(.SUFFIXES:) Clearing all suffixes -Parsing line 12: .SUFFIXES: .a .b .c +Parsing suff-rebuild.mk:12: .SUFFIXES: .a .b .c ParseDependency(.SUFFIXES: .a .b .c) Adding suffix ".a" Adding suffix ".b" Adding suffix ".c" -Parsing line 14: suff-rebuild-example.a: +Parsing suff-rebuild.mk:14: suff-rebuild-example.a: ParseDependency(suff-rebuild-example.a:) Adding "suff-rebuild-example.a" to all targets. -Parsing line 15: : Making ${.TARGET} out of nothing. -Parsing line 17: .a.b: +Parsing suff-rebuild.mk:15: : Making ${.TARGET} out of nothing. +Parsing suff-rebuild.mk:17: .a.b: ParseDependency(.a.b:) defining transformation from `.a' to `.b' inserting ".a" (1) at end of list inserting ".b" (2) at end of list -Parsing line 18: : Making ${.TARGET} from ${.IMPSRC}. -Parsing line 19: .b.c: +Parsing suff-rebuild.mk:18: : Making ${.TARGET} from ${.IMPSRC}. +Parsing suff-rebuild.mk:19: .b.c: transformation .a.b complete ParseDependency(.b.c:) defining transformation from `.b' to `.c' inserting ".b" (2) at end of list inserting ".c" (3) at end of list -Parsing line 20: : Making ${.TARGET} from ${.IMPSRC}. -Parsing line 21: .c: +Parsing suff-rebuild.mk:20: : Making ${.TARGET} from ${.IMPSRC}. +Parsing suff-rebuild.mk:21: .c: transformation .b.c complete ParseDependency(.c:) defining transformation from `.c' to `' inserting ".c" (3) at end of list inserting "" (0) at end of list -Parsing line 22: : Making ${.TARGET} from ${.IMPSRC}. -Parsing line 44: .SUFFIXES: .c .b .a +Parsing suff-rebuild.mk:22: : Making ${.TARGET} from ${.IMPSRC}. +Parsing suff-rebuild.mk:44: .SUFFIXES: .c .b .a transformation .c complete ParseDependency(.SUFFIXES: .c .b .a) Adding ".END" to all targets. Wildcard expanding "all"... SuffFindDeps "all" No known suffix on all. Using .NULL suffix adding suffix rules trying all.c...not there trying all.b...not there trying all.a...not there Wildcard expanding "suff-rebuild-example"... SuffFindDeps "suff-rebuild-example" No known suffix on suff-rebuild-example. Using .NULL suffix adding suffix rules trying suff-rebuild-example.c...not there trying suff-rebuild-example.b...not there trying suff-rebuild-example.a...got it Adding "suff-rebuild-example.b" to all targets. applying .a -> .b to "suff-rebuild-example.b" Adding "suff-rebuild-example.c" to all targets. applying .b -> .c to "suff-rebuild-example.c" applying .c -> to "suff-rebuild-example" suffix is ".c"... suffix is ".b"... suffix is ".a"... SuffFindDeps "suff-rebuild-example.a" suffix is ".a"... : Making suff-rebuild-example.a out of nothing. : Making suff-rebuild-example.b from suff-rebuild-example.a. : Making suff-rebuild-example.c from suff-rebuild-example.b. : Making suff-rebuild-example from suff-rebuild-example.c. Wildcard expanding "all"... SuffFindDeps ".END" No known suffix on .END. Using .NULL suffix adding suffix rules trying .END.c...not there trying .END.b...not there trying .END.a...not there Wildcard expanding ".END"... exit status 0 diff --git a/contrib/bmake/unit-tests/suff.exp b/contrib/bmake/unit-tests/suff.exp new file mode 100644 index 000000000000..adf646850d6e --- /dev/null +++ b/contrib/bmake/unit-tests/suff.exp @@ -0,0 +1,146 @@ +Adding suffix ".from" +Adding suffix ".to" +defining transformation from `.from' to `.to' +inserting ".from" (1) at end of list +inserting ".to" (2) at end of list +transformation .from.to complete +Var_Parse: ${.PREFIX}${.ARCHIVE}.additional (eval) +Var_Parse: ${.ARCHIVE}.additional (eval) +Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional +Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from +Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional +Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk +Global: delete .PARSEDIR +Global: delete .PARSEFILE +Global: ignoring delete '.INCLUDEDFROMDIR' as it is not found +Global: ignoring delete '.INCLUDEDFROMFILE' as it is not found +Var_Parse: ${.MAKE.DEPENDFILE} (eval) +Var_Parse: ${.MAKE.MODE:tl} (eval) +Evaluating modifier ${.MAKE.MODE:t...} on value "" (eval, undefined) +Result of ${.MAKE.MODE:tl} is "" (eval, undefined) +Global: MFLAGS = -r -k -d sv +Var_Parse: ${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@} (eval) +Var_Parse: ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@} (eval) +Evaluating modifier ${.MAKEOVERRIDES:O} on value "" +Result of ${.MAKEOVERRIDES:O} is "" +Evaluating modifier ${.MAKEOVERRIDES:u} on value "" +Result of ${.MAKEOVERRIDES:u} is "" +Evaluating modifier ${.MAKEOVERRIDES:@...} on value "" +Modifier part: "v" +Modifier part: "$v=${$v:Q}" +ModifyWords: split "" into 1 word +Command: ignoring delete 'v' as it is not found +Result of ${.MAKEOVERRIDES:@v@$v=${$v:Q}@} is "" +Global: .INCLUDES = # (empty) +Global: .LIBS = # (empty) +Global: ignoring delete '.SHELL' as it is not found +Command: .SHELL = /bin/sh +Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk .END +step1: @ = step1 +step1: @ = step1 +step1: * = step1 +SuffFindDeps "step1" +step1: @ = step1 +step1: * = step1 + No valid suffix on step1 +Wildcard expanding "edge-case.to"...suffix is ".to"... +edge-case.to: @ = edge-case.to +edge-case.to: @ = edge-case.to +edge-case.to: * = edge-case.to +SuffFindDeps "edge-case.to" + trying edge-case.from...got it +edge-case.to: @ = edge-case.to +edge-case.to: * = edge-case +Expanding "${.PREFIX}${.ARCHIVE}.additional"...Var_Parse: ${.PREFIX}${.ARCHIVE}.additional (eval) +Var_Parse: ${.ARCHIVE}.additional (eval) +edge-case.additional... + applying .from -> .to to "edge-case.to" +everything: @ = everything +everything: @ = everything +everything: * = everything +SuffFindDeps "everything" +everything: @ = everything +everything: * = everything +Wildcard expanding "a*.mk"... +archive-suffix.mk...Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk .END archive-suffix.mk +archive.mk...Global: .ALLTARGETS = step1 edge-case.to everything ${.PREFIX}${.ARCHIVE}.additional edge-case.from edge-case.additional a*.mk .END archive-suffix.mk archive.mk + + No valid suffix on everything +Wildcard expanding "edge-case.additional"... +edge-case.additional: @ = edge-case.additional +edge-case.additional: @ = edge-case.additional +edge-case.additional: * = edge-case.additional +SuffFindDeps "edge-case.additional" + No known suffix on edge-case.additional. Using .NULL suffix +not adding suffix rules +edge-case.additional: @ = edge-case.additional +edge-case.additional: * = edge-case.additional +suffix is ".from"... +edge-case.from: @ = edge-case.from +edge-case.from: @ = edge-case.from +edge-case.from: * = edge-case.from +SuffFindDeps "edge-case.from" +edge-case.from: @ = edge-case.from +edge-case.from: * = edge-case +Wildcard expanding "archive-suffix.mk"... +archive-suffix.mk: @ = archive-suffix.mk +archive-suffix.mk: @ = archive-suffix.mk +archive-suffix.mk: * = archive-suffix.mk +SuffFindDeps "archive-suffix.mk" + No known suffix on archive-suffix.mk. Using .NULL suffix +adding suffix rules +archive-suffix.mk: @ = archive-suffix.mk +archive-suffix.mk: * = archive-suffix.mk +archive-suffix.mk: @ = archive-suffix.mk +archive-suffix.mk: * = archive-suffix.mk +Wildcard expanding "archive.mk"... +archive.mk: @ = archive.mk +archive.mk: @ = archive.mk +archive.mk: * = archive.mk +SuffFindDeps "archive.mk" + No known suffix on archive.mk. Using .NULL suffix +adding suffix rules +archive.mk: @ = archive.mk +archive.mk: * = archive.mk +archive.mk: @ = archive.mk +archive.mk: * = archive.mk +Wildcard expanding "edge-case.additional"... +edge-case.additional: ? = # (empty) +edge-case.additional: > = # (empty) +Var_Parse: ${.TARGET} out of nothing. (eval) +: Making edge-case.additional out of nothing. +edge-case.to: < = edge-case.from +suffix is ".from"... +edge-case.from: ? = # (empty) +edge-case.from: > = # (empty) +Var_Parse: ${.TARGET} out of nothing. (eval) +: Making edge-case.from out of nothing. +edge-case.to: > = edge-case.additional +edge-case.to: ? = edge-case.additional +edge-case.to: > = edge-case.additional edge-case.from +edge-case.to: ? = edge-case.additional edge-case.from +Var_Parse: ${.TARGET} from ${.ALLSRC}. (eval) +Var_Parse: ${.ALLSRC}. (eval) +: Making edge-case.to from edge-case.additional edge-case.from. +everything: > = archive-suffix.mk +everything: ? = archive-suffix.mk +everything: > = archive-suffix.mk archive.mk +everything: ? = archive-suffix.mk archive.mk +Var_Parse: ${.TARGET} from ${.ALLSRC}. (eval) +Var_Parse: ${.ALLSRC}. (eval) +: Making everything from archive-suffix.mk archive.mk. +step1: > = edge-case.to +step1: ? = edge-case.to +step1: > = edge-case.to everything +step1: ? = edge-case.to everything +.END: @ = .END +.END: * = .END +SuffFindDeps ".END" + No known suffix on .END. Using .NULL suffix +adding suffix rules +.END: @ = .END +.END: * = .END +Wildcard expanding ".END"... +.END: ? = # (empty) +.END: > = # (empty) +exit status 0 diff --git a/contrib/bmake/unit-tests/suff.mk b/contrib/bmake/unit-tests/suff.mk new file mode 100644 index 000000000000..53f6eb82b224 --- /dev/null +++ b/contrib/bmake/unit-tests/suff.mk @@ -0,0 +1,41 @@ +# $NetBSD: suff.mk,v 1.3 2025/01/14 21:39:25 rillig Exp $ +# +# Demonstrate suffix rules and dependency resolution. + + +# Circumvent the file system cache. +.if !make(init) && !make(step*) +all: + @${MAKE} -f ${MAKEFILE} init + @${MAKE} -f ${MAKEFILE} step1 +.endif + + +.if make(init) +init: +. if ${.PARSEDIR:tA} != ${.CURDIR:tA} +${:U}!= cd ${MAKEFILE:H} && cp a*.mk ${.CURDIR} +. endif +.endif + + +.if make(step1) +step1: .PHONY edge-case.to everything + +.MAKEFLAGS: -dsv + +.SUFFIXES: .from .to + +.from.to: + : Making ${.TARGET} from ${.ALLSRC}. + +# When making this target, ${.ARCHIVE} is undefined, but there's no warning. +# expect: Var_Parse: ${.ARCHIVE}.additional (eval) +edge-case.to: ${.PREFIX}${.ARCHIVE}.additional + +edge-case.from edge-case.additional: + : Making ${.TARGET} out of nothing. + +everything: .PHONY a*.mk + : Making ${.TARGET} from ${.ALLSRC}. +.endif diff --git a/contrib/bmake/unit-tests/var-eval-short.exp b/contrib/bmake/unit-tests/var-eval-short.exp index 12e2b78d7a3d..2b2e95750a1c 100644 --- a/contrib/bmake/unit-tests/var-eval-short.exp +++ b/contrib/bmake/unit-tests/var-eval-short.exp @@ -1,31 +1,31 @@ -make: "var-eval-short.mk" line 46: while parsing "${:Uword:@${FAIL}@expr@}": In the :@ modifier, the variable name "${FAIL}" must not contain a dollar -make: "var-eval-short.mk" line 46: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@}) -Parsing line 159: .if 0 && ${0:?${FAIL}then:${FAIL}else} +make: "var-eval-short.mk" line 45: In the :@ modifier, the variable name "${FAIL}" must not contain a dollar + while parsing "${:Uword:@${FAIL}@expr@}" +Parsing var-eval-short.mk:158: .if 0 && ${0:?${FAIL}then:${FAIL}else} CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else} Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse) Parsing modifier ${0:?...} Var_Parse: ${FAIL}then:${FAIL}else} (parse) Modifier part: "${FAIL}then" Var_Parse: ${FAIL}else} (parse) Modifier part: "${FAIL}else" Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse, defined) -Parsing line 167: DEFINED= defined +Parsing var-eval-short.mk:166: DEFINED= defined Global: DEFINED = defined -Parsing line 168: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} +Parsing var-eval-short.mk:167: .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse) Parsing modifier ${DEFINED:L} Result of ${DEFINED:L} is "defined" (parse, regular) Parsing modifier ${DEFINED:?...} Var_Parse: ${FAIL}then:${FAIL}else} (parse) Modifier part: "${FAIL}then" Var_Parse: ${FAIL}else} (parse) Modifier part: "${FAIL}else" Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse, regular) -Parsing line 170: .MAKEFLAGS: -d0 +Parsing var-eval-short.mk:169: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d cpv -d Global: .MAKEFLAGS = -r -k -d cpv -d 0 make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/var-eval-short.mk b/contrib/bmake/unit-tests/var-eval-short.mk index d6872d158e32..d6dc36254851 100644 --- a/contrib/bmake/unit-tests/var-eval-short.mk +++ b/contrib/bmake/unit-tests/var-eval-short.mk @@ -1,172 +1,171 @@ -# $NetBSD: var-eval-short.mk,v 1.14 2024/07/05 20:01:52 rillig Exp $ +# $NetBSD: var-eval-short.mk,v 1.17 2025/01/11 20:54:45 rillig Exp $ # # Tests for each variable modifier to ensure that they only do the minimum # necessary computations. If the result of the expression is irrelevant, # the modifier should only be parsed. The modifier should not be evaluated, # but if it is evaluated for simplicity of the code (such as ':ts'), it must # not have any observable side effects. # # See also: # var.c, the comment starting with 'The ApplyModifier functions' # ParseModifierPart, for evaluating nested expressions # cond-short.mk FAIL= ${:!echo unexpected 1>&2!} # The following tests only ensure that nested expressions are not evaluated. # They cannot ensure that any unexpanded text returned from ParseModifierPart # is ignored as well. To do that, it is necessary to step through the code of # each modifier. # TODO: Test the modifiers in the same order as they occur in ApplyModifier. .if 0 && ${FAIL} .endif .if 0 && ${VAR::=${FAIL}} .elif defined(VAR) . error .endif .if 0 && ${${FAIL}:?then:else} .endif .if 0 && ${1:?${FAIL}:${FAIL}} .endif .if 0 && ${0:?${FAIL}:${FAIL}} .endif # Before var.c 1.870 from 2021-03-14, the expression ${FAIL} was evaluated # after the loop, when undefining the temporary global loop variable. # Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the # variable name. -# expect+2: while parsing "${:Uword:@${FAIL}@expr@}": In the :@ modifier, the variable name "${FAIL}" must not contain a dollar -# expect+1: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@}) +# expect+1: In the :@ modifier, the variable name "${FAIL}" must not contain a dollar .if 0 && ${:Uword:@${FAIL}@expr@} .endif .if 0 && ${:Uword:@var@${FAIL}@} .endif # Before var.c 1.877 from 2021-03-14, the modifier ':[...]' did not expand # the nested expression ${FAIL} and then tried to parse the unexpanded text, # which failed since '$' is not a valid range character. .if 0 && ${:Uword:[${FAIL}]} .endif # Before var.c 1.867 from 2021-03-14, the modifier ':_' defined the variable # even though the whole expression should have only been parsed, not # evaluated. .if 0 && ${:Uword:_=VAR} .elif defined(VAR) . error .endif # Before var.c 1.856 from 2021-03-14, the modifier ':C' did not expand the # nested expression ${FAIL}, which is correct, and then tried to compile the # unexpanded text as a regular expression, which is unnecessary since the # right-hand side of the '&&' cannot influence the outcome of the condition. # Compiling the regular expression then failed both because of the '{FAIL}', # which is not a valid repetition of the form '{1,5}', and because of the # '****', which are repeated repetitions as well. # '${FAIL}' .if 0 && ${:Uword:C,${FAIL}****,,} .endif DEFINED= # defined .if 0 && ${DEFINED:D${FAIL}} .endif .if 0 && ${:Uword:E} .endif # Before var.c 1.1050 from 2023-05-09, the ':gmtime' modifier produced the # error message 'Invalid time value: ${FAIL}}' since it did not expand its # argument. .if 0 && ${:Uword:gmtime=${FAIL}} .endif .if 0 && ${:Uword:H} .endif .if 0 && ${:Uword:hash} .endif .if 0 && ${value:L} .endif # Before var.c 1.1050 from 2023-05-09, the ':localtime' modifier produced the # error message 'Invalid time value: ${FAIL}}' since it did not expand its # argument. .if 0 && ${:Uword:localtime=${FAIL}} .endif .if 0 && ${:Uword:M${FAIL}} .endif .if 0 && ${:Uword:N${FAIL}} .endif .if 0 && ${:Uword:O} .endif .if 0 && ${:Uword:Ox} .endif .if 0 && ${:Uword:P} .endif .if 0 && ${:Uword:Q} .endif .if 0 && ${:Uword:q} .endif .if 0 && ${:Uword:R} .endif .if 0 && ${:Uword:range} .endif .if 0 && ${:Uword:S,${FAIL},${FAIL},} .endif .if 0 && ${:Uword:sh} .endif .if 0 && ${:Uword:T} .endif .if 0 && ${:Uword:ts/} .endif .if 0 && ${:U${FAIL}} .endif .if 0 && ${:Uword:u} .endif .if 0 && ${:Uword:word=replacement} .endif # Before var.c 1.875 from 2021-03-14, Var_Parse returned "${FAIL}else" for the # irrelevant right-hand side of the condition, even though this was not # necessary. Since the return value from Var_Parse is supposed to be ignored # anyway, and since it is actually ignored in an overly complicated way, # an empty string suffices. .MAKEFLAGS: -dcpv .if 0 && ${0:?${FAIL}then:${FAIL}else} .endif # The ':L' is applied before the ':?' modifier, giving the expression a name # and a value, just to see whether this value gets passed through or whether # the parse-only mode results in an empty string (only visible in the debug # log). As of var.c 1.875 from 2021-03-14, the value of the variable gets # through, even though an empty string would suffice. DEFINED= defined .if 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else} .endif .MAKEFLAGS: -d0 all: diff --git a/contrib/bmake/unit-tests/var-op-expand.exp b/contrib/bmake/unit-tests/var-op-expand.exp index 63d74f313737..655a46023a11 100644 --- a/contrib/bmake/unit-tests/var-op-expand.exp +++ b/contrib/bmake/unit-tests/var-op-expand.exp @@ -1,7 +1,11 @@ -make: "var-op-expand.mk" line 274: while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}": while evaluating variable "later" with value "": Unknown modifier "s,value,replaced," +make: "var-op-expand.mk" line 274: Unknown modifier "s,value,replaced," + while evaluating variable "later" with value "" + while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}" make: "var-op-expand.mk" line 278: warning: XXX Neither branch should be taken. -make: "var-op-expand.mk" line 283: while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}": while evaluating variable "later" with value "lowercase-value": Unknown modifier "s,value,replaced," +make: "var-op-expand.mk" line 283: Unknown modifier "s,value,replaced," + while evaluating variable "later" with value "lowercase-value" + while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}" make: "var-op-expand.mk" line 285: warning: XXX Neither branch should be taken. make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/var-op-expand.mk b/contrib/bmake/unit-tests/var-op-expand.mk index a2ae8b0e22d1..a8e64bf45424 100644 --- a/contrib/bmake/unit-tests/var-op-expand.mk +++ b/contrib/bmake/unit-tests/var-op-expand.mk @@ -1,292 +1,292 @@ -# $NetBSD: var-op-expand.mk,v 1.21 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: var-op-expand.mk,v 1.22 2024/08/29 20:20:36 rillig Exp $ # # Tests for the := variable assignment operator, which expands its # right-hand side. # # See also: # varname-dot-make-save_dollars.mk # Force the test results to be independent of the default value of this # setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake # distribution and pkgsrc/devel/bmake. .MAKE.SAVE_DOLLARS:= yes # If the right-hand side does not contain a dollar sign, the ':=' assignment # operator has the same effect as the '=' assignment operator. VAR:= value .if ${VAR} != "value" . error .endif # When a ':=' assignment is performed, its right-hand side is evaluated and # expanded as far as possible. Contrary to other situations, '$$' and # expressions based on undefined variables are preserved though. # # Whether an expression is undefined or not is determined at the end # of evaluating the expression. The consequence is that ${:Ufallback} expands # to "fallback"; initially this expression is undefined since it is based on # the variable named "", which is guaranteed to be never defined, but at the # end of evaluating the expression ${:Ufallback}, the modifier ':U' has turned # the expression into a defined expression. # literal dollar signs VAR:= $$ $$$$ $$$$$$$$ .if ${VAR} != "\$ \$\$ \$\$\$\$" . error .endif # reference to a variable containing literal dollar signs REF= $$ $$$$ $$$$$$$$ VAR:= ${REF} REF= too late .if ${VAR} != "\$ \$\$ \$\$\$\$" . error .endif # reference to an undefined variable .undef UNDEF VAR:= <${UNDEF}> .if ${VAR} != "<>" . error .endif UNDEF= after .if ${VAR} != "" . error .endif # reference to a variable whose name is computed from another variable REF2= referred to REF= REF2 VAR:= ${${REF}} REF= too late .if ${VAR} != "referred to" . error .endif # expression with an indirect modifier referring to an undefined variable .undef UNDEF VAR:= ${:${UNDEF}} .if ${VAR} != "" . error .endif UNDEF= Uwas undefined .if ${VAR} != "was undefined" . error .endif # expression with an indirect modifier referring to another variable that # in turn refers to an undefined variable # # XXX: Even though this is a ':=' assignment, the '${UNDEF}' in the part of # the variable modifier is not preserved. To preserve it, ParseModifierPart # would have to call VarSubstExpr somehow since this is the only piece of # code that takes care of this global variable. .undef UNDEF REF= U${UNDEF} #.MAKEFLAGS: -dv VAR:= ${:${REF}} #.MAKEFLAGS: -d0 REF= too late UNDEF= Uwas undefined .if ${VAR} != "" . error .endif # In variable assignments using the ':=' operator, undefined variables are # preserved, no matter how indirectly they are referenced. .undef REF3 REF2= <${REF3}> REF= ${REF2} VAR:= ${REF} .if ${VAR} != "<>" . error .endif REF3= too late .if ${VAR} != "" . error .endif # In variable assignments using the ':=' operator, '$$' are preserved, no # matter how indirectly they are referenced. REF2= REF2:$$ $$$$ REF= REF:$$ $$$$ ${REF2} VAR:= VAR:$$ $$$$ ${REF} .if ${VAR} != "VAR:\$ \$\$ REF:\$ \$\$ REF2:\$ \$\$" . error .endif # In variable assignments using the ':=' operator, '$$' are preserved in the # expressions of the top level, but not in expressions that are nested. VAR:= top:$$ ${:Unest1\:\$\$} ${:Unest2${:U\:\$\$}} .if ${VAR} != "top:\$ nest1:\$ nest2:\$" . error .endif # In variable assignments using the ':=' operator, there may be expressions # containing variable modifiers, and these modifiers may refer to other # variables. These referred-to variables are expanded at the time of # assignment. The undefined variables are kept as-is and are later expanded # when evaluating the condition. # # Contrary to the assignment operator '=', the assignment operator ':=' # consumes the '$' from modifier parts. REF.word= 1:$$ 2:$$$$ 4:$$$$$$$$ .undef REF.undef VAR:= ${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef} REF.word= word.after REF.undef= undef.after .if ${VAR} != "1:2:\$ 4:\$\$ undef.after, direct: 1:\$ 2:\$\$ 4:\$\$\$\$ undef.after" . error .endif # Just for comparison, the previous example using the assignment operator '=' # instead of ':='. The right-hand side of the assignment is not evaluated at # the time of assignment but only later, when ${VAR} appears in the condition. # # At that point, both REF.word and REF.undef are defined. REF.word= 1:$$ 2:$$$$ 4:$$$$$$$$ .undef REF.undef VAR= ${:Uword undef:@word@${REF.${word}}@}, direct: ${REF.word} ${REF.undef} REF.word= word.after REF.undef= undef.after .if ${VAR} != "word.after undef.after, direct: word.after undef.after" . error .endif # Between var.c 1.42 from 2000-05-11 and before parse.c 1.520 from 2020-12-27, # if the variable name in a ':=' assignment referred to an undefined variable, # there were actually 2 assignments to different variables: # # Global["VAR_SUBST_${UNDEF}"] = "" # Global["VAR_SUBST_"] = "" # # The variable name with the empty value actually included a dollar sign. # Variable names with dollars are not used in practice. # # It might be a good idea to forbid undefined variables on the left-hand side # of a variable assignment. .undef UNDEF VAR_ASSIGN_${UNDEF}= assigned by '=' VAR_SUBST_${UNDEF}:= assigned by ':=' .if ${VAR_ASSIGN_} != "assigned by '='" . error .endif .if defined(${:UVAR_SUBST_\${UNDEF\}}) . error .endif .if ${VAR_SUBST_} != "assigned by ':='" . error .endif # The following test case demonstrates that the variable 'LATER' is preserved # in the ':=' assignment since the variable 'LATER' is not yet defined. # After the assignment to 'LATER', evaluating the variable 'INDIRECT' # evaluates 'LATER' as well. # .undef LATER INDIRECT:= ${LATER:S,value,replaced,} .if ${INDIRECT} != "" . error .endif LATER= late-value .if ${INDIRECT} != "late-replaced" . error .endif # Same as the test case above, except for the additional modifier ':tl' when # evaluating the variable 'INDIRECT'. Nothing surprising here. .undef LATER .undef later INDIRECT:= ${LATER:S,value,replaced,} .if ${INDIRECT:tl} != "" . error .endif LATER= uppercase-value later= lowercase-value .if ${INDIRECT:tl} != "uppercase-replaced" . error .endif # Similar to the two test cases above, the situation gets a bit more involved # here, due to the double indirection. The variable 'indirect' is supposed to # be the lowercase version of the variable 'INDIRECT'. # # The assignment operator ':=' for the variable 'INDIRECT' could be a '=' as # well, it wouldn't make a difference in this case. The crucial detail is the # assignment operator ':=' for the variable 'indirect'. During this # assignment, the variable modifier ':S,value,replaced,' is converted to # lowercase, which turns 'S' into 's', thus producing an unknown modifier. # In this case, make issues a warning, but in cases where the modifier # includes a '=', the modifier would be interpreted as a SysV-style # substitution like '.c=.o', and make would not issue a warning, leading to # silent unexpected behavior. # # As of 2021-11-20, the actual behavior is unexpected. Fixing it is not # trivial. When the assignment to 'indirect' takes place, the expressions # from the nested expression could be preserved, like this: # # Start with: # # indirect:= ${INDIRECT:tl} # # Since INDIRECT is defined, expand it, remembering that the modifier # ':tl' must still be applied to the final result. # # indirect:= ${LATER:S,value,replaced,} \ # OK \ # ${LATER:value=sysv} # # The variable 'LATER' is not defined. An idea may be to append the # remaining modifier ':tl' to each expression that is starting with an # undefined variable, resulting in: # # indirect:= ${LATER:S,value,replaced,:tl} \ # OK \ # ${LATER:value=sysv:tl} # # This would work for the first expression. The second expression ends # with the SysV modifier ':from=to', and when this modifier is parsed, # it consumes all characters until the end of the expression, which in # this case would replace the suffix 'value' with the literal 'sysv:tl', # ignoring that the ':tl' was intended to be an additional modifier. # # Due to all of this, this surprising behavior is not easy to fix. # .undef LATER .undef later INDIRECT:= ${LATER:S,value,replaced,} OK ${LATER:value=sysv} indirect:= ${INDIRECT:tl} -# expect+1: while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}": while evaluating variable "later" with value "": Unknown modifier "s,value,replaced," +# expect+1: Unknown modifier "s,value,replaced," .if ${indirect} != " ok " . error .else # expect+1: warning: XXX Neither branch should be taken. . warning XXX Neither branch should be taken. .endif LATER= uppercase-value later= lowercase-value -# expect+1: while evaluating variable "indirect" with value "${later:s,value,replaced,} ok ${later:value=sysv}": while evaluating variable "later" with value "lowercase-value": Unknown modifier "s,value,replaced," +# expect+1: Unknown modifier "s,value,replaced," .if ${indirect} != "uppercase-replaced ok uppercase-sysv" # expect+1: warning: XXX Neither branch should be taken. . warning XXX Neither branch should be taken. .else . error .endif all: @:; diff --git a/contrib/bmake/unit-tests/var-op-shell.exp b/contrib/bmake/unit-tests/var-op-shell.exp index 952af6df5518..9117001d40a7 100644 --- a/contrib/bmake/unit-tests/var-op-shell.exp +++ b/contrib/bmake/unit-tests/var-op-shell.exp @@ -1,11 +1,17 @@ make: "var-op-shell.mk" line 32: warning: Command "echo "failed"; (exit 13)" exited with status 13 make: "var-op-shell.mk" line 39: warning: Command "exit 13" exited with status 13 make: "var-op-shell.mk" line 62: warning: "kill $$" exited on a signal /bin/no/such/command: not found make: "var-op-shell.mk" line 69: warning: Command "/bin/no/such/command" exited with status 127 stderr Capturing the output of command "echo '$$$$'" Global: OUTPUT = $$$$ Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 +Var_Parse: ${UNDEF}y (eval) +Capturing the output of command "echo xy" +Global: OUTPUT_OF_UNDEF = xy +Var_Parse: ${OUTPUT_OF_UNDEF} != "xy" (eval-defined-loud) +Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/var-op-shell.mk b/contrib/bmake/unit-tests/var-op-shell.mk index bfc2e4f1361d..f9c7c717b8f1 100644 --- a/contrib/bmake/unit-tests/var-op-shell.mk +++ b/contrib/bmake/unit-tests/var-op-shell.mk @@ -1,112 +1,121 @@ -# $NetBSD: var-op-shell.mk,v 1.10 2024/07/11 20:09:16 sjg Exp $ +# $NetBSD: var-op-shell.mk,v 1.11 2025/01/11 21:21:33 rillig Exp $ # # Tests for the != variable assignment operator, which runs its right-hand # side through the shell. # The variable OUTPUT gets the output from running the shell command. OUTPUT!= echo "success"'ful' .if ${OUTPUT} != "successful" . error .endif # Since 2014-08-20, the output of the shell command may be empty. # # On 1996-05-29, when the '!=' assignment operator and Cmd_Exec were added, # an empty output produced the error message "Couldn't read shell's output # for \"%s\"". # # The error message is still in Cmd_Exec but reserved for technical errors. # It may be possible to trigger the error message by killing the shell after # reading part of its output. OUTPUT!= true .if ${OUTPUT} != "" . error .endif # The output of a shell command that failed is processed nevertheless. # Unlike the other places that run external commands (expression modifier # '::!=', expression modifier ':!...!'), a failed command generates only a # warning, not an "error". These "errors" are ignored in default mode, for # compatibility, but not in lint mode (-dL). # expect+1: warning: Command "echo "failed"; (exit 13)" exited with status 13 OUTPUT!= echo "failed"; (exit 13) .if ${OUTPUT} != "failed" . error .endif # A command with empty output may fail as well. # expect+1: warning: Command "exit 13" exited with status 13 OUTPUT!= exit 13 .if ${OUTPUT} != "" . error .endif # In the output of the command, each newline is replaced with a space. # Except for the very last one, which is discarded. OUTPUT!= echo "line 1"; echo "line 2" .if ${OUTPUT} != "line 1 line 2" . error .endif # A failing command in the middle results in the exit status 0, which in the # end means that the whole sequence of commands succeeded. OUTPUT!= echo "before"; (exit 13); echo "after" .if ${OUTPUT} != "before after" . error .endif # This should result in a warning about "exited on a signal". # This used to be kill -14 (SIGALRM), but that stopped working on # Darwin18 after recent update. # expect+1: warning: "kill $$" exited on a signal OUTPUT!= kill $$$$ .if ${OUTPUT} != "" . error .endif # A nonexistent command produces a non-zero exit status. # expect+1: warning: Command "/bin/no/such/command" exited with status 127 OUTPUT!= /bin/no/such/command .if ${OUTPUT} != "" . error .endif # The output from the shell's stderr is not captured, it just passes through. OUTPUT!= echo "stdout"; echo "stderr" 1>&2 .if ${OUTPUT} != "stdout" . error .endif # The 8 dollar signs end up as 4 dollar signs when expanded. The shell sees # the command "echo '$$$$'". The 4 dollar signs are stored in OUTPUT, and # when that variable is expanded, they expand to 2 dollar signs. OUTPUT!= echo '$$$$$$$$' .if ${OUTPUT} != "\$\$" . error .endif # As a debugging aid, log the exact command that is run via the shell. .MAKEFLAGS: -dv OUTPUT!= echo '$$$$$$$$' .MAKEFLAGS: -d0 # Since main.c 1.607 from 2024-01-05, long shell commands are not run directly # via '$shell -c $command', they are first written to a temporary file that is # then fed to the shell via '$shell $tmpfile'. OUTPUT_SHORT!= echo "$$0" OUTPUT_LONG!= echo "$$0" || : ${:U:range=1000} # When running '$shell -c $command', '$0' in the shell evaluates to the name # of the shell. .if ${OUTPUT_SHORT:T} != ${.SHELL:T} . error .endif # When running '$shell $tmpfile', '$0' in the shell evaluates to the name of # the temporary file. .if !${OUTPUT_LONG:M*/make*} . error .endif +# An undefined expression results in an empty string. +.MAKEFLAGS: -dv +OUTPUT_OF_UNDEF!= echo x${UNDEF}y +.if ${OUTPUT_OF_UNDEF} != "xy" +. error +.endif +.MAKEFLAGS: -d0 + + all: diff --git a/contrib/bmake/unit-tests/var-recursive.exp b/contrib/bmake/unit-tests/var-recursive.exp index c92c22b125dc..59812e0e71ff 100644 --- a/contrib/bmake/unit-tests/var-recursive.exp +++ b/contrib/bmake/unit-tests/var-recursive.exp @@ -1,22 +1,27 @@ -make: "var-recursive.mk" line 21: still there -make: Variable DIRECT is recursive. - in var-recursive.mk:22 +make: "var-recursive.mk" line 11: Variable DIRECT is recursive. + while evaluating variable "DIRECT" with value "${DIRECT}" in directory - -make: stopped in unit-tests -make: Variable INDIRECT1 is recursive. - in var-recursive.mk:29 +make: "var-recursive.mk" line 11: <> +make: "var-recursive.mk" line 19: Variable INDIRECT1 is recursive. + while evaluating variable "INDIRECT2" with value "${INDIRECT1}" + while evaluating variable "INDIRECT1" with value "${INDIRECT2}" in directory - -make: stopped in unit-tests -make: "var-recursive.mk" line 37: ok -make: Variable V is recursive. - in var-recursive.mk:45 +make: "var-recursive.mk" line 19: <> +make: "var-recursive.mk" line 26: +make: "var-recursive.mk" line 34: Variable MODIFIERS is recursive. + while evaluating variable "MODIFIERS" with value "${MODIFIERS:Mpattern}" in directory - -make: stopped in unit-tests -: OK -In a command near "var-recursive.mk" line 57: make[1]: Variable VAR is recursive. - -make: stopped making "target" in unit-tests +make: "var-recursive.mk" line 34: +make: "var-recursive.mk" line 43: Variable V is recursive. + while evaluating variable "V" with value "$V" + in directory +make: "var-recursive.mk" line 43: <> +make: Fatal errors encountered -- cannot continue +make: stopped making "loadtime" in unit-tests +sub-exit status 1 +: before-recursive +make: Variable VAR is recursive. + while evaluating variable "VAR" with value "${VAR}" + in target "runtime" +sub-exit status 2 exit status 0 diff --git a/contrib/bmake/unit-tests/var-recursive.mk b/contrib/bmake/unit-tests/var-recursive.mk index 72231656673c..e2951c900b11 100644 --- a/contrib/bmake/unit-tests/var-recursive.mk +++ b/contrib/bmake/unit-tests/var-recursive.mk @@ -1,63 +1,64 @@ -# $NetBSD: var-recursive.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: var-recursive.mk,v 1.10 2024/08/29 20:20:36 rillig Exp $ # -# Tests for expressions that refer to themselves and thus -# cannot be evaluated. +# Tests for expressions that refer to themselves and thus cannot be +# evaluated, as that would lead to an endless loop. -TESTS= direct indirect conditional short target +.if make(loadtime) -# Since make exits immediately when it detects a recursive expression, -# the actual tests are run in sub-makes. -TEST?= # none -.if ${TEST} == "" -all: -.for test in ${TESTS} - @${.MAKE} -f ${MAKEFILE} TEST=${test} || : -.endfor - -.elif ${TEST} == direct +DIRECT= ${DIRECT} # Defining a recursive variable is not an error. +# expect+2: Variable DIRECT is recursive. +# expect+1: <> +. info <${DIRECT}> # But expanding such a variable is an error. -DIRECT= ${DIRECT} # Defining a recursive variable is not yet an error. -# expect+1: still there -. info still there # Therefore this line is printed. -. info ${DIRECT} # But expanding the variable is an error. - -.elif ${TEST} == indirect # The chain of variables that refer to each other may be long. INDIRECT1= ${INDIRECT2} INDIRECT2= ${INDIRECT1} -. info ${INDIRECT1} +# expect+2: Variable INDIRECT1 is recursive. +# expect+1: <> +. info <${INDIRECT1}> -.elif ${TEST} == conditional # The variable refers to itself, but only in the branch of a condition that -# is never satisfied and is thus not evaluated. +# is not satisfied and is thus not evaluated. CONDITIONAL= ${1:?ok:${CONDITIONAL}} -# expect+1: ok -. info ${CONDITIONAL} +# expect+1: +. info <${CONDITIONAL}> + + +# An expression with modifiers is skipped halfway. This can lead to wrong +# follow-up error messages, but recursive variables occur seldom. +MODIFIERS= ${MODIFIERS:Mpattern} +# expect+2: Variable MODIFIERS is recursive. +# expect+1: +. info <${MODIFIERS}> -.elif ${TEST} == short # Short variable names can be expanded using the short-hand $V notation, # which takes a different code path in Var_Parse for parsing the variable # name. Ensure that these are checked as well. V= $V -. info $V +# expect+2: Variable V is recursive. +# expect+1: <> +. info <$V> -.elif ${TEST} == target +.elif make(runtime) -# If a recursive variable is accessed in a command of a target, the makefiles -# are not parsed anymore, so there is no location information from the -# .includes and .for directives. In such a case, use the location of the last -# command of the target to provide at least a hint to the location. VAR= ${VAR} -target: - : OK - : ${VAR} - : OK +runtime: +# expect: : before-recursive + : before-recursive +# expect: make: Variable VAR is recursive. +# expect-not: recursive-line-before +# expect-not: recursive-line-after + : recursive-line-before <${VAR}> recursive-line-after +# expect-not: after-recursive + : after-recursive .else -. error Unknown test "${TEST}" -.endif all: + @${MAKE} -f ${MAKEFILE} loadtime || echo "sub-exit status $$?" + @${MAKE} -f ${MAKEFILE} runtime || echo "sub-exit status $$?" + +.endif diff --git a/contrib/bmake/unit-tests/vardebug.exp b/contrib/bmake/unit-tests/vardebug.exp index 9937185ae807..5e220f24df74 100644 --- a/contrib/bmake/unit-tests/vardebug.exp +++ b/contrib/bmake/unit-tests/vardebug.exp @@ -1,69 +1,78 @@ Global: ignoring delete 'FROM_CMDLINE' as it is not found Command: FROM_CMDLINE = # (empty) Global: .MAKEOVERRIDES = FROM_CMDLINE Global: VAR = added Global: VAR = overwritten Global: delete VAR Global: ignoring delete 'VAR' as it is not found Global: ignoring ' = empty name' as the variable name '${:U}' expands to empty Global: ignoring ' += empty name' as the variable name '${:U}' expands to empty Global: ignoring 'FROM_CMDLINE = overwritten' due to a command line variable of the same name Global: VAR = 1 Global: VAR = 1 2 Global: VAR = 1 2 3 -Var_Parse: ${VAR:M[2]} (eval-defined) +Var_Parse: ${VAR:M[2]} (eval-defined-loud) Evaluating modifier ${VAR:M...} on value "1 2 3" Pattern for ':M' is "[2]" ModifyWords: split "1 2 3" into 3 words Result of ${VAR:M[2]} is "2" -Var_Parse: ${VAR:N[2]} (eval-defined) +Var_Parse: ${VAR:N[2]} (eval-defined-loud) Evaluating modifier ${VAR:N...} on value "1 2 3" Pattern for ':N' is "[2]" ModifyWords: split "1 2 3" into 3 words Result of ${VAR:N[2]} is "1 3" -Var_Parse: ${VAR:S,2,two,} (eval-defined) +Var_Parse: ${VAR:S,2,two,} (eval-defined-loud) Evaluating modifier ${VAR:S...} on value "1 2 3" Modifier part: "2" Modifier part: "two" ModifyWords: split "1 2 3" into 3 words Result of ${VAR:S,2,two,} is "1 two 3" -Var_Parse: ${VAR:Q} (eval-defined) +Var_Parse: ${VAR:Q} (eval-defined-loud) Evaluating modifier ${VAR:Q} on value "1 2 3" Result of ${VAR:Q} is "1\ 2\ 3" -Var_Parse: ${VAR:tu:tl:Q} (eval-defined) +Var_Parse: ${VAR:tu:tl:Q} (eval-defined-loud) Evaluating modifier ${VAR:t...} on value "1 2 3" Result of ${VAR:tu} is "1 2 3" Evaluating modifier ${VAR:t...} on value "1 2 3" Result of ${VAR:tl} is "1 2 3" Evaluating modifier ${VAR:Q} on value "1 2 3" Result of ${VAR:Q} is "1\ 2\ 3" -Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} (eval-defined) -Evaluating modifier ${:U...} on value "" (eval-defined, undefined) -Result of ${:Uvalue} is "value" (eval-defined, defined) +Var_Parse: ${:Uvalue:${:UM*e}:Mvalu[e]} (eval-defined-loud) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:Uvalue} is "value" (eval, defined) Indirect modifier "M*e" from "${:UM*e}" -Evaluating modifier ${:M...} on value "value" (eval-defined, defined) +Evaluating modifier ${:M...} on value "value" (eval, defined) Pattern for ':M' is "*e" ModifyWords: split "value" into 1 word -Result of ${:M*e} is "value" (eval-defined, defined) -Evaluating modifier ${:M...} on value "value" (eval-defined, defined) +Result of ${:M*e} is "value" (eval, defined) +Evaluating modifier ${:M...} on value "value" (eval, defined) Pattern for ':M' is "valu[e]" ModifyWords: split "value" into 1 word -Result of ${:Mvalu[e]} is "value" (eval-defined, defined) +Result of ${:Mvalu[e]} is "value" (eval, defined) Global: delete VAR -Var_Parse: ${:Uvariable:unknown} (eval-defined) -Evaluating modifier ${:U...} on value "" (eval-defined, undefined) -Result of ${:Uvariable} is "variable" (eval-defined, defined) -Evaluating modifier ${:u...} on value "variable" (eval-defined, defined) -make: "vardebug.mk" line 63: while evaluating "${:Uvariable:unknown}" with value "variable": Unknown modifier "unknown" -Result of ${:unknown} is error (eval-defined, defined) -make: "vardebug.mk" line 63: Malformed conditional (${:Uvariable:unknown}) -Var_Parse: ${UNDEFINED} (eval-defined) -make: "vardebug.mk" line 73: Malformed conditional (${UNDEFINED}) +Var_Parse: ${:Uvariable:unknown} (eval-defined-loud) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:Uvariable} is "variable" (eval, defined) +Evaluating modifier ${:u...} on value "variable" (eval, defined) +make: "vardebug.mk" line 62: Unknown modifier "unknown" + while evaluating "${:Uvariable:unknown}" with value "variable" +Result of ${:unknown} is error (eval, defined) +Var_Parse: ${UNDEFINED} (eval-defined-loud) +make: "vardebug.mk" line 66: Variable "UNDEFINED" is undefined Global: ignoring delete '.SHELL' as it is not found Command: .SHELL = Command: ignoring '.SHELL = overwritten' as it is read-only +Global: DYN = ${:U$@} $@ ${@} +Var_Parse: ${DYN} (eval-keep-dollar-and-undefined) +Var_Parse: ${:U$@} $@ ${@} (eval-keep-dollar-and-undefined) +Evaluating modifier ${:U...} on value "" (eval-keep-dollar-and-undefined, undefined) +Var_Parse: $@} $@ ${@} (eval-keep-dollar-and-undefined) +Result of ${:U$@} is "$(.TARGET)" (eval-keep-dollar-and-undefined, defined) +Var_Parse: $@ ${@} (eval-keep-dollar-and-undefined) +Var_Parse: ${@} (eval-keep-dollar-and-undefined) +Global: DYN = $(.TARGET) $(.TARGET) ${@} Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/vardebug.mk b/contrib/bmake/unit-tests/vardebug.mk index 7e6154d7e52c..975f32b62286 100644 --- a/contrib/bmake/unit-tests/vardebug.mk +++ b/contrib/bmake/unit-tests/vardebug.mk @@ -1,82 +1,79 @@ -# $NetBSD: vardebug.mk,v 1.11 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: vardebug.mk,v 1.16 2025/01/11 21:21:33 rillig Exp $ # # Demonstrates the debugging output for var.c. .MAKEFLAGS: -dv FROM_CMDLINE= # expect: Global: VAR = added VAR= added # VarAdd # expect: Global: VAR = overwritten VAR= overwritten # Var_Set # expect: Global: delete VAR .undef VAR # expect: Global: ignoring delete 'VAR' as it is not found .undef VAR # The variable with the empty name cannot be set at all. # expect: Global: ignoring ' = empty name' as the variable name '${:U}' expands to empty ${:U}= empty name # Var_Set # expect: Global: ignoring ' += empty name' as the variable name '${:U}' expands to empty ${:U}+= empty name # Var_Append FROM_CMDLINE= overwritten # Var_Set (ignored) # expect: Global: VAR = 1 VAR= 1 # expect: Global: VAR = 1 2 VAR+= 2 # expect: Global: VAR = 1 2 3 VAR+= 3 # expect: Pattern for ':M' is "[2]" # expect: Result of ${VAR:M[2]} is "2" .if ${VAR:M[2]} # ModifyWord_Match .endif # expect: Pattern for ':N' is "[2]" # expect: Result of ${VAR:N[2]} is "1 3" .if ${VAR:N[2]} # ModifyWord_NoMatch .endif .if ${VAR:S,2,two,} # ParseModifierPart .endif # expect: Result of ${VAR:Q} is "1\ 2\ 3" .if ${VAR:Q} # VarQuote .endif .if ${VAR:tu:tl:Q} # ApplyModifiers .endif # ApplyModifiers, "Got ..." -# expect: Result of ${:Mvalu[e]} is "value" (eval-defined, defined) +# expect: Result of ${:Mvalu[e]} is "value" (eval, defined) .if ${:Uvalue:${:UM*e}:Mvalu[e]} .endif # expect: Global: delete VAR .undef ${:UVAR} # Var_Delete # When ApplyModifiers results in an error, this appears in the debug log # as "is error", without surrounding quotes. -# expect: Result of ${:unknown} is error (eval-defined, defined) -# expect+2: Malformed conditional (${:Uvariable:unknown}) -# expect+1: while evaluating "${:Uvariable:unknown}" with value "variable": Unknown modifier "unknown" +# expect: Result of ${:unknown} is error (eval, defined) +# expect+1: Unknown modifier "unknown" .if ${:Uvariable:unknown} .endif -# XXX: The error message is "Malformed conditional", which is wrong. -# The condition is syntactically fine, it just contains an undefined variable. -# -# There is a specialized error message for "Undefined variable", but as of -# 2020-08-08, that is not covered by any unit tests. It might even be -# unreachable. -# expect+1: Malformed conditional (${UNDEFINED}) +# expect+1: Variable "UNDEFINED" is undefined .if ${UNDEFINED} .endif # By default, .SHELL is not defined and thus can be set. As soon as it is # accessed, it is initialized in the command line scope (during VarFind), # where it is set to read-only. Assigning to it is ignored. # expect: Command: ignoring '.SHELL = overwritten' as it is read-only .MAKEFLAGS: .SHELL=overwritten +DYN = ${:U$@} $@ ${@} +# expect: Global: DYN = $(.TARGET) $(.TARGET) ${@} +DYN := ${DYN} + .MAKEFLAGS: -d0 diff --git a/contrib/bmake/unit-tests/varmisc.exp b/contrib/bmake/unit-tests/varmisc.exp index 5e63f9f6563a..79d2c19e4d1b 100644 --- a/contrib/bmake/unit-tests/varmisc.exp +++ b/contrib/bmake/unit-tests/varmisc.exp @@ -1,71 +1,72 @@ :D expanded when var set true TRUE :U expanded when var undef true TRUE :D skipped if var undef :U skipped when var set is set :? only lhs when value true true TRUE :? only rhs when value false false FALSE do not evaluate or expand :? if discarding is set Version=123.456.789 == 123456789 Literal=3.4.5 == 3004005 We have target specific vars save-dollars: 0 = $ save-dollars: 1 = $$ save-dollars: 2 = $$ save-dollars: False = $ save-dollars: True = $$ save-dollars: false = $ save-dollars: true = $$ save-dollars: Yes = $$ save-dollars: No = $ save-dollars: yes = $$ save-dollars: no = $ save-dollars: On = $$ save-dollars: Off = $ save-dollars: ON = $$ save-dollars: OFF = $ save-dollars: on = $$ save-dollars: off = $ export-appended: env export-appended: env export-appended: env mk parse-dynamic: parse-dynamic parse-dynamic before parse-dynamic: parse-dynamic parse-dynamic after parse-dynamic: parse-dynamic parse-dynamic after -varerror-unclosed:begin -make: in target "varerror-unclosed": Unclosed variable "" +varerror-unclosed-1:begin +make: Unclosed variable "" + in target "varerror-unclosed-2" +make: Unclosed variable "UNCLOSED" + in target "varerror-unclosed-3" +make: Unclosed variable "UNCLOSED" + in target "varerror-unclosed-4" +make: Unclosed variable "PATTERN" + while evaluating variable "UNCLOSED" with value "" + in target "varerror-unclosed-5" +make: Unclosed expression, expecting '}' for modifier "M${PATTERN" + while evaluating variable "UNCLOSED" with value "" + in target "varerror-unclosed-5" +make: Unclosed variable "param" + in target "varerror-unclosed-6" +make: Unclosed variable "UNCLOSED." + in target "varerror-unclosed-6" -make: in target "varerror-unclosed": Unclosed variable "UNCLOSED" - -make: in target "varerror-unclosed": Unclosed variable "UNCLOSED" - -make: in target "varerror-unclosed": while evaluating variable "UNCLOSED" with value "": Unclosed variable "PATTERN" -make: in target "varerror-unclosed": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' for modifier "M${PATTERN" - -make: in target "varerror-unclosed": Unclosed variable "param" -make: in target "varerror-unclosed": Unclosed variable "UNCLOSED." - - -make: in target "varerror-unclosed": Unclosed variable "UNCLOSED.1" - -make: in target "varerror-unclosed": Unclosed variable "UNCLOSED.2" - -make: in target "varerror-unclosed": Unclosed variable "UNCLOSED.3" - -make: in target "varerror-unclosed": while evaluating variable "UNCLOSED_INDIR_2" with value "${UNCLOSED_INDIR_1}": while evaluating variable "UNCLOSED_INDIR_1" with value "${UNCLOSED_ORIG": Unclosed variable "UNCLOSED_ORIG" - -varerror-unclosed:end +make: Unclosed variable "UNCLOSED.1" + in target "varerror-unclosed-7" +make: Unclosed variable "UNCLOSED_ORIG" + while evaluating variable "UNCLOSED_INDIR_1" with value "${UNCLOSED_ORIG" + while evaluating variable "UNCLOSED_INDIR_2" with value "${UNCLOSED_INDIR_1}" + in target "varerror-unclosed-8" target1-flags: we have: one two target2-flags: we have: one two three four exit status 2 diff --git a/contrib/bmake/unit-tests/varmisc.mk b/contrib/bmake/unit-tests/varmisc.mk index 94ac32812d87..b067742e9ac4 100644 --- a/contrib/bmake/unit-tests/varmisc.mk +++ b/contrib/bmake/unit-tests/varmisc.mk @@ -1,213 +1,217 @@ -# $Id: varmisc.mk,v 1.27 2024/07/06 18:23:18 sjg Exp $ -# $NetBSD: varmisc.mk,v 1.35 2024/07/05 18:59:33 rillig Exp $ +# $NetBSD: varmisc.mk,v 1.37 2024/08/29 20:20:36 rillig Exp $ # # Miscellaneous variable tests. all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \ cmpv all: save-dollars all: export-appended all: parse-dynamic -all: varerror-unclosed +all: varerror-unclosed-{1,2,3,4,5,6,7,8} unmatched_var_paren: @echo ${foo::=foo-text} True= ${echo true >&2:L:sh}TRUE False= ${echo false >&2:L:sh}FALSE VSET= is set .undef UNDEF U_false: @echo :U skipped when var set @echo ${VSET:U${False}} D_false: @echo :D skipped if var undef @echo ${UNDEF:D${False}} U_true: @echo :U expanded when var undef @echo ${UNDEF:U${True}} D_true: @echo :D expanded when var set @echo ${VSET:D${True}} Q_lhs: @echo :? only lhs when value true @echo ${1:L:?${True}:${False}} Q_rhs: @echo :? only rhs when value false @echo ${0:L:?${True}:${False}} NQ_none: @echo do not evaluate or expand :? if discarding @echo ${VSET:U${1:L:?${True}:${False}}} # big jumps to handle 3 digits per step M_cmpv.units= 1 1000 1000000 M_cmpv= S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh Version= 123.456.789 cmpv.only= target specific vars cmpv: @echo Version=${Version} == ${Version:${M_cmpv}} @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}} @echo We have ${${.TARGET:T}.only} # Test parsing of boolean values. # begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean. SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off SD_4_DOLLARS= $$$$ .for val in ${SD_VALUES} # The assignment must be done using ':=' since a simple '=' would be # interpreted as 'yes', due to the leading '$'; see ParseBoolean. .MAKE.SAVE_DOLLARS:= ${val} SD.${val}:= ${SD_4_DOLLARS} .endfor .MAKE.SAVE_DOLLARS:= yes save-dollars: .for val in ${SD_VALUES} @printf '%s: %-8s = %s\n' $@ ${val} ${SD.${val}:Q} .endfor # end .MAKE.SAVE_DOLLARS # Appending to an undefined variable does not add a space in front. .undef APPENDED APPENDED+= value .if ${APPENDED} != "value" . error "${APPENDED}" .endif # Appending to an empty variable adds a space between the old value # and the additional value. APPENDED= # empty APPENDED+= value .if ${APPENDED} != " value" . error "${APPENDED}" .endif # Appending to parameterized variables works as well. PARAM= param VAR.${PARAM}= 1 VAR.${PARAM}+= 2 .if ${VAR.param} != "1 2" . error "${VAR.param}" .endif # The variable name can contain arbitrary characters. # If the expanded variable name ends in a +, this still does not influence # the parser. The assignment operator is still a simple assignment. # Therefore, there is no need to add a space between the variable name # and the assignment operator. PARAM= + VAR.${PARAM}= 1 VAR.${PARAM}+= 2 .if ${VAR.+} != "1 2" . error "${VAR.+}" .endif .for param in : + ! ? VAR.${param}= ${param} .endfor .if ${VAR.${:U\:}} != ":" || ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?" . error "${VAR.+}" "${VAR.!}" "${VAR.?}" .endif # Appending to a variable from the environment creates a copy of that variable # in the global scope. # The appended value is not exported automatically. # When a variable is exported, the exported value is taken at the time of the # .export directive. Later changes to the variable have no effect. .export FROM_ENV_BEFORE FROM_ENV+= mk FROM_ENV_BEFORE+= mk FROM_ENV_AFTER+= mk .export FROM_ENV_AFTER export-appended: @echo $@: "$$FROM_ENV" @echo $@: "$$FROM_ENV_BEFORE" @echo $@: "$$FROM_ENV_AFTER" # begin parse-dynamic # # Demonstrate that the target-specific variables are not evaluated in # the global scope. Their expressions are preserved until there is a local # scope in which resolving them makes sense. # There are different code paths for short names ... ${:U>}= before GS_TARGET:= $@ GS_MEMBER:= $% GS_PREFIX:= $* GS_ARCHIVE:= $! GS_ALLSRC:= $> ${:U>}= after # ... and for braced short names ... GB_TARGET:= ${@} GB_MEMBER:= ${%} GB_PREFIX:= ${*} GB_ARCHIVE:= ${!} GB_ALLSRC:= ${>} # ... and for long names. GL_TARGET:= ${.TARGET} GL_MEMBER:= ${.MEMBER} GL_PREFIX:= ${.PREFIX} GL_ARCHIVE:= ${.ARCHIVE} GL_ALLSRC:= ${.ALLSRC} parse-dynamic: @echo $@: ${GS_TARGET} ${GS_MEMBER} ${GS_PREFIX} ${GS_ARCHIVE} ${GS_ALLSRC} @echo $@: ${GB_TARGET} ${GB_MEMBER} ${GB_PREFIX} ${GB_ARCHIVE} ${GB_ALLSRC} @echo $@: ${GL_TARGET} ${GL_MEMBER} ${GL_PREFIX} ${GL_ARCHIVE} ${GL_ALLSRC} # Since 2020-07-28, make complains about unclosed variables. # Before that, it had complained about unclosed variables only when # parsing the modifiers, but not when parsing the variable name. UNCLOSED_INDIR_1= ${UNCLOSED_ORIG UNCLOSED_INDIR_2= ${UNCLOSED_INDIR_1} FLAGS= one two FLAGS+= ${FLAGS.${.ALLSRC:M*.c:T:u}} FLAGS.target2.c= three four target1.c: target2.c: all: target1-flags target2-flags target1-flags: target1.c @echo $@: we have: ${FLAGS} target2-flags: target2.c @echo $@: we have: ${FLAGS} -varerror-unclosed: +varerror-unclosed-1: @echo $@:begin -# expect: make: in target "varerror-unclosed": Unclosed variable "" +varerror-unclosed-2: +# expect: make: Unclosed variable "" @echo $( -# expect: make: in target "varerror-unclosed": Unclosed variable "UNCLOSED" +varerror-unclosed-3: +# expect: make: Unclosed variable "UNCLOSED" @echo $(UNCLOSED -# expect: make: in target "varerror-unclosed": Unclosed variable "UNCLOSED" +varerror-unclosed-4: +# expect: make: Unclosed variable "UNCLOSED" @echo ${UNCLOSED -# expect: make: in target "varerror-unclosed": while evaluating variable "UNCLOSED" with value "": Unclosed expression, expecting '}' for modifier "M${PATTERN" +varerror-unclosed-5: +# expect: make: Unclosed expression, expecting '}' for modifier "M${PATTERN" @echo ${UNCLOSED:M${PATTERN -# expect: make: in target "varerror-unclosed": Unclosed variable "param" -# expect: make: in target "varerror-unclosed": Unclosed variable "UNCLOSED." +varerror-unclosed-6: +# expect: make: Unclosed variable "param" +# expect: make: Unclosed variable "UNCLOSED." @echo ${UNCLOSED.${param +varerror-unclosed-7: @echo $ .for i in 1 2 3 -# expect: make: in target "varerror-unclosed": Unclosed variable "UNCLOSED.1" -# expect: make: in target "varerror-unclosed": Unclosed variable "UNCLOSED.2" -# expect: make: in target "varerror-unclosed": Unclosed variable "UNCLOSED.3" +# expect: make: Unclosed variable "UNCLOSED.1" @echo ${UNCLOSED.${i} .endfor +varerror-unclosed-8: @echo ${UNCLOSED_INDIR_2} @echo $@:end diff --git a/contrib/bmake/unit-tests/varmod-assign-shell.exp b/contrib/bmake/unit-tests/varmod-assign-shell.exp index 819fa6a5f304..712636491fc1 100644 --- a/contrib/bmake/unit-tests/varmod-assign-shell.exp +++ b/contrib/bmake/unit-tests/varmod-assign-shell.exp @@ -1,14 +1,14 @@ make: "varmod-assign-shell.mk" line 21: warning: Command "echo output; (exit 13)" exited with status 13 Global: _ = # (empty) Var_Parse: ${ASSIGNED::!=echo output; ${:U(exit 13)}} (eval-keep-dollar-and-undefined) Evaluating modifier ${ASSIGNED::...} on value "previous" (eval-keep-dollar-and-undefined, regular) Modifier part: "echo output; (exit 13)" Capturing the output of command "echo output; (exit 13)" -make: "varmod-assign-shell.mk" line 26: warning: while evaluating variable "ASSIGNED" with value "previous": Command "echo output; (exit 13)" exited with status 13 +make: "varmod-assign-shell.mk" line 26: warning: Command "echo output; (exit 13)" exited with status 13 Result of ${ASSIGNED::!=echo output; ${:U(exit 13)}} is "" (eval-keep-dollar-and-undefined, regular) Global: _ = # (empty) Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 DIRECT=output ASSIGNED=previous exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-assign-shell.mk b/contrib/bmake/unit-tests/varmod-assign-shell.mk index 0b3e553055b3..7bbea0ff9463 100644 --- a/contrib/bmake/unit-tests/varmod-assign-shell.mk +++ b/contrib/bmake/unit-tests/varmod-assign-shell.mk @@ -1,31 +1,31 @@ -# $NetBSD: varmod-assign-shell.mk,v 1.8 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: varmod-assign-shell.mk,v 1.11 2025/01/11 21:21:33 rillig Exp $ # # Tests for the variable modifier '::!=', which assigns the output of a shell # command to the variable, but only if the command exited successfully. This # is different from the other places that capture the output of an external # command (variable assignment operator '!=', expression modifier ':sh', # expression modifier ':!...!'), which also use the output when the shell # command fails or crashes. # # The variable modifier '::!=' and its close relatives have been around since # var.c 1.45 from 2000-06-01. # # Before 2020.08.25.21.16.53, the variable modifier '::!=' had a bug for # unsuccessful commands, it put the previous value of the variable into the # error message instead of the command that was executed. That's where the # counterintuitive error message 'make: "previous" returned non-zero status' # comes from. DIRECT= previous # expect+1: warning: Command "echo output; (exit 13)" exited with status 13 DIRECT!= echo output; (exit 13) ASSIGNED= previous .MAKEFLAGS: -dv # to see the "Capturing" debug output -# expect+1: warning: while evaluating variable "ASSIGNED" with value "previous": Command "echo output; (exit 13)" exited with status 13 +# expect+1: warning: Command "echo output; (exit 13)" exited with status 13 _:= ${ASSIGNED::!=echo output; ${:U(exit 13)}} .MAKEFLAGS: -d0 all: @echo DIRECT=${DIRECT:Q} @echo ASSIGNED=${ASSIGNED:Q} diff --git a/contrib/bmake/unit-tests/varmod-assign.exp b/contrib/bmake/unit-tests/varmod-assign.exp index 6c8bfb5cd6e8..4a7dc509642a 100644 --- a/contrib/bmake/unit-tests/varmod-assign.exp +++ b/contrib/bmake/unit-tests/varmod-assign.exp @@ -1,60 +1,64 @@ Global: param = twice Global: VARNAME = VAR.$${param} Var_Parse: ${VARNAME} (eval) Global: VAR.${param} = initial-value -Var_Parse: ${${VARNAME}::=assigned-value} (eval-defined) -Var_Parse: ${VARNAME}::=assigned-value} (eval-defined) +Var_Parse: ${${VARNAME}::=assigned-value} (eval-defined-loud) +Var_Parse: ${VARNAME}::=assigned-value} (eval) Evaluating modifier ${VAR.${param}::...} on value "initial-value" Modifier part: "assigned-value" Global: VAR.${param} = assigned-value Result of ${VAR.${param}::=assigned-value} is "" -Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined) -Var_Parse: ${VARNAME}} != "assigned-value" (eval-defined) +Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined-loud) +Var_Parse: ${VARNAME}} != "assigned-value" (eval) Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 -Var_Parse: ${CMD_CMD_VAR::=new-value} || ${CMD_GLOBAL_VAR::=new-value} || ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined) +Var_Parse: ${CMD_CMD_VAR::=new-value} || ${CMD_GLOBAL_VAR::=new-value} || ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined-loud) Evaluating modifier ${CMD_CMD_VAR::...} on value "cmd-value" Modifier part: "new-value" Command: CMD_CMD_VAR = new-value Global: .MAKEOVERRIDES = FIRST LAST LAST LAST APPENDED RAN RAN RAN IT1 THEN1 IE2 ELSE2 CMD_CMD_VAR CMD_CMD_VAR Result of ${CMD_CMD_VAR::=new-value} is "" -Var_Parse: ${CMD_GLOBAL_VAR::=new-value} || ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined) +Var_Parse: ${CMD_GLOBAL_VAR::=new-value} || ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined-loud) Evaluating modifier ${CMD_GLOBAL_VAR::...} on value "global-value" Modifier part: "new-value" Global: CMD_GLOBAL_VAR = new-value Result of ${CMD_GLOBAL_VAR::=new-value} is "" -Var_Parse: ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined) +Var_Parse: ${CMD_ENV_VAR::=new-value} || "${CMD_NEW_VAR::=new-value}" (eval-defined-loud) Evaluating modifier ${CMD_ENV_VAR::...} on value "env-value" Modifier part: "new-value" Global: CMD_ENV_VAR = new-value Result of ${CMD_ENV_VAR::=new-value} is "" Var_Parse: ${CMD_NEW_VAR::=new-value}" (eval) Evaluating modifier ${CMD_NEW_VAR::...} on value "" (eval, undefined) Modifier part: "new-value" Global: ignoring delete 'CMD_NEW_VAR' as it is not found Command: CMD_NEW_VAR = new-value Global: .MAKEOVERRIDES = FIRST LAST LAST LAST APPENDED RAN RAN RAN IT1 THEN1 IE2 ELSE2 CMD_CMD_VAR CMD_CMD_VAR CMD_NEW_VAR Result of ${CMD_NEW_VAR::=new-value} is "" (eval, undefined) Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -make: in target "mod-assign-empty": while evaluating "${::=value}" with value "": Bad modifier ":" -mod-assign-empty: value} -make: in target "mod-assign-empty": while evaluating "${:Uvalue::=overwritten}" with value "value": Bad modifier ":" -mod-assign-empty: overwritten} -mod-assign-empty: VAR=overwritten -make: in target "mod-assign-parse": while evaluating variable "ASSIGN" with value "": Unknown modifier ":x" - +make: Bad modifier ":" + while evaluating "${::=value}" with value "" + in target "mod-assign-empty-1" +make: Bad modifier ":" + while evaluating "${:Uvalue::=overwritten}" with value "value" + in target "mod-assign-empty-2" +mod-assign-empty-3: VAR=overwritten +make: Unknown modifier ":x" + while evaluating variable "ASSIGN" with value "" + in target "mod-assign-parse-1" sysv:y -make: in target "mod-assign-parse": while evaluating variable "ASSIGN" with value "": Unfinished modifier ('}' missing) - +make: Unfinished modifier ('}' missing) + while evaluating variable "ASSIGN" with value "" + in target "mod-assign-parse-3" ok=word -make: warning: in target "mod-assign-shell-error": while evaluating variable "SH_ERR" with value "previous": Command " echo word; (exit 13) " exited with status 13 +make: warning: Command " echo word; (exit 13) " exited with status 13 err=previous Command: TARGET_CMD_VAR = cmd-value Global: TARGET_GLOBAL_VAR = global-value target: TARGET_TARGET_VAR = target-value target: TARGET_TARGET_VAR = new-value Global: TARGET_GLOBAL_VAR = new-value Global: TARGET_ENV_VAR = new-value target: TARGET_NEW_VAR = new-value exit status 2 diff --git a/contrib/bmake/unit-tests/varmod-assign.mk b/contrib/bmake/unit-tests/varmod-assign.mk index ee1c86c039f5..237198faf254 100644 --- a/contrib/bmake/unit-tests/varmod-assign.mk +++ b/contrib/bmake/unit-tests/varmod-assign.mk @@ -1,209 +1,214 @@ -# $NetBSD: varmod-assign.mk,v 1.23 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: varmod-assign.mk,v 1.25 2024/08/29 20:20:36 rillig Exp $ # # Tests for the obscure ::= variable modifiers, which perform variable # assignments during evaluation, just like the = operator in C. .if !make(target) -all: mod-assign-empty -all: mod-assign-parse +all: mod-assign-empty-{1,2,3} +all: mod-assign-parse-{1,2,3} all: mod-assign-shell-error # In the following loop expression, # the '::?=' modifier applies the assignment operator '?=' 3 times. The # operator '?=' only has an effect for the first time, therefore the variable # FIRST ends up with the value 1. .if "${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}" != " first=1" . error .endif # In the following loop expression, # the modifier '::=' applies the assignment operator '=' 3 times. The # operator '=' overwrites the previous value, therefore the variable LAST ends # up with the value 3. .if "${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}" != " last=3" . error .endif # In the following loop expression, # the modifier '::+=' applies the assignment operator '+=' 3 times. The # operator '+=' appends 3 times to the variable, therefore the variable # APPENDED ends up with the value "1 2 3". .if "${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}" != " appended=1 2 3" . error .endif # In the following loop expression, # the modifier '::!=' applies the assignment operator '!=' 3 times. Just as # with the modifier '::=', the last value is stored in the RAN variable. .if "${1 2 3:L:@i@${RAN::!=${i:%=echo '<%>';}}@} ran=${RAN}" != " ran=<3>" . error .endif # When a '::=' modifier is evaluated as part of an .if condition, it happens # in the command line scope. .if "${FIRST}, ${LAST}, ${APPENDED}, ${RAN}" != "1, 3, 1 2 3, <3>" . error .endif # Tests for nested assignments, which are hard to read and therefore seldom # used in practice. # The condition "1" is true, therefore THEN1 gets assigned a value, # and the inner IT1 as well. Nothing surprising here. .if "${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}} ${THEN1}${ELSE1}${IT1}${IE1}" != " then1t1" . error .endif # The condition "0" is false, therefore ELSE2 gets assigned a value, # and the inner IE2 as well. Nothing surprising here as well. .if "${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}} ${THEN2}${ELSE2}${IT2}${IE2}" != " else2e2" . error .endif # The same effects happen when the variables are defined elsewhere. SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}} ${THEN3}${ELSE3}${IT3}${IE3} SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}} ${THEN4}${ELSE4}${IT4}${IE4} .if ${SINK3} != " then3t3" . error .endif .if ${SINK4} != " else4e4" . error .endif -mod-assign-empty: +mod-assign-empty-1: # Assigning to the empty variable would obviously not work since that - # variable is write-protected. Therefore it is rejected early with a - # "Bad modifier" message. + # variable is write-protected. +# expect: make: Bad modifier ":" @echo $@: ${::=value} +mod-assign-empty-2: # In this variant, it is not as obvious that the name of the - # expression is empty. Assigning to it is rejected as well, with the - # same "Bad modifier" message. + # expression is empty. +# expect: make: Bad modifier ":" @echo $@: ${:Uvalue::=overwritten} +mod-assign-empty-3: # The :L modifier sets the value of the expression to its variable # name. The name of the expression is "VAR", therefore assigning to # that variable works. +# expect: mod-assign-empty-3: VAR=overwritten @echo $@: ${VAR:L::=overwritten} VAR=${VAR} -mod-assign-parse: +mod-assign-parse-1: # The modifier for assignment operators starts with a ':'. # An 'x' after that is an invalid modifier. - # expect: make: in target "mod-assign-parse": while evaluating variable "ASSIGN" with value "": Unknown modifier ":x" +# expect: make: Unknown modifier ":x" @echo ${ASSIGN::x} +mod-assign-parse-2: # When parsing an assignment operator fails because the operator is # incomplete, make falls back to the SysV modifier. @echo ${SYSV::=sysv\:x}${SYSV::x=:y} -# expect: make: in target "mod-assign-parse": while evaluating variable "ASSIGN" with value "": Unfinished modifier ('}' missing) +mod-assign-parse-3: +# expect: make: Unfinished modifier ('}' missing) @echo ${ASSIGN::=value # missing closing brace mod-assign-shell-error: # If the command succeeds, the variable is assigned. @${SH_OK::!= echo word; true } echo ok=${SH_OK} # If the command fails, the variable keeps its previous value. @${SH_ERR::=previous} @${SH_ERR::!= echo word; (exit 13) } echo err=${SH_ERR} # XXX: The ::= modifier expands its right-hand side exactly once. # This differs subtly from normal assignments such as '+=' or '=', which copy # their right-hand side literally. APPEND.prev= previous APPEND.var= ${APPEND.prev} APPEND.indirect= indirect $${:Unot expanded} APPEND.dollar= $${APPEND.indirect} .if ${APPEND.var::+=${APPEND.dollar}} != "" . error .endif .if ${APPEND.var} != "previous indirect \${:Unot expanded}" . error .endif # The assignment modifier can be used in an expression that is # enclosed in parentheses. In such a case, parsing stops at the first ')', # not at the first '}'. VAR= previous _:= $(VAR::=current}) .if ${VAR} != "current}" . error .endif # Before var.c 1.888 from 2021-03-15, an expression using the modifier '::=' # expanded its variable name once too often during evaluation. This was only # relevant for variable names containing a '$' sign in their actual name, not # the usual VAR.${param}. .MAKEFLAGS: -dv param= twice VARNAME= VAR.$${param} # Indirect variable name because of the '$', # to avoid difficult escaping rules. ${VARNAME}= initial-value # Sets 'VAR.${param}' to 'expanded'. .if defined(VAR.twice) # At this point, the '$$' is not expanded. . error .endif .if ${${VARNAME}::=assigned-value} # Here the variable name gets expanded once . error # too often. .endif .if defined(VAR.twice) . error The variable name in the '::=' modifier is expanded once too often. .endif .if ${${VARNAME}} != "assigned-value" . error .endif .MAKEFLAGS: -d0 # Conditional directives are evaluated in command line scope. An assignment # modifier that creates a new variable creates it in the command line scope. # Existing variables are updated in their previous scope, and environment # variables are created in the global scope, as in other situations. .MAKEFLAGS: CMD_CMD_VAR=cmd-value CMD_GLOBAL_VAR=global-value export CMD_ENV_VAR=env-value .MAKEFLAGS: -dv # expect-reset # expect: Command: CMD_CMD_VAR = new-value # expect: Global: CMD_GLOBAL_VAR = new-value # expect: Global: CMD_ENV_VAR = new-value # expect: Global: ignoring delete 'CMD_NEW_VAR' as it is not found # expect: Command: CMD_NEW_VAR = new-value .if ${CMD_CMD_VAR::=new-value} \ || ${CMD_GLOBAL_VAR::=new-value} \ || ${CMD_ENV_VAR::=new-value} \ || "${CMD_NEW_VAR::=new-value}" . error .endif .MAKEFLAGS: -d0 # Run the 'target' test in a separate sub-make, with reduced debug logging. all: run-target run-target: .PHONY @${MAKE} -r -f ${MAKEFILE} -dv target 2>&1 | grep ': TARGET_' .else # make(target) # The commands of a target are evaluated in target scope. An assignment # modifier that creates a new variable creates it in the target scope. # Existing variables are updated in their previous scope, and environment # variables are created in the global scope, as in other situations. # # expect: target: TARGET_TARGET_VAR = new-value # expect: Global: TARGET_GLOBAL_VAR = new-value # expect: Global: TARGET_ENV_VAR = new-value # expect: target: TARGET_NEW_VAR = new-value .MAKEFLAGS: TARGET_CMD_VAR=cmd-value TARGET_GLOBAL_VAR=global-value export TARGET_ENV_VAR=env-value target: .PHONY TARGET_TARGET_VAR=target-value : ${TARGET_TARGET_VAR::=new-value} : ${TARGET_CMD_VAR::=new-value} : ${TARGET_GLOBAL_VAR::=new-value} : ${TARGET_ENV_VAR::=new-value} : ${TARGET_NEW_VAR::=new-value} .endif diff --git a/contrib/bmake/unit-tests/varmod-edge.exp b/contrib/bmake/unit-tests/varmod-edge.exp index ff4f73d30939..2b3b038373b3 100644 --- a/contrib/bmake/unit-tests/varmod-edge.exp +++ b/contrib/bmake/unit-tests/varmod-edge.exp @@ -1,12 +1,23 @@ -make: "varmod-edge.mk" line 60: while evaluating variable "MOD" with value "${INP:M${:U*)}}": while evaluating variable "INP" with value "(parentheses)": while evaluating "${:U*)" with value "*)": Unclosed expression, expecting '}' for modifier "U*)" -make: "varmod-edge.mk" line 88: while evaluating variable "MOD" with value "${INP:M${:U[[}}": while evaluating variable "INP" with value "[ [[ [[[": Unfinished character list in pattern '[[' of modifier ':M' -make: "varmod-edge.mk" line 178: while evaluating variable "MOD" with value "${INP:a\=b}": while evaluating variable "INP" with value "file.c file...": Unfinished modifier ('=' missing) -make: "varmod-edge.mk" line 194: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "value": Unknown modifier ":" -make: "varmod-edge.mk" line 194: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "": Unknown modifier ":" -make: "varmod-edge.mk" line 203: while evaluating "${:Z}" with value "": Unknown modifier "Z" -make: "varmod-edge.mk" line 203: Malformed conditional (${:Z}) -make: "varmod-edge.mk" line 217: while evaluating "${:S,}" with value "": Unfinished modifier (',' missing) -make: "varmod-edge.mk" line 217: Malformed conditional (${:S,}) +make: "varmod-edge.mk" line 60: Unclosed expression, expecting '}' for modifier "U*)" + while evaluating "${:U*)" with value "*)" + while evaluating variable "INP" with value "(parentheses)" + while evaluating variable "MOD" with value "${INP:M${:U*)}}" +make: "varmod-edge.mk" line 88: Unfinished character list in pattern '[[' of modifier ':M' + while evaluating variable "INP" with value "[ [[ [[[" + while evaluating variable "MOD" with value "${INP:M${:U[[}}" +make: "varmod-edge.mk" line 178: Unfinished modifier ('=' missing) + while evaluating variable "INP" with value "file.c file..." + while evaluating variable "MOD" with value "${INP:a\=b}" +make: "varmod-edge.mk" line 194: Unknown modifier ":" + while evaluating variable "INP" with value "value" + while evaluating variable "MOD" with value "${INP::::}" +make: "varmod-edge.mk" line 194: Unknown modifier ":" + while evaluating variable "INP" with value "" + while evaluating variable "MOD" with value "${INP::::}" +make: "varmod-edge.mk" line 200: Unknown modifier "Z" + while evaluating "${:Z}" with value "" +make: "varmod-edge.mk" line 213: Unfinished modifier (',' missing) + while evaluating "${:S,}" with value "" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-edge.mk b/contrib/bmake/unit-tests/varmod-edge.mk index 77c080ef3e5f..ee21b44e00f2 100644 --- a/contrib/bmake/unit-tests/varmod-edge.mk +++ b/contrib/bmake/unit-tests/varmod-edge.mk @@ -1,221 +1,217 @@ -# $NetBSD: varmod-edge.mk,v 1.29 2024/07/09 17:07:23 rillig Exp $ +# $NetBSD: varmod-edge.mk,v 1.33 2025/01/11 20:54:45 rillig Exp $ # # Tests for edge cases in variable modifiers. # # These tests demonstrate the current implementation in small examples. # They may contain surprising behavior. # # Each test consists of: # - INP, the input to the test # - MOD, the expression for testing the modifier # - EXP, the expected output INP= (parentheses) {braces} (opening closing) () MOD= ${INP:M(*)} EXP= (parentheses) () .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The first closing brace matches the opening parenthesis. # The second closing brace actually ends the expression. # # XXX: This is unexpected but rarely occurs in practice. INP= (paren-brace} ( MOD= ${INP:M(*}} EXP= (paren-brace} .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # After the :M modifier has parsed the pattern, only the closing brace # and the colon are unescaped. The other characters are left as-is. # To actually see this effect, the backslashes in the :M modifier need # to be doubled since single backslashes would simply be unescaped by # Str_Match. # # XXX: This is unexpected. The opening brace should also be unescaped. INP= ({}): \(\{\}\)\: \(\{}\): MOD= ${INP:M\\(\\{\\}\\)\\:} EXP= \(\{}\): .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # When the :M and :N modifiers are parsed, the pattern finishes as soon # as open_parens + open_braces == closing_parens + closing_braces. This # means that ( and } form a matching pair. # # Nested expressions are not parsed as such. Instead, only the # parentheses and braces are counted. This leads to a parse error since # the nested expression is not "${:U*)}" but only "${:U*)", which is # missing the closing brace. The expression is evaluated anyway. # The final brace in the output comes from the end of M.nest-mix. # # XXX: This is unexpected but rarely occurs in practice. INP= (parentheses) MOD= ${INP:M${:U*)}} EXP= (parentheses)} -# expect+1: while evaluating variable "MOD" with value "${INP:M${:U*)}}": while evaluating variable "INP" with value "(parentheses)": while evaluating "${:U*)" with value "*)": Unclosed expression, expecting '}' for modifier "U*)" +# expect+1: Unclosed expression, expecting '}' for modifier "U*)" .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # In contrast to parentheses and braces, the brackets are not counted # when the :M modifier is parsed since Makefile expressions only take the # ${VAR} or $(VAR) forms, but not $[VAR]. # # The final ] in the pattern is needed to close the character class. INP= [ [[ [[[ MOD= ${INP:M${:U[[[[[]}} EXP= [ .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The pattern in the nested variable has an unclosed character class. # # Before str.c 1.104 from 2024-07-06, no error was reported. # # Before 2019-12-02, this test case triggered an out-of-bounds read # in Str_Match. INP= [ [[ [[[ MOD= ${INP:M${:U[[}} EXP= [ -# expect+1: while evaluating variable "MOD" with value "${INP:M${:U[[}}": while evaluating variable "INP" with value "[ [[ [[[": Unfinished character list in pattern '[[' of modifier ':M' +# expect+1: Unfinished character list in pattern '[[' of modifier ':M' .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The first backslash does not escape the second backslash. # Therefore, the second backslash escapes the parenthesis. # This means that the pattern ends there. # The final } in the output comes from the end of MOD. # # If the first backslash were to escape the second backslash, the first # closing brace would match the opening parenthesis (see paren-brace), and # the second closing brace would be needed to close the variable. # After that, the remaining backslash would escape the parenthesis in # the pattern, therefore (} would match. INP= (} \( \(} MOD= ${INP:M\\(}} EXP= \(} #EXP= (} # If the first backslash were to escape ... .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The backslash in \( does not escape the parenthesis, therefore it # counts for the nesting level and matches with the first closing brace. # The second closing brace closes the variable, and the third is copied # literally. # # The second :M in the pattern is nested between ( and }, therefore it # does not start a new modifier. INP= ( (:M (:M} \( \(:M \(:M} MOD= ${INP:M\(:M*}}} EXP= (:M}} .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The double backslash is passed verbatim to the pattern matcher. # The Str_Match pattern is \\(:M*}, and there the backslash is unescaped. # Again, the ( takes place in the nesting level, and there is no way to # prevent this, no matter how many backslashes are used. INP= ( (:M (:M} \( \(:M \(:M} MOD= ${INP:M\\(:M*}}} EXP= \(:M}} .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for # matching the '*' patterns and did not optimize for multiple '*' in a row. # Test a pattern with 65536 asterisks. INP= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g} PAT= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g} MOD= ${INP:M${PAT}} EXP= ${INP} .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # This is the normal SysV substitution. Nothing surprising here. INP= file.c file.cc MOD= ${INP:%.c=%.o} EXP= file.o file.cc .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The SysV := modifier is greedy and consumes all the modifier text # up until the closing brace or parenthesis. The :Q may look like a # modifier, but it really isn't, that's why it appears in the output. INP= file.c file.cc MOD= ${INP:%.c=%.o:Q} EXP= file.o:Q file.cc .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # The = in the := modifier can be escaped. INP= file.c file.c=%.o MOD= ${INP:%.c\=%.o=%.ext} EXP= file.c file.ext .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # Having only an escaped '=' results in a parse error. # The call to "pattern.lhs = ParseModifierPart" fails. INP= file.c file... MOD= ${INP:a\=b} EXP= # empty -# expect+1: while evaluating variable "MOD" with value "${INP:a\=b}": while evaluating variable "INP" with value "file.c file...": Unfinished modifier ('=' missing) +# expect+1: Unfinished modifier ('=' missing) .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif INP= value MOD= ${INP:} EXP= value .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif INP= value MOD= ${INP::::} EXP= # empty -# expect+2: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "value": Unknown modifier ":" -# expect+1: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "": Unknown modifier ":" +# expect+2: Unknown modifier ":" +# expect+1: Unknown modifier ":" .if ${MOD} != ${EXP} . warning expected "${EXP}", got "${MOD}" .endif # Even in expressions based on an unnamed variable, there may be errors. -# XXX: The error message should mention the variable name of the expression, -# even though that name is empty in this case. -# expect+2: Malformed conditional (${:Z}) -# expect+1: while evaluating "${:Z}" with value "": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" .if ${:Z} . error .else . error .endif # Even in expressions based on an unnamed variable, there may be errors. # # Before var.c 1.842 from 2021-02-23, the error message did not surround the # variable name with quotes, leading to the rather confusing "Unfinished # modifier for (',' missing)", having two spaces in a row. # -# expect+2: while evaluating "${:S,}" with value "": Unfinished modifier (',' missing) -# expect+1: Malformed conditional (${:S,}) +# expect+1: Unfinished modifier (',' missing) .if ${:S,} . error .else . error .endif diff --git a/contrib/bmake/unit-tests/varmod-gmtime.exp b/contrib/bmake/unit-tests/varmod-gmtime.exp index c41e96723f65..d34cb5c6a93f 100644 --- a/contrib/bmake/unit-tests/varmod-gmtime.exp +++ b/contrib/bmake/unit-tests/varmod-gmtime.exp @@ -1,13 +1,13 @@ -make: "varmod-gmtime.mk" line 61: while evaluating "${:L:gmtime=-1} != """ with value "": Invalid time value "-1" -make: "varmod-gmtime.mk" line 61: Malformed conditional (${:L:gmtime=-1} != "") -make: "varmod-gmtime.mk" line 72: while evaluating "${:L:gmtime= 1} != """ with value "": Invalid time value " 1" -make: "varmod-gmtime.mk" line 72: Malformed conditional (${:L:gmtime= 1} != "") -make: "varmod-gmtime.mk" line 120: while evaluating "${:L:gmtime=10000000000000000000000000000000} != """ with value "": Invalid time value "10000000000000000000000000000000" -make: "varmod-gmtime.mk" line 120: Malformed conditional (${:L:gmtime=10000000000000000000000000000000} != "") -make: "varmod-gmtime.mk" line 133: while evaluating "${:L:gmtime=error} != """ with value "": Invalid time value "error" -make: "varmod-gmtime.mk" line 133: Malformed conditional (${:L:gmtime=error} != "") -make: "varmod-gmtime.mk" line 144: while evaluating variable "%Y" with value "%Y": Invalid time value "100000S,1970,bad," -make: "varmod-gmtime.mk" line 144: Malformed conditional (${%Y:L:gmtime=100000S,1970,bad,} != "bad") +make: "varmod-gmtime.mk" line 60: Invalid time value "-1" + while evaluating "${:L:gmtime=-1} != """ with value "" +make: "varmod-gmtime.mk" line 70: Invalid time value " 1" + while evaluating "${:L:gmtime= 1} != """ with value "" +make: "varmod-gmtime.mk" line 117: Invalid time value "10000000000000000000000000000000" + while evaluating "${:L:gmtime=10000000000000000000000000000000} != """ with value "" +make: "varmod-gmtime.mk" line 129: Invalid time value "error" + while evaluating "${:L:gmtime=error} != """ with value "" +make: "varmod-gmtime.mk" line 139: Invalid time value "100000S,1970,bad," + while evaluating variable "%Y" with value "%Y" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-gmtime.mk b/contrib/bmake/unit-tests/varmod-gmtime.mk index a2b983508be5..610aa2d76fd1 100644 --- a/contrib/bmake/unit-tests/varmod-gmtime.mk +++ b/contrib/bmake/unit-tests/varmod-gmtime.mk @@ -1,188 +1,183 @@ -# $NetBSD: varmod-gmtime.mk,v 1.24 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-gmtime.mk,v 1.27 2025/01/11 20:54:45 rillig Exp $ # # Tests for the :gmtime variable modifier, which formats a timestamp # using strftime(3) in UTC. # # See also: # varmod-localtime.mk .if ${TZ:Uundefined} != "undefined" # see unit-tests/Makefile . error .endif # Test for the default time format, %c. Since the time always varies, it's # only possible to check for the general format here. The names of the # month and weekday are always in English, independent from the locale. # Example: Thu Oct 29 18:56:41 2020 .if ${:U:gmtime:tW:M??? ??? ?? ??\:??\:?? ????} == "" . error .endif # modifier name too short, falling back to the SysV modifier. .if ${%Y:L:gmtim=1593536400} != "%Y" . error .endif # 2020-07-01T00:00:00Z .if ${%Y:L:gmtime=1593536400} != "2020" . error .endif # modifier name too long, falling back to the SysV modifier. .if ${%Y:L:gmtimer=1593536400} != "%Y" . error .endif # If the modifier name is not matched exactly, fall back to the # :from=to modifier. .if ${gmtime:L:gm%=local%} != "localtime" . error .endif # Before var.c 1.1050 from 2023-05-09, it was not possible to pass the # seconds via an expression. .if ${%Y:L:gmtime=${:U1593536400}} != "2020" . error .endif # Before var.c 1.631 from 2020-10-31 21:40:20, it was possible to pass # negative time stamps to the :gmtime modifier, resulting in dates before # 1970. Going back 50 years in the past is not a practical use case for # make. Therefore, since var.c 1.631, negative time stamps produce a # parse error. -# expect+2: while evaluating "${:L:gmtime=-1} != """ with value "": Invalid time value "-1" -# expect+1: Malformed conditional (${:L:gmtime=-1} != "") +# expect+1: Invalid time value "-1" .if ${:L:gmtime=-1} != "" . error .else . error .endif # Spaces were allowed before var.c 1.631 from 2020-10-31 21:40:20, not # because it would make sense but just as a side-effect from using strtoul. -# expect+2: while evaluating "${:L:gmtime= 1} != """ with value "": Invalid time value " 1" -# expect+1: Malformed conditional (${:L:gmtime= 1} != "") +# expect+1: Invalid time value " 1" .if ${:L:gmtime= 1} != "" . error .else . error .endif # 0 means now; this differs from GNode.mtime, where a 0 means nonexistent. # Since "now" constantly changes, the strongest possible test is to match the # resulting pattern. .if !${:L:gmtime=0:tW:M??? ??? ?? ??\:??\:?? 20??} . error .endif .if ${:L:gmtime=1} != "Thu Jan 1 00:00:01 1970" . error .endif # INT32_MAX .if ${:L:gmtime=2147483647} != "Tue Jan 19 03:14:07 2038" . error .endif .if ${:L:gmtime=2147483648} == "Tue Jan 19 03:14:08 2038" # All systems that have unsigned time_t or 64-bit time_t. .elif ${:L:gmtime=2147483648} == "Fri Dec 13 20:45:52 1901" # FreeBSD-12.0-i386 still has 32-bit signed time_t, see # sys/x86/include/_types.h, __LP64__. # # Linux on 32-bit systems may still have 32-bit signed time_t, see # sysdeps/unix/sysv/linux/generic/bits/typesizes.h, __TIMESIZE. .else . error .endif # Integer overflow, at least before var.c 1.631 from 2020-10-31. # Because this modifier is implemented using strtoul, the parsed time was # ULONG_MAX, which got converted to -1. This resulted in a time stamp of # the second before 1970. # # Since var.c 1.631 from 2020-10-31, the overflow is detected and produces a # parse error. -# expect+2: while evaluating "${:L:gmtime=10000000000000000000000000000000} != """ with value "": Invalid time value "10000000000000000000000000000000" -# expect+1: Malformed conditional (${:L:gmtime=10000000000000000000000000000000} != "") +# expect+1: Invalid time value "10000000000000000000000000000000" .if ${:L:gmtime=10000000000000000000000000000000} != "" . error .else . error .endif # Before var.c 1.631 from 2020-10-31, there was no error handling while # parsing the :gmtime modifier, thus no error message was printed. Parsing # stopped after the '=', and the remaining string was parsed for more variable # modifiers. Because of the unknown modifier 'e' from the 'error', the whole # variable value was discarded and thus not printed. -# expect+2: while evaluating "${:L:gmtime=error} != """ with value "": Invalid time value "error" -# expect+1: Malformed conditional (${:L:gmtime=error} != "") +# expect+1: Invalid time value "error" .if ${:L:gmtime=error} != "" . error .else . error .endif # Before var.c 1.1050 from 2023-05-09, the timestamp could be directly # followed by the next modifier, without a ':' separator. This was the same # bug as for the ':L' and ':P' modifiers. -# expect+2: while evaluating variable "%Y" with value "%Y": Invalid time value "100000S,1970,bad," -# expect+1: Malformed conditional (${%Y:L:gmtime=100000S,1970,bad,} != "bad") +# expect+1: Invalid time value "100000S,1970,bad," .if ${%Y:L:gmtime=100000S,1970,bad,} != "bad" . error .endif # Before var.c 1.1062 from 2023-08-19, ':gmtime' but not ':localtime' reported # wrong values for '%s', depending on the operating system and the timezone. export TZ=UTC .for t in ${%s:L:gmtime} ${%s:L:localtime} TIMESTAMPS+= $t .endfor export TZ=Europe/Berlin .for t in ${%s:L:gmtime} ${%s:L:localtime} TIMESTAMPS+= $t .endfor export TZ=UTC .for t in ${%s:L:gmtime} ${%s:L:localtime} TIMESTAMPS+= $t .endfor export TZ=America/Los_Angeles .for t in ${%s:L:gmtime} ${%s:L:localtime} TIMESTAMPS+= $t .endfor export TZ=UTC .for t in ${%s:L:gmtime} ${%s:L:localtime} TIMESTAMPS+= $t .endfor .for a b in ${TIMESTAMPS:[1]} ${TIMESTAMPS:@t@$t $t@} ${TIMESTAMPS:[-1]} . if $a > $b . warning timestamp $a > $b . endif .endfor .if ${year=%Y month=%m day=%d:L:gmtime=1459494000} != "year=2016 month=04 day=01" . error .endif # Slightly contorted syntax to convert a UTC timestamp from an expression to a # formatted timestamp. .if ${%Y%m%d:L:${gmtime=${:U1459494000}:L}} != "20160401" . error .endif all: diff --git a/contrib/bmake/unit-tests/varmod-hash.exp b/contrib/bmake/unit-tests/varmod-hash.exp index 55f43dbb56d8..030923cf09d6 100644 --- a/contrib/bmake/unit-tests/varmod-hash.exp +++ b/contrib/bmake/unit-tests/varmod-hash.exp @@ -1,9 +1,12 @@ -make: in target "all": while evaluating variable "12345" with value "12345": Unknown modifier "has" - +make: Unknown modifier "has" + while evaluating variable "12345" with value "12345" + in target "step-1" 26bb0f5f 12345 -make: in target "all": while evaluating variable "12345" with value "12345": Unknown modifier "hasX" - -make: in target "all": while evaluating variable "12345" with value "12345": Unknown modifier "hashed" - +make: Unknown modifier "hasX" + while evaluating variable "12345" with value "12345" + in target "step-4" +make: Unknown modifier "hashed" + while evaluating variable "12345" with value "12345" + in target "step-5" exit status 2 diff --git a/contrib/bmake/unit-tests/varmod-hash.mk b/contrib/bmake/unit-tests/varmod-hash.mk index 5407e8299f9e..7b34b74226f2 100644 --- a/contrib/bmake/unit-tests/varmod-hash.mk +++ b/contrib/bmake/unit-tests/varmod-hash.mk @@ -1,64 +1,69 @@ -# $NetBSD: varmod-hash.mk,v 1.5 2020/09/04 06:54:07 rillig Exp $ +# $NetBSD: varmod-hash.mk,v 1.6 2024/07/20 11:05:12 rillig Exp $ # # Tests for the :hash variable modifier, which computes a 32-bit hash from # the value of the expression. # Test vectors for generating certain hashes. Found by a brute force # search over [a-z]{8}. # VECTORS+= 00000000 adjbuqnt VECTORS+= 00000001 beiiyxdp VECTORS+= 00000002 ajriwzqe VECTORS+= 00000004 aimszzcb VECTORS+= 00000008 afffvsgz VECTORS+= 00000010 alkksbun VECTORS+= 00000020 arqeianj VECTORS+= 00000040 acgaltwv VECTORS+= 00000080 addsjxec VECTORS+= 00000100 acbozubm VECTORS+= 00000200 acnbugtp VECTORS+= 00000400 ajyfkpcl VECTORS+= 00000800 akobyelz VECTORS+= 00001000 aclmaggk VECTORS+= 00002000 aauwlqiq VECTORS+= 00004000 ankfvoqf VECTORS+= 00008000 airtytts VECTORS+= 00010000 bfwwrqfi VECTORS+= 00020000 actwkzix VECTORS+= 00040000 alsfbgvo VECTORS+= 00080000 aioiauem VECTORS+= 00100000 bxexhpji VECTORS+= 00200000 awtxcwch VECTORS+= 00400000 aoqpmqam VECTORS+= 00800000 akgtvjhz VECTORS+= 01000000 bcmsuvrm VECTORS+= 02000000 aqnktorm VECTORS+= 04000000 aweqylny VECTORS+= 08000000 crvkuyze VECTORS+= 10000000 alxiatjv VECTORS+= 20000000 aezwuukx VECTORS+= 40000000 abdpnifu VECTORS+= 80000000 auusgoii VECTORS+= ffffffff ahnvmfdw VECTORS+= b2af338b "" VECTORS+= 3360ac65 a VECTORS+= 7747f046 ab VECTORS+= 9ca87054 abc VECTORS+= 880fe816 abcd VECTORS+= 208fcbd3 abcde VECTORS+= d5d376eb abcdef VECTORS+= de41416c abcdefghijklmnopqrstuvwxyz .for hash input in ${VECTORS} . if ${input:S,^""$,,:hash} != ${hash} . warning Expected ${hash} for ${input}, but was ${input:hash}. . endif .endfor -all: +all: step-{1,2,3,4,5} +step-1: @echo ${12345:L:has} # modifier name too short +step-2: @echo ${12345:L:hash} # ok +step-3: @echo ${12345:L:hash=SHA-256} # :hash does not accept '=' +step-4: @echo ${12345:L:hasX} # misspelled +step-5: @echo ${12345:L:hashed} # modifier name too long diff --git a/contrib/bmake/unit-tests/varmod-ifelse.exp b/contrib/bmake/unit-tests/varmod-ifelse.exp index c93a9ec71716..02f434f1d411 100644 --- a/contrib/bmake/unit-tests/varmod-ifelse.exp +++ b/contrib/bmake/unit-tests/varmod-ifelse.exp @@ -1,53 +1,62 @@ -make: "varmod-ifelse.mk" line 29: while evaluating condition "bare words == "literal"": Bad condition -make: "varmod-ifelse.mk" line 29: Malformed conditional (${${:Ubare words} == "literal":?bad:bad}) -make: "varmod-ifelse.mk" line 40: while evaluating condition " == """: Bad condition -make: "varmod-ifelse.mk" line 49: while evaluating condition " == """: Bad condition -make: "varmod-ifelse.mk" line 49: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) -make: "varmod-ifelse.mk" line 73: while evaluating condition "1 == == 2": Bad condition -make: "varmod-ifelse.mk" line 73: Malformed conditional (${1 == == 2:?yes:no} != "") +make: "varmod-ifelse.mk" line 28: Bad condition + while evaluating condition "bare words == "literal"" +make: "varmod-ifelse.mk" line 39: Bad condition + while evaluating condition " == """ +make: "varmod-ifelse.mk" line 47: Bad condition + while evaluating condition " == """ +make: "varmod-ifelse.mk" line 70: Bad condition + while evaluating condition "1 == == 2" CondParser_Eval: "${1 == == 2:?yes:no}" != "" CondParser_Eval: 1 == == 2 Comparing 1.000000 == 0.000000 -make: "varmod-ifelse.mk" line 97: while evaluating condition "1 == == 2": Bad condition +make: "varmod-ifelse.mk" line 94: Bad condition + while evaluating condition "1 == == 2" Comparing "" != "" -make: "varmod-ifelse.mk" line 101: warning: Oops, the parse error should have been propagated. +make: "varmod-ifelse.mk" line 98: warning: Oops, the parse error should have been propagated. CondParser_Eval: ${ ${:U\$}{VAR} == value:?ok:bad} != "ok" CondParser_Eval: ${VAR} == value Comparing "value" == "value" Comparing "ok" != "ok" -make: "varmod-ifelse.mk" line 163: no. -make: "varmod-ifelse.mk" line 167: while evaluating condition "string == "literal" || no >= 10": Comparison with '>=' requires both operands 'no' and '10' to be numeric -make: "varmod-ifelse.mk" line 167: while evaluating condition "string == "literal" || no >= 10": Bad condition -make: "varmod-ifelse.mk" line 167: . -make: "varmod-ifelse.mk" line 174: while evaluating condition "string == "literal" && >= 10": Bad condition -make: "varmod-ifelse.mk" line 174: . -make: "varmod-ifelse.mk" line 177: while evaluating condition "string == "literal" || >= 10": Bad condition -make: "varmod-ifelse.mk" line 177: . -make: "varmod-ifelse.mk" line 185: -make: "varmod-ifelse.mk" line 188: -make: "varmod-ifelse.mk" line 192: while evaluating condition " ": Bad condition -make: "varmod-ifelse.mk" line 192: <> +make: "varmod-ifelse.mk" line 160: no. +make: "varmod-ifelse.mk" line 163: Comparison with '>=' requires both operands 'no' and '10' to be numeric + while evaluating condition "string == "literal" || no >= 10" +make: "varmod-ifelse.mk" line 163: . +make: "varmod-ifelse.mk" line 170: Bad condition + while evaluating condition "string == "literal" && >= 10" +make: "varmod-ifelse.mk" line 170: . +make: "varmod-ifelse.mk" line 173: Bad condition + while evaluating condition "string == "literal" || >= 10" +make: "varmod-ifelse.mk" line 173: . +make: "varmod-ifelse.mk" line 181: +make: "varmod-ifelse.mk" line 184: +make: "varmod-ifelse.mk" line 188: Bad condition + while evaluating condition " " +make: "varmod-ifelse.mk" line 188: <> CondParser_Eval: 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated" CondParser_Eval: 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1" CondParser_Eval: 0 Comparing "else1" != "else1" CondParser_Eval: 2 && ${1:?${:Uthen2:S,}},,}:${:Uelse2:S,}},,}} != "then2" CondParser_Eval: 1 Comparing "then2" != "then2" CondParser_Eval: ${DELAYED} == "one" Comparing "two" == "one" -make: "varmod-ifelse.mk" line 288: no +make: "varmod-ifelse.mk" line 284: no CondParser_Eval: ${DELAYED} == "two" Comparing "two" == "two" -make: "varmod-ifelse.mk" line 290: yes +make: "varmod-ifelse.mk" line 286: yes CondParser_Eval: ${DELAYED} == "one" Comparing "two" == "one" -make: "varmod-ifelse.mk" line 293: no +make: "varmod-ifelse.mk" line 289: no CondParser_Eval: ${DELAYED} == "two" Comparing "two" == "two" -make: "varmod-ifelse.mk" line 296: yes -make: "varmod-ifelse.mk" line 318: while evaluating then-branch of condition "1": while evaluating "${:X-then}:${:X-else}}" with value "": Unknown modifier "X-then" -make: "varmod-ifelse.mk" line 318: while evaluating else-branch of condition "1": while parsing "${:X-else}}": Unknown modifier "X-else" +make: "varmod-ifelse.mk" line 292: yes +make: "varmod-ifelse.mk" line 314: Unknown modifier "X-then" + while evaluating "${:X-then}:${:X-else}}" with value "" + while evaluating then-branch of condition "1" +make: "varmod-ifelse.mk" line 314: Unknown modifier "X-else" + while parsing "${:X-else}}" + while evaluating else-branch of condition "1" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-ifelse.mk b/contrib/bmake/unit-tests/varmod-ifelse.mk index 292bc076366e..b2f5419e65ad 100644 --- a/contrib/bmake/unit-tests/varmod-ifelse.mk +++ b/contrib/bmake/unit-tests/varmod-ifelse.mk @@ -1,319 +1,315 @@ -# $NetBSD: varmod-ifelse.mk,v 1.32 2024/07/05 20:01:52 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.35 2025/01/11 20:54:45 rillig Exp $ # # Tests for the ${cond:?then:else} variable modifier, which evaluates either # the then-expression or the else-expression, depending on the condition. # # The modifier was added on 1998-04-01. # # Until 2015-10-11, the modifier always evaluated both the "then" and the # "else" expressions. # TODO: Implementation # The variable name of the expression is expanded and then taken as the # condition. In the below example it becomes: # # bare words == "literal" # # This confuses the parser, which expects an operator instead of the bare # word "expression". If the name were expanded lazily, everything would be # fine since the condition would be: # # ${:Ubare words} == "literal" # # Evaluating the variable name lazily would require additional code in # Var_Parse and ParseVarname, it would be more useful and predictable # though. -# expect+2: while evaluating condition "bare words == "literal"": Bad condition -# expect+1: Malformed conditional (${${:Ubare words} == "literal":?bad:bad}) +# expect+1: Bad condition .if ${${:Ubare words} == "literal":?bad:bad} . error .else . error .endif # In a variable assignment, undefined variables are not an error. # Because of the early expansion, the whole condition evaluates to # ' == ""' though, which cannot be parsed because the left-hand side looks # empty. -# expect+1: while evaluating condition " == """: Bad condition +# expect+1: Bad condition COND:= ${${UNDEF} == "":?bad-assign:bad-assign} # In a condition, undefined variables generate a "Malformed conditional" # error. That error message is wrong though. In lint mode, the correct # "Undefined variable" error message is generated. # The difference to the ':=' variable assignment is the additional # "Malformed conditional" error message. -# expect+2: while evaluating condition " == """: Bad condition -# expect+1: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) +# expect+1: Bad condition .if ${${UNDEF} == "":?bad-cond:bad-cond} . error .else . error .endif # When the :? is parsed, it is greedy. The else branch spans all the # text, up until the closing character '}', even if the text looks like # another modifier. .if ${1:?then:else:Q} != "then" . error .endif .if ${0:?then:else:Q} != "else:Q" . error .endif # This line generates 2 error messages. The first comes from evaluating the # malformed conditional "1 == == 2", which is reported as "Bad conditional # expression" by ApplyModifier_IfElse. The expression containing that # conditional therefore returns a parse error from Var_Parse, and this parse # error propagates to CondEvalExpression, where the "Malformed conditional" # comes from. -# expect+2: while evaluating condition "1 == == 2": Bad condition -# expect+1: Malformed conditional (${1 == == 2:?yes:no} != "") +# expect+1: Bad condition .if ${1 == == 2:?yes:no} != "" . error .else . error .endif # If the "Bad conditional expression" appears in a quoted string literal, the # error message "Malformed conditional" is not printed, leaving only the "Bad # conditional expression". # # XXX: The left-hand side is enclosed in quotes. This results in Var_Parse # being called without VARE_EVAL_DEFINED. When ApplyModifier_IfElse # returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the # value of the expression is still undefined. CondParser_String is # then supposed to do proper error handling, but since varUndefined is local # to var.c, it cannot distinguish this return value from an ordinary empty # string. The left-hand side of the comparison is therefore just an empty # string, which is obviously equal to the empty string on the right-hand side. # # XXX: The debug log for -dc shows a comparison between 1.0 and 0.0. The # condition should be detected as being malformed before any comparison is # done since there is no well-formed comparison in the condition at all. .MAKEFLAGS: -dc -# expect+1: while evaluating condition "1 == == 2": Bad condition +# expect+1: Bad condition .if "${1 == == 2:?yes:no}" != "" . error .else # expect+1: warning: Oops, the parse error should have been propagated. . warning Oops, the parse error should have been propagated. .endif .MAKEFLAGS: -d0 # As of 2020-12-10, the variable "VAR" is first expanded, and the result of # this expansion is then taken as the condition. To force the # expression in the condition to be evaluated at exactly the right point, # the '$' of the intended '${VAR}' escapes from the parser in form of the # expression ${:U\$}. Because of this escaping, the variable "VAR" and thus # the condition ends up as "${VAR} == value", just as intended. # # This hack does not work for variables from .for loops since these are # expanded at parse time to their corresponding ${:Uvalue} expressions. # Making the '$' of the '${VAR}' expression indirect hides this expression # from the parser of the .for loop body. See ForLoop_SubstVarLong. .MAKEFLAGS: -dc VAR= value .if ${ ${:U\$}{VAR} == value:?ok:bad} != "ok" . error .endif .MAKEFLAGS: -d0 # On 2021-04-19, when building external/bsd/tmux with HAVE_LLVM=yes and # HAVE_GCC=no, the following conditional generated this error message: # # make: Bad conditional expression 'string == "literal" && no >= 10' # in 'string == "literal" && no >= 10?yes:no' # # Despite the error message (which was not clearly marked with "error:"), # the build continued, for historical reasons, see main_Exit. # # The tricky detail here is that the condition that looks so obvious in the # form written in the makefile becomes tricky when it is actually evaluated. # This is because the condition is written in the place of the variable name # of the expression, and in an expression, the variable name is always # expanded first, before even looking at the modifiers. This happens for the # modifier ':?' as well, so when CondEvalExpression gets to see the # expression, it already looks like this: # # string == "literal" && no >= 10 # # When parsing such an expression, the parser used to be strict. It first # evaluated the left-hand side of the operator '&&' and then started parsing # the right-hand side 'no >= 10'. The word 'no' is obviously a string # literal, not enclosed in quotes, which is OK, even on the left-hand side of # the comparison operator, but only because this is a condition in the # modifier ':?'. In an ordinary directive '.if', this would be a parse error. # For strings, only the comparison operators '==' and '!=' are defined, # therefore parsing stopped at the '>', producing the 'Bad conditional # expression'. # # Ideally, the conditional expression would not be expanded before parsing # it. This would allow to write the conditions exactly as seen below. That # change has a high chance of breaking _some_ existing code and would need # to be thoroughly tested. # # Since cond.c 1.262 from 2021-04-20, make reports a more specific error # message in situations like these, pointing directly to the specific problem # instead of just saying that the whole condition is bad. STRING= string NUMBER= no # not really a number # expect+1: no. .info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. -# expect+3: while evaluating condition "string == "literal" || no >= 10": Comparison with '>=' requires both operands 'no' and '10' to be numeric -# expect+2: while evaluating condition "string == "literal" || no >= 10": Bad condition +# expect+2: Comparison with '>=' requires both operands 'no' and '10' to be numeric # expect+1: . .info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. # The following situation occasionally occurs with MKINET6 or similar # variables. NUMBER= # empty, not really a number either -# expect+2: while evaluating condition "string == "literal" && >= 10": Bad condition +# expect+2: Bad condition # expect+1: . .info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. -# expect+2: while evaluating condition "string == "literal" || >= 10": Bad condition +# expect+2: Bad condition # expect+1: . .info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. # CondParser_LeafToken handles [0-9-+] specially, treating them as a number. PLUS= + ASTERISK= * EMPTY= # empty # "true" since "+" is not the empty string. # expect+1: .info <${${PLUS} :?true:false}> # "false" since the variable named "*" is not defined. # expect+1: .info <${${ASTERISK} :?true:false}> # syntax error since the condition is completely blank. -# expect+2: while evaluating condition " ": Bad condition +# expect+2: Bad condition # expect+1: <> .info <${${EMPTY} :?true:false}> # Since the condition of the '?:' modifier is expanded before being parsed and # evaluated, it is common practice to enclose expressions in quotes, to avoid # producing syntactically invalid conditions such as ' == value'. This only # works if the expanded values neither contain quotes nor backslashes. For # strings containing quotes or backslashes, the '?:' modifier should not be # used. PRIMES= 2 3 5 7 11 .if ${1 2 3 4 5:L:@n@$n:${ ("${PRIMES:M$n}" != "") :?prime:not_prime}@} != \ "1:not_prime 2:prime 3:prime 4:not_prime 5:prime" . error .endif # When parsing the modifier ':?', there are 3 possible cases: # # 1. The whole expression is only parsed. # 2. The expression is parsed and the 'then' branch is evaluated. # 3. The expression is parsed and the 'else' branch is evaluated. # # In all of these cases, the expression must be parsed in the same way, # especially when one of the branches contains unbalanced '{}' braces. # # At 2020-01-01, the expressions from the 'then' and 'else' branches were # parsed differently, depending on whether the branch was taken or not. When # the branch was taken, the parser recognized that in the modifier ':S,}},,', # the '}}' were ordinary characters. When the branch was not taken, the # parser only counted balanced '{' and '}', ignoring any escaping or other # changes in the interpretation. # # In var.c 1.285 from 2020-07-20, the parsing of the expressions changed so # that in both cases the expression is parsed in the same way, taking the # unbalanced braces in the ':S' modifiers into account. This change was not # on purpose, the commit message mentioned 'has the same effect', which was a # wrong assumption. # # In var.c 1.323 from 2020-07-26, the unintended fix from var.c 1.285 was # reverted, still not knowing about the difference between regular parsing and # balanced-mode parsing. # # In var.c 1.1028 from 2022-08-08, there was another attempt at fixing this # inconsistency in parsing, but since that broke parsing of the modifier ':@', # it was reverted in var.c 1.1029 from 2022-08-23. # # In var.c 1.1047 from 2023-02-18, the inconsistency in parsing was finally # fixed. The modifier ':@' now parses the body in balanced mode, while # everywhere else the modifier parts have their subexpressions parsed in the # same way, no matter whether they are evaluated or not. # # The modifiers ':@' and ':?' are similar in that they conceptually contain # text to be evaluated later or conditionally, still they parse that text # differently. The crucial difference is that the body of the modifier ':@' # is always parsed using balanced mode. The modifier ':?', on the other hand, # must parse both of its branches in the same way, no matter whether they are # evaluated or not. Since balanced mode and standard mode are incompatible, # it's impossible to use balanced mode in the modifier ':?'. .MAKEFLAGS: -dc .if 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated" # At 2020-01-07, the expression evaluated to 'then0,,}}', even though it was # irrelevant as the '0' had already been evaluated to 'false'. . error .endif .if 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1" . error .endif .if 2 && ${1:?${:Uthen2:S,}},,}:${:Uelse2:S,}},,}} != "then2" # At 2020-01-07, the whole expression evaluated to 'then2,,}}' instead of the # expected 'then2'. The 'then' branch of the ':?' modifier was parsed # normally, parsing and evaluating the ':S' modifier, thereby treating the # '}}' as ordinary characters and resulting in 'then2'. The 'else' branch was # parsed in balanced mode, ignoring that the inner '}}' were ordinary # characters. The '}}' were thus interpreted as the end of the 'else' branch # and the whole expression. This left the trailing ',,}}', which together # with the 'then2' formed the result 'then2,,}}'. . error .endif # Since the condition is taken from the variable name of the expression, not # from its value, it is evaluated early. It is possible though to construct # conditions that are evaluated lazily, at exactly the right point. There is # no way to escape a '$' directly in the variable name, but there are # alternative ways to bring a '$' into the condition. # # In an indirect condition using the ':U' modifier, each '$', ':' and # '}' must be escaped as '\$', '\:' and '\}', respectively, but '{' must # not be escaped. # # In an indirect condition using a separate variable, each '$' must be # escaped as '$$'. # # These two forms allow the variables to contain arbitrary characters, as the # condition parser does not see them. DELAYED= two # expect+1: no .info ${ ${:U \${DELAYED\} == "one"}:?yes:no} # expect+1: yes .info ${ ${:U \${DELAYED\} == "two"}:?yes:no} INDIRECT_COND1= $${DELAYED} == "one" # expect+1: no .info ${ ${INDIRECT_COND1}:?yes:no} INDIRECT_COND2= $${DELAYED} == "two" # expect+1: yes .info ${ ${INDIRECT_COND2}:?yes:no} .MAKEFLAGS: -d0 # In the modifier parts for the 'then' and 'else' branches, subexpressions are # parsed by inspecting the actual modifiers. In 2008, 2015, 2020, 2022 and # 2023, the exact parsing algorithm switched a few times, counting balanced # braces instead of proper subexpressions, which meant that unbalanced braces # were parsed differently, depending on whether the branch was active or not. BRACES= }}} NO= ${0:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}} YES= ${1:?${BRACES:S,}}},yes,}:${BRACES:S,}}},no,}} BOTH= <${YES}> <${NO}> .if ${BOTH} != " " . error .endif -# expect+2: while evaluating then-branch of condition "1": while evaluating "${:X-then}:${:X-else}}" with value "": Unknown modifier "X-then" -# expect+1: while evaluating else-branch of condition "1": while parsing "${:X-else}}": Unknown modifier "X-else" +# expect+2: Unknown modifier "X-then" +# expect+1: Unknown modifier "X-else" .if ${1:?${:X-then}:${:X-else}} .endif diff --git a/contrib/bmake/unit-tests/varmod-indirect.exp b/contrib/bmake/unit-tests/varmod-indirect.exp index 360cec21e291..b572e34fcf8d 100644 --- a/contrib/bmake/unit-tests/varmod-indirect.exp +++ b/contrib/bmake/unit-tests/varmod-indirect.exp @@ -1,43 +1,47 @@ -make: "varmod-indirect.mk" line 19: while evaluating variable "value" with value "value": Unknown modifier "${" -make: "varmod-indirect.mk" line 52: while evaluating variable "value" with value "value": Unknown modifier "${" +make: "varmod-indirect.mk" line 19: Unknown modifier "${" + while evaluating variable "value" with value "value" +make: "varmod-indirect.mk" line 52: Unknown modifier "${" + while evaluating variable "value" with value "value" make: "varmod-indirect.mk" line 54: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression. make: "varmod-indirect.mk" line 143: before make: "varmod-indirect.mk" line 143: after make: "varmod-indirect.mk" line 151: before make: "varmod-indirect.mk" line 151: after make: "varmod-indirect.mk" line 159: before make: "varmod-indirect.mk" line 159: after -make: "varmod-indirect.mk" line 164: while evaluating variable "UNDEF" with value "": Unknown modifier "Z" +make: "varmod-indirect.mk" line 164: Unknown modifier "Z" + while evaluating variable "UNDEF" with value "" make: "varmod-indirect.mk" line 167: before make: "varmod-indirect.mk" line 167: after -Parsing line 176: _:= before ${UNDEF} after +Parsing varmod-indirect.mk:176: _:= before ${UNDEF} after Global: _ = # (empty) Var_Parse: ${UNDEF} after (eval-keep-dollar-and-undefined) Global: _ = before ${UNDEF} after -Parsing line 179: _:= before ${UNDEF:${:US,a,a,}} after +Parsing varmod-indirect.mk:179: _:= before ${UNDEF:${:US,a,a,}} after Var_Parse: ${UNDEF:${:US,a,a,}} after (eval-keep-dollar-and-undefined) Indirect modifier "S,a,a," from "${:US,a,a,}" Evaluating modifier ${UNDEF:S...} on value "" (eval-keep-dollar-and-undefined, undefined) Modifier part: "a" Modifier part: "a" ModifyWords: split "" into 1 word Result of ${UNDEF:S,a,a,} is "" (eval-keep-dollar-and-undefined, undefined) Global: _ = before ${UNDEF:S,a,a,} after -Parsing line 189: _:= before ${UNDEF:${:U}} after +Parsing varmod-indirect.mk:189: _:= before ${UNDEF:${:U}} after Var_Parse: ${UNDEF:${:U}} after (eval-keep-dollar-and-undefined) Indirect modifier "" from "${:U}" Global: _ = before ${UNDEF:} after -Parsing line 195: _:= before ${UNDEF:${:UZ}} after +Parsing varmod-indirect.mk:195: _:= before ${UNDEF:${:UZ}} after Var_Parse: ${UNDEF:${:UZ}} after (eval-keep-dollar-and-undefined) Indirect modifier "Z" from "${:UZ}" Evaluating modifier ${UNDEF:Z} on value "" (eval-keep-dollar-and-undefined, undefined) -make: "varmod-indirect.mk" line 195: while evaluating variable "UNDEF" with value "": Unknown modifier "Z" +make: "varmod-indirect.mk" line 195: Unknown modifier "Z" + while evaluating variable "UNDEF" with value "" Result of ${UNDEF:Z} is error (eval-keep-dollar-and-undefined, undefined) Global: _ = before ${UNDEF:Z} after -Parsing line 197: .MAKEFLAGS: -d0 +Parsing varmod-indirect.mk:197: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d 0 -d pv -d Global: .MAKEFLAGS = -r -k -d 0 -d pv -d 0 make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-indirect.mk b/contrib/bmake/unit-tests/varmod-indirect.mk index a58cb870aac8..f54c0e232d2a 100644 --- a/contrib/bmake/unit-tests/varmod-indirect.mk +++ b/contrib/bmake/unit-tests/varmod-indirect.mk @@ -1,282 +1,282 @@ -# $NetBSD: varmod-indirect.mk,v 1.20 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: varmod-indirect.mk,v 1.21 2024/08/29 20:20:36 rillig Exp $ # # Tests for indirect variable modifiers, such as in ${VAR:${M_modifiers}}. # These can be used for very basic purposes like converting a string to either # uppercase or lowercase, as well as for fairly advanced modifiers that first # look like line noise and are hard to decipher. # # Initial support for indirect modifiers was added in var.c 1.101 from # 2006-02-18. Since var.c 1.108 from 2006-05-11 it is possible to use # indirect modifiers for all but the very first modifier as well. # To apply a modifier indirectly via another variable, the whole # modifier must be put into a single expression. # The following expression generates a parse error since its indirect # modifier contains more than a sole expression. # -# expect+1: while evaluating variable "value" with value "value": Unknown modifier "${" +# expect+1: Unknown modifier "${" .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" . warning unexpected .endif # Adding another level of indirection (the 2 nested :U expressions) helps. .if ${value:L:${:U${:US}${:U,value,replacement,}}} != "replacement" . warning unexpected .endif # Multiple indirect modifiers can be applied one after another as long as # they are separated with colons. .if ${value:L:${:US,a,A,}:${:US,e,E,}} != "vAluE" . warning unexpected .endif # An indirect variable that evaluates to the empty string is allowed. # It is even allowed to write another modifier directly afterwards. # There is no practical use case for this feature though, as demonstrated # in the test case directly below. .if ${value:L:${:Dempty}S,value,replaced,} != "replaced" . warning unexpected .endif # If an expression for an indirect modifier evaluates to anything else than an # empty string and is neither followed by a ':' nor '}', this produces a parse # error. Because of this parse error, this feature cannot be used reasonably # in practice. # -# expect+2: while evaluating variable "value" with value "value": Unknown modifier "${" +# expect+2: Unknown modifier "${" #.MAKEFLAGS: -dvc .if ${value:L:${:UM*}S,value,replaced,} == "M*S,value,replaced,}" # expect+1: warning: FIXME: this expression should have resulted in a parse error rather than returning the unparsed portion of the expression. . warning FIXME: this expression should have resulted in a parse $\ error rather than returning the unparsed portion of the $\ expression. .else . error .endif #.MAKEFLAGS: -d0 # An indirect modifier can be followed by other modifiers, no matter if the # indirect modifier evaluates to an empty string or not. # # This makes it possible to define conditional modifiers, like this: # # M.little-endian= S,1234,4321, # M.big-endian= # none .if ${value:L:${:D empty }:S,value,replaced,} != "replaced" . error .endif # The nested expression expands to "tu", and this is interpreted as # a variable modifier for the value "Upper", resulting in "UPPER". .if ${Upper:L:${:Utu}} != "UPPER" . error .endif # The nested expression expands to "tl", and this is interpreted as # a variable modifier for the value "Lower", resulting in "lower". .if ${Lower:L:${:Utl}} != "lower" . error .endif # The nested expression is ${1 != 1:?Z:tl}, consisting of the # condition "1 != 1", the then-branch "Z" and the else-branch "tl". Since # the condition evaluates to false, the then-branch is ignored (it would # have been an unknown modifier anyway) and the ":tl" modifier is applied. .if ${Mixed:L:${1 != 1:?Z:tl}} != "mixed" . error .endif # The indirect modifier can also replace an ':L' modifier, which allows for # brain twisters since by reading the expression alone, it is not possible # to say whether the variable name will be evaluated as a variable name or # as the immediate value of the expression. VAR= value M_ExpandVar= # an empty modifier M_VarAsValue= L # .if ${VAR:${M_ExpandVar}} != "value" . error .endif .if ${VAR:${M_VarAsValue}} != "VAR" . error .endif # The indirect modifier M_ListToSkip, when applied to a list of patterns, # expands to a sequence of ':N' modifiers, each of which filters one of the # patterns. This list of patterns can then be applied to another variable # to actually filter that variable. # M_ListToSkip= @pat@N$${pat}@:ts: # # The dollar signs need to be doubled in the above modifier expression, # otherwise they would be expanded too early, that is, when parsing the # modifier itself. # # In the following example, M_NoPrimes expands to 'N2:N3:N5:N7:N1[1379]'. # The 'N' comes from the expression 'N${pat}', the separating colons come # from the modifier ':ts:'. # #.MAKEFLAGS: -dcv # Uncomment this line to see the details # PRIMES= 2 3 5 7 1[1379] M_NoPrimes= ${PRIMES:${M_ListToSkip}} .if ${:U:range=20:${M_NoPrimes}} != "1 4 6 8 9 10 12 14 15 16 18 20" . error .endif .MAKEFLAGS: -d0 # In contrast to the .if conditions, the .for loop allows undefined # expressions. These expressions expand to empty strings. # An undefined expression without any modifiers expands to an empty string. .for var in before ${UNDEF} after # expect+2: before # expect+1: after . info ${var} .endfor # An undefined expression with only modifiers that keep the expression # undefined expands to an empty string. .for var in before ${UNDEF:${:US,a,a,}} after # expect+2: before # expect+1: after . info ${var} .endfor # Even in an indirect modifier based on an undefined variable, the value of # the expression in Var_Parse is a simple empty string. .for var in before ${UNDEF:${:U}} after # expect+2: before # expect+1: after . info ${var} .endfor # An error in an indirect modifier. -# expect+1: while evaluating variable "UNDEF" with value "": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" .for var in before ${UNDEF:${:UZ}} after # expect+2: before # expect+1: after . info ${var} .endfor # Another slightly different evaluation context is the right-hand side of # a variable assignment using ':='. .MAKEFLAGS: -dpv # The undefined expression is kept as-is. _:= before ${UNDEF} after # The undefined expression is kept as-is. _:= before ${UNDEF:${:US,a,a,}} after # XXX: The subexpression ${:U} is fully defined, therefore it is expanded. # This results in ${UNDEF:}, which can lead to tricky parse errors later, # when the variable '_' is expanded further. # # XXX: What should be the correct strategy here? One possibility is to # expand the defined subexpression and replace it with ${:U...}, just like # in .for loops. This would preserve the structure of the expression while # at the same time expanding the expression as far as possible. _:= before ${UNDEF:${:U}} after # XXX: This expands to ${UNDEF:Z}, which will behave differently if the # variable '_' is used in a context where the expression ${_} is # parsed but not evaluated. -# expect+1: while evaluating variable "UNDEF" with value "": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" _:= before ${UNDEF:${:UZ}} after .MAKEFLAGS: -d0 .undef _ # When evaluating indirect modifiers, these modifiers may expand to ':tW', # which modifies the interpretation of the expression value. This modified # interpretation only lasts until the end of the indirect modifier, it does # not influence the outer expression. .if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#] . error .endif .if ${1 2 3:L:${:UtW}:[#]} != 3 # indirect :tW does not apply to :[#] . error .endif # When evaluating indirect modifiers, these modifiers may expand to ':ts*', # which modifies the interpretation of the expression value. This modified # interpretation only lasts until the end of the indirect modifier, it does # not influence the outer expression. # # In this first expression, the direct ':ts*' has no effect since ':U' does not # treat the expression value as a list of words but as a single word. It has # to be ':U', not ':D', since the "expression name" is "1 2 3" and there is no # variable of that name. #.MAKEFLAGS: -dcpv .if ${1 2 3:L:ts*:Ua b c} != "a b c" . error .endif # In this expression, the direct ':ts*' affects the ':M' at the end. .if ${1 2 3:L:ts*:Ua b c:M*} != "a*b*c" . error .endif # In this expression, the ':ts*' is indirect, therefore the changed separator # only applies to the modifiers from the indirect text. It does not affect # the ':M' since that is not part of the text from the indirect modifier. # # Implementation detail: when ApplyModifiersIndirect calls ApplyModifiers # (which creates a new ModChain containing a fresh separator), # the outer separator character is not passed by reference to the inner # evaluation, therefore the scope of the inner separator ends after applying # the modifier ':ts*'. .if ${1 2 3:L:${:Uts*}:Ua b c:M*} != "a b c" . error .endif # A direct modifier ':U' turns the expression from undefined to defined. # An indirect modifier ':U' has the same effect, unlike the separator from # ':ts*' or the single-word marker from ':tW'. # # This is because when ApplyModifiersIndirect calls ApplyModifiers, it passes # the definedness of the outer expression by reference. If that weren't the # case, the first condition below would result in a parse error because its # left-hand side would be undefined. .if ${UNDEF:${:UUindirect-fallback}} != "indirect-fallback" . error .endif .if ${UNDEF:${:UUindirect-fallback}:Uouter-fallback} != "outer-fallback" . error .endif # In parse-only mode, the indirect modifiers must not be evaluated. # # Before var.c 1.1098 from 2024-02-04, the expression for an indirect modifier # was partially evaluated (only the variable value, without applying any # modifiers) and then interpreted as modifiers to the main expression. # # The expression ${:UZ} starts with the value "", and in parse-only mode, the # modifier ':UZ' does not modify the expression value. This results in an # empty string for the indirect modifiers, generating no warning. .if 0 && ${VAR:${:UZ}} .endif # The expression ${M_invalid} starts with the value "Z", which is an unknown # modifier. Trying to apply this unknown modifier generated a warning. M_invalid= Z .if 0 && ${VAR:${M_invalid}} .endif # The ':S' modifier does not change the expression value in parse-only mode, # keeping the "Z", which is then skipped in parse-only mode. .if 0 && ${VAR:${M_invalid:S,^,N*,:ts:}} .endif # The ':@' modifier does not change the expression value in parse-only mode, # keeping the "Z", which is then skipped in parse-only mode. .if 0 && ${VAR:${M_invalid:@m@N*$m@:ts:}} .endif diff --git a/contrib/bmake/unit-tests/varmod-localtime.exp b/contrib/bmake/unit-tests/varmod-localtime.exp index 0183ae6dcc2f..b22bfe479227 100644 --- a/contrib/bmake/unit-tests/varmod-localtime.exp +++ b/contrib/bmake/unit-tests/varmod-localtime.exp @@ -1,13 +1,13 @@ -make: "varmod-localtime.mk" line 61: while evaluating "${:L:localtime=-1} != """ with value "": Invalid time value "-1" -make: "varmod-localtime.mk" line 61: Malformed conditional (${:L:localtime=-1} != "") -make: "varmod-localtime.mk" line 72: while evaluating "${:L:localtime= 1} != """ with value "": Invalid time value " 1" -make: "varmod-localtime.mk" line 72: Malformed conditional (${:L:localtime= 1} != "") -make: "varmod-localtime.mk" line 120: while evaluating "${:L:localtime=10000000000000000000000000000000} != """ with value "": Invalid time value "10000000000000000000000000000000" -make: "varmod-localtime.mk" line 120: Malformed conditional (${:L:localtime=10000000000000000000000000000000} != "") -make: "varmod-localtime.mk" line 133: while evaluating "${:L:localtime=error} != """ with value "": Invalid time value "error" -make: "varmod-localtime.mk" line 133: Malformed conditional (${:L:localtime=error} != "") -make: "varmod-localtime.mk" line 144: while evaluating variable "%Y" with value "%Y": Invalid time value "100000S,1970,bad," -make: "varmod-localtime.mk" line 144: Malformed conditional (${%Y:L:localtime=100000S,1970,bad,} != "bad") +make: "varmod-localtime.mk" line 60: Invalid time value "-1" + while evaluating "${:L:localtime=-1} != """ with value "" +make: "varmod-localtime.mk" line 70: Invalid time value " 1" + while evaluating "${:L:localtime= 1} != """ with value "" +make: "varmod-localtime.mk" line 117: Invalid time value "10000000000000000000000000000000" + while evaluating "${:L:localtime=10000000000000000000000000000000} != """ with value "" +make: "varmod-localtime.mk" line 129: Invalid time value "error" + while evaluating "${:L:localtime=error} != """ with value "" +make: "varmod-localtime.mk" line 139: Invalid time value "100000S,1970,bad," + while evaluating variable "%Y" with value "%Y" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-localtime.mk b/contrib/bmake/unit-tests/varmod-localtime.mk index b6295965ddb8..41c99c86df6e 100644 --- a/contrib/bmake/unit-tests/varmod-localtime.mk +++ b/contrib/bmake/unit-tests/varmod-localtime.mk @@ -1,148 +1,143 @@ -# $NetBSD: varmod-localtime.mk,v 1.17 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-localtime.mk,v 1.20 2025/01/11 20:54:46 rillig Exp $ # # Tests for the :localtime variable modifier, which formats a timestamp # using strftime(3) in local time. # # See also: # varmod-gmtime.mk .if ${TZ:Uno:NEurope/Berlin:NUTC-1} != "" # see unit-tests/Makefile . error .endif # Test for the default time format, %c. Since the time always varies, it's # only possible to check for the general format here. The names of the # month and weekday are always in English, independent from the locale. # Example: Thu Oct 29 18:56:41 2020 .if ${:U:localtime:tW:M??? ??? ?? ??\:??\:?? ????} == "" . error .endif # modifier name too short, falling back to the SysV modifier. .if ${%Y:L:localtim=1593536400} != "%Y" . error .endif # 2020-07-01T00:00:00Z .if ${%Y:L:localtime=1593536400} != "2020" . error .endif # modifier name too long, falling back to the SysV modifier. .if ${%Y:L:localtimer=1593536400} != "%Y" . error .endif # If the modifier name is not matched exactly, fall back to the # :from=to modifier. .if ${localtime:L:local%=gm%} != "gmtime" . error .endif # Before var.c 1.1050 from 2023-05-09, it was not possible to pass the # seconds via an expression. .if ${%Y:L:localtime=${:U1593536400}} != "2020" . error .endif # Before var.c 1.631 from 2020-10-31 21:40:20, it was possible to pass # negative time stamps to the :localtime modifier, resulting in dates before # 1970. Going back 50 years in the past is not a practical use case for # make. Therefore, since var.c 1.631, negative time stamps produce a # parse error. -# expect+2: while evaluating "${:L:localtime=-1} != """ with value "": Invalid time value "-1" -# expect+1: Malformed conditional (${:L:localtime=-1} != "") +# expect+1: Invalid time value "-1" .if ${:L:localtime=-1} != "" . error .else . error .endif # Spaces were allowed before var.c 1.631 from 2020-10-31 21:40:20, not # because it would make sense but just as a side-effect from using strtoul. -# expect+2: while evaluating "${:L:localtime= 1} != """ with value "": Invalid time value " 1" -# expect+1: Malformed conditional (${:L:localtime= 1} != "") +# expect+1: Invalid time value " 1" .if ${:L:localtime= 1} != "" . error .else . error .endif # 0 means now; this differs from GNode.mtime, where a 0 means nonexistent. # Since "now" constantly changes, the strongest possible test is to match the # resulting pattern. .if !${:L:localtime=0:tW:M??? ??? ?? ??\:??\:?? 20??} . error .endif .if ${:L:localtime=1} != "Thu Jan 1 01:00:01 1970" . error .endif # INT32_MAX .if ${:L:localtime=2147483647} != "Tue Jan 19 04:14:07 2038" . error .endif .if ${:L:localtime=2147483648} == "Tue Jan 19 04:14:08 2038" # All systems that have unsigned time_t or 64-bit time_t. .elif ${:L:localtime=2147483648} == "Fri Dec 13 21:45:52 1901" # FreeBSD-12.0-i386 still has 32-bit signed time_t, see # sys/x86/include/_types.h, __LP64__. # # Linux on 32-bit systems may still have 32-bit signed time_t, see # sysdeps/unix/sysv/linux/generic/bits/typesizes.h, __TIMESIZE. .else . error .endif # Integer overflow, at least before var.c 1.631 from 2020-10-31. # Because this modifier is implemented using strtoul, the parsed time was # ULONG_MAX, which got converted to -1. This resulted in a time stamp of # the second before 1970. # # Since var.c 1.631 from 2020-10-31, the overflow is detected and produces a # parse error. -# expect+2: while evaluating "${:L:localtime=10000000000000000000000000000000} != """ with value "": Invalid time value "10000000000000000000000000000000" -# expect+1: Malformed conditional (${:L:localtime=10000000000000000000000000000000} != "") +# expect+1: Invalid time value "10000000000000000000000000000000" .if ${:L:localtime=10000000000000000000000000000000} != "" . error .else . error .endif # Before var.c 1.631 from 2020-10-31, there was no error handling while # parsing the :localtime modifier, thus no error message was printed. Parsing # stopped after the '=', and the remaining string was parsed for more variable # modifiers. Because of the unknown modifier 'e' from the 'error', the whole # variable value was discarded and thus not printed. -# expect+2: while evaluating "${:L:localtime=error} != """ with value "": Invalid time value "error" -# expect+1: Malformed conditional (${:L:localtime=error} != "") +# expect+1: Invalid time value "error" .if ${:L:localtime=error} != "" . error .else . error .endif # Before var.c 1.1050 from 2023-05-09, the timestamp could be directly # followed by the next modifier, without a ':' separator. This was the same # bug as for the ':L' and ':P' modifiers. -# expect+2: while evaluating variable "%Y" with value "%Y": Invalid time value "100000S,1970,bad," -# expect+1: Malformed conditional (${%Y:L:localtime=100000S,1970,bad,} != "bad") +# expect+1: Invalid time value "100000S,1970,bad," .if ${%Y:L:localtime=100000S,1970,bad,} != "bad" . error .endif all: diff --git a/contrib/bmake/unit-tests/varmod-loop-delete.exp b/contrib/bmake/unit-tests/varmod-loop-delete.exp index eb3124a9adfd..874dce4495c1 100644 --- a/contrib/bmake/unit-tests/varmod-loop-delete.exp +++ b/contrib/bmake/unit-tests/varmod-loop-delete.exp @@ -1,4 +1,6 @@ -make: "varmod-loop-delete.mk" line 20: while evaluating variable "VAR" with value "${:U:@VAR@@} rest of the value": while evaluating "${:U:@VAR@@} rest of the value" with value "": Cannot delete variable "VAR" while it is used +make: "varmod-loop-delete.mk" line 20: Cannot delete variable "VAR" while it is used + while evaluating "${:U:@VAR@@} rest of the value" with value "" + while evaluating variable "VAR" with value "${:U:@VAR@@} rest of the value" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-loop-delete.mk b/contrib/bmake/unit-tests/varmod-loop-delete.mk index 5a4b273afcaf..478a25e91f6e 100644 --- a/contrib/bmake/unit-tests/varmod-loop-delete.mk +++ b/contrib/bmake/unit-tests/varmod-loop-delete.mk @@ -1,34 +1,34 @@ -# $NetBSD: varmod-loop-delete.mk,v 1.6 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-loop-delete.mk,v 1.7 2024/08/29 20:20:36 rillig Exp $ # # Tests for the variable modifier ':@', which as a side effect allows to # delete an arbitrary variable. # A side effect of the modifier ':@' is that the loop variable is created as # an actual variable in the current evaluation scope (Command/Global/target), # and at the end of the loop, this variable is deleted. Since var.c 1.204 # from 2016-02-18 and before var.c 1.963 from 2021-12-05, a variable could be # deleted while it was in use, leading to a use-after-free bug. # # See Var_Parse, comment 'the value of the variable must not change'. # Set up the variable that deletes itself when it is evaluated. VAR= ${:U:@VAR@@} rest of the value # In an assignment, the scope is 'Global'. Since the variable 'VAR' is # defined in the global scope, it deletes itself. -# expect+1: while evaluating variable "VAR" with value "${:U:@VAR@@} rest of the value": while evaluating "${:U:@VAR@@} rest of the value" with value "": Cannot delete variable "VAR" while it is used +# expect+1: Cannot delete variable "VAR" while it is used EVAL:= ${VAR} .if ${EVAL} != " rest of the value" . error .endif VAR= ${:U:@VAR@@} rest of the value all: .PHONY # In the command that is associated with a target, the scope is the # one from the target. That scope only contains a few variables like # '.TARGET', '.ALLSRC', '.IMPSRC'. Make does not expect that these # variables get modified from the outside. # # There is no variable named 'VAR' in the local scope, so nothing # happens. : $@: '${VAR}' diff --git a/contrib/bmake/unit-tests/varmod-loop-varname.exp b/contrib/bmake/unit-tests/varmod-loop-varname.exp index 8ad1842c4d09..442f9bb6d684 100644 --- a/contrib/bmake/unit-tests/varmod-loop-varname.exp +++ b/contrib/bmake/unit-tests/varmod-loop-varname.exp @@ -1,11 +1,11 @@ -make: "varmod-loop-varname.mk" line 18: while evaluating "${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+"" with value "one two three": In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar -make: "varmod-loop-varname.mk" line 18: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+") -make: "varmod-loop-varname.mk" line 89: while evaluating variable "1 2 3" with value "1 2 3": In the :@ modifier, the variable name "v$" must not contain a dollar -make: "varmod-loop-varname.mk" line 89: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)") -make: "varmod-loop-varname.mk" line 96: while evaluating variable "1 2 3" with value "1 2 3": In the :@ modifier, the variable name "v$$" must not contain a dollar -make: "varmod-loop-varname.mk" line 96: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()") -make: "varmod-loop-varname.mk" line 103: while evaluating variable "1 2 3" with value "1 2 3": In the :@ modifier, the variable name "v$$$" must not contain a dollar -make: "varmod-loop-varname.mk" line 103: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()") +make: "varmod-loop-varname.mk" line 17: In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar + while evaluating "${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+"" with value "one two three" +make: "varmod-loop-varname.mk" line 87: In the :@ modifier, the variable name "v$" must not contain a dollar + while evaluating variable "1 2 3" with value "1 2 3" +make: "varmod-loop-varname.mk" line 93: In the :@ modifier, the variable name "v$$" must not contain a dollar + while evaluating variable "1 2 3" with value "1 2 3" +make: "varmod-loop-varname.mk" line 99: In the :@ modifier, the variable name "v$$$" must not contain a dollar + while evaluating variable "1 2 3" with value "1 2 3" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-loop-varname.mk b/contrib/bmake/unit-tests/varmod-loop-varname.mk index a41d1d30d019..703e81941c59 100644 --- a/contrib/bmake/unit-tests/varmod-loop-varname.mk +++ b/contrib/bmake/unit-tests/varmod-loop-varname.mk @@ -1,140 +1,136 @@ -# $NetBSD: varmod-loop-varname.mk,v 1.9 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-loop-varname.mk,v 1.12 2025/01/11 20:54:46 rillig Exp $ # # Tests for the first part of the variable modifier ':@var@...@', which # contains the variable name to use during the loop. # Force the test results to be independent of the default value of this # setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake # distribution and pkgsrc/devel/bmake. .MAKE.SAVE_DOLLARS= yes # Before 2021-04-04, the name of the loop variable could be generated # dynamically. There was no practical use-case for this. # Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the # variable name. -# expect+2: while evaluating "${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+"" with value "one two three": In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar -# expect+1: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+") +# expect+1: In the :@ modifier, the variable name "${:Ubar:S,b,v,}" must not contain a dollar .if ${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+" . error .else . error .endif # ":::" is a very creative variable name, unlikely to occur in practice. # The expression ${\:\:\:} would not work since backslashes can only # be escaped in the modifiers, but not in the variable name, therefore # the extra indirection via the modifier ':U'. .if ${:U1 2 3:@:::@x${${:U\:\:\:}}y@} != "x1y x2y x3y" . error .endif # "@@" is another creative variable name. .if ${:U1 2 3:@\@\@@x${@@}y@} != "x1y x2y x3y" . error .endif # In extreme cases, even the backslash can be used as variable name. # It needs to be doubled though. .if ${:U1 2 3:@\\@x${${:Ux:S,x,\\,}}y@} != "x1y x2y x3y" . error .endif # The variable name can technically be empty, and in this situation # the variable value cannot be accessed since the empty "variable" # is protected to always return an empty string. .if ${:U1 2 3:@@x${}y@} != "xy xy xy" . error .endif # The :@ modifier resolves the variables from the replacement text once more # than expected. In particular, it resolves _all_ variables from the scope, # and not only the loop variable (in this case v). SRCS= source CFLAGS.source= before ALL_CFLAGS:= ${SRCS:@src@${CFLAGS.${src}}@} # note the ':=' CFLAGS.source+= after .if ${ALL_CFLAGS} != "before" . error .endif # In the following example, the modifier ':@' expands the '$$' to '$'. This # means that when the resulting expression is evaluated, these resulting '$' # will be interpreted as starting a subexpression. # # The d means direct reference, the i means indirect reference. RESOLVE= ${RES1} $${RES1} RES1= 1d${RES2} 1i$${RES2} RES2= 2d${RES3} 2i$${RES3} RES3= 3 .if ${RESOLVE:@v@w${v}w@} != "w1d2d3w w2i3w w1i2d3 2i\${RES3}w w1d2d3 2i\${RES3} 1i\${RES2}w" . error .endif # Until 2020-07-20, the variable name of the :@ modifier could end with one # or two dollar signs, which were silently ignored. # There's no point in allowing a dollar sign in that position. # Since var.c 1.907 from 2021-04-04, a '$' is no longer allowed in the # variable name. -# expect+2: while evaluating variable "1 2 3" with value "1 2 3": In the :@ modifier, the variable name "v$" must not contain a dollar -# expect+1: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)") +# expect+1: In the :@ modifier, the variable name "v$" must not contain a dollar .if ${1 2 3:L:@v$@($v)@} != "(1) (2) (3)" . error .else . error .endif -# expect+2: while evaluating variable "1 2 3" with value "1 2 3": In the :@ modifier, the variable name "v$$" must not contain a dollar -# expect+1: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()") +# expect+1: In the :@ modifier, the variable name "v$$" must not contain a dollar .if ${1 2 3:L:@v$$@($v)@} != "() () ()" . error .else . error .endif -# expect+2: while evaluating variable "1 2 3" with value "1 2 3": In the :@ modifier, the variable name "v$$$" must not contain a dollar -# expect+1: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()") +# expect+1: In the :@ modifier, the variable name "v$$$" must not contain a dollar .if ${1 2 3:L:@v$$$@($v)@} != "() () ()" . error .else . error .endif # It may happen that there are nested :@ modifiers that use the same name for # for the loop variable. These modifiers influence each other. # # As of 2020-10-18, the :@ modifier is implemented by actually setting a # variable in the scope of the expression and deleting it again after the # loop. This is different from the .for loops, which substitute the # expression with ${:Uvalue}, leading to different unwanted side effects. # # To make the behavior more predictable, the :@ modifier should restore the # loop variable to the value it had before the loop. This would result in # the string "1a b c1 2a b c2 3a b c3", making the two loops independent. .if ${:U1 2 3:@i@$i${:Ua b c:@i@$i@}${i:Uu}@} != "1a b cu 2a b cu 3a b cu" . error .endif # During the loop, the variable is actually defined and nonempty. # If the loop were implemented in the same way as the .for loop, the variable # would be neither defined nor nonempty since all expressions of the form # ${var} would have been replaced with ${:Uword} before evaluating them. .if defined(var) . error .endif .if ${:Uword:@var@${defined(var):?def:undef} ${empty(var):?empty:nonempty}@} \ != "def nonempty" . error .endif .if defined(var) . error .endif all: .PHONY diff --git a/contrib/bmake/unit-tests/varmod-loop.exp b/contrib/bmake/unit-tests/varmod-loop.exp index ccfcd0b2dc06..510b151957f8 100644 --- a/contrib/bmake/unit-tests/varmod-loop.exp +++ b/contrib/bmake/unit-tests/varmod-loop.exp @@ -1,24 +1,24 @@ -Parsing line 89: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ -Parsing line 90: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" +Parsing varmod-loop.mk:89: USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$ +Parsing varmod-loop.mk:90: .if ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$" Comparing "$$$$ $$$$ $$$$" != "$$$$ $$$$ $$$$" -Parsing line 94: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} -Parsing line 116: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" +Parsing varmod-loop.mk:94: SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} +Parsing varmod-loop.mk:116: .if ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$" Comparing "$$ $$$$ $$$$" != "$$ $$$$ $$$$" -Parsing line 119: .MAKEFLAGS: -d0 +Parsing varmod-loop.mk:119: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) :varname-overwriting-target: :x1y x2y x3y: :: mod-loop-dollar:1: mod-loop-dollar:${word}$: mod-loop-dollar:$3$: mod-loop-dollar:$${word}$$: mod-loop-dollar:$$5$$: mod-loop-dollar:$$${word}$$$: : t=$(( ${t:-0} + 1 )) : dollar=end : backslash=\ end : dollar=$ at=@ backslash=\ end : dollar=$$ at=@@ backslash=\\ end : dollar=$$ at=@@ backslash=\\ end exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-match-escape.exp b/contrib/bmake/unit-tests/varmod-match-escape.exp index e15445de356b..a972b14816bd 100755 --- a/contrib/bmake/unit-tests/varmod-match-escape.exp +++ b/contrib/bmake/unit-tests/varmod-match-escape.exp @@ -1,43 +1,46 @@ Global: SPECIALS = \: : \\ * \* CondParser_Eval: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} -Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} (eval-defined) +Var_Parse: ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} (eval-defined-loud) Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*" Pattern for ':M' is "\:" ModifyWords: split "\: : \\ * \*" into 5 words Result of ${SPECIALS:M${:U}\:} is ":" -Var_Parse: ${SPECIALS:M\:${:U}} (eval-defined) +Var_Parse: ${SPECIALS:M\:${:U}} (eval-defined-loud) Evaluating modifier ${SPECIALS:M...} on value "\: : \\ * \*" Pattern for ':M' is ":" ModifyWords: split "\: : \\ * \*" into 5 words Result of ${SPECIALS:M\:${:U}} is ":" Comparing ":" != ":" Global: VALUES = : :: :\: CondParser_Eval: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} -Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} (eval-defined) +Var_Parse: ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} (eval-defined-loud) Evaluating modifier ${VALUES:M...} on value ": :: :\:" -Var_Parse: ${:U:} (eval-defined) -Evaluating modifier ${:U} on value "" (eval-defined, undefined) -Result of ${:U} is "" (eval-defined, defined) +Var_Parse: ${:U:} (eval) +Evaluating modifier ${:U} on value "" (eval, undefined) +Result of ${:U} is "" (eval, defined) Pattern for ':M' is ":" ModifyWords: split ": :: :\:" into 3 words Result of ${VALUES:M\:${:U\:}} is ":" -Var_Parse: ${VALUES:M${:U\:}\:} (eval-defined) +Var_Parse: ${VALUES:M${:U\:}\:} (eval-defined-loud) Evaluating modifier ${VALUES:M...} on value ": :: :\:" -Var_Parse: ${:U\:}\: (eval-defined) -Evaluating modifier ${:U...} on value "" (eval-defined, undefined) -Result of ${:U\:} is ":" (eval-defined, defined) +Var_Parse: ${:U\:}\: (eval) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:U\:} is ":" (eval, defined) Pattern for ':M' is ":\:" ModifyWords: split ": :: :\:" into 3 words Result of ${VALUES:M${:U\:}\:} is "::" Comparing ":" != "::" make: "varmod-match-escape.mk" line 43: warning: XXX: Oops Global: .MAKEFLAGS = -r -k -d cv -d Global: .MAKEFLAGS = -r -k -d cv -d 0 -make: "varmod-match-escape.mk" line 69: while evaluating "${:U\$:M\$} != """ with value "$": Dollar followed by nothing -make: "varmod-match-escape.mk" line 110: while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]": Unfinished character list in pattern '[A-]' of modifier ':M' +make: "varmod-match-escape.mk" line 69: Dollar followed by nothing + while evaluating "${:U\$:M\$} != """ with value "$" +make: "varmod-match-escape.mk" line 110: Unfinished character list in pattern '[A-]' of modifier ':M' + while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]" in .for loop from varmod-match-escape.mk:107 with pattern = [A-] -make: "varmod-match-escape.mk" line 110: while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]": Unfinished character list in pattern '[^A-]' of modifier ':M' +make: "varmod-match-escape.mk" line 110: Unfinished character list in pattern '[^A-]' of modifier ':M' + while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]" in .for loop from varmod-match-escape.mk:107 with pattern = [^A-] make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-match-escape.mk b/contrib/bmake/unit-tests/varmod-match-escape.mk index eb5730d3e5a1..d9e2009f9b60 100755 --- a/contrib/bmake/unit-tests/varmod-match-escape.mk +++ b/contrib/bmake/unit-tests/varmod-match-escape.mk @@ -1,121 +1,121 @@ -# $NetBSD: varmod-match-escape.mk,v 1.17 2024/07/09 17:07:23 rillig Exp $ +# $NetBSD: varmod-match-escape.mk,v 1.18 2024/08/29 20:20:36 rillig Exp $ # # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently, # depending on whether there was an expression somewhere before the # first backslash or not. See ParseModifier_Match, "copy = true". # # Apart from the different and possibly confusing debug output, there is no # difference in behavior. When parsing the modifier text, only \{, \} and \: # are unescaped, and in the pattern matching these have the same meaning as # their plain variants '{', '}' and ':'. In the pattern matching from # Str_Match, only \*, \? or \[ would make a noticeable difference. .MAKEFLAGS: -dcv SPECIALS= \: : \\ * \* .if ${SPECIALS:M${:U}\:} != ${SPECIALS:M\:${:U}} . warning unexpected .endif # And now both cases combined: A single modifier with both an escaped ':' # as well as an expression that expands to a ':'. # # XXX: As of 2020-11-01, when an escaped ':' occurs before the # expression, the whole modifier text is subject to unescaping '\:' to ':', # before the expression is expanded. This means that the '\:' in # the expression is expanded as well, turning ${:U\:} into a simple # ${:U:}, which silently expands to an empty string, instead of generating # an error message. # # XXX: As of 2020-11-01, the modifier on the right-hand side of the # comparison is parsed differently though. First, the expression # is parsed, resulting in ':' and needSubst=true. After that, the escaped # ':' is seen, and this time, copy=true is not executed but stays copy=false. # Therefore the escaped ':' is kept as-is, and the final pattern becomes # ':\:'. # # If ParseModifier_Match had used the same parsing algorithm as Var_Subst, # both patterns would end up as '::'. # VALUES= : :: :\: .if ${VALUES:M\:${:U\:}} != ${VALUES:M${:U\:}\:} # expect+1: warning: XXX: Oops . warning XXX: Oops .endif .MAKEFLAGS: -d0 # XXX: As of 2020-11-01, unlike all other variable modifiers, a '$' in the # :M and :N modifiers is written as '$$', not as '\$'. This is confusing, # undocumented and hopefully not used in practice. .if ${:U\$:M$$} != "\$" . error .endif # XXX: As of 2020-11-01, unlike all other variable modifiers, '\$' is not # parsed as an escaped '$'. Instead, ParseModifier_Match first scans for # the ':' at the end of the modifier, which results in the pattern '\$'. # No unescaping takes place since the pattern neither contained '\:' nor # '\{' nor '\}'. But the text is expanded, and a lonely '$' at the end # is silently discarded. The resulting expanded pattern is thus '\', that # is a single backslash. .if ${:U\$:M\$} != "" . error .endif # In lint mode, the case of a lonely '$' is covered with an error message. .MAKEFLAGS: -dL -# expect+1: while evaluating "${:U\$:M\$} != """ with value "$": Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${:U\$:M\$} != "" . error .endif # The control flow of the pattern parser depends on the actual string that # is being matched. There needs to be either a test that shows a difference # in behavior, or a proof that the behavior does not depend on the actual # string. # # TODO: Str_Match("a-z]", "[a-z]") # TODO: Str_Match("012", "[0-]]") # TODO: Str_Match("[", "[[]") # TODO: Str_Match("]", "[]") # TODO: Str_Match("]", "[[-]]") # Demonstrate an inconsistency between positive and negative character lists # when the range ends with the character ']'. # # 'A' begins the range, 'B' is in the middle of the range, ']' ends the range, # 'a' is outside the range. WORDS= A A] A]] B B] B]] ] ]] ]]] a a] a]] # The ']' is part of the character range and at the same time ends the # character list. EXP.[A-]= A B ] # The first ']' is part of the character range and at the same time ends the # character list. EXP.[A-]]= A] B] ]] # The first ']' is part of the character range and at the same time ends the # character list. EXP.[A-]]]= A]] B]] ]]] # For negative character lists, the ']' ends the character range but does not # end the character list. # XXX: This is unnecessarily inconsistent but irrelevant in practice as there # is no practical need for a character range that ends at ']'. EXP.[^A-]= a EXP.[^A-]]= a EXP.[^A-]]]= a] .for pattern in [A-] [A-]] [A-]]] [^A-] [^A-]] [^A-]]] -# expect+2: while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]": Unfinished character list in pattern '[A-]' of modifier ':M' -# expect+1: while evaluating variable "WORDS" with value "A A] A]] B B] B]] ] ]] ]]] a a] a]]": Unfinished character list in pattern '[^A-]' of modifier ':M' +# expect+2: Unfinished character list in pattern '[A-]' of modifier ':M' +# expect+1: Unfinished character list in pattern '[^A-]' of modifier ':M' . if ${WORDS:M${pattern}} != ${EXP.${pattern}} . warning ${pattern}: ${WORDS:M${pattern}} != ${EXP.${pattern}} . endif .endfor # In brackets, the backslash is just an ordinary character. # Outside brackets, it is an escape character for a few special characters. # TODO: Str_Match("\\", "[\\-]]") # TODO: Str_Match("-]", "[\\-]]") all: @:; diff --git a/contrib/bmake/unit-tests/varmod-match.exp b/contrib/bmake/unit-tests/varmod-match.exp index 377a43082fed..fdc76c3ae83a 100644 --- a/contrib/bmake/unit-tests/varmod-match.exp +++ b/contrib/bmake/unit-tests/varmod-match.exp @@ -1,14 +1,23 @@ -make: "varmod-match.mk" line 289: while evaluating variable "WORDS" with value "a a[": Unfinished character list in pattern 'a[' of modifier ':M' -make: "varmod-match.mk" line 297: while evaluating variable "WORDS" with value "a a[ aX": Unfinished character list in pattern 'a[^' of modifier ':M' -make: "varmod-match.mk" line 305: while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3": Unfinished character list in pattern '[-x1-3' of modifier ':M' -make: "varmod-match.mk" line 313: while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3": Unfinished character list in pattern '*[-x1-3' of modifier ':M' -make: "varmod-match.mk" line 322: while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3": Unfinished character list in pattern '[^-x1-3' of modifier ':M' -make: "varmod-match.mk" line 336: while evaluating variable "WORDS" with value "\\ \a x\": Unfinished character list in pattern '?[\' of modifier ':M' -make: "varmod-match.mk" line 344: while evaluating variable "WORDS" with value "[x- x x- y": Unfinished character range in pattern '[x-' of modifier ':M' -make: "varmod-match.mk" line 356: while evaluating variable "WORDS" with value "[x- x x- y yyyyy": Unfinished character range in pattern '[^x-' of modifier ':M' -make: "varmod-match.mk" line 364: while evaluating variable " : :: " with value " : :: ": Unfinished character list in pattern '[' of modifier ':M' -make: "varmod-match.mk" line 364: while evaluating variable " : :: " with value "": Unknown modifier "]" -make: "varmod-match.mk" line 364: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") +make: "varmod-match.mk" line 289: Unfinished character list in pattern 'a[' of modifier ':M' + while evaluating variable "WORDS" with value "a a[" +make: "varmod-match.mk" line 297: Unfinished character list in pattern 'a[^' of modifier ':M' + while evaluating variable "WORDS" with value "a a[ aX" +make: "varmod-match.mk" line 305: Unfinished character list in pattern '[-x1-3' of modifier ':M' + while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3" +make: "varmod-match.mk" line 313: Unfinished character list in pattern '*[-x1-3' of modifier ':M' + while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3" +make: "varmod-match.mk" line 322: Unfinished character list in pattern '[^-x1-3' of modifier ':M' + while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3" +make: "varmod-match.mk" line 336: Unfinished character list in pattern '?[\' of modifier ':M' + while evaluating variable "WORDS" with value "\\ \a x\" +make: "varmod-match.mk" line 344: Unfinished character range in pattern '[x-' of modifier ':M' + while evaluating variable "WORDS" with value "[x- x x- y" +make: "varmod-match.mk" line 356: Unfinished character range in pattern '[^x-' of modifier ':M' + while evaluating variable "WORDS" with value "[x- x x- y yyyyy" +make: "varmod-match.mk" line 363: Unfinished character list in pattern '[' of modifier ':M' + while evaluating variable " : :: " with value " : :: " +make: "varmod-match.mk" line 363: Unknown modifier "]" + while evaluating variable " : :: " with value "" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-match.mk b/contrib/bmake/unit-tests/varmod-match.mk index ce2bbab7eb53..3e9ada78231e 100644 --- a/contrib/bmake/unit-tests/varmod-match.mk +++ b/contrib/bmake/unit-tests/varmod-match.mk @@ -1,387 +1,386 @@ -# $NetBSD: varmod-match.mk,v 1.26 2024/07/09 17:07:23 rillig Exp $ +# $NetBSD: varmod-match.mk,v 1.29 2025/01/11 20:54:46 rillig Exp $ # # Tests for the ':M' modifier, which keeps only those words that match the # given pattern. # # Table of contents # # 1. Pattern characters '*', '?' and '\' # 2. Character lists and character ranges # 3. Parsing and escaping # 4. Interaction with other modifiers # 5. Performance # 6. Error handling # 7. Historical bugs # # See ApplyModifier_Match, ParseModifier_Match, ModifyWord_Match and # Str_Match. # 1. Pattern characters '*', '?' and '\' # # * matches 0 or more characters # ? matches 1 character # \x matches the character 'x' # The pattern is anchored both at the beginning and at the end of the word. # Since the pattern 'e' does not contain any pattern matching characters, it # matches exactly the word 'e', twice. .if ${a c e aa cc ee e f g:L:Me} != "e e" . error .endif # The pattern character '?' matches exactly 1 character, the pattern character # '*' matches 0 or more characters. The whole pattern matches all words that # start with 's' and have 3 or more characters. .if ${One Two Three Four five six seven so s:L:Ms??*} != "six seven" . error .endif # A pattern without placeholders only matches itself. .if ${a aa aaa b ba baa bab:L:Ma} != "a" . error .endif # A pattern that does not start with '*' is anchored at the beginning. .if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa" . error .endif # A pattern that does not end with '*' is anchored at the end. .if ${a aa aaa b ba baa bab:L:M*a} != "a aa aaa ba baa" . error .endif # Test the fast code path for '*' followed by a regular character. .if ${:U file.c file.*c file.h file\.c :M*.c} != "file.c file\\.c" . error .endif # Ensure that the fast code path correctly handles the backslash. .if ${:U file.c file.*c file.h file\.c :M*\.c} != "file.c file\\.c" . error .endif # Ensure that the fast code path correctly handles '\*'. .if ${:U file.c file.*c file.h file\.c :M*\*c} != "file.*c" . error .endif # Ensure that the partial match '.c' doesn't confuse the fast code path. .if ${:U file.c.cc file.cc.cc file.cc.c :M*.cc} != "file.c.cc file.cc.cc" . error .endif # Ensure that the substring '.cc' doesn't confuse the fast code path for '.c'. .if ${:U file.c.cc file.cc.cc file.cc.c :M*.c} != "file.cc.c" . error .endif # 2. Character lists and character ranges # # [...] matches 1 character from the listed characters # [^...] matches 1 character from the unlisted characters # [a-z] matches 1 character from the range 'a' to 'z' # [z-a] matches 1 character from the range 'a' to 'z' # Only keep words that start with an uppercase letter. .if ${One Two Three Four five six seven:L:M[A-Z]*} != "One Two Three Four" . error .endif # Only keep words that start with a character other than an uppercase letter. .if ${One Two Three Four five six seven:L:M[^A-Z]*} != "five six seven" . error .endif # [] matches never .if ${ ab a[]b a[b a b :L:M[]} != "" . error .endif # a[]b matches never .if ${ ab a[]b a[b a b [ ] :L:Ma[]b} != "" . error .endif # [^] matches exactly 1 arbitrary character .if ${ ab a[]b a[b a b [ ] :L:M[^]} != "a b [ ]" . error .endif # a[^]b matches 'a', then exactly 1 arbitrary character, then 'b' .if ${ ab a[]b a[b a b :L:Ma[^]b} != "a[b" . error .endif # [Nn0] matches exactly 1 character from the set 'N', 'n', '0' .if ${ a b N n 0 Nn0 [ ] :L:M[Nn0]} != "N n 0" . error .endif # [a-c] matches exactly 1 character from the range 'a' to 'c' .if ${ A B C a b c d [a-c] [a] :L:M[a-c]} != "a b c" . error .endif # [c-a] matches the same as [a-c] .if ${ A B C a b c d [a-c] [a] :L:M[c-a]} != "a b c" . error .endif # [^a-c67] # matches a single character, except for 'a', 'b', 'c', '6' or # '7' .if ${ A B C a b c d 5 6 7 8 [a-c] [a] :L:M[^a-c67]} != "A B C d 5 8" . error .endif # [\] matches a single backslash; no escaping takes place in # character ranges # Without the 'b' in the below words, the backslash would end a word and thus # influence how the string is split into words. WORDS= a\b a[\]b ab a\\b .if ${WORDS:Ma[\]b} != "a\\b" . error .endif # [[-]] May look like it would match a single '[', '\' or ']', but # the inner ']' has two roles: it is the upper bound of the # character range as well as the closing character of the # character list. The outer ']' is just a regular character. WORDS= [ ] [] \] ]] .if ${WORDS:M[[-]]} != "[] \\] ]]" . error .endif # [b[-]a] # Same as for '[[-]]': the character list stops at the first # ']', and the 'a]' is treated as a literal string. WORDS= [a \a ]a []a \]a ]]a [a] \a] ]a] ba] .if ${WORDS:M[b[-]a]} != "[a] \\a] ]a] ba]" . error .endif # [-] Matches a single '-' since the '-' only becomes part of a # character range if it is preceded and followed by another # character. WORDS= - -] .if ${WORDS:M[-]} != "-" . error .endif # Only keep words that don't start with s and at the same time end with # either of [ex]. # # This test case ensures that the negation from the first character list # '[^s]' does not propagate to the second character list '[ex]'. .if ${One Two Three Four five six seven:L:M[^s]*[ex]} != "One Three five" . error .endif # 3. Parsing and escaping # # * matches 0 or more characters # ? matches 1 character # \ outside a character list, escapes the following character # [ starts a character list for matching 1 character # ] ends a character list for matching 1 character # - in a character list, forms a character range # ^ at the beginning of a character list, negates the list # ( while parsing the pattern, starts a nesting level # ) while parsing the pattern, ends a nesting level # { while parsing the pattern, starts a nesting level # } while parsing the pattern, ends a nesting level # : while parsing the pattern, terminates the pattern # $ while parsing the pattern, starts a nested expression # # in a line except a shell command, starts a comment # The pattern can come from an expression. For single-letter # variables, either the short form or the long form can be used, just as # everywhere else. PRIMES= 2 3 5 7 11 n= 2 .if ${PRIMES:M$n} != "2" . error .endif .if ${PRIMES:M${n}} != "2" . error .endif .if ${PRIMES:M${:U2}} != "2" . error .endif # : terminates the pattern .if ${ A * :L:M:} != "" . error .endif # \: matches a colon .if ${ ${:U\: \:\:} :L:M\:} != ":" . error .endif # ${:U\:} matches a colon .if ${ ${:U\:} ${:U\:\:} :L:M${:U\:}} != ":" . error .endif # To match a dollar sign in a word, double it. # # This is different from the :S and :C modifiers, where a '$' has to be # escaped as '\$'. .if ${:Ua \$ sign:M*$$*} != "\$" . error .endif # In the :M modifier, '\$' does not escape a dollar. Instead it is # interpreted as a backslash followed by whatever expression the # '$' starts. # # This differs from the :S, :C and several other modifiers. ${:U*}= asterisk .if ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk" . error .endif # TODO: ${VAR:M(((}}}} # TODO: ${VAR:M{{{)))} # TODO: ${VAR:M${UNBALANCED}} # TODO: ${VAR:M${:U(((\}\}\}}} # 4. Interaction with other modifiers # The modifier ':tW' prevents splitting at whitespace. Even leading and # trailing whitespace is preserved. .if ${ plain string :L:tW:M*} != " plain string " . error .endif # Without the modifier ':tW', the string is split into words. Whitespace # around the words is discarded, and whitespace between the words is # normalized to a single space. .if ${ plain string :L:M*} != "plain string" . error .endif # 5. Performance # Before 2020-06-13, this expression called Str_Match 601,080,390 times. # Since 2020-06-13, this expression calls Str_Match 1 time. .if ${:U****************:M****************b} .endif # Before 2023-06-22, this expression called Str_Match 2,621,112 times. # Adding another '*?' to the pattern called Str_Match 20,630,572 times. # Adding another '*?' to the pattern called Str_Match 136,405,672 times. # Adding another '*?' to the pattern called Str_Match 773,168,722 times. # Adding another '*?' to the pattern called Str_Match 3,815,481,072 times. # Since 2023-06-22, Str_Match no longer backtracks. .if ${:U..................................................b:M*?*?*?*?*?a} .endif # 6. Error handling # [ Incomplete empty character list, never matches. WORDS= a a[ -# expect+1: while evaluating variable "WORDS" with value "a a[": Unfinished character list in pattern 'a[' of modifier ':M' +# expect+1: Unfinished character list in pattern 'a[' of modifier ':M' .if ${WORDS:Ma[} != "" . error .endif # [^ Incomplete negated empty character list, matches any single # character. WORDS= a a[ aX -# expect+1: while evaluating variable "WORDS" with value "a a[ aX": Unfinished character list in pattern 'a[^' of modifier ':M' +# expect+1: Unfinished character list in pattern 'a[^' of modifier ':M' .if ${WORDS:Ma[^} != "a[ aX" . error .endif # [-x1-3 Incomplete character list, matches those elements that can be # parsed without lookahead. WORDS= - + x xx 0 1 2 3 4 [x1-3 -# expect+1: while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3": Unfinished character list in pattern '[-x1-3' of modifier ':M' +# expect+1: Unfinished character list in pattern '[-x1-3' of modifier ':M' .if ${WORDS:M[-x1-3} != "- x 1 2 3" . error .endif # *[-x1-3 Incomplete character list after a wildcard, matches those # words that end with one of the characters from the list. WORDS= - + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3 -# expect+1: while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 00 01 10 11 000 001 010 011 100 101 110 111 [x1-3": Unfinished character list in pattern '*[-x1-3' of modifier ':M' +# expect+1: Unfinished character list in pattern '*[-x1-3' of modifier ':M' .if ${WORDS:M*[-x1-3} != "- x xx 1 2 3 01 11 001 011 101 111 [x1-3" . warning ${WORDS:M*[-x1-3} .endif # [^-x1-3 # Incomplete negated character list, matches any character # except those elements that can be parsed without lookahead. WORDS= - + x xx 0 1 2 3 4 [x1-3 -# expect+1: while evaluating variable "WORDS" with value "- + x xx 0 1 2 3 4 [x1-3": Unfinished character list in pattern '[^-x1-3' of modifier ':M' +# expect+1: Unfinished character list in pattern '[^-x1-3' of modifier ':M' .if ${WORDS:M[^-x1-3} != "+ 0 4" . error .endif # [\ Incomplete character list containing a single '\'. # # A word can only end with a backslash if the preceding # character is a backslash as well; in all other cases the final # backslash would escape the following space, making the space # part of the word. Only the very last word of a string can be # '\', as there is no following space that could be escaped. WORDS= \\ \a ${:Ux\\} PATTERN= ${:U?[\\} -# expect+1: while evaluating variable "WORDS" with value "\\ \a x\": Unfinished character list in pattern '?[\' of modifier ':M' +# expect+1: Unfinished character list in pattern '?[\' of modifier ':M' .if ${WORDS:M${PATTERN}} != "\\\\ x\\" . error .endif # [x- Incomplete character list containing an incomplete character # range, matches only the 'x'. WORDS= [x- x x- y -# expect+1: while evaluating variable "WORDS" with value "[x- x x- y": Unfinished character range in pattern '[x-' of modifier ':M' +# expect+1: Unfinished character range in pattern '[x-' of modifier ':M' .if ${WORDS:M[x-} != "x" . error .endif # [^x- Incomplete negated character list containing an incomplete # character range; matches each word that does not have an 'x' # at the position of the character list. # # XXX: Even matches strings that are longer than a single # character. WORDS= [x- x x- y yyyyy -# expect+1: while evaluating variable "WORDS" with value "[x- x x- y yyyyy": Unfinished character range in pattern '[^x-' of modifier ':M' +# expect+1: Unfinished character range in pattern '[^x-' of modifier ':M' .if ${WORDS:M[^x-} != "[x- y yyyyy" . error .endif # [:] matches never since the ':' starts the next modifier -# expect+3: while evaluating variable " : :: " with value " : :: ": Unfinished character list in pattern '[' of modifier ':M' -# expect+2: while evaluating variable " : :: " with value "": Unknown modifier "]" -# expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") +# expect+2: Unfinished character list in pattern '[' of modifier ':M' +# expect+1: Unknown modifier "]" .if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":" . error .else . error .endif # 7. Historical bugs # Before var.c 1.1031 from 2022-08-24, the following expressions caused an # out-of-bounds read beyond the indirect ':M' modifiers. # # The argument to the inner ':U' is unescaped to 'M\'. # This 'M\' becomes an indirect modifier ':M' with the pattern '\'. # The pattern '\' never matches. .if ${:U:${:UM\\}} . error .endif # The argument to the inner ':U' is unescaped to 'M\:\'. # This 'M\:\' becomes an indirect modifier ':M' with the pattern ':\'. # The pattern ':\' never matches. .if ${:U:${:UM\\\:\\}} . error .endif diff --git a/contrib/bmake/unit-tests/varmod-mtime.exp b/contrib/bmake/unit-tests/varmod-mtime.exp index 153b7ff79f08..160bfcdd6cf8 100644 --- a/contrib/bmake/unit-tests/varmod-mtime.exp +++ b/contrib/bmake/unit-tests/varmod-mtime.exp @@ -1,14 +1,15 @@ -make: "varmod-mtime.mk" line 47: while evaluating variable "no/such/file" with value "no/such/file": Invalid argument '123x' for modifier ':mtime' -make: "varmod-mtime.mk" line 47: Malformed conditional (${no/such/file:L:mtime=123x}) -make: "varmod-mtime.mk" line 70: while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2": Cannot determine mtime for 'no/such/file1': -make: "varmod-mtime.mk" line 70: while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2": Cannot determine mtime for 'no/such/file2': -make: "varmod-mtime.mk" line 70: Malformed conditional (${no/such/file1 no/such/file2:L:mtime=error}) -make: "varmod-mtime.mk" line 81: while evaluating variable "MAKEFILE" with value "varmod-mtime.mk": Invalid argument 'errorhandler-no' for modifier ':mtime' -make: "varmod-mtime.mk" line 81: Malformed conditional (${MAKEFILE:mtime=errorhandler-no} > 0) -make: "varmod-mtime.mk" line 90: while evaluating variable "MAKEFILE" with value "varmod-mtime.mk": Invalid argument 'warn' for modifier ':mtime' -make: "varmod-mtime.mk" line 90: Malformed conditional (${MAKEFILE:mtime=warn} > 0) -make: "varmod-mtime.mk" line 115: while evaluating variable "anything" with value "anything": Unknown modifier "mtim" -make: "varmod-mtime.mk" line 115: Malformed conditional (${anything:L:mtim}) +make: "varmod-mtime.mk" line 46: Invalid argument '123x' for modifier ':mtime' + while evaluating variable "no/such/file" with value "no/such/file" +make: "varmod-mtime.mk" line 68: Cannot determine mtime for 'no/such/file1': + while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2" +make: "varmod-mtime.mk" line 68: Cannot determine mtime for 'no/such/file2': + while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2" +make: "varmod-mtime.mk" line 78: Invalid argument 'errorhandler-no' for modifier ':mtime' + while evaluating variable "MAKEFILE" with value "varmod-mtime.mk" +make: "varmod-mtime.mk" line 86: Invalid argument 'warn' for modifier ':mtime' + while evaluating variable "MAKEFILE" with value "varmod-mtime.mk" +make: "varmod-mtime.mk" line 110: Unknown modifier "mtim" + while evaluating variable "anything" with value "anything" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-mtime.mk b/contrib/bmake/unit-tests/varmod-mtime.mk index be627fcfe040..0d0ddfc26b52 100644 --- a/contrib/bmake/unit-tests/varmod-mtime.mk +++ b/contrib/bmake/unit-tests/varmod-mtime.mk @@ -1,125 +1,120 @@ -# $NetBSD: varmod-mtime.mk,v 1.11 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: varmod-mtime.mk,v 1.14 2025/01/11 20:54:46 rillig Exp $ # # Tests for the ':mtime' variable modifier, which maps each word of the # expression to that file's modification time. # Note: strftime() uses mktime() for %s and mktime() assumes localtime # so this should match time() start:= ${%s:L:localtime} # see varmod-gmtime.mk, keyword '%s' # Ensure that this makefile exists and has a modification time. If the file # didn't exist, the ':mtime' modifier would return the current time. .if ${MAKEFILE:mtime} >= ${start} . error .endif # For a file that doesn't exist, the ':mtime' modifier returns the current # time, without an error or warning message. The returned timestamp differs # from the 'now' that is used when updating the timestamps in archives or for # touching files using the '-t' option, which is taken once when make is # started. not_found_mtime:= ${no/such/file:L:mtime} .if ${not_found_mtime} < ${start} . error .endif # The ':mtime' modifier accepts a timestamp in seconds as an optional # argument. This timestamp is used as a fallback in case the file's time # cannot be determined, without any error or warning message. .if ${no/such/file:L:mtime=0} != "0" . error .endif # The fallback timestamp must start with a digit, and it is interpreted as a # decimal integer. .if ${no/such/file:L:mtime=00042} != "42" . error .endif # The fallback timestamp must only be an integer, without trailing characters. -# expect+2: while evaluating variable "no/such/file" with value "no/such/file": Invalid argument '123x' for modifier ':mtime' -# expect+1: Malformed conditional (${no/such/file:L:mtime=123x}) +# expect+1: Invalid argument '123x' for modifier ':mtime' .if ${no/such/file:L:mtime=123x} . error .else . error .endif # The timestamp of a newly created file must be at least as great as the # timestamp when parsing of this makefile started. COOKIE= ${TMPDIR:U/tmp}/varmod-mtime.cookie _!= touch ${COOKIE} .if ${COOKIE:mtime=0} < ${start} . error ${COOKIE:mtime=0} < ${start} .endif _!= rm -f ${COOKIE} # If the optional argument of the ':mtime' modifier is the word 'error', the # modifier fails with an error message, once for each affected file. # -# expect+3: while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2": Cannot determine mtime for 'no/such/file1': -# expect+2: while evaluating variable "no/such/file1 no/such/file2" with value "no/such/file1 no/such/file2": Cannot determine mtime for 'no/such/file2': -# expect+1: Malformed conditional (${no/such/file1 no/such/file2:L:mtime=error}) +# expect+2: Cannot determine mtime for 'no/such/file1': +# expect+1: Cannot determine mtime for 'no/such/file2': .if ${no/such/file1 no/such/file2:L:mtime=error} . error .else . error .endif # Only the word 'error' is a special argument to the ':mtime' modifier, all # other words result in a parse error. -# expect+2: while evaluating variable "MAKEFILE" with value "varmod-mtime.mk": Invalid argument 'errorhandler-no' for modifier ':mtime' -# expect+1: Malformed conditional (${MAKEFILE:mtime=errorhandler-no} > 0) +# expect+1: Invalid argument 'errorhandler-no' for modifier ':mtime' .if ${MAKEFILE:mtime=errorhandler-no} > 0 .else . error .endif # Only the word 'error' can be used as a fallback argument to the modifier. -# expect+2: while evaluating variable "MAKEFILE" with value "varmod-mtime.mk": Invalid argument 'warn' for modifier ':mtime' -# expect+1: Malformed conditional (${MAKEFILE:mtime=warn} > 0) +# expect+1: Invalid argument 'warn' for modifier ':mtime' .if ${MAKEFILE:mtime=warn} > 0 . error .else . error .endif # Ensure that the fallback for a missing modification time is indeed the # current time, and not any later time. end:= ${%s:L:gmtime} .if ${not_found_mtime} > ${end} . error .endif # If the expression is irrelevant, the ':mtime' modifier is only parsed, it # does not perform any filesystem operations. .if 0 && ${no/such/file:L:mtime=error} . error .endif # If there is a typo in the modifier name, it does not match. -# expect+2: while evaluating variable "anything" with value "anything": Unknown modifier "mtim" -# expect+1: Malformed conditional (${anything:L:mtim}) +# expect+1: Unknown modifier "mtim" .if ${anything:L:mtim} . error .else . error .endif # An empty word list results in an empty mtime list. .if ${:U:mtime} != "" . error .endif diff --git a/contrib/bmake/unit-tests/varmod-order.exp b/contrib/bmake/unit-tests/varmod-order.exp index 591e1f160f61..2b955c123412 100644 --- a/contrib/bmake/unit-tests/varmod-order.exp +++ b/contrib/bmake/unit-tests/varmod-order.exp @@ -1,26 +1,29 @@ -make: "varmod-order.mk" line 17: while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten": Bad modifier ":OX" -make: "varmod-order.mk" line 17: Undefined variable "${WORDS:OX" -make: "varmod-order.mk" line 23: while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten": Bad modifier ":OxXX" -make: "varmod-order.mk" line 23: Undefined variable "${WORDS:Ox" -make: "varmod-order.mk" line 27: while evaluating variable "WORDS" with value "eight five four nine one seven six ten three two": Unclosed expression, expecting '}' for modifier "O" -make: "varmod-order.mk" line 29: while evaluating variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10": Unclosed expression, expecting '}' for modifier "On" -make: "varmod-order.mk" line 31: while evaluating variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1": Unclosed expression, expecting '}' for modifier "Onr" -make: "varmod-order.mk" line 38: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Oxn" -make: "varmod-order.mk" line 38: Malformed conditional (${NUMBERS:Oxn}) -make: "varmod-order.mk" line 48: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":On_typo" -make: "varmod-order.mk" line 48: Malformed conditional (${NUMBERS:On_typo}) -make: "varmod-order.mk" line 58: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Onr_typo" -make: "varmod-order.mk" line 58: Malformed conditional (${NUMBERS:Onr_typo}) -make: "varmod-order.mk" line 68: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Orn_typo" -make: "varmod-order.mk" line 68: Malformed conditional (${NUMBERS:Orn_typo}) -make: "varmod-order.mk" line 80: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Onn" -make: "varmod-order.mk" line 80: Malformed conditional (${NUMBERS:Onn}) -make: "varmod-order.mk" line 90: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Onrr" -make: "varmod-order.mk" line 90: Malformed conditional (${NUMBERS:Onrr}) -make: "varmod-order.mk" line 100: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Orrn" -make: "varmod-order.mk" line 100: Malformed conditional (${NUMBERS:Orrn}) -make: "varmod-order.mk" line 115: while evaluating variable "SWITCH" with value "On": Bad modifier ":On=Off" -make: "varmod-order.mk" line 115: Malformed conditional (${SWITCH:On=Off} != "Off") +make: "varmod-order.mk" line 14: Bad modifier ":OX" + while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten" +make: "varmod-order.mk" line 17: Bad modifier ":OxXX" + while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten" +make: "varmod-order.mk" line 20: Unclosed expression, expecting '}' for modifier "O" + while evaluating variable "WORDS" with value "eight five four nine one seven six ten three two" +make: "varmod-order.mk" line 22: Unclosed expression, expecting '}' for modifier "On" + while evaluating variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10" +make: "varmod-order.mk" line 24: Unclosed expression, expecting '}' for modifier "Onr" + while evaluating variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1" +make: "varmod-order.mk" line 30: Bad modifier ":Oxn" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 39: Bad modifier ":On_typo" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 48: Bad modifier ":Onr_typo" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 57: Bad modifier ":Orn_typo" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 68: Bad modifier ":Onn" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 77: Bad modifier ":Onrr" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 86: Bad modifier ":Orrn" + while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2" +make: "varmod-order.mk" line 100: Bad modifier ":On=Off" + while evaluating variable "SWITCH" with value "On" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-order.mk b/contrib/bmake/unit-tests/varmod-order.mk index b8a631857e6f..0389b3f023af 100644 --- a/contrib/bmake/unit-tests/varmod-order.mk +++ b/contrib/bmake/unit-tests/varmod-order.mk @@ -1,121 +1,106 @@ -# $NetBSD: varmod-order.mk,v 1.14 2024/07/05 18:59:33 rillig Exp $ +# $NetBSD: varmod-order.mk,v 1.18 2025/01/11 20:54:46 rillig Exp $ # # Tests for the :O variable modifier and its variants, which either sort the # words of the value or shuffle them. WORDS= one two three four five six seven eight nine ten NUMBERS= 8 5 4 9 1 7 6 10 3 2 # in English alphabetical order .if ${WORDS:O} != "eight five four nine one seven six ten three two" . error ${WORDS:O} .endif -# Unknown modifier "OX" -# FIXME: The error message "Undefined variable" is wrong. -# expect+2: Undefined variable "${WORDS:OX" -# expect+1: while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten": Bad modifier ":OX" +# expect+1: Bad modifier ":OX" _:= ${WORDS:OX} -# Unknown modifier "OxXX" -# FIXME: The error message "Undefined variable" is wrong. -# expect+2: Undefined variable "${WORDS:Ox" -# expect+1: while evaluating variable "WORDS" with value "one two three four five six seven eight nine ten": Bad modifier ":OxXX" +# expect+1: Bad modifier ":OxXX" _:= ${WORDS:OxXX} -# Missing closing brace, to cover the error handling code. -# expect+1: while evaluating variable "WORDS" with value "eight five four nine one seven six ten three two": Unclosed expression, expecting '}' for modifier "O" +# expect+1: Unclosed expression, expecting '}' for modifier "O" _:= ${WORDS:O -# expect+1: while evaluating variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10": Unclosed expression, expecting '}' for modifier "On" +# expect+1: Unclosed expression, expecting '}' for modifier "On" _:= ${NUMBERS:On -# expect+1: while evaluating variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1": Unclosed expression, expecting '}' for modifier "Onr" +# expect+1: Unclosed expression, expecting '}' for modifier "Onr" _:= ${NUMBERS:Onr # Shuffling numerically doesn't make sense, so don't allow 'x' and 'n' to be # combined. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Oxn" -# expect+1: Malformed conditional (${NUMBERS:Oxn}) +# expect+1: Bad modifier ":Oxn" .if ${NUMBERS:Oxn} . error .else . error .endif # Extra characters after ':On' are detected and diagnosed. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":On_typo" -# expect+1: Malformed conditional (${NUMBERS:On_typo}) +# expect+1: Bad modifier ":On_typo" .if ${NUMBERS:On_typo} . error .else . error .endif # Extra characters after ':Onr' are detected and diagnosed. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Onr_typo" -# expect+1: Malformed conditional (${NUMBERS:Onr_typo}) +# expect+1: Bad modifier ":Onr_typo" .if ${NUMBERS:Onr_typo} . error .else . error .endif # Extra characters after ':Orn' are detected and diagnosed. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Orn_typo" -# expect+1: Malformed conditional (${NUMBERS:Orn_typo}) +# expect+1: Bad modifier ":Orn_typo" .if ${NUMBERS:Orn_typo} . error .else . error .endif # Repeating the 'n' is not supported. In the typical use cases, the sorting # criteria are fixed, not computed, therefore allowing this redundancy does # not make sense. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Onn" -# expect+1: Malformed conditional (${NUMBERS:Onn}) +# expect+1: Bad modifier ":Onn" .if ${NUMBERS:Onn} . error .else . error .endif # Repeating the 'r' is not supported as well, for the same reasons as above. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Onrr" -# expect+1: Malformed conditional (${NUMBERS:Onrr}) +# expect+1: Bad modifier ":Onrr" .if ${NUMBERS:Onrr} . error .else . error .endif # Repeating the 'r' is not supported as well, for the same reasons as above. # -# expect+2: while evaluating variable "NUMBERS" with value "8 5 4 9 1 7 6 10 3 2": Bad modifier ":Orrn" -# expect+1: Malformed conditional (${NUMBERS:Orrn}) +# expect+1: Bad modifier ":Orrn" .if ${NUMBERS:Orrn} . error .else . error .endif # If a modifier that starts with ':O' is not one of the known sort or shuffle # forms, it is a parse error. Several other modifiers such as ':H' or ':u' # fall back to the SysV modifier, for example, ':H=new' is not the standard # ':H' modifier but instead replaces a trailing 'H' with 'new' in each word. # There is no such fallback for the ':O' modifiers. SWITCH= On -# expect+2: while evaluating variable "SWITCH" with value "On": Bad modifier ":On=Off" -# expect+1: Malformed conditional (${SWITCH:On=Off} != "Off") +# expect+1: Bad modifier ":On=Off" .if ${SWITCH:On=Off} != "Off" . error .else . error .endif all: diff --git a/contrib/bmake/unit-tests/varmod-range.exp b/contrib/bmake/unit-tests/varmod-range.exp index 2848193e0db1..dbd50c6def80 100644 --- a/contrib/bmake/unit-tests/varmod-range.exp +++ b/contrib/bmake/unit-tests/varmod-range.exp @@ -1,14 +1,14 @@ -make: "varmod-range.mk" line 43: Malformed conditional (${:range=5} != "") -make: "varmod-range.mk" line 67: while evaluating "${:U:range=x}Rest" != "Rest"" with value "": Invalid number "x}Rest" != "Rest"" for ':range' modifier -make: "varmod-range.mk" line 67: Malformed conditional ("${:U:range=x}Rest" != "Rest") -make: "varmod-range.mk" line 78: while evaluating "${:U:range=0x0}Rest" != "Rest"" with value "1": Unknown modifier "x0" -make: "varmod-range.mk" line 78: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") -make: "varmod-range.mk" line 96: while evaluating variable "a b c" with value "a b c": Unknown modifier "rang" -make: "varmod-range.mk" line 96: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") -make: "varmod-range.mk" line 105: while evaluating variable "a b c" with value "a b c": Unknown modifier "rango" -make: "varmod-range.mk" line 105: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") -make: "varmod-range.mk" line 114: while evaluating variable "a b c" with value "a b c": Unknown modifier "ranger" -make: "varmod-range.mk" line 114: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest") +make: "varmod-range.mk" line 43: Malformed conditional '${:range=5} != ""' +make: "varmod-range.mk" line 66: Invalid number "x}Rest" != "Rest"" for ':range' modifier + while evaluating "${:U:range=x}Rest" != "Rest"" with value "" +make: "varmod-range.mk" line 76: Unknown modifier "x0" + while evaluating "${:U:range=0x0}Rest" != "Rest"" with value "1" +make: "varmod-range.mk" line 93: Unknown modifier "rang" + while evaluating variable "a b c" with value "a b c" +make: "varmod-range.mk" line 101: Unknown modifier "rango" + while evaluating variable "a b c" with value "a b c" +make: "varmod-range.mk" line 109: Unknown modifier "ranger" + while evaluating variable "a b c" with value "a b c" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-range.mk b/contrib/bmake/unit-tests/varmod-range.mk index 4b07b659489c..277935dc8bc7 100644 --- a/contrib/bmake/unit-tests/varmod-range.mk +++ b/contrib/bmake/unit-tests/varmod-range.mk @@ -1,120 +1,115 @@ -# $NetBSD: varmod-range.mk,v 1.13 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-range.mk,v 1.16 2025/01/11 20:54:46 rillig Exp $ # # Tests for the :range variable modifier, which generates sequences # of integers from the given range. # # See also: # modword.mk # The :range modifier generates a sequence of integers, one number per # word of the expression's value. .if ${a b c:L:range} != "1 2 3" . error .endif # To preserve spaces in a word, they can be enclosed in quotes, just like # everywhere else. .if ${:U first "the second word" third 4 :range} != "1 2 3 4" . error .endif # The :range modifier takes the number of words from the value of the # expression. If that expression is undefined, the range is # undefined as well. This should not come as a surprise. .if "${:range}" != "" . error .endif # An empty expression results in a sequence of a single number, even though # the expression contains 0 words. .if ${:U:range} != "1" . error .endif # The :range modifier can be given a parameter, which makes the generated # range independent from the value or the name of the expression. .if "${:range=5}" != "" . error .endif # XXX: As of 2023-12-17, the ':range=n' modifier does not turn the undefined # expression into a defined one, even though it does not depend on the value # of the expression. This looks like an oversight. -# expect+1: Malformed conditional (${:range=5} != "") +# expect+1: Malformed conditional '${:range=5} != ""' .if ${:range=5} != "" . error .else . error .endif # Negative ranges don't make sense. # As of 2020-11-01, they are accepted though, using up all available memory. #.if "${:range=-1}" #. error #.else #. error #.endif # The :range modifier requires a number as parameter. # # Until 2020-11-01, the parser tried to read the 'x' as a number, failed and # stopped there. It then tried to parse the next modifier at that point, # which failed with the message "Unknown modifier". # # Since 2020-11-01, the parser issues a more precise "Invalid number" error # instead. -# expect+2: while evaluating "${:U:range=x}Rest" != "Rest"" with value "": Invalid number "x}Rest" != "Rest"" for ':range' modifier -# expect+1: Malformed conditional ("${:U:range=x}Rest" != "Rest") +# expect+1: Invalid number "x}Rest" != "Rest"" for ':range' modifier .if "${:U:range=x}Rest" != "Rest" . error .else . error .endif # The upper limit of the range must always be given in decimal. # This parse error stops at the 'x', trying to parse it as a variable # modifier. -# expect+2: while evaluating "${:U:range=0x0}Rest" != "Rest"" with value "1": Unknown modifier "x0" -# expect+1: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") +# expect+1: Unknown modifier "x0" .if "${:U:range=0x0}Rest" != "Rest" . error .else . error .endif # As of 2020-11-01, numeric overflow is not detected. # Since strtoul returns ULONG_MAX in such a case, it is interpreted as a # very large number, consuming all available memory. #.if "${:U:range=18446744073709551619}Rest" != "Rest" #. error #.else #. error #.endif # modifier name too short -# expect+2: while evaluating variable "a b c" with value "a b c": Unknown modifier "rang" -# expect+1: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") +# expect+1: Unknown modifier "rang" .if "${a b c:L:rang}Rest" != "Rest" . error .else . error .endif # misspelled modifier name -# expect+2: while evaluating variable "a b c" with value "a b c": Unknown modifier "rango" -# expect+1: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") +# expect+1: Unknown modifier "rango" .if "${a b c:L:rango}Rest" != "Rest" . error .else . error .endif # modifier name too long -# expect+2: while evaluating variable "a b c" with value "a b c": Unknown modifier "ranger" -# expect+1: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest") +# expect+1: Unknown modifier "ranger" .if "${a b c:L:ranger}Rest" != "Rest" . error .else . error .endif all: diff --git a/contrib/bmake/unit-tests/varmod-select-words.exp b/contrib/bmake/unit-tests/varmod-select-words.exp index c503e57b384b..7877837744ea 100644 --- a/contrib/bmake/unit-tests/varmod-select-words.exp +++ b/contrib/bmake/unit-tests/varmod-select-words.exp @@ -1,126 +1,138 @@ -make: in target "mod-squarebrackets-0-star-at": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[]" -LIST:[]="" is an error +make: Bad modifier ":[]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-empty" LIST:[0]="one two three four five six" LIST:[0x0]="one two three four five six" LIST:[000]="one two three four five six" LIST:[*]="one two three four five six" LIST:[@]="one two three four five six" LIST:[0]:C/ /,/="one,two three four five six" LIST:[0]:C/ /,/g="one,two,three,four,five,six" LIST:[0]:C/ /,/1g="one,two,three,four,five,six" LIST:[*]:C/ /,/="one,two three four five six" LIST:[*]:C/ /,/g="one,two,three,four,five,six" LIST:[*]:C/ /,/1g="one,two,three,four,five,six" LIST:[@]:C/ /,/="one two three four five six" LIST:[@]:C/ /,/g="one two three four five six" LIST:[@]:C/ /,/1g="one two three four five six" LIST:[@]:[0]:C/ /,/="one,two three four five six" LIST:[0]:[@]:C/ /,/="one two three four five six" LIST:[@]:[*]:C/ /,/="one,two three four five six" LIST:[*]:[@]:C/ /,/="one two three four five six" EMPTY="" EMPTY:[#]="1" == 1 ? ESCAPEDSPACE="\ " ESCAPEDSPACE:[#]="1" == 1 ? REALLYSPACE=" " REALLYSPACE:[#]="1" == 1 ? LIST:[#]="6" LIST:[0]:[#]="1" == 1 ? LIST:[*]:[#]="1" == 1 ? LIST:[@]:[#]="6" LIST:[1]:[#]="1" LIST:[1..3]:[#]="3" EMPTY:[1]="" ESCAPEDSPACE="\ " ESCAPEDSPACE:[1]="\ " REALLYSPACE=" " REALLYSPACE:[1]="" == "" ? REALLYSPACE:[*]:[1]=" " == " " ? LIST:[1]="one" -make: in target "mod-squarebrackets-n": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[1.]" -LIST:[1.]="" is an error -make: in target "mod-squarebrackets-n": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[1]." -LIST:[1].="}" is an error +make: Bad modifier ":[1.]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-n-error-1" +make: Bad modifier ":[1]." + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-n-error-2" LIST:[2]="two" LIST:[6]="six" LIST:[7]="" LIST:[999]="" -make: in target "mod-squarebrackets-n": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[-]" -LIST:[-]="" is an error -make: in target "mod-squarebrackets-n": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[--]" -LIST:[--]="" is an error +make: Bad modifier ":[-]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-n-error-3" +make: Bad modifier ":[--]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-n-error-4" LIST:[-1]="six" LIST:[-2]="five" LIST:[-6]="one" LIST:[-7]="" LIST:[-999]="" LONGLIST:[17]="17" LONGLIST:[0x11]="17" LONGLIST:[021]="17" LIST:[0]:[1]="one two three four five six" LIST:[*]:[1]="one two three four five six" LIST:[@]:[1]="one" LIST:[0]:[2]="" LIST:[*]:[2]="" LIST:[@]:[2]="two" LIST:[*]:C/ /,/:[2]="" LIST:[*]:C/ /,/:[*]:[2]="" LIST:[*]:C/ /,/:[@]:[2]="three" LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18" -make: in target "mod-squarebrackets-start-end": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[1.]" -LIST:[1.]="" is an error -make: in target "mod-squarebrackets-start-end": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[1..]" -LIST:[1..]="" is an error -make: in target "mod-squarebrackets-start-end": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[1.. ]" -LIST:[1.. ]="" is an error +make: Bad modifier ":[1.]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-start-end-error-1" +make: Bad modifier ":[1..]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-start-end-error-2" +make: Bad modifier ":[1.. ]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-start-end-error-3" LIST:[1..1]="one" -make: in target "mod-squarebrackets-start-end": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[1..1.]" -LIST:[1..1.]="" is an error +make: Bad modifier ":[1..1.]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-start-end-error-4" LIST:[1..2]="one two" LIST:[2..1]="two one" LIST:[3..-2]="three four five" LIST:[-4..4]="three four" -make: in target "mod-squarebrackets-start-end": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[0..1]" -LIST:[0..1]="" is an error -make: in target "mod-squarebrackets-start-end": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[-1..0]" -LIST:[-1..0]="" is an error +make: Bad modifier ":[0..1]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-start-end-error-5" +make: Bad modifier ":[-1..0]" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-start-end-error-6" LIST:[-1..1]="six five four three two one" LIST:[0..0]="one two three four five six" LIST:[3..99]="three four five six" LIST:[-3..-99]="four three two one" LIST:[-99..-3]="one two three four" HASH="#" == "#" ? LIST:[${HASH}]="6" LIST:[${ZERO}]="one two three four five six" LIST:[${ZERO}x${ONE}]="one" LIST:[${ONE}]="one" LIST:[${MINUSONE}]="six" LIST:[${STAR}]="one two three four five six" LIST:[${AT}]="one two three four five six" -make: in target "mod-squarebrackets-nested": while evaluating variable "LIST" with value "one two three four five six": Bad modifier ":[${EMPTY" -LIST:[${EMPTY}]="" is an error +make: Bad modifier ":[${EMPTY" + while evaluating variable "LIST" with value "one two three four five six" + in target "mod-squarebrackets-nested-error-1" LIST:[${LONGLIST:[21]:S/2//}]="one" LIST:[${LIST:[#]}]="six" LIST:[${LIST:[${HASH}]}]="six" LIST:[ -1.. +3]="six five four three" LIST:S/ /,/="one two three four five six" LIST:S/ /,/W="one,two three four five six" LIST:S/ /,/gW="one,two,three,four,five,six" EMPTY:S/^/,/="," EMPTY:S/^/,/W="," LIST:C/ /,/="one two three four five six" LIST:C/ /,/W="one,two three four five six" LIST:C/ /,/gW="one,two,three,four,five,six" EMPTY:C/^/,/="," EMPTY:C/^/,/W="," LIST:tW="one two three four five six" LIST:tw="one two three four five six" LIST:tW:C/ /,/="one,two three four five six" LIST:tW:C/ /,/g="one,two,three,four,five,six" LIST:tW:C/ /,/1g="one,two,three,four,five,six" LIST:tw:C/ /,/="one two three four five six" LIST:tw:C/ /,/g="one two three four five six" LIST:tw:C/ /,/1g="one two three four five six" LIST:tw:tW:C/ /,/="one,two three four five six" LIST:tW:tw:C/ /,/="one two three four five six" exit status 2 diff --git a/contrib/bmake/unit-tests/varmod-select-words.mk b/contrib/bmake/unit-tests/varmod-select-words.mk index 910b67a24e39..25a085891c0a 100644 --- a/contrib/bmake/unit-tests/varmod-select-words.mk +++ b/contrib/bmake/unit-tests/varmod-select-words.mk @@ -1,166 +1,214 @@ -# $NetBSD: varmod-select-words.mk,v 1.4 2022/01/23 16:09:38 rillig Exp $ +# $NetBSD: varmod-select-words.mk,v 1.6 2024/08/29 20:20:36 rillig Exp $ # # Tests for the :[...] variable modifier, which selects a single word # or a range of words from a variable. # # History: # The variable modifier ':[...]' was added on 2003-09-27. # # See also: # modword.mk (should be migrated here) all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw LIST= one two three four five six LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 EMPTY= # the space should be ignored ESCAPEDSPACE= \ # escaped space before the '#', the actual value is '\ ' REALLYSPACE:= ${:U } HASH= \# AT= @ STAR= * ZERO= 0 ONE= 1 MINUSONE= -1 -mod-squarebrackets: mod-squarebrackets-0-star-at \ +mod-squarebrackets: \ + mod-squarebrackets-empty \ + mod-squarebrackets-0-star-at \ mod-squarebrackets-hash \ - mod-squarebrackets-n \ - mod-squarebrackets-start-end \ - mod-squarebrackets-nested \ + mod-squarebrackets-n-ok-1 \ + mod-squarebrackets-n-error-1 \ + mod-squarebrackets-n-error-2 \ + mod-squarebrackets-n-ok-2 \ + mod-squarebrackets-n-error-3 \ + mod-squarebrackets-n-error-4 \ + mod-squarebrackets-n-ok-3 \ + mod-squarebrackets-start-end-error-1 \ + mod-squarebrackets-start-end-error-2 \ + mod-squarebrackets-start-end-error-3 \ + mod-squarebrackets-start-end-ok-1 \ + mod-squarebrackets-start-end-error-4 \ + mod-squarebrackets-start-end-ok-2 \ + mod-squarebrackets-start-end-error-5 \ + mod-squarebrackets-start-end-error-6 \ + mod-squarebrackets-start-end-ok-3 \ + mod-squarebrackets-nested-ok-1 \ + mod-squarebrackets-nested-error-1 \ + mod-squarebrackets-nested-ok-2 \ mod-squarebrackets-space -mod-squarebrackets-0-star-at: +mod-squarebrackets-empty: +# expect: make: Bad modifier ":[]" @echo 'LIST:[]="${LIST:[]}" is an error' + +mod-squarebrackets-0-star-at: @echo 'LIST:[0]="${LIST:[0]}"' @echo 'LIST:[0x0]="${LIST:[0x0]}"' @echo 'LIST:[000]="${LIST:[000]}"' @echo 'LIST:[*]="${LIST:[*]}"' @echo 'LIST:[@]="${LIST:[@]}"' @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"' @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"' @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"' @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"' @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"' @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"' @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"' @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"' @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"' @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"' @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"' @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"' @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"' mod-squarebrackets-hash: @echo 'EMPTY="${EMPTY}"' @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?' @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?' @echo 'REALLYSPACE="${REALLYSPACE}"' @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?' @echo 'LIST:[#]="${LIST:[#]}"' @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?' @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?' @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"' @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"' @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"' -mod-squarebrackets-n: +mod-squarebrackets-n-ok-1: @echo 'EMPTY:[1]="${EMPTY:[1]}"' @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"' @echo 'REALLYSPACE="${REALLYSPACE}"' @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?' @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?' @echo 'LIST:[1]="${LIST:[1]}"' +mod-squarebrackets-n-error-1: +# expect: make: Bad modifier ":[1.]" @echo 'LIST:[1.]="${LIST:[1.]}" is an error' +mod-squarebrackets-n-error-2: +# expect: make: Bad modifier ":[1]." @echo 'LIST:[1].="${LIST:[1].}" is an error' +mod-squarebrackets-n-ok-2: @echo 'LIST:[2]="${LIST:[2]}"' @echo 'LIST:[6]="${LIST:[6]}"' @echo 'LIST:[7]="${LIST:[7]}"' @echo 'LIST:[999]="${LIST:[999]}"' +mod-squarebrackets-n-error-3: +# expect: make: Bad modifier ":[-]" @echo 'LIST:[-]="${LIST:[-]}" is an error' +mod-squarebrackets-n-error-4: +# expect: make: Bad modifier ":[--]" @echo 'LIST:[--]="${LIST:[--]}" is an error' +mod-squarebrackets-n-ok-3: @echo 'LIST:[-1]="${LIST:[-1]}"' @echo 'LIST:[-2]="${LIST:[-2]}"' @echo 'LIST:[-6]="${LIST:[-6]}"' @echo 'LIST:[-7]="${LIST:[-7]}"' @echo 'LIST:[-999]="${LIST:[-999]}"' @echo 'LONGLIST:[17]="${LONGLIST:[17]}"' @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"' @echo 'LONGLIST:[021]="${LONGLIST:[021]}"' @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"' @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"' @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"' @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"' @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"' @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"' @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"' -mod-squarebrackets-start-end: +mod-squarebrackets-start-end-error-1: +# expect: make: Bad modifier ":[1.]" @echo 'LIST:[1.]="${LIST:[1.]}" is an error' +mod-squarebrackets-start-end-error-2: +# expect: make: Bad modifier ":[1..]" @echo 'LIST:[1..]="${LIST:[1..]}" is an error' +mod-squarebrackets-start-end-error-3: +# expect: make: Bad modifier ":[1.. ]" @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error' +mod-squarebrackets-start-end-ok-1: @echo 'LIST:[1..1]="${LIST:[1..1]}"' +mod-squarebrackets-start-end-error-4: +# expect: make: Bad modifier ":[1..1.]" @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' +mod-squarebrackets-start-end-ok-2: @echo 'LIST:[1..2]="${LIST:[1..2]}"' @echo 'LIST:[2..1]="${LIST:[2..1]}"' @echo 'LIST:[3..-2]="${LIST:[3..-2]}"' @echo 'LIST:[-4..4]="${LIST:[-4..4]}"' +mod-squarebrackets-start-end-error-5: +# expect: make: Bad modifier ":[0..1]" @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error' +mod-squarebrackets-start-end-error-6: +# expect: make: Bad modifier ":[-1..0]" @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error' +mod-squarebrackets-start-end-ok-3: @echo 'LIST:[-1..1]="${LIST:[-1..1]}"' @echo 'LIST:[0..0]="${LIST:[0..0]}"' @echo 'LIST:[3..99]="${LIST:[3..99]}"' @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"' @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"' -mod-squarebrackets-nested: +mod-squarebrackets-nested-ok-1: @echo 'HASH="${HASH}" == "#" ?' @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"' @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"' @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"' @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"' @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"' @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"' @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"' +mod-squarebrackets-nested-error-1: +# expect: make: Bad modifier ":[${EMPTY" @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error' +mod-squarebrackets-nested-ok-2: @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"' @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' mod-squarebrackets-space: # As of 2020-11-01, it is possible to have spaces before the numbers # but not after them. This is an unintended side-effect of using # strtol for parsing the numbers. @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"' mod-C-W: @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"' @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"' @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"' mod-S-W: @echo 'LIST:S/ /,/="${LIST:S/ /,/}"' @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"' @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"' @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"' @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"' mod-tW-tw: @echo 'LIST:tW="${LIST:tW}"' @echo 'LIST:tw="${LIST:tw}"' @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"' @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"' @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"' @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"' @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"' @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"' @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"' @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"' diff --git a/contrib/bmake/unit-tests/varmod-shell.exp b/contrib/bmake/unit-tests/varmod-shell.exp index 6ada8f74d75e..b2c6c59eea40 100644 --- a/contrib/bmake/unit-tests/varmod-shell.exp +++ b/contrib/bmake/unit-tests/varmod-shell.exp @@ -1,13 +1,13 @@ -make: "varmod-shell.mk" line 25: warning: while evaluating "${:!echo word; (exit 13)!} != "word"" with value "word": Command "echo word; (exit 13)" exited with status 13 -make: "varmod-shell.mk" line 29: warning: while evaluating "${:Uprevious value:!echo word; (exit 13)!} != "word"" with value "word": Command "echo word; (exit 13)" exited with status 13 +make: "varmod-shell.mk" line 25: warning: Command "echo word; (exit 13)" exited with status 13 +make: "varmod-shell.mk" line 29: warning: Command "echo word; (exit 13)" exited with status 13 Global: _ = # (empty) Var_Parse: ${:!echo word; ${:U(exit 13)}!} (eval-keep-dollar-and-undefined) Evaluating modifier ${:!...} on value "" (eval-keep-dollar-and-undefined, undefined) Modifier part: "echo word; (exit 13)" Capturing the output of command "echo word; (exit 13)" -make: "varmod-shell.mk" line 36: warning: while evaluating "${:!echo word; ${:U(exit 13)}!}" with value "word": Command "echo word; (exit 13)" exited with status 13 +make: "varmod-shell.mk" line 36: warning: Command "echo word; (exit 13)" exited with status 13 Result of ${:!echo word; ${:U(exit 13)}!} is "word" (eval-keep-dollar-and-undefined, defined) Global: _ = word Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-shell.mk b/contrib/bmake/unit-tests/varmod-shell.mk index 87918c5bbbf6..5f614d0c7f3c 100644 --- a/contrib/bmake/unit-tests/varmod-shell.mk +++ b/contrib/bmake/unit-tests/varmod-shell.mk @@ -1,40 +1,40 @@ -# $NetBSD: varmod-shell.mk,v 1.10 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-shell.mk,v 1.11 2024/08/29 20:20:37 rillig Exp $ # # Tests for the ':!cmd!' variable modifier, which runs the shell command # given by the variable modifier and returns its output. # # This modifier has been added on 2000-04-29. # # See also: # ApplyModifier_ShellCommand # The command to be run is enclosed between exclamation marks. # The previous value of the expression is irrelevant for this modifier. # The :!cmd! modifier turns an undefined expression into a defined one. .if ${:!echo word!} != "word" . error .endif # If the command exits with non-zero, an error message is printed. # XXX: Processing continues as usual though. # # Between 2000-04-29 and 2020-11-17, the error message mentioned the previous # value of the expression (which is usually an empty string) instead of the # command that was executed. -# expect+1: warning: while evaluating "${:!echo word; (exit 13)!} != "word"" with value "word": Command "echo word; (exit 13)" exited with status 13 +# expect+1: warning: Command "echo word; (exit 13)" exited with status 13 .if ${:!echo word; (exit 13)!} != "word" . error .endif -# expect+1: warning: while evaluating "${:Uprevious value:!echo word; (exit 13)!} != "word"" with value "word": Command "echo word; (exit 13)" exited with status 13 +# expect+1: warning: Command "echo word; (exit 13)" exited with status 13 .if ${:Uprevious value:!echo word; (exit 13)!} != "word" . error .endif .MAKEFLAGS: -dv # to see the "Capturing" debug output -# expect+1: warning: while evaluating "${:!echo word; ${:U(exit 13)}!}" with value "word": Command "echo word; (exit 13)" exited with status 13 +# expect+1: warning: Command "echo word; (exit 13)" exited with status 13 _:= ${:!echo word; ${:U(exit 13)}!} .MAKEFLAGS: -d0 all: diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.exp b/contrib/bmake/unit-tests/varmod-subst-regex.exp index 722abdff5813..98c37f6fd8e7 100644 --- a/contrib/bmake/unit-tests/varmod-subst-regex.exp +++ b/contrib/bmake/unit-tests/varmod-subst-regex.exp @@ -1,46 +1,71 @@ -make: in target "mod-regex-compile-error": while evaluating "${:Uword1 word2:C,****,____,g:C,word,____,:Q}." with value "word1 word2": Regex compilation error: (details omitted) -mod-regex-compile-error: C,word,____,:Q}. -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456": No subexpression \1 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456": No subexpression \1 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456": No subexpression \1 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456": No subexpression \1 -mod-regex-limits:11-missing:1 6 -mod-regex-limits:11-ok:1 22 446 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456": No subexpression \2 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456": No subexpression \2 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456": No subexpression \2 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456": No subexpression \2 -mod-regex-limits:22-missing:1 6 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456": No subexpression \2 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456": No subexpression \2 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456": No subexpression \2 -make: in target "mod-regex-limits": while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456": No subexpression \2 -mod-regex-limits:22-missing:1 6 -mod-regex-limits:22-ok:1 33 556 -mod-regex-limits:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest -make: in target "mod-regex-errors": while evaluating variable "UNDEF" with value "value": Regex compilation error: (details omitted) -mod-regex-errors: -make: in target "mod-regex-errors": while evaluating variable "word" with value "word": while evaluating "${:U:Z}y,W}" with value "": Unknown modifier "Z" -mod-regex-errors: xy +make: Regex compilation error: (details omitted) + while evaluating "${:Uword1 word2:C,****,____,g:C,word,____,:Q}." with value "word1 word2" + in target "mod-regex-compile-error" +make: No subexpression \1 + while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456" + in target "mod-regex-limits-1" +make: No subexpression \1 + while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456" + in target "mod-regex-limits-1" +make: No subexpression \1 + while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456" + in target "mod-regex-limits-1" +make: No subexpression \1 + while evaluating "${:U1 23 456:C,..,\1\1,:Q}" with value "1 23 456" + in target "mod-regex-limits-1" +mod-regex-limits-2:11-ok:1 22 446 +make: No subexpression \2 + while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-3" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-3" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-3" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,..,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-3" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-4" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-4" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-4" +make: No subexpression \2 + while evaluating "${:U1 23 456:C,(.).,\2\2,:Q}" with value "1 23 456" + in target "mod-regex-limits-4" +mod-regex-limits-5:22-ok:1 33 556 +mod-regex-limits-6:capture:ihgfedcbaabcdefghijABCDEFGHIJa0a1a2rest +make: Regex compilation error: (details omitted) + while evaluating variable "UNDEF" with value "value" + in target "mod-regex-errors-1" +make: Unknown modifier "Z" + while evaluating "${:U:Z}y,W}" with value "" + while evaluating variable "word" with value "word" + in target "mod-regex-errors-2" unmatched-subexpression.ok: one one 2 3 5 8 one3 2one 34 make: No match for subexpression \2 unmatched-subexpression.1: ()() make: No match for subexpression \2 unmatched-subexpression.1: ()() make: No match for subexpression \1 unmatched-subexpression.2: ()() unmatched-subexpression.3: 3 unmatched-subexpression.5: 5 unmatched-subexpression.8: 8 make: No match for subexpression \2 unmatched-subexpression.13: (3)() make: No match for subexpression \1 unmatched-subexpression.21: ()(1) unmatched-subexpression.34: 34 make: No match for subexpression \2 make: No match for subexpression \2 make: No match for subexpression \1 make: No match for subexpression \2 make: No match for subexpression \1 unmatched-subexpression.all: ()() ()() ()() 3 5 8 (3)() ()(1) 34 exit status 2 diff --git a/contrib/bmake/unit-tests/varmod-subst-regex.mk b/contrib/bmake/unit-tests/varmod-subst-regex.mk index c1ffc4580d3a..bc04bc5fffb9 100644 --- a/contrib/bmake/unit-tests/varmod-subst-regex.mk +++ b/contrib/bmake/unit-tests/varmod-subst-regex.mk @@ -1,206 +1,212 @@ -# $NetBSD: varmod-subst-regex.mk,v 1.11 2023/12/18 11:13:51 rillig Exp $ +# $NetBSD: varmod-subst-regex.mk,v 1.12 2024/07/20 11:05:12 rillig Exp $ # # Tests for the :C,from,to, variable modifier. # report unmatched subexpressions .MAKEFLAGS: -dL all: mod-regex-compile-error -all: mod-regex-limits -all: mod-regex-errors +all: mod-regex-limits-{1,2,3,4,5,6} +all: mod-regex-errors-{1,2} all: unmatched-subexpression # The expression expands to 4 words. Of these words, none matches # the regular expression "a b" since these words don't contain any # whitespace. .if ${:Ua b b c:C,a b,,} != "a b b c" . error .endif # Using the '1' modifier does not change anything. The '1' modifier just # means to apply at most 1 replacement in the whole expression. .if ${:Ua b b c:C,a b,,1} != "a b b c" . error .endif # The 'W' modifier treats the whole variable value as a single big word, # containing whitespace. This big word matches the regular expression, # therefore it gets replaced. Whitespace is preserved after replacing. .if ${:Ua b b c:C,a b,,W} != " b c" . error .endif # The 'g' modifier does not have any effect here since each of the words # contains the character 'b' a single time. .if ${:Ua b b c:C,b,,g} != "a c" . error .endif # The first :C modifier has the 'W' modifier, which makes the whole # expression a single word. The 'g' modifier then replaces all occurrences # of "1 2" with "___". The 'W' modifier only applies to this single :C # modifier. This is demonstrated by the :C modifier that follows. If the # 'W' modifier would be preserved, only a single underscore would have been # replaced with an 'x'. .if ${:U1 2 3 1 2 3:C,1 2,___,Wg:C,_,x,} != "x__ 3 x__ 3" . error .endif # The regular expression does not match in the first word. # It matches once in the second word, and the \0\0 doubles that word. # In the third word, the regular expression matches as early as possible, # and since the matches must not overlap, the next possible match would # start at the 6, but at that point, there is only one character left, # and that cannot match the regular expression "..". Therefore only the # "45" is doubled in the third word. .if ${:U1 23 456:C,..,\0\0,} != "1 2323 45456" . error .endif # The modifier '1' applies the replacement at most once, across the whole # expression value, no matter whether it is a single big word or many small # words. # # Up to 2020-08-28, the manual page said that the modifiers '1' and 'g' # were orthogonal, which was wrong. It doesn't make sense to specify both # 'g' and '1' at the same time. .if ${:U12345 12345:C,.,\0\0,1} != "112345 12345" . error .endif # A regular expression that matches the empty string applies before every # single character of the word. # XXX: Most other places where regular expression are used match at the end # of the string as well. .if ${:U1a2b3c:C,a*,*,g} != "*1**2*b*3*c" . error .endif # A dot in the regular expression matches any character, even a newline. # In most other contexts where regular expressions are used, a dot matches # any character except newline. In make, regcomp is called without # REG_NEWLINE, thus newline is an ordinary character. .if ${:U"${.newline}":C,.,.,g} != "..." . error .endif # Like the ':S' modifier, the ':C' modifier matches on an expression # that contains no words at all, but only if the regular expression matches an # empty string, for example, when the regular expression is anchored at the # beginning or the end of the word. An unanchored regular expression that # matches the empty string is uncommon in practice, as it would match before # each character of the word. .if "<${:U:S,,unanchored,}> <${:U:C,.?,unanchored,}>" != "<> " . error .endif .if "<${:U:S,^,prefix,}> <${:U:C,^,prefix,}>" != " " . error .endif .if "<${:U:S,$,suffix,}> <${:U:C,$,suffix,}>" != " " . error .endif .if "<${:U:S,^$,whole,}> <${:U:C,^$,whole,}>" != " " . error .endif .if "<${:U:S,,unanchored,g}> <${:U:C,.?,unanchored,g}>" != "<> " . error .endif .if "<${:U:S,^,prefix,g}> <${:U:C,^,prefix,g}>" != " " . error .endif .if "<${:U:S,$,suffix,g}> <${:U:C,$,suffix,g}>" != " " . error .endif .if "<${:U:S,^$,whole,g}> <${:U:C,^$,whole,g}>" != " " . error .endif .if "<${:U:S,,unanchored,W}> <${:U:C,.?,unanchored,W}>" != "<> " . error .endif .if "<${:U:S,^,prefix,W}> <${:U:C,^,prefix,W}>" != " " . error .endif .if "<${:U:S,$,suffix,W}> <${:U:C,$,suffix,W}>" != " " . error .endif .if "<${:U:S,^$,whole,W}> <${:U:C,^$,whole,W}>" != " " . error .endif # Multiple asterisks form an invalid regular expression. This produces an # error message and (as of 2020-08-28) stops parsing in the middle of the # expression. The unparsed part of the expression is then copied # verbatim to the output, which is unexpected and can lead to strange shell # commands being run. mod-regex-compile-error: @echo $@: ${:Uword1 word2:C,****,____,g:C,word,____,:Q}. # These tests generate error messages but as of 2020-08-28 just continue # parsing and execution as if nothing bad had happened. -mod-regex-limits: +mod-regex-limits-1: @echo $@:11-missing:${:U1 23 456:C,..,\1\1,:Q} +mod-regex-limits-2: @echo $@:11-ok:${:U1 23 456:C,(.).,\1\1,:Q} +mod-regex-limits-3: @echo $@:22-missing:${:U1 23 456:C,..,\2\2,:Q} +mod-regex-limits-4: @echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q} +mod-regex-limits-5: @echo $@:22-ok:${:U1 23 456:C,(.)(.),\2\2,:Q} +mod-regex-limits-6: # The :C modifier only handles single-digit capturing groups, # which is enough for all practical use cases. @echo $@:capture:${:UabcdefghijABCDEFGHIJrest:C,(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.),\9\8\7\6\5\4\3\2\1\0\10\11\12,} -mod-regex-errors: +mod-regex-errors-1: @echo $@: ${UNDEF:Uvalue:C,[,,} +mod-regex-errors-2: # If the replacement pattern produces a parse error because of an # unknown modifier, the parse error is ignored in ParseModifierPart # and the faulty expression expands to "". @echo $@: ${word:L:C,.*,x${:U:Z}y,W} # In regular expressions with alternatives, not all capturing groups are # always set; some may be missing. Make calls these "unmatched # subexpressions". # # Between var.c 1.16 from 1996-12-24 until before var.c 1.933 from 2021-06-21, # unmatched subexpressions produced an "error message" but did not have any # further effect since the "error handling" didn't influence the exit status. # # Before 2021-06-21 there was no way to turn off this warning, thus the # combination of alternative matches and capturing groups was seldom used, if # at all. # # Since var.c 1.933 from 2021-06-21, the error message is only printed in lint # mode (-dL), but not in default mode. # # As an alternative to the change from var.c 1.933 from 2021-06-21, a possible # mitigation would have been to add a new modifier 'U' to the already existing # '1Wg' modifiers of the ':C' modifier. That modifier could have been used in # the modifier ':C,(a.)|(b.),\1\2,U' to treat unmatched subexpressions as # empty. This approach would have created a syntactical ambiguity since the # modifiers ':S' and ':C' are open-ended (see mod-subst-chain), that is, they # do not need to be followed by a ':' to separate them from the next modifier. # Luckily the modifier :U does not make sense after :C, therefore this case # does not happen in practice. unmatched-subexpression: # In each of the following cases, if the regular expression matches at # all, the subexpression \1 matches as well. @echo $@.ok: ${:U1 1 2 3 5 8 13 21 34:C,1(.*),one\1,} # In the following cases: # * The subexpression \1 is only defined for 1 and 13. # * The subexpression \2 is only defined for 2 and 21. # * If the regular expression does not match at all, the # replacement string is not analyzed, thus no error messages. # In total, there are 5 error messages about unmatched subexpressions. @echo $@.1: ${:U 1:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 @echo $@.1: ${:U 1:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 @echo $@.2: ${:U 2:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \1 @echo $@.3: ${:U 3:C,1(.*)|2(.*),(\1)(\2),:Q} @echo $@.5: ${:U 5:C,1(.*)|2(.*),(\1)(\2),:Q} @echo $@.8: ${:U 8:C,1(.*)|2(.*),(\1)(\2),:Q} @echo $@.13: ${:U 13:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \2 @echo $@.21: ${:U 21:C,1(.*)|2(.*),(\1)(\2),:Q} # missing \1 @echo $@.34: ${:U 34:C,1(.*)|2(.*),(\1)(\2),:Q} # And now all together: 5 error messages for 1, 1, 2, 13, 21. @echo $@.all: ${:U1 1 2 3 5 8 13 21 34:C,1(.*)|2(.*),(\1)(\2),:Q} diff --git a/contrib/bmake/unit-tests/varmod-subst.exp b/contrib/bmake/unit-tests/varmod-subst.exp index 9b91c0e50857..1547201bb404 100644 --- a/contrib/bmake/unit-tests/varmod-subst.exp +++ b/contrib/bmake/unit-tests/varmod-subst.exp @@ -1,62 +1,63 @@ mod-subst: :a b b c: :a b b c: : b c: :a c: :x__ 3 x__ 3: 12345 mod-subst-delimiter: 1 two 3 horizontal tabulator 1 two 3 space 1 two 3 exclamation mark 1 two 3 quotation mark 1 two 3 number sign 1 two 3 dollar sign 1 two 3 percent sign 1 two 3 ampersand 1 two 3 apostrophe 1 two 3 left parenthesis 1 two 3 right parenthesis 1 two 3 asterisk 1 two 3 plus sign 1 two 3 comma 1 two 3 hyphen-minus 1 two 3 full stop 1 two 3 solidus 1 two 3 digit 1 two 3 colon 1 two 3 semicolon 1 two 3 less-than sign 1 two 3 equals sign 1 two 3 greater-than sign 1 two 3 question mark 1 two 3 commercial at 1 two 3 capital letter 1 two 3 left square bracket 1 two 3 reverse solidus 1 two 3 right square bracket 1 two 3 circumflex accent 1 two 3 low line 1 two 3 grave accent 1 two 3 small letter 1 two 3 left curly bracket 1 two 3 vertical line 1 two 3 right curly bracket 1 two 3 tilde mod-subst-chain: A B c. -make: in target "mod-subst-chain": while evaluating "${:Uvalue:S,a,x,i}." with value "vxlue": Unknown modifier "i" -. +make: Unknown modifier "i" + while evaluating "${:Uvalue:S,a,x,i}." with value "vxlue" + in target "mod-subst-chain" mod-subst-dollar:$1: mod-subst-dollar:$2: mod-subst-dollar:$3: mod-subst-dollar:$4: mod-subst-dollar:$5: mod-subst-dollar:$6: mod-subst-dollar:$7: mod-subst-dollar:$8: mod-subst-dollar:$40: mod-subst-dollar:U8: mod-subst-dollar:$$$$: mod-subst-dollar:$$$good3 exit status 2 diff --git a/contrib/bmake/unit-tests/varmod-subst.mk b/contrib/bmake/unit-tests/varmod-subst.mk index 2903d36445f8..34f20a5f96f2 100644 --- a/contrib/bmake/unit-tests/varmod-subst.mk +++ b/contrib/bmake/unit-tests/varmod-subst.mk @@ -1,286 +1,287 @@ -# $NetBSD: varmod-subst.mk,v 1.14 2023/12/18 11:13:51 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.16 2024/08/29 20:20:37 rillig Exp $ # # Tests for the :S,from,to, variable modifier. all: mod-subst all: mod-subst-delimiter all: mod-subst-chain all: mod-subst-dollar WORDS= sequences of letters # The empty pattern never matches anything, except if it is anchored at the # beginning or the end of the word. .if ${WORDS:S,,,} != ${WORDS} . error .endif # The :S modifier flag '1' is applied exactly once. .if ${WORDS:S,e,*,1} != "s*quences of letters" . error .endif # The :S modifier flag '1' is applied to the first occurrence, no matter if # the occurrence is in the first word or not. .if ${WORDS:S,f,*,1} != "sequences o* letters" . error .endif # The :S modifier replaces every first match per word. .if ${WORDS:S,e,*,} != "s*quences of l*tters" . error .endif # The :S modifier flag 'g' replaces every occurrence. .if ${WORDS:S,e,*,g} != "s*qu*nc*s of l*tt*rs" . error .endif # The '^' in the search pattern anchors the pattern at the beginning of each # word, thereby matching a prefix. .if ${WORDS:S,^sequ,occurr,} != "occurrences of letters" . error .endif # The :S modifier with a '^' anchor replaces the whole word if that word is # exactly the pattern. .if ${WORDS:S,^of,with,} != "sequences with letters" . error .endif # The :S modifier does not match if the pattern is longer than the word. .if ${WORDS:S,^office,does not match,} != ${WORDS} . warning .endif # The '$' in the search pattern anchors the pattern at the end of each word, # thereby matching a suffix. .if ${WORDS:S,f$,r,} != "sequences or letters" . error .endif # The :S modifier with a '$' anchor replaces at most one occurrence per word. .if ${WORDS:S,s$,,} != "sequence of letter" . error .endif # The :S modifier with a '$' anchor replaces the whole word if that word is # exactly the pattern. .if ${WORDS:S,of$,,} != "sequences letters" . error .endif # The :S modifier with a '$' anchor and a pattern that is longer than a word # cannot match that word. .if ${WORDS:S,eof$,,} != ${WORDS} . warning .endif # The :S modifier with the '^' and '$' anchors matches an exact word. .if ${WORDS:S,^of$,,} != "sequences letters" . error .endif # The :S modifier with the '^' and '$' anchors does not match a word that # starts with the pattern but is longer than the pattern. .if ${WORDS:S,^o$,,} != ${WORDS} . error .endif # The :S modifier with the '^' and '$' anchors does not match a word that ends # with the pattern but is longer than the pattern. .if ${WORDS:S,^f$,,} != ${WORDS} . error .endif # The :S modifier with the '^' and '$' anchors does not match a word if the # pattern ends with the word but is longer than the word. .if ${WORDS:S,^eof$,,} != ${WORDS} . error .endif # The :S modifier with the '^' and '$' anchors does not match a word if the # pattern starts with the word but is longer than the word. .if ${WORDS:S,^office$,,} != ${WORDS} . error .endif # Except for the '^' and '$' anchors, the pattern does not contain any special # characters, so the '*' from the pattern would only match a literal '*' in a # word. .if ${WORDS:S,*,replacement,} != ${WORDS} . error .endif # Except for the '^' and '$' anchors, the pattern does not contain any special # characters, so the '.' from the pattern would only match a literal '.' in a # word. .if ${WORDS:S,.,replacement,} != ${WORDS} . error .endif # The '&' in the replacement is a placeholder for the text matched by the # pattern. .if ${:Uvalue:S,^val,&,} != "value" . error .endif .if ${:Uvalue:S,ue$,&,} != "value" . error .endif .if ${:Uvalue:S,^val,&-&-&,} != "val-val-value" . error .endif .if ${:Uvalue:S,ue$,&-&-&,} != "value-ue-ue" . error .endif # When a word is replaced with nothing, the remaining words are separated by a # single space, not two. .if ${1 2 3:L:S,2,,} != "1 3" . error .endif # In an empty expression, the ':S' modifier matches a single time, but only if # the search string is empty and anchored at either the beginning or the end # of the word. .if ${:U:S,,out-of-nothing,} != "" . error .endif .if ${:U:S,^,out-of-nothing,} != "out-of-nothing" . error .endif .if ${:U:S,$,out-of-nothing,} != "out-of-nothing" . error .endif .if ${:U:S,^$,out-of-nothing,} != "out-of-nothing" . error .endif .if ${:U:S,,out-of-nothing,g} != "" . error .endif .if ${:U:S,^,out-of-nothing,g} != "out-of-nothing" . error .endif .if ${:U:S,$,out-of-nothing,g} != "out-of-nothing" . error .endif .if ${:U:S,^$,out-of-nothing,g} != "out-of-nothing" . error .endif .if ${:U:S,,out-of-nothing,W} != "" . error .endif .if ${:U:S,^,out-of-nothing,W} != "out-of-nothing" . error .endif .if ${:U:S,$,out-of-nothing,W} != "out-of-nothing" . error .endif .if ${:U:S,^$,out-of-nothing,W} != "out-of-nothing" . error .endif mod-subst: @echo $@: @echo :${:Ua b b c:S,a b,,:Q}: @echo :${:Ua b b c:S,a b,,1:Q}: @echo :${:Ua b b c:S,a b,,W:Q}: @echo :${:Ua b b c:S,b,,g:Q}: @echo :${:U1 2 3 1 2 3:S,1 2,___,Wg:S,_,x,:Q}: @echo ${:U12345:S,,sep,g:Q} # The :S and :C modifiers accept an arbitrary character as the delimiter, # including characters that are otherwise used as escape characters or # interpreted in a special way. This can be used to confuse humans. mod-subst-delimiter: @echo $@: @echo ${:U1 2 3:S 2 two :Q} horizontal tabulator @echo ${:U1 2 3:S 2 two :Q} space @echo ${:U1 2 3:S!2!two!:Q} exclamation mark @echo ${:U1 2 3:S"2"two":Q} quotation mark # In shell command lines, the hash does not need to be escaped. # It needs to be escaped in variable assignment lines though. @echo ${:U1 2 3:S#2#two#:Q} number sign @echo ${:U1 2 3:S$2$two$:Q} dollar sign @echo ${:U1 2 3:S%2%two%:Q} percent sign @echo ${:U1 2 3:S&2&two&:Q} ampersand @echo ${:U1 2 3:S'2'two':Q} apostrophe @echo ${:U1 2 3:S(2(two(:Q} left parenthesis @echo ${:U1 2 3:S)2)two):Q} right parenthesis @echo ${:U1 2 3:S*2*two*:Q} asterisk @echo ${:U1 2 3:S+2+two+:Q} plus sign @echo ${:U1 2 3:S,2,two,:Q} comma @echo ${:U1 2 3:S-2-two-:Q} hyphen-minus @echo ${:U1 2 3:S.2.two.:Q} full stop @echo ${:U1 2 3:S/2/two/:Q} solidus @echo ${:U1 2 3:S121two1:Q} digit @echo ${:U1 2 3:S:2:two::Q} colon @echo ${:U1 2 3:S;2;two;:Q} semicolon @echo ${:U1 2 3:S<22>two>:Q} greater-than sign @echo ${:U1 2 3:S?2?two?:Q} question mark @echo ${:U1 2 3:S@2@two@:Q} commercial at @echo ${:U1 2 3:SA2AtwoA:Q} capital letter @echo ${:U1 2 3:S[2[two[:Q} left square bracket @echo ${:U1 2 3:S\2\two\:Q} reverse solidus @echo ${:U1 2 3:S]2]two]:Q} right square bracket @echo ${:U1 2 3:S^2^two^:Q} circumflex accent @echo ${:U1 2 3:S_2_two_:Q} low line @echo ${:U1 2 3:S`2`two`:Q} grave accent @echo ${:U1 2 3:Sa2atwoa:Q} small letter @echo ${:U1 2 3:S{2{two{:Q} left curly bracket @echo ${:U1 2 3:S|2|two|:Q} vertical line @echo ${:U1 2 3:S}2}two}:Q} right curly bracket @echo ${:U1 2 3:S~2~two~:Q} tilde # The :S and :C modifiers can be chained without a separating ':'. # This is not documented in the manual page. # It works because ApplyModifier_Subst scans for the known modifiers g1W # and then just returns to ApplyModifiers. There, the colon is optionally # skipped (see the *st.next == ':' at the end of the loop). # # Most other modifiers cannot be chained since their parsers skip until # the next ':' or '}' or ')'. mod-subst-chain: @echo $@: @echo ${:Ua b c:S,a,A,S,b,B,}. # There is no 'i' modifier for the :S or :C modifiers. # The error message is "make: Unknown modifier 'i'", which is # kind of correct, although it is mixing the terms for variable # modifiers with the matching modifiers. +# expect: make: Unknown modifier "i" @echo ${:Uvalue:S,a,x,i}. # No matter how many dollar signs there are, they all get merged # into a single dollar by the :S modifier. # # As of 2020-08-09, this is because ParseModifierPart sees a '$' and # calls Var_Parse to expand the variable. In all other places, the "$$" # is handled outside of Var_Parse. Var_Parse therefore considers "$$" # one of the "really stupid names", skips the first dollar, and parsing # continues with the next character. This repeats for the other dollar # signs, except the one before the delimiter. That one is handled by # the code that optionally interprets the '$' as the end-anchor in the # first part of the :S modifier. That code doesn't call Var_Parse but # simply copies the dollar to the result. mod-subst-dollar: @echo $@:${:U1:S,^,$,:Q}: @echo $@:${:U2:S,^,$$,:Q}: @echo $@:${:U3:S,^,$$$,:Q}: @echo $@:${:U4:S,^,$$$$,:Q}: @echo $@:${:U5:S,^,$$$$$,:Q}: @echo $@:${:U6:S,^,$$$$$$,:Q}: @echo $@:${:U7:S,^,$$$$$$$,:Q}: @echo $@:${:U8:S,^,$$$$$$$$,:Q}: @echo $@:${:U40:S,^,$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$,:Q}: # This generates no dollar at all: @echo $@:${:UU8:S,^,${:U$$$$$$$$},:Q}: # Here is an alternative way to generate dollar signs. # It's unexpectedly complicated though. @echo $@:${:U:range=5:ts\x24:C,[0-9],,g:Q}: # In modifiers, dollars are escaped using the backslash, not using another # dollar sign. Therefore, creating a dollar sign is pretty simple: @echo $@:${:Ugood3:S,^,\$\$\$,:Q} diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.exp b/contrib/bmake/unit-tests/varmod-sun-shell.exp index 80dec0935f28..5a557b97cec8 100644 --- a/contrib/bmake/unit-tests/varmod-sun-shell.exp +++ b/contrib/bmake/unit-tests/varmod-sun-shell.exp @@ -1,13 +1,13 @@ -make: "varmod-sun-shell.mk" line 17: warning: while evaluating variable "echo word; (exit 13)" with value "echo word; (exit 13)": Command "echo word; (exit 13)" exited with status 13 +make: "varmod-sun-shell.mk" line 17: warning: Command "echo word; (exit 13)" exited with status 13 Global: _ = # (empty) Var_Parse: ${echo word; ${:U(exit 13)}:L:sh} (eval-keep-dollar-and-undefined) Evaluating modifier ${echo word; (exit 13):L} on value "" (eval-keep-dollar-and-undefined, undefined) Result of ${echo word; (exit 13):L} is "echo word; (exit 13)" (eval-keep-dollar-and-undefined, defined) Evaluating modifier ${echo word; (exit 13):s...} on value "echo word; (exit 13)" (eval-keep-dollar-and-undefined, defined) Capturing the output of command "echo word; (exit 13)" -make: "varmod-sun-shell.mk" line 24: warning: while evaluating variable "echo word; (exit 13)" with value "echo word; (exit 13)": Command "echo word; (exit 13)" exited with status 13 +make: "varmod-sun-shell.mk" line 24: warning: Command "echo word; (exit 13)" exited with status 13 Result of ${echo word; (exit 13):sh} is "word" (eval-keep-dollar-and-undefined, defined) Global: _ = word Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/varmod-sun-shell.mk b/contrib/bmake/unit-tests/varmod-sun-shell.mk index 4a37a271eced..8f6a5bc4cc05 100644 --- a/contrib/bmake/unit-tests/varmod-sun-shell.mk +++ b/contrib/bmake/unit-tests/varmod-sun-shell.mk @@ -1,28 +1,28 @@ -# $NetBSD: varmod-sun-shell.mk,v 1.5 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: varmod-sun-shell.mk,v 1.6 2024/08/29 20:20:37 rillig Exp $ # # Tests for the :sh variable modifier, which runs the shell command # given by the variable value and returns its output. # # This modifier has been added on 1996-05-29. # # See also: # ApplyModifier_SunShell .if ${echo word:L:sh} != "word" . error .endif # If the command exits with non-zero, a warning is printed. -# expect+1: warning: while evaluating variable "echo word; (exit 13)" with value "echo word; (exit 13)": Command "echo word; (exit 13)" exited with status 13 +# expect+1: warning: Command "echo word; (exit 13)" exited with status 13 .if ${echo word; (exit 13):L:sh} != "word" . error .endif .MAKEFLAGS: -dv # to see the "Capturing" debug output -# expect+1: warning: while evaluating variable "echo word; (exit 13)" with value "echo word; (exit 13)": Command "echo word; (exit 13)" exited with status 13 +# expect+1: warning: Command "echo word; (exit 13)" exited with status 13 _:= ${echo word; ${:U(exit 13)}:L:sh} .MAKEFLAGS: -d0 all: diff --git a/contrib/bmake/unit-tests/varmod-sysv.exp b/contrib/bmake/unit-tests/varmod-sysv.exp index be87193b4066..ecda358758e1 100644 --- a/contrib/bmake/unit-tests/varmod-sysv.exp +++ b/contrib/bmake/unit-tests/varmod-sysv.exp @@ -1,152 +1,152 @@ -make: "varmod-sysv.mk" line 216: while evaluating variable "word216" with value "word216": Unfinished modifier ('=' missing) -make: "varmod-sysv.mk" line 216: Malformed conditional (${word216:L:from${:D=}to}) +make: "varmod-sysv.mk" line 215: Unfinished modifier ('=' missing) + while evaluating variable "word216" with value "word216" word modifier result '' = "" suffix = "suffix" prefix = "prefix" pre-middle-suffix = "pre-middle-suffix" '' =NS "" suffix =NS "suffixNS" prefix =NS "prefixNS" pre-middle-suffix =NS "pre-middle-suffixNS" '' =% "" suffix =% "suffix%" prefix =% "prefix%" pre-middle-suffix =% "pre-middle-suffix%" '' =%NS "" suffix =%NS "suffix%NS" prefix =%NS "prefix%NS" pre-middle-suffix =%NS "pre-middle-suffix%NS" '' =NPre% "" suffix =NPre% "suffixNPre%" prefix =NPre% "prefixNPre%" pre-middle-suffix =NPre% "pre-middle-suffixNPre%" '' =NPre%NS "" suffix =NPre%NS "suffixNPre%NS" prefix =NPre%NS "prefixNPre%NS" pre-middle-suffix =NPre%NS "pre-middle-suffixNPre%NS" '' ffix= "" suffix ffix= "su" prefix ffix= "prefix" pre-middle-suffix ffix= "pre-middle-su" '' ffix=NS "" suffix ffix=NS "suNS" prefix ffix=NS "prefix" pre-middle-suffix ffix=NS "pre-middle-suNS" '' ffix=% "" suffix ffix=% "su%" prefix ffix=% "prefix" pre-middle-suffix ffix=% "pre-middle-su%" '' ffix=%NS "" suffix ffix=%NS "su%NS" prefix ffix=%NS "prefix" pre-middle-suffix ffix=%NS "pre-middle-su%NS" '' ffix=NPre% "" suffix ffix=NPre% "suNPre%" prefix ffix=NPre% "prefix" pre-middle-suffix ffix=NPre% "pre-middle-suNPre%" '' ffix=NPre%NS "" suffix ffix=NPre%NS "suNPre%NS" prefix ffix=NPre%NS "prefix" pre-middle-suffix ffix=NPre%NS "pre-middle-suNPre%NS" '' %= "" suffix %= "" prefix %= "" pre-middle-suffix %= "" '' %=NS "" suffix %=NS "NS" prefix %=NS "NS" pre-middle-suffix %=NS "NS" '' %=% "" suffix %=% "suffix" prefix %=% "prefix" pre-middle-suffix %=% "pre-middle-suffix" '' %=%NS "" suffix %=%NS "suffixNS" prefix %=%NS "prefixNS" pre-middle-suffix %=%NS "pre-middle-suffixNS" '' %=NPre% "" suffix %=NPre% "NPresuffix" prefix %=NPre% "NPreprefix" pre-middle-suffix %=NPre% "NPrepre-middle-suffix" '' %=NPre%NS "" suffix %=NPre%NS "NPresuffixNS" prefix %=NPre%NS "NPreprefixNS" pre-middle-suffix %=NPre%NS "NPrepre-middle-suffixNS" '' pre%= "" suffix pre%= "suffix" prefix pre%= "" pre-middle-suffix pre%= "" '' pre%=NS "" suffix pre%=NS "suffix" prefix pre%=NS "NS" pre-middle-suffix pre%=NS "NS" '' pre%=% "" suffix pre%=% "suffix" prefix pre%=% "fix" pre-middle-suffix pre%=% "-middle-suffix" '' pre%=%NS "" suffix pre%=%NS "suffix" prefix pre%=%NS "fixNS" pre-middle-suffix pre%=%NS "-middle-suffixNS" '' pre%=NPre% "" suffix pre%=NPre% "suffix" prefix pre%=NPre% "NPrefix" pre-middle-suffix pre%=NPre% "NPre-middle-suffix" '' pre%=NPre%NS "" suffix pre%=NPre%NS "suffix" prefix pre%=NPre%NS "NPrefixNS" pre-middle-suffix pre%=NPre%NS "NPre-middle-suffixNS" '' %ffix= "" suffix %ffix= "" prefix %ffix= "prefix" pre-middle-suffix %ffix= "" '' %ffix=NS "" suffix %ffix=NS "NS" prefix %ffix=NS "prefix" pre-middle-suffix %ffix=NS "NS" '' %ffix=% "" suffix %ffix=% "su" prefix %ffix=% "prefix" pre-middle-suffix %ffix=% "pre-middle-su" '' %ffix=%NS "" suffix %ffix=%NS "suNS" prefix %ffix=%NS "prefix" pre-middle-suffix %ffix=%NS "pre-middle-suNS" '' %ffix=NPre% "" suffix %ffix=NPre% "NPresu" prefix %ffix=NPre% "prefix" pre-middle-suffix %ffix=NPre% "NPrepre-middle-su" '' %ffix=NPre%NS "" suffix %ffix=NPre%NS "NPresuNS" prefix %ffix=NPre%NS "prefix" pre-middle-suffix %ffix=NPre%NS "NPrepre-middle-suNS" '' pre%ffix= "" suffix pre%ffix= "suffix" prefix pre%ffix= "prefix" pre-middle-suffix pre%ffix= "" '' pre%ffix=NS "" suffix pre%ffix=NS "suffix" prefix pre%ffix=NS "prefix" pre-middle-suffix pre%ffix=NS "NS" '' pre%ffix=% "" suffix pre%ffix=% "suffix" prefix pre%ffix=% "prefix" pre-middle-suffix pre%ffix=% "-middle-su" '' pre%ffix=%NS "" suffix pre%ffix=%NS "suffix" prefix pre%ffix=%NS "prefix" pre-middle-suffix pre%ffix=%NS "-middle-suNS" '' pre%ffix=NPre% "" suffix pre%ffix=NPre% "suffix" prefix pre%ffix=NPre% "prefix" pre-middle-suffix pre%ffix=NPre% "NPre-middle-su" '' pre%ffix=NPre%NS "" suffix pre%ffix=NPre%NS "suffix" prefix pre%ffix=NPre%NS "prefix" pre-middle-suffix pre%ffix=NPre%NS "NPre-middle-suNS" -make: "varmod-sysv.mk" line 261: while evaluating variable "error" with value "error": Unfinished modifier ('}' missing) -make: "varmod-sysv.mk" line 261: Malformed conditional (${error:L:from=$(})) +make: "varmod-sysv.mk" line 259: Unfinished modifier ('}' missing) + while evaluating variable "error" with value "error" make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-sysv.mk b/contrib/bmake/unit-tests/varmod-sysv.mk index 8db41d7fb364..f7d7a1ffe81a 100644 --- a/contrib/bmake/unit-tests/varmod-sysv.mk +++ b/contrib/bmake/unit-tests/varmod-sysv.mk @@ -1,265 +1,263 @@ -# $NetBSD: varmod-sysv.mk,v 1.19 2024/07/04 17:47:54 rillig Exp $ +# $NetBSD: varmod-sysv.mk,v 1.22 2025/01/11 20:54:46 rillig Exp $ # # Tests for the variable modifier ':from=to', which replaces the suffix # "from" with "to". It can also use '%' as a wildcard. # # This modifier is applied when the other modifiers don't match exactly. # # See ApplyModifier_SysV. # A typical use case for the modifier ':from=to' is conversion of filename # extensions. .if ${src.c:L:.c=.o} != "src.o" . error .endif # The modifier applies to each word on its own. .if ${one.c two.c three.c:L:.c=.o} != "one.o two.o three.o" . error .endif # Words that don't match the pattern are passed unmodified. .if ${src.c src.h:L:.c=.o} != "src.o src.h" . error .endif # The modifier ':from=to' is therefore often combined with the modifier ':M'. .if ${src.c src.h:L:M*.c:.c=.o} != "src.o" . error .endif # Another use case for the modifier ':from=to' is to append a suffix to each # word. In this case, the "from" string is empty, therefore it always # matches. The same effect can be achieved with the modifier ':S,$,teen,'. .if ${four six seven nine:L:=teen} != "fourteen sixteen seventeen nineteen" . error .endif # The modifier ':from=to' can also be used to surround each word by strings. # It might be tempting to use this for enclosing a string in quotes for the # shell, but that's the job of the modifier ':Q'. .if ${one two three:L:%=(%)} != "(one) (two) (three)" . error .endif # When the modifier ':from=to' is parsed, it lasts until the closing brace # or parenthesis. The ':Q' in the below expression may look like a modifier # but it isn't. It is part of the replacement string. .if ${a b c d e:L:%a=x:Q} != "x:Q b c d e" . error .endif # In the modifier ':from=to', both parts can contain expressions. .if ${one two:L:${:Uone}=${:U1}} != "1 two" . error .endif # In the modifier ':from=to', the "from" part is expanded exactly once. .if ${:U\$ \$\$ \$\$\$\$:${:U\$\$\$\$}=4} != "\$ \$\$ 4" . error .endif # In the modifier ':from=to', the "to" part is expanded exactly twice. # XXX: The right-hand side should be expanded only once. # XXX: It's hard to get the escaping correct here, and to read that. # XXX: It's not intuitive why the closing brace must be escaped but not # the opening brace. .if ${:U1 2 4:4=${:Uonce\${\:Utwice\}}} != "1 2 oncetwice" . error .endif # The replacement string can contain spaces, thereby changing the number # of words in the expression. .if ${In:L:%=% ${:Uthe Sun}} != "In the Sun" . error .endif # If the variable value is empty, it is debatable whether it consists of a # single empty word, or no word at all. The modifier ':from=to' treats it as # no word at all. # # See SysVMatch, which doesn't handle w_len == p_len specially. .if ${:L:=suffix} != "" . error .endif # If the variable value is empty, it is debatable whether it consists of a # single empty word (before 2020-05-06), or no word at all (since 2020-05-06). # # See SysVMatch, percent != NULL && w[0] == '\0'. .if ${:L:%=suffix} != "" . error .endif # Before 2020-07-19, an ampersand could be used in the replacement part # of a SysV substitution modifier, and it was replaced with the whole match, # just like in the modifier ':S'. # # This was probably a copy-and-paste mistake since the code for the SysV # modifier looked a lot like the code for the modifiers ':S' and ':C'. # The ampersand is not mentioned in the manual page. .if ${a.bcd.e:L:a.%=%} != "bcd.e" . error .endif # Before 2020-07-19, the result of the expression was "a.bcd.e". .if ${a.bcd.e:L:a.%=&} != "&" . error .endif # Before 2020-07-20, when a SysV modifier was parsed, a single dollar # before the '=' was parsed (but not interpreted) as an anchor. # Parsing something without then evaluating it accordingly doesn't make # sense, so this has been fixed. .if ${value:L:e$=x} != "value" . error .endif # Before 2020-07-20, the modifier ':e$=x' was parsed as having a left-hand # side 'e' and a right-hand side 'x'. The dollar was parsed (but not # interpreted) as 'anchor at the end'. Therefore the modifier was equivalent # to ':e=x', which doesn't match the string "value$". Therefore the whole # expression evaluated to "value$". .if ${${:Uvalue\$}:L:e$=x} != "valux" . error .endif .if ${value:L:e=x} != "valux" . error .endif # Words that don't match are copied unmodified. .if ${:Ufile.c file.h:%.c=%.cpp} != "file.cpp file.h" . error .endif # The % placeholder can be anywhere in the string, it doesn't have to be at # the beginning of the pattern. .if ${:Ufile.c other.c:file.%=renamed.%} != "renamed.c other.c" . error .endif # It's also possible to modify each word by replacing the prefix and adding # a suffix. .if ${one two:L:o%=a%w} != "anew two" . error .endif # Each word gets the suffix "X" appended. .if ${one two:L:=X} != "oneX twoX" . error .endif # The suffix "o" is replaced with "X". .if ${one two:L:o=X} != "one twX" . error .endif # The suffix "o" is replaced with nothing. .if ${one two:L:o=} != "one tw" . error .endif # The suffix "o" is replaced with a literal percent. The percent is only # a wildcard when it appears on the left-hand side. .if ${one two:L:o=%} != "one tw%" . error .endif # Each word with the suffix "o" is replaced with "X". The percent is a # wildcard even though the right-hand side does not contain another percent. .if ${one two:L:%o=X} != "one X" . error .endif # Each word with the prefix "o" is replaced with "X". The percent is a # wildcard even though the right-hand side does not contain another percent. .if ${one two:L:o%=X} != "X two" . error .endif # For each word with the prefix "o" and the suffix "e", the whole word is # replaced with "X". .if ${one two oe oxen:L:o%e=X} != "X two X oxen" . error .endif # Only the first '%' is the wildcard. .if ${one two o%e other%e:L:o%%e=X} != "one two X X" . error .endif # In the replacement, only the first '%' is the placeholder, all others # are literal percent characters. .if ${one two:L:%=%%} != "one% two%" . error .endif # In the word "one", only a prefix of the pattern suffix "nes" matches, # the whole word is too short. Therefore it doesn't match. .if ${one two:L:%nes=%xxx} != "one two" . error .endif # The modifier ':from=to' can be used to replace both the prefix and a suffix # of a word with other strings. This is not possible with a single :S # modifier, and using a :C modifier for the same task looks more complicated # in many cases. .if ${prefix-middle-suffix:L:prefix-%-suffix=p-%-s} != "p-middle-s" . error .endif # This is not a SysV modifier since the nested expression expands # to an empty string. The '=' in it should be irrelevant during parsing. # XXX: As of 2024-06-30, this expression generates an "Unfinished modifier" # error, while the correct error message would be "Unknown modifier" since # there is no modifier named "fromto". -# expect+2: while evaluating variable "word216" with value "word216": Unfinished modifier ('=' missing) -# expect+1: Malformed conditional (${word216:L:from${:D=}to}) +# expect+1: Unfinished modifier ('=' missing) .if ${word216:L:from${:D=}to} . error .endif # XXX: This specially constructed case demonstrates that the SysV modifier # lasts longer than expected. The whole expression initially has the value # "fromto}...". The next modifier is a SysV modifier. ApplyModifier_SysV # parses the modifier as "from${:D=}to", ending at the '}'. Next, the two # parts of the modifier are parsed using ParseModifierPart, which scans # differently, properly handling nested expressions. The two parts # are now "fromto}..." and "replaced". .if "${:Ufromto\}...:from${:D=}to}...=replaced}" != "replaced" . error .endif # As of 2020-10-06, the right-hand side of the SysV modifier is expanded # twice. The first expansion happens in ApplyModifier_SysV, where the # modifier is split into its two parts. The second expansion happens # when each word is replaced in ModifyWord_SYSVSubst. # XXX: This is unexpected. Add more test case to demonstrate the effects # of removing one of the expansions. VALUE= value INDIRECT= 1:${VALUE} 2:$${VALUE} 4:$$$${VALUE} .if ${x:L:x=${INDIRECT}} != "1:value 2:value 4:\${VALUE}" . error .endif # Test all relevant combinations of prefix, '%' and suffix in both the pattern # and the replacement. !=1>&2 printf '%-24s %-24s %-24s\n' 'word' 'modifier' 'result' .for from in '' ffix % pre% %ffix pre%ffix . for to in '' NS % %NS NPre% NPre%NS . for word in '' suffix prefix pre-middle-suffix . for mod in ${from:N''}=${to:N''} !=1>&2 printf '%-24s %-24s "%s"\n' ''${word:Q} ''${mod:Q} ''${word:N'':${mod}:Q} . endfor . endfor . endfor .endfor # The error case of an unfinished ':from=to' modifier after the '=' requires # an expression that is missing the closing '}'. -# expect+2: while evaluating variable "error" with value "error": Unfinished modifier ('}' missing) -# expect+1: Malformed conditional (${error:L:from=$(})) +# expect+1: Unfinished modifier ('}' missing) .if ${error:L:from=$(}) .endif all: diff --git a/contrib/bmake/unit-tests/varmod-to-separator.exp b/contrib/bmake/unit-tests/varmod-to-separator.exp index fe61ada505df..368def5f7ffe 100644 --- a/contrib/bmake/unit-tests/varmod-to-separator.exp +++ b/contrib/bmake/unit-tests/varmod-to-separator.exp @@ -1,29 +1,29 @@ -make: "varmod-to-separator.mk" line 155: while evaluating variable "WORDS" with value "one two three": Invalid character number at "400:tu}" -make: "varmod-to-separator.mk" line 155: Malformed conditional (${WORDS:[1..3]:ts\400:tu}) -make: "varmod-to-separator.mk" line 171: while evaluating variable "WORDS" with value "one two three": Invalid character number at "100:tu}" -make: "varmod-to-separator.mk" line 171: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) -make: "varmod-to-separator.mk" line 180: while evaluating variable "word" with value "word": Invalid character number at ",}" -make: "varmod-to-separator.mk" line 180: Malformed conditional (${word:L:ts\x,}) -make: "varmod-to-separator.mk" line 187: while evaluating variable "word" with value "word": Invalid character number at "112233445566778899}" -make: "varmod-to-separator.mk" line 187: Malformed conditional (${word:L:ts\x112233445566778899}) -make: "varmod-to-separator.mk" line 193: while evaluating variable "WORDS" with value "one two three": Bad modifier ":ts\-300" -make: "varmod-to-separator.mk" line 193: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) -make: "varmod-to-separator.mk" line 203: while evaluating variable "1 2 3" with value "1 2 3": Bad modifier ":ts\8" -make: "varmod-to-separator.mk" line 203: Malformed conditional (${1 2 3:L:ts\8:tu}) -make: "varmod-to-separator.mk" line 212: while evaluating variable "1 2 3" with value "1 2 3": Bad modifier ":ts\100L" -make: "varmod-to-separator.mk" line 212: Malformed conditional (${1 2 3:L:ts\100L}) -make: "varmod-to-separator.mk" line 221: while evaluating variable "1 2 3" with value "1 2 3": Bad modifier ":ts\x40g" -make: "varmod-to-separator.mk" line 221: Malformed conditional (${1 2 3:L:ts\x40g}) -make: "varmod-to-separator.mk" line 231: while evaluating variable "WORDS" with value "one two three four five six": Bad modifier ":tx" -make: "varmod-to-separator.mk" line 231: Malformed conditional (${WORDS:tx}) -make: "varmod-to-separator.mk" line 240: while evaluating variable "WORDS" with value "one two three four five six": Bad modifier ":ts\X" -make: "varmod-to-separator.mk" line 240: Malformed conditional (${WORDS:ts\X}) -make: "varmod-to-separator.mk" line 250: while evaluating variable "WORDS" with value "one two three four five six": Bad modifier ":t\X" -make: "varmod-to-separator.mk" line 250: Malformed conditional (${WORDS:t\X} != "anything") -make: "varmod-to-separator.mk" line 267: while evaluating "${:Ua b:ts\69}" with value "a b": Bad modifier ":ts\69" -make: "varmod-to-separator.mk" line 267: Malformed conditional (${:Ua b:ts\69}) -make: "varmod-to-separator.mk" line 276: while evaluating "${:Ua b:ts\x1F60E}" with value "a b": Invalid character number at "1F60E}" -make: "varmod-to-separator.mk" line 276: Malformed conditional (${:Ua b:ts\x1F60E}) +make: "varmod-to-separator.mk" line 154: Invalid character number at "400:tu}" + while evaluating variable "WORDS" with value "one two three" +make: "varmod-to-separator.mk" line 169: Invalid character number at "100:tu}" + while evaluating variable "WORDS" with value "one two three" +make: "varmod-to-separator.mk" line 177: Invalid character number at ",}" + while evaluating variable "word" with value "word" +make: "varmod-to-separator.mk" line 183: Invalid character number at "112233445566778899}" + while evaluating variable "word" with value "word" +make: "varmod-to-separator.mk" line 188: Bad modifier ":ts\-300" + while evaluating variable "WORDS" with value "one two three" +make: "varmod-to-separator.mk" line 197: Bad modifier ":ts\8" + while evaluating variable "1 2 3" with value "1 2 3" +make: "varmod-to-separator.mk" line 205: Bad modifier ":ts\100L" + while evaluating variable "1 2 3" with value "1 2 3" +make: "varmod-to-separator.mk" line 213: Bad modifier ":ts\x40g" + while evaluating variable "1 2 3" with value "1 2 3" +make: "varmod-to-separator.mk" line 222: Bad modifier ":tx" + while evaluating variable "WORDS" with value "one two three four five six" +make: "varmod-to-separator.mk" line 230: Bad modifier ":ts\X" + while evaluating variable "WORDS" with value "one two three four five six" +make: "varmod-to-separator.mk" line 239: Bad modifier ":t\X" + while evaluating variable "WORDS" with value "one two three four five six" +make: "varmod-to-separator.mk" line 255: Bad modifier ":ts\69" + while evaluating "${:Ua b:ts\69}" with value "a b" +make: "varmod-to-separator.mk" line 263: Invalid character number at "1F60E}" + while evaluating "${:Ua b:ts\x1F60E}" with value "a b" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod-to-separator.mk b/contrib/bmake/unit-tests/varmod-to-separator.mk index b4c6839a0a2b..5cb838bf5bb7 100644 --- a/contrib/bmake/unit-tests/varmod-to-separator.mk +++ b/contrib/bmake/unit-tests/varmod-to-separator.mk @@ -1,280 +1,267 @@ -# $NetBSD: varmod-to-separator.mk,v 1.18 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.21 2025/01/11 20:54:46 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. WORDS= one two three four five six # The words are separated by a single space, just as usual. .if ${WORDS:ts } != "one two three four five six" . warning Space as separator does not work. .endif # The separator can be an arbitrary character, for example a comma. .if ${WORDS:ts,} != "one,two,three,four,five,six" . warning Comma as separator does not work. .endif # After the :ts modifier, other modifiers can follow. .if ${WORDS:ts/:tu} != "ONE/TWO/THREE/FOUR/FIVE/SIX" . warning Chaining modifiers does not work. .endif # To use the ':' as the separator, just write it normally. # The first colon is the separator, the second ends the modifier. .if ${WORDS:ts::tu} != "ONE:TWO:THREE:FOUR:FIVE:SIX" . warning Colon as separator does not work. .endif # When there is just a colon but no other character, the words are # "separated" by an empty string, that is, they are all squashed # together. .if ${WORDS:ts:tu} != "ONETWOTHREEFOURFIVESIX" . warning Colon as separator does not work. .endif # Applying the :tu modifier first and then the :ts modifier does not change # anything since neither of these modifiers is related to how the string is # split into words. Beware of separating the words using a single or double # quote though, or other special characters like dollar or backslash. # # This example also demonstrates that the closing brace is not interpreted # as a separator, but as the closing delimiter of the whole # expression. .if ${WORDS:tu:ts} != "ONETWOTHREEFOURFIVESIX" . warning Colon as separator does not work. .endif # The '}' plays the same role as the ':' in the preceding examples. # Since there is a single character before it, that character is taken as # the separator. .if ${WORDS:tu:ts/} != "ONE/TWO/THREE/FOUR/FIVE/SIX" . warning Colon as separator does not work. .endif # Now it gets interesting and ambiguous: The separator could either be empty # since it is followed by a colon. Or it could be the colon since that # colon is followed by the closing brace. It's the latter case. .if ${WORDS:ts:} != "one:two:three:four:five:six" . warning Colon followed by closing brace does not work. .endif # As in the ${WORDS:tu:ts} example above, the separator is empty. .if ${WORDS:ts} != "onetwothreefourfivesix" . warning Empty separator before closing brace does not work. .endif # The :ts modifier can be followed by other modifiers. .if ${WORDS:ts:S/two/2/} != "one2threefourfivesix" . warning Separator followed by :S modifier does not work. .endif # The :ts modifier can follow other modifiers. .if ${WORDS:S/two/2/:ts} != "one2threefourfivesix" . warning :S modifier followed by :ts modifier does not work. .endif # The :ts modifier with an actual separator can be followed by other # modifiers. .if ${WORDS:ts/:S/two/2/} != "one/2/three/four/five/six" . warning The :ts modifier followed by an :S modifier does not work. .endif # After the modifier ':ts/', the expression value is a single word since all # spaces have been replaced with '/'. This single word does not start with # 'two', which makes the modifier ':S' a no-op. .if ${WORDS:ts/:S/^two/2/} != "one/two/three/four/five/six" . error .endif # After the :ts modifier, the whole string is interpreted as a single # word since all spaces have been replaced with x. Because of this single # word, only the first 'b' is replaced with 'B'. .if ${aa bb aa bb aa bb:L:tsx:S,b,B,} != "aaxBbxaaxbbxaaxbb" . error .endif # The :ts modifier also applies to word separators that are added # afterwards. First, the modifier ':tsx' joins the 3 words, then the modifier # ':S' replaces the 2 'b's with spaces. These spaces are part of the word, # so when the words are joined at the end of the modifier ':S', there is only # a single word, and the custom separator from the modifier ':tsx' has no # effect. .if ${a ababa c:L:tsx:S,b, ,g} != "axa a axc" . error .endif # Adding the modifier ':M*' at the end of the above chain splits the # expression value and then joins it again. At this point of splitting, the # newly added spaces are treated as word separators, resulting in 3 words. # When these 3 words are joined, the separator from the modifier ':tsx' is # used. .if ${a ababa c:L:tsx:S,b, ,g:M*} != "axaxaxaxc" . error .endif # Not all modifiers use the separator from the previous modifier ':ts' though. # The modifier ':@' always uses a space as word separator instead. This has # probably been an oversight during implementation. For consistency, the # result should rather be "axaxaxaxc", as in the previous example. .if ${a ababa c:L:tsx:S,b, ,g:@v@$v@} != "axa a axc" . error .endif # Adding a final :M* modifier applies the :ts separator again, though. .if ${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*} != "axaxaxaxc" . error .endif # The separator can be \n, which is a newline. .if ${WORDS:[1..3]:ts\n} != "one${.newline}two${.newline}three" . warning The separator \n does not produce a newline. .endif # The separator can be \t, which is a tab. .if ${WORDS:[1..3]:ts\t} != "one two three" . warning The separator \t does not produce a tab. .endif # The separator can be given as octal number. .if ${WORDS:[1..3]:ts\012:tu} != "ONE${.newline}TWO${.newline}THREE" . warning The separator \012 is not interpreted in octal ASCII. .endif # The octal number can have as many digits as it wants. .if ${WORDS:[1..2]:ts\000000000000000000000000012:tu} != "ONE${.newline}TWO" . warning The separator \012 cannot have many leading zeroes. .endif # The value of the separator character must not be outside the value space # for an unsigned character though. # # Since 2020-11-01, these out-of-bounds values are rejected. -# expect+2: while evaluating variable "WORDS" with value "one two three": Invalid character number at "400:tu}" -# expect+1: Malformed conditional (${WORDS:[1..3]:ts\400:tu}) +# expect+1: Invalid character number at "400:tu}" .if ${WORDS:[1..3]:ts\400:tu} . warning The separator \400 is accepted even though it is out of bounds. .else . warning The separator \400 is accepted even though it is out of bounds. .endif # The separator can be given as hexadecimal number. .if ${WORDS:[1..3]:ts\xa:tu} != "ONE${.newline}TWO${.newline}THREE" . warning The separator \xa is not interpreted in hexadecimal ASCII. .endif # The hexadecimal number must be in the range of an unsigned char. # # Since 2020-11-01, these out-of-bounds values are rejected. -# expect+2: while evaluating variable "WORDS" with value "one two three": Invalid character number at "100:tu}" -# expect+1: Malformed conditional (${WORDS:[1..3]:ts\x100:tu}) +# expect+1: Invalid character number at "100:tu}" .if ${WORDS:[1..3]:ts\x100:tu} . warning The separator \x100 is accepted even though it is out of bounds. .else . warning The separator \x100 is accepted even though it is out of bounds. .endif # The number after ':ts\x' must be hexadecimal. -# expect+2: while evaluating variable "word" with value "word": Invalid character number at ",}" -# expect+1: Malformed conditional (${word:L:ts\x,}) +# expect+1: Invalid character number at ",}" .if ${word:L:ts\x,} .endif # The hexadecimal number must be in the range of 'unsigned long' on all # supported platforms. -# expect+2: while evaluating variable "word" with value "word": Invalid character number at "112233445566778899}" -# expect+1: Malformed conditional (${word:L:ts\x112233445566778899}) +# expect+1: Invalid character number at "112233445566778899}" .if ${word:L:ts\x112233445566778899} .endif # Negative numbers are not allowed for the separator character. -# expect+2: while evaluating variable "WORDS" with value "one two three": Bad modifier ":ts\-300" -# expect+1: Malformed conditional (${WORDS:[1..3]:ts\-300:tu}) +# expect+1: Bad modifier ":ts\-300" .if ${WORDS:[1..3]:ts\-300:tu} . warning The separator \-300 is accepted even though it is negative. .else . warning The separator \-300 is accepted even though it is negative. .endif # The character number is interpreted as octal number by default. # The digit '8' is not an octal digit though. -# expect+2: while evaluating variable "1 2 3" with value "1 2 3": Bad modifier ":ts\8" -# expect+1: Malformed conditional (${1 2 3:L:ts\8:tu}) +# expect+1: Bad modifier ":ts\8" .if ${1 2 3:L:ts\8:tu} . warning The separator \8 is accepted even though it is not octal. .else . warning The separator \8 is accepted even though it is not octal. .endif # Trailing characters after the octal character number are rejected. -# expect+2: while evaluating variable "1 2 3" with value "1 2 3": Bad modifier ":ts\100L" -# expect+1: Malformed conditional (${1 2 3:L:ts\100L}) +# expect+1: Bad modifier ":ts\100L" .if ${1 2 3:L:ts\100L} . warning The separator \100L is accepted even though it contains an 'L'. .else . warning The separator \100L is accepted even though it contains an 'L'. .endif # Trailing characters after the hexadecimal character number are rejected. -# expect+2: while evaluating variable "1 2 3" with value "1 2 3": Bad modifier ":ts\x40g" -# expect+1: Malformed conditional (${1 2 3:L:ts\x40g}) +# expect+1: Bad modifier ":ts\x40g" .if ${1 2 3:L:ts\x40g} . warning The separator \x40g is accepted even though it contains a 'g'. .else . warning The separator \x40g is accepted even though it contains a 'g'. .endif # In the :t modifier, the :t must be followed by any of A, l, s, u. -# expect+2: while evaluating variable "WORDS" with value "one two three four five six": Bad modifier ":tx" -# expect+1: Malformed conditional (${WORDS:tx}) +# expect+1: Bad modifier ":tx" .if ${WORDS:tx} . error .else . error .endif # The word separator can only be a single character. -# expect+2: while evaluating variable "WORDS" with value "one two three four five six": Bad modifier ":ts\X" -# expect+1: Malformed conditional (${WORDS:ts\X}) +# expect+1: Bad modifier ":ts\X" .if ${WORDS:ts\X} . error .else . error .endif # After the backslash, only n, t, an octal number, or x and a hexadecimal # number are allowed. -# expect+2: while evaluating variable "WORDS" with value "one two three four five six": Bad modifier ":t\X" -# expect+1: Malformed conditional (${WORDS:t\X} != "anything") +# expect+1: Bad modifier ":t\X" .if ${WORDS:t\X} != "anything" . info This line is not reached. .endif # Since 2003.07.23.18.06.46 and before 2016.03.07.20.20.35, the modifier ':ts' # interpreted an "octal escape" as decimal if the first digit was not '0'. .if ${:Ua b:ts\61} != "a1b" # decimal would have been "a=b" . error .endif # Since the character escape is always interpreted as octal, let's see what # happens for non-octal digits. From 2003.07.23.18.06.46 to # 2016.02.27.16.20.06, the result was '1E2', since 2016.03.07.20.20.35 make no # longer accepts this escape and complains. -# expect+2: while evaluating "${:Ua b:ts\69}" with value "a b": Bad modifier ":ts\69" -# expect+1: Malformed conditional (${:Ua b:ts\69}) +# expect+1: Bad modifier ":ts\69" .if ${:Ua b:ts\69} . error .else . error .endif # Try whether bmake is Unicode-ready. -# expect+2: while evaluating "${:Ua b:ts\x1F60E}" with value "a b": Invalid character number at "1F60E}" -# expect+1: Malformed conditional (${:Ua b:ts\x1F60E}) +# expect+1: Invalid character number at "1F60E}" .if ${:Ua b:ts\x1F60E} # U+1F60E "smiling face with sunglasses" . error .else . error .endif diff --git a/contrib/bmake/unit-tests/varmod.exp b/contrib/bmake/unit-tests/varmod.exp index 0948b585a477..91968a7f310f 100644 --- a/contrib/bmake/unit-tests/varmod.exp +++ b/contrib/bmake/unit-tests/varmod.exp @@ -1,33 +1,44 @@ make: "varmod.mk" line 101: To escape a dollar, use \$, not $$, at "$$:L} != """ make: "varmod.mk" line 101: Invalid variable name ':', at "$:L} != """ -make: "varmod.mk" line 107: while evaluating "${:Uword:@word@${word}$@} != "word"" with value "word": Dollar followed by nothing -make: "varmod.mk" line 117: while evaluating variable "VAR" with value "VAR": Missing delimiter ':' after modifier "P" +make: "varmod.mk" line 107: Dollar followed by nothing + while evaluating "${:Uword:@word@${word}$@} != "word"" with value "word" +make: "varmod.mk" line 117: Missing delimiter ':' after modifier "P" + while evaluating variable "VAR" with value "VAR" make: "varmod.mk" line 119: Missing argument for ".error" -make: "varmod.mk" line 126: while evaluating variable "word" with value "word": Bad modifier ":[99333000222000111000]" -make: "varmod.mk" line 126: Malformed conditional (${word:L:[99333000222000111000]}) -make: "varmod.mk" line 130: while evaluating variable "word" with value "word": Bad modifier ":[2147483648]" -make: "varmod.mk" line 130: Malformed conditional (${word:L:[2147483648]}) -make: "varmod.mk" line 137: while evaluating variable "word" with value "word": Invalid number "99333000222000111000}" for ':range' modifier -make: "varmod.mk" line 137: Malformed conditional (${word:L:range=99333000222000111000}) -make: "varmod.mk" line 145: while evaluating "${:${:Ugmtime=\\}}" with value "": Invalid time value "\" -make: "varmod.mk" line 145: Malformed conditional (${:${:Ugmtime=\\}}) -make: "varmod.mk" line 160: while evaluating variable "VAR" with value "value$": Dollar followed by nothing -make: "varmod.mk" line 166: while evaluating variable "VAR" with value "value$": Dollar followed by nothing -make: "varmod.mk" line 166: while evaluating variable "VAR" with value "value$ appended$": Dollar followed by nothing -make: "varmod.mk" line 176: while evaluating variable "word" with value "word": Dollar followed by nothing -make: "varmod.mk" line 181: while evaluating variable "word" with value "": Bad modifier ":[$]" -make: "varmod.mk" line 181: Malformed conditional (${word:[$]}) -make: "varmod.mk" line 198: while evaluating variable "VAR" with value "value$ appended$": Dollar followed by nothing -make: "varmod.mk" line 198: while evaluating variable "VAR" with value "valueappended": Invalid variable name '}', at "$} != "set"" -make: "varmod.mk" line 202: while evaluating "${:Ufallback$} != "fallback"" with value "": Invalid variable name '}', at "$} != "fallback"" -make: "varmod.mk" line 207: while evaluating variable "%y" with value "%y": Invalid time value "1000$" -make: "varmod.mk" line 207: Malformed conditional (${%y:L:gmtime=1000$}) -make: "varmod.mk" line 214: while evaluating variable "%y" with value "%y": Invalid time value "1000$" -make: "varmod.mk" line 214: Malformed conditional (${%y:L:localtime=1000$}) -make: "varmod.mk" line 220: while evaluating variable "word" with value "word": Dollar followed by nothing -make: "varmod.mk" line 224: while evaluating variable "word" with value "word": Dollar followed by nothing -make: "varmod.mk" line 229: while evaluating variable "." with value ".": Invalid argument 'fallback$' for modifier ':mtime' -make: "varmod.mk" line 229: Malformed conditional (${.:L:mtime=fallback$}) +make: "varmod.mk" line 125: Bad modifier ":[99333000222000111000]" + while evaluating variable "word" with value "word" +make: "varmod.mk" line 128: Bad modifier ":[2147483648]" + while evaluating variable "word" with value "word" +make: "varmod.mk" line 134: Invalid number "99333000222000111000}" for ':range' modifier + while evaluating variable "word" with value "word" +make: "varmod.mk" line 141: Invalid time value "\" + while evaluating "${:${:Ugmtime=\\}}" with value "" +make: "varmod.mk" line 156: Dollar followed by nothing + while evaluating variable "VAR" with value "value$" +make: "varmod.mk" line 162: Dollar followed by nothing + while evaluating variable "VAR" with value "value$" +make: "varmod.mk" line 162: Dollar followed by nothing + while evaluating variable "VAR" with value "value$ appended$" +make: "varmod.mk" line 172: Dollar followed by nothing + while evaluating variable "word" with value "word" +make: "varmod.mk" line 176: Bad modifier ":[$]" + while evaluating variable "word" with value "" +make: "varmod.mk" line 193: Dollar followed by nothing + while evaluating variable "VAR" with value "value$ appended$" +make: "varmod.mk" line 193: Invalid variable name '}', at "$} != "set"" + while evaluating variable "VAR" with value "valueappended" +make: "varmod.mk" line 197: Invalid variable name '}', at "$} != "fallback"" + while evaluating "${:Ufallback$} != "fallback"" with value "" +make: "varmod.mk" line 201: Invalid time value "1000$" + while evaluating variable "%y" with value "%y" +make: "varmod.mk" line 207: Invalid time value "1000$" + while evaluating variable "%y" with value "%y" +make: "varmod.mk" line 213: Dollar followed by nothing + while evaluating variable "word" with value "word" +make: "varmod.mk" line 217: Dollar followed by nothing + while evaluating variable "word" with value "word" +make: "varmod.mk" line 221: Invalid argument 'fallback$' for modifier ':mtime' + while evaluating variable "." with value "." make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varmod.mk b/contrib/bmake/unit-tests/varmod.mk index fbb60d80680f..e0398965229b 100644 --- a/contrib/bmake/unit-tests/varmod.mk +++ b/contrib/bmake/unit-tests/varmod.mk @@ -1,239 +1,231 @@ -# $NetBSD: varmod.mk,v 1.18 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varmod.mk,v 1.22 2025/01/11 20:54:46 rillig Exp $ # # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback. # # See also: # varparse-errors.mk # As of 2024-06-05, the possible behaviors during parsing are: # # * `strict`: the parsing style used by most modifiers: # * either uses `ParseModifierPart` or parses the modifier literal # * other modifiers may follow, separated by a ':' # # * `greedy`: calls `ParseModifierPart` with `ch->endc`; this means # that no further modifiers are parsed in that expression. # # * `no-colon`: after parsing this modifier, the following modifier # does not need to be separated by a colon. # Omitting this colon is bad style. # # * `individual`: parsing this modifier does not follow the common # pattern of calling `ParseModifierPart`. # # The SysV column says whether a parse error in the modifier falls back # trying the `:from=to` System V modifier. # # | **Operator** | **Behavior** | **Remarks** | **SysV** | # |--------------|--------------|--------------------|----------| # | `!` | no-colon | | no | # | `:=` | greedy | | yes | # | `?:` | greedy | | no | # | `@` | no-colon | | no | # | `C` | no-colon | | no | # | `D` | individual | custom parser | N/A | # | `E` | strict | | yes | # | `H` | strict | | yes | # | `L` | no-colon | | N/A | # | `M` | individual | custom parser | N/A | # | `N` | individual | custom parser | N/A | # | `O` | strict | only literal value | no | # | `P` | no-colon | | N/A | # | `Q` | strict | | yes | # | `R` | strict | | yes | # | `S` | no-colon | | N/A | # | `T` | strict | | N/A | # | `U` | individual | custom parser | N/A | # | `[` | strict | | no | # | `_` | individual | strcspn | yes | # | `gmtime` | strict | | yes | # | `hash` | strict | | N/A | # | `localtime` | strict | | yes | # | `q` | strict | | yes | # | `range` | strict | | N/A | # | `sh` | strict | | N/A | # | `t` | strict | | no | # | `u` | strict | | yes | # | `from=to` | greedy | SysV, fallback | N/A | # These tests assume .MAKE.SAVE_DOLLARS = yes DOLLAR1= $$ DOLLAR2= ${:U\$} # To get a single '$' sign in the value of an expression, it has to # be written as '$$' in a literal variable value. # # See Var_Parse, where it calls Var_Subst. .if ${DOLLAR1} != "\$" . error .endif # Another way to get a single '$' sign is to use the :U modifier. In the # argument of that modifier, a '$' is escaped using the backslash instead. # # See Var_Parse, where it calls Var_Subst. .if ${DOLLAR2} != "\$" . error .endif # It is also possible to use the :U modifier directly in the expression. # # See Var_Parse, where it calls Var_Subst. .if ${:U\$} != "\$" . error .endif # XXX: As of 2020-09-13, it is not possible to use '$$' in a variable name # to mean a single '$'. This contradicts the manual page, which says that # '$' can be escaped as '$$'. .if ${$$:L} != "" . error .endif # In lint mode, make prints helpful error messages. # For compatibility, make does not print these error messages in normal mode. # Should it? .MAKEFLAGS: -dL # expect+2: To escape a dollar, use \$, not $$, at "$$:L} != """ # expect+1: Invalid variable name ':', at "$:L} != """ .if ${$$:L} != "" . error .endif # A '$' followed by nothing is an error as well. -# expect+1: while evaluating "${:Uword:@word@${word}$@} != "word"" with value "word": Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${:Uword:@word@${word}$@} != "word" . error .endif # The variable modifier :P does not fall back to the SysV modifier. # Therefore the modifier :P=RE generates a parse error. # XXX: The .error should not be reached since the expression is # malformed, and this error should be propagated up to Cond_EvalLine. VAR= STOP -# expect+1: while evaluating variable "VAR" with value "VAR": Missing delimiter ':' after modifier "P" +# expect+1: Missing delimiter ':' after modifier "P" .if ${VAR:P=RE} != "STORE" # expect+1: Missing argument for ".error" . error .endif # Test the word selection modifier ':[n]' with a very large number that is # larger than ULONG_MAX for any supported platform. -# expect+2: while evaluating variable "word" with value "word": Bad modifier ":[99333000222000111000]" -# expect+1: Malformed conditional (${word:L:[99333000222000111000]}) +# expect+1: Bad modifier ":[99333000222000111000]" .if ${word:L:[99333000222000111000]} .endif -# expect+2: while evaluating variable "word" with value "word": Bad modifier ":[2147483648]" -# expect+1: Malformed conditional (${word:L:[2147483648]}) +# expect+1: Bad modifier ":[2147483648]" .if ${word:L:[2147483648]} .endif # Test the range generation modifier ':range=n' with a very large number that # is larger than SIZE_MAX for any supported platform. -# expect+2: Malformed conditional (${word:L:range=99333000222000111000}) -# expect+1: while evaluating variable "word" with value "word": Invalid number "99333000222000111000}" for ':range' modifier +# expect+1: Invalid number "99333000222000111000}" for ':range' modifier .if ${word:L:range=99333000222000111000} .endif # In an indirect modifier, the delimiter is '\0', which at the same time marks # the end of the string. The sequence '\\' '\0' is not an escaped delimiter, # as it would be wrong to skip past the end of the string. -# expect+2: while evaluating "${:${:Ugmtime=\\}}" with value "": Invalid time value "\" -# expect+1: Malformed conditional (${:${:Ugmtime=\\}}) +# expect+1: Invalid time value "\" .if ${:${:Ugmtime=\\}} . error .endif # Test a '$' at the end of a modifier part, for all modifiers in the order # listed in ApplyModifier. # # The only modifier parts where an unescaped '$' makes sense at the end are # the 'from' parts of the ':S' and ':C' modifiers. In all other modifier # parts, an unescaped '$' is an undocumented and discouraged edge case, as it # means the same as an escaped '$'. .if ${:U:!printf '%s\n' $!} != "\$" . error .endif -# expect+1: while evaluating variable "VAR" with value "value$": Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${VAR::=value$} != "" || ${VAR} != "value" . error .endif ${:U }= -# expect+2: while evaluating variable "VAR" with value "value$": Dollar followed by nothing -# expect+1: while evaluating variable "VAR" with value "value$ appended$": Dollar followed by nothing +# expect+2: Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${VAR::+=appended$} != "" || ${VAR} != "valueappended" . error .endif .if ${1:?then$:else$} != "then\$" . error .endif .if ${0:?then$:else$} != "else\$" . error .endif -# expect+1: while evaluating variable "word" with value "word": Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${word:L:@w@$w$@} != "word" . error .endif -# expect+2: while evaluating variable "word" with value "": Bad modifier ":[$]" -# expect+1: Malformed conditional (${word:[$]}) +# expect+1: Bad modifier ":[$]" .if ${word:[$]} . error .else . error .endif VAR_DOLLAR= VAR$$ .if ${word:L:_=VAR$} != "word" || ${${VAR_DOLLAR}} != "word" . error .endif .if ${word:L:C,d$,m,} != "worm" . error .endif .if ${word:L:C,d,$,} != "wor\$" . error .endif -# expect+2: while evaluating variable "VAR" with value "value$ appended$": Dollar followed by nothing -# expect+1: while evaluating variable "VAR" with value "valueappended": Invalid variable name '}', at "$} != "set"" +# expect+2: Dollar followed by nothing +# expect+1: Invalid variable name '}', at "$} != "set"" .if ${VAR:Dset$} != "set" . error .endif -# expect+1: while evaluating "${:Ufallback$} != "fallback"" with value "": Invalid variable name '}', at "$} != "fallback"" +# expect+1: Invalid variable name '}', at "$} != "fallback"" .if ${:Ufallback$} != "fallback" . error .endif -# expect+2: Malformed conditional (${%y:L:gmtime=1000$}) -# expect+1: while evaluating variable "%y" with value "%y": Invalid time value "1000$" +# expect+1: Invalid time value "1000$" .if ${%y:L:gmtime=1000$} . error .else . error .endif -# expect+2: Malformed conditional (${%y:L:localtime=1000$}) -# expect+1: while evaluating variable "%y" with value "%y": Invalid time value "1000$" +# expect+1: Invalid time value "1000$" .if ${%y:L:localtime=1000$} . error .else . error .endif -# expect+1: while evaluating variable "word" with value "word": Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${word:L:Mw*$} != "word" . error .endif -# expect+1: while evaluating variable "word" with value "word": Dollar followed by nothing +# expect+1: Dollar followed by nothing .if ${word:L:NX*$} != "word" . error .endif -# expect+2: while evaluating variable "." with value ".": Invalid argument 'fallback$' for modifier ':mtime' -# expect+1: Malformed conditional (${.:L:mtime=fallback$}) +# expect+1: Invalid argument 'fallback$' for modifier ':mtime' .if ${.:L:mtime=fallback$} . error .else . error .endif .if ${word:L:S,d$,m,} != "worm" . error .endif .if ${word:L:S,d,m$,} != "worm\$" . error .endif diff --git a/contrib/bmake/unit-tests/varname-dot-make-level.exp b/contrib/bmake/unit-tests/varname-dot-make-level.exp index 963b6a3a4f96..005bc85bed0f 100644 --- a/contrib/bmake/unit-tests/varname-dot-make-level.exp +++ b/contrib/bmake/unit-tests/varname-dot-make-level.exp @@ -1,4 +1,12 @@ level 1: variable 0, env 1 level 2: variable 1, env 2 level 3: variable 2, env 3 -exit status 0 +make: Cannot override read-only global variable ".MAKE.LEVEL.ENV" with a command line variable +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped making "all" in unit-tests +exit status 1 diff --git a/contrib/bmake/unit-tests/varname-dot-make-level.mk b/contrib/bmake/unit-tests/varname-dot-make-level.mk index 246890b23171..c4b6dc90028e 100644 --- a/contrib/bmake/unit-tests/varname-dot-make-level.mk +++ b/contrib/bmake/unit-tests/varname-dot-make-level.mk @@ -1,22 +1,29 @@ -# $NetBSD: varname-dot-make-level.mk,v 1.3 2024/06/01 18:44:05 rillig Exp $ +# $NetBSD: varname-dot-make-level.mk,v 1.5 2024/11/23 22:59:51 rillig Exp $ # # Tests for the special .MAKE.LEVEL variable, which informs about the # recursion level. It is related to the environment variable MAKELEVEL, # even though they don't have the same value. +all: level_1 set-env + level_1: .PHONY @printf 'level 1: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}" @${MAKE} -f ${MAKEFILE} level_2 level_2: .PHONY @printf 'level 2: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}" @${MAKE} -f ${MAKEFILE} level_3 level_3: .PHONY @printf 'level 3: variable %s, env %s\n' ${.MAKE.LEVEL} "$$${.MAKE.LEVEL.ENV}" # The .unexport-env directive clears the environment, except for the # MAKE_LEVEL variable. .if make(level_2) .unexport-env .endif + + +# expect: make: Cannot override read-only global variable ".MAKE.LEVEL.ENV" with a command line variable +set-env: + @${MAKE} -f /dev/null .MAKE.LEVEL.ENV=MAKELEVEL diff --git a/contrib/bmake/unit-tests/varname-dot-shell.exp b/contrib/bmake/unit-tests/varname-dot-shell.exp index 7d481fdf6ff1..7cc17435895b 100755 --- a/contrib/bmake/unit-tests/varname-dot-shell.exp +++ b/contrib/bmake/unit-tests/varname-dot-shell.exp @@ -1,35 +1,35 @@ -Parsing line 10: ORIG_SHELL:= ${.SHELL} +Parsing varname-dot-shell.mk:10: ORIG_SHELL:= ${.SHELL} Global: ORIG_SHELL = # (empty) Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined) Global: ignoring delete '.SHELL' as it is not found Command: .SHELL = (details omitted) Global: ORIG_SHELL = (details omitted) -Parsing line 12: .SHELL= overwritten +Parsing varname-dot-shell.mk:12: .SHELL= overwritten Global: ignoring '.SHELL = overwritten' due to a command line variable of the same name -Parsing line 13: .if ${.SHELL} != ${ORIG_SHELL} +Parsing varname-dot-shell.mk:13: .if ${.SHELL} != ${ORIG_SHELL} CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} -Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) -Var_Parse: ${ORIG_SHELL} (eval-defined) +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined-loud) +Var_Parse: ${ORIG_SHELL} (eval-defined-loud) Comparing "(details omitted)" != "(details omitted)" -Parsing line 19: .MAKEFLAGS: .SHELL+=appended +Parsing varname-dot-shell.mk:19: .MAKEFLAGS: .SHELL+=appended ParseDependency(.MAKEFLAGS: .SHELL+=appended) Command: ignoring '.SHELL += appended' as it is read-only -Parsing line 20: .if ${.SHELL} != ${ORIG_SHELL} +Parsing varname-dot-shell.mk:20: .if ${.SHELL} != ${ORIG_SHELL} CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} -Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) -Var_Parse: ${ORIG_SHELL} (eval-defined) +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined-loud) +Var_Parse: ${ORIG_SHELL} (eval-defined-loud) Comparing "(details omitted)" != "(details omitted)" -Parsing line 27: .undef .SHELL +Parsing varname-dot-shell.mk:27: .undef .SHELL Global: ignoring delete '.SHELL' as it is not found -Parsing line 28: .SHELL= newly overwritten +Parsing varname-dot-shell.mk:28: .SHELL= newly overwritten Global: ignoring '.SHELL = newly overwritten' due to a command line variable of the same name -Parsing line 29: .if ${.SHELL} != ${ORIG_SHELL} +Parsing varname-dot-shell.mk:29: .if ${.SHELL} != ${ORIG_SHELL} CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} -Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) -Var_Parse: ${ORIG_SHELL} (eval-defined) +Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined-loud) +Var_Parse: ${ORIG_SHELL} (eval-defined-loud) Comparing "(details omitted)" != "(details omitted)" -Parsing line 33: .MAKEFLAGS: -d0 +Parsing varname-dot-shell.mk:33: .MAKEFLAGS: -d0 ParseDependency(.MAKEFLAGS: -d0) Global: .MAKEFLAGS = -r -k -d cpv -d Global: .MAKEFLAGS = -r -k -d cpv -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/varname-dot-suffixes.exp b/contrib/bmake/unit-tests/varname-dot-suffixes.exp index 230ba36d56ed..96f68d245d54 100644 --- a/contrib/bmake/unit-tests/varname-dot-suffixes.exp +++ b/contrib/bmake/unit-tests/varname-dot-suffixes.exp @@ -1,39 +1,39 @@ Global: ignoring delete '.SUFFIXES' as it is not found Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 Global: ignoring '.SUFFIXES = set' as it is read-only Global: ignoring '.SUFFIXES = append' as it is read-only Global: _ = # (empty) Var_Parse: ${.SUFFIXES::=assign} (eval-keep-dollar-and-undefined) Evaluating modifier ${.SUFFIXES::...} on value ".c .o .1 .err .tar.gz" (eval-keep-dollar-and-undefined, regular) Modifier part: "assign" Global: ignoring '.SUFFIXES = assign' as it is read-only Result of ${.SUFFIXES::=assign} is "" (eval-keep-dollar-and-undefined, regular) Global: _ = # (empty) Var_Parse: ${preserve:L:_=.SUFFIXES} (eval-keep-dollar-and-undefined) Evaluating modifier ${preserve:L} on value "" (eval-keep-dollar-and-undefined, undefined) Result of ${preserve:L} is "preserve" (eval-keep-dollar-and-undefined, defined) Evaluating modifier ${preserve:_...} on value "preserve" (eval-keep-dollar-and-undefined, defined) Global: ignoring '.SUFFIXES = preserve' as it is read-only Result of ${preserve:_=.SUFFIXES} is "preserve" (eval-keep-dollar-and-undefined, defined) Global: _ = preserve Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -Var_Parse: ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined) -Evaluating modifier ${1 2:L} on value "" (eval-defined, undefined) -Result of ${1 2:L} is "1 2" (eval-defined, defined) -Evaluating modifier ${1 2:@...} on value "1 2" (eval-defined, defined) +Var_Parse: ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined-loud) +Evaluating modifier ${1 2:L} on value "" (eval, undefined) +Result of ${1 2:L} is "1 2" (eval, defined) +Evaluating modifier ${1 2:@...} on value "1 2" (eval, defined) Modifier part: ".SUFFIXES" Modifier part: "${.SUFFIXES}" ModifyWords: split "1 2" into 2 words Command: ignoring '.SUFFIXES = 1' as it is read-only -Var_Parse: ${.SUFFIXES} (eval-defined) +Var_Parse: ${.SUFFIXES} (eval) ModifyWord_Loop: expand "${.SUFFIXES}" to ".c .o .1 .err .tar.gz" Command: ignoring '.SUFFIXES = 2' as it is read-only -Var_Parse: ${.SUFFIXES} (eval-defined) +Var_Parse: ${.SUFFIXES} (eval) ModifyWord_Loop: expand "${.SUFFIXES}" to ".c .o .1 .err .tar.gz" Command: ignoring delete '.SUFFIXES' as it is not found -Result of ${1 2:@.SUFFIXES@${.SUFFIXES}@} is ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined, defined) +Result of ${1 2:@.SUFFIXES@${.SUFFIXES}@} is ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval, defined) Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -d v -d 0 exit status 0 diff --git a/contrib/bmake/unit-tests/varname.exp b/contrib/bmake/unit-tests/varname.exp index 9351b0761e98..46e6d13d41bd 100644 --- a/contrib/bmake/unit-tests/varname.exp +++ b/contrib/bmake/unit-tests/varname.exp @@ -1,21 +1,21 @@ Global: VAR{{{}}} = 3 braces Var_Parse: ${VAR{{{}}}}" != "3 braces" (eval) Global: VARNAME = VAR((( Var_Parse: ${VARNAME} (eval) Global: VAR((( = 3 open parentheses Var_Parse: ${VAR(((}}}}" != "3 open parentheses}}}" (eval) Global: .ALLTARGETS = VAR(((=) -make: "varname.mk" line 32: No closing parenthesis in archive specification +make: "varname.mk" line 32: Missing ')' in archive specification make: "varname.mk" line 32: Error in archive specification: "VAR" -Var_Parse: ${:UVAR\(\(\(}= try2 (eval-defined) -Evaluating modifier ${:U...} on value "" (eval-defined, undefined) -Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (eval-defined, defined) +Var_Parse: ${:UVAR\(\(\(}= try2 (eval) +Evaluating modifier ${:U...} on value "" (eval, undefined) +Result of ${:UVAR\(\(\(} is "VAR\(\(\(" (eval, defined) Global: .ALLTARGETS = VAR(((=) VAR\(\(\(= make: "varname.mk" line 38: Invalid line '${:UVAR\(\(\(}= try2', expanded to 'VAR\(\(\(= try2' Var_Parse: ${VARNAME} (eval) Global: VAR((( = try3 Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 make: Fatal errors encountered -- cannot continue make: stopped making "all" in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varname.mk b/contrib/bmake/unit-tests/varname.mk index cad0a10fe563..d2c36afb3aa2 100644 --- a/contrib/bmake/unit-tests/varname.mk +++ b/contrib/bmake/unit-tests/varname.mk @@ -1,89 +1,89 @@ -# $NetBSD: varname.mk,v 1.14 2023/11/19 21:47:52 rillig Exp $ +# $NetBSD: varname.mk,v 1.16 2025/01/11 20:16:40 rillig Exp $ # # Tests for special variables, such as .MAKE or .PARSEDIR. # And for variable names in general. .MAKEFLAGS: -dv # In variable names, braces are allowed, but they must be balanced. # Parentheses and braces may be mixed. VAR{{{}}}= 3 braces .if "${VAR{{{}}}}" != "3 braces" . error .endif # In expressions, the parser works differently. It doesn't treat # braces and parentheses equally, therefore the first closing brace already # marks the end of the variable name. VARNAME= VAR((( ${VARNAME}= 3 open parentheses .if "${VAR(((}}}}" != "3 open parentheses}}}" . error .endif # In the above test, the variable name is constructed indirectly. Neither # of the following expressions produces the intended effect. # # This is not a variable assignment since the parentheses and braces are not # balanced. At the end of the line, there are still 3 levels open, which # means the variable name is not finished. -# expect+2: Error in archive specification: "VAR" -# expect+1: No closing parenthesis in archive specification +# expect+2: Missing ')' in archive specification +# expect+1: Error in archive specification: "VAR" ${:UVAR(((}= try1 # On the left-hand side of a variable assignments, the backslash is not parsed # as an escape character, therefore the parentheses still count to the nesting # level, which at the end of the line is still 3. Therefore this is not a # variable assignment as well. # expect+1: Invalid line '${:UVAR\(\(\(}= try2', expanded to 'VAR\(\(\(= try2' ${:UVAR\(\(\(}= try2 # To assign to a variable with an arbitrary name, the variable name has to # come from an external source, not the text that is parsed in the assignment # itself. This is exactly the reason why further above, the indirect # ${VARNAME} works, while all other attempts fail. ${VARNAME}= try3 .MAKEFLAGS: -d0 # All variable names of a scope are stored in the same hash table, using a # simple hash function. Ensure that HashTable_Find handles collisions # correctly and that the correct variable is looked up. The strings "0x" and # "1Y" have the same hash code, as 31 * '0' + 'x' == 31 * '1' + 'Y'. V.0x= 0x V.1Y= 1Y .if ${V.0x} != "0x" || ${V.1Y} != "1Y" . error .endif # The string "ASDZguv", when used as a prefix of a variable name, keeps the # hash code unchanged, its own hash code is 0. ASDZguvV.0x= 0x ASDZguvV.1Y= 1Y .if ${ASDZguvV.0x} != "0x" . error .elif ${ASDZguvV.1Y} != "1Y" . error .endif # Ensure that variables with the same hash code whose name is a prefix of the # other can be accessed. In this case, the shorter variable name is defined # first to make it appear later in the bucket of the hash table. ASDZguv= once ASDZguvASDZguv= twice .if ${ASDZguv} != "once" . error .elif ${ASDZguvASDZguv} != "twice" . error .endif # Ensure that variables with the same hash code whose name is a prefix of the # other can be accessed. In this case, the longer variable name is defined # first to make it appear later in the bucket of the hash table. ASDZguvASDZguv.param= twice ASDZguv.param= once .if ${ASDZguv.param} != "once" . error .elif ${ASDZguvASDZguv.param} != "twice" . error .endif all: diff --git a/contrib/bmake/unit-tests/varparse-dynamic.exp b/contrib/bmake/unit-tests/varparse-dynamic.exp index caf8424fa91a..940c61163b9c 100644 --- a/contrib/bmake/unit-tests/varparse-dynamic.exp +++ b/contrib/bmake/unit-tests/varparse-dynamic.exp @@ -1,5 +1,5 @@ -make: "varparse-dynamic.mk" line 9: Malformed conditional (${.TARGEX}) -make: "varparse-dynamic.mk" line 12: Malformed conditional (${.TARGXX}) +make: "varparse-dynamic.mk" line 9: Variable ".TARGEX" is undefined +make: "varparse-dynamic.mk" line 12: Variable ".TARGXX" is undefined make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varparse-dynamic.mk b/contrib/bmake/unit-tests/varparse-dynamic.mk index 40f43b049b13..aafe3cca5329 100644 --- a/contrib/bmake/unit-tests/varparse-dynamic.mk +++ b/contrib/bmake/unit-tests/varparse-dynamic.mk @@ -1,34 +1,34 @@ -# $NetBSD: varparse-dynamic.mk,v 1.8 2023/11/19 22:32:44 rillig Exp $ +# $NetBSD: varparse-dynamic.mk,v 1.10 2025/01/11 21:21:34 rillig Exp $ # Before 2020-07-27, there was an off-by-one error in Var_Parse that skipped # the last character in the variable name. # To trigger the bug, the variable had to be undefined. .if ${.TARGET} # exact match, may be undefined .endif -# expect+1: Malformed conditional (${.TARGEX}) +# expect+1: Variable ".TARGEX" is undefined .if ${.TARGEX} # 1 character difference, must be defined .endif -# expect+1: Malformed conditional (${.TARGXX}) +# expect+1: Variable ".TARGXX" is undefined .if ${.TARGXX} # 2 characters difference, must be defined .endif # When a dynamic variable (such as .TARGET) is evaluated in the global # scope, it is not yet ready to be expanded. Therefore the complete # expression is returned as the variable value, hoping that it can be # resolved at a later point. # # This test covers the code in Var_Parse that deals with DEF_UNDEF but not # DEF_DEFINED for dynamic variables. .if ${.TARGET:S,^,,} != "\${.TARGET:S,^,,}" . error .endif # If a dynamic variable is expanded in a non-local scope, the expression # based on this variable is not expanded. But there may be nested # expressions in the modifiers, and these are kept unexpanded as well. .if ${.TARGET:M${:Ufallback}} != "\${.TARGET:M\${:Ufallback}}" . error .endif .if ${.TARGET:M${UNDEF}} != "\${.TARGET:M\${UNDEF}}" . error .endif diff --git a/contrib/bmake/unit-tests/varparse-errors.exp b/contrib/bmake/unit-tests/varparse-errors.exp index 9ca443c6fd67..b2212cb8baf0 100644 --- a/contrib/bmake/unit-tests/varparse-errors.exp +++ b/contrib/bmake/unit-tests/varparse-errors.exp @@ -1,25 +1,45 @@ -make: "varparse-errors.mk" line 38: while evaluating "${:U:Z}" with value "": Unknown modifier "Z" -make: "varparse-errors.mk" line 47: while evaluating "${:U:Z}post" with value "": Unknown modifier "Z" -make: "varparse-errors.mk" line 75: while evaluating "${:U:OX:U${IND}} ${:U:OX:U${IND}}" with value "": Bad modifier ":OX" -make: "varparse-errors.mk" line 75: Undefined variable "${:U:OX" -make: "varparse-errors.mk" line 75: while evaluating variable "IND" with value "${:OX}": while evaluating "${:OX}" with value "": Bad modifier ":OX" -make: "varparse-errors.mk" line 75: while evaluating "${:U:OX:U${IND}}" with value "": Bad modifier ":OX" -make: "varparse-errors.mk" line 75: Undefined variable "${:U:OX" -make: "varparse-errors.mk" line 75: while evaluating variable "IND" with value "${:OX}": while evaluating "${:OX}" with value "": Bad modifier ":OX" -make: "varparse-errors.mk" line 83: while evaluating "${:U:Q" with value "": Unclosed expression, expecting '}' for modifier "Q" -make: "varparse-errors.mk" line 85: while evaluating "${:U:sh" with value "": Unclosed expression, expecting '}' for modifier "sh" -make: "varparse-errors.mk" line 87: while evaluating "${:U:tA" with value "": Unclosed expression, expecting '}' for modifier "tA" -make: "varparse-errors.mk" line 89: while evaluating "${:U:tsX" with value "": Unclosed expression, expecting '}' for modifier "tsX" -make: "varparse-errors.mk" line 91: while evaluating "${:U:ts" with value "": Unclosed expression, expecting '}' for modifier "ts" -make: "varparse-errors.mk" line 93: while evaluating "${:U:ts\040" with value "": Unclosed expression, expecting '}' for modifier "ts\040" -make: "varparse-errors.mk" line 95: while evaluating "${:U:u" with value "": Unclosed expression, expecting '}' for modifier "u" -make: "varparse-errors.mk" line 97: while evaluating "${:U:H" with value ".": Unclosed expression, expecting '}' for modifier "H" -make: "varparse-errors.mk" line 99: while evaluating "${:U:[1]" with value "": Unclosed expression, expecting '}' for modifier "[1]" -make: "varparse-errors.mk" line 101: while evaluating "${:U:hash" with value "b2af338b": Unclosed expression, expecting '}' for modifier "hash" -make: "varparse-errors.mk" line 103: while evaluating "${:U:range" with value "1": Unclosed expression, expecting '}' for modifier "range" -make: "varparse-errors.mk" line 105: while evaluating "${:U:_" with value "": Unclosed expression, expecting '}' for modifier "_" -make: "varparse-errors.mk" line 107: while evaluating "${:U:gmtime" with value "": Unclosed expression, expecting '}' for modifier "gmtime" -make: "varparse-errors.mk" line 109: while evaluating "${:U:localtime" with value "": Unclosed expression, expecting '}' for modifier "localtime" +make: "varparse-errors.mk" line 38: Unknown modifier "Z" + while evaluating "${:U:Z}" with value "" +make: "varparse-errors.mk" line 47: Unknown modifier "Z" + while evaluating "${:U:Z}post" with value "" +make: "varparse-errors.mk" line 73: Bad modifier ":OX" + while evaluating "${:U:OX:U${IND}} ${:U:OX:U${IND}}" with value "" +make: "varparse-errors.mk" line 73: Bad modifier ":OX" + while evaluating "${:OX}" with value "" + while evaluating variable "IND" with value "${:OX}" +make: "varparse-errors.mk" line 73: Bad modifier ":OX" + while evaluating "${:U:OX:U${IND}}" with value "" +make: "varparse-errors.mk" line 73: Bad modifier ":OX" + while evaluating "${:OX}" with value "" + while evaluating variable "IND" with value "${:OX}" +make: "varparse-errors.mk" line 81: Unclosed expression, expecting '}' for modifier "Q" + while evaluating "${:U:Q" with value "" +make: "varparse-errors.mk" line 83: Unclosed expression, expecting '}' for modifier "sh" + while evaluating "${:U:sh" with value "" +make: "varparse-errors.mk" line 85: Unclosed expression, expecting '}' for modifier "tA" + while evaluating "${:U:tA" with value "" +make: "varparse-errors.mk" line 87: Unclosed expression, expecting '}' for modifier "tsX" + while evaluating "${:U:tsX" with value "" +make: "varparse-errors.mk" line 89: Unclosed expression, expecting '}' for modifier "ts" + while evaluating "${:U:ts" with value "" +make: "varparse-errors.mk" line 91: Unclosed expression, expecting '}' for modifier "ts\040" + while evaluating "${:U:ts\040" with value "" +make: "varparse-errors.mk" line 93: Unclosed expression, expecting '}' for modifier "u" + while evaluating "${:U:u" with value "" +make: "varparse-errors.mk" line 95: Unclosed expression, expecting '}' for modifier "H" + while evaluating "${:U:H" with value "." +make: "varparse-errors.mk" line 97: Unclosed expression, expecting '}' for modifier "[1]" + while evaluating "${:U:[1]" with value "" +make: "varparse-errors.mk" line 99: Unclosed expression, expecting '}' for modifier "hash" + while evaluating "${:U:hash" with value "b2af338b" +make: "varparse-errors.mk" line 101: Unclosed expression, expecting '}' for modifier "range" + while evaluating "${:U:range" with value "1" +make: "varparse-errors.mk" line 103: Unclosed expression, expecting '}' for modifier "_" + while evaluating "${:U:_" with value "" +make: "varparse-errors.mk" line 105: Unclosed expression, expecting '}' for modifier "gmtime" + while evaluating "${:U:gmtime" with value "" +make: "varparse-errors.mk" line 107: Unclosed expression, expecting '}' for modifier "localtime" + while evaluating "${:U:localtime" with value "" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/contrib/bmake/unit-tests/varparse-errors.mk b/contrib/bmake/unit-tests/varparse-errors.mk index d10160b816e5..b28ac1ec5dc1 100644 --- a/contrib/bmake/unit-tests/varparse-errors.mk +++ b/contrib/bmake/unit-tests/varparse-errors.mk @@ -1,109 +1,107 @@ -# $NetBSD: varparse-errors.mk,v 1.17 2024/07/05 19:47:22 rillig Exp $ +# $NetBSD: varparse-errors.mk,v 1.19 2024/08/29 20:20:37 rillig Exp $ # Tests for parsing and evaluating all kinds of expressions. # # This is the basis for redesigning the error handling in Var_Parse and # Var_Subst, collecting typical and not so typical use cases. # # See also: # Var_Parse # Var_Subst PLAIN= plain value LITERAL_DOLLAR= To get a dollar, double $$ it. INDIRECT= An ${:Uindirect} value. REF_UNDEF= A reference to an ${UNDEF}undefined variable. ERR_UNCLOSED= An ${UNCLOSED expression. ERR_BAD_MOD= An ${:Uindirect:Z} expression with an unknown modifier. ERR_EVAL= An evaluation error ${:Uvalue:C,.,\3,}. # In a conditional, an expression that is not enclosed in quotes is # expanded using the mode VARE_EVAL_DEFINED. # The variable itself must be defined. # It may refer to undefined variables though. .if ${REF_UNDEF} != "A reference to an undefined variable." . error .endif # As of 2020-12-01, errors in the variable name are silently ignored. # Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result # in an error message and a non-zero exit status. -# expect+1: while evaluating "${:U:Z}" with value "": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" VAR.${:U:Z}= unknown modifier in the variable name .if ${VAR.} != "unknown modifier in the variable name" . error .endif # As of 2020-12-01, errors in the variable name are silently ignored. # Since var.c 1.754 from 2020-12-20, unknown modifiers at parse time result # in an error message and a non-zero exit status. -# expect+1: while evaluating "${:U:Z}post" with value "": Unknown modifier "Z" +# expect+1: Unknown modifier "Z" VAR.${:U:Z}post= unknown modifier with text in the variable name .if ${VAR.post} != "unknown modifier with text in the variable name" . error .endif # Demonstrate an edge case in which the 'static' for 'errorReported' in # Var_Subst actually makes a difference, preventing "a plethora of messages". # Given that this is an edge case and the error message is wrong and thus # misleading anyway, that piece of code is probably not necessary. The wrong # condition was added in var.c 1.185 from 2014-05-19. # # To trigger this difference, the variable assignment must use the assignment # operator ':=' to make VarEvalMode_ShouldKeepUndef return true. There must # be 2 expressions that create a parse error, which in this case is ':OX'. # These expressions must be nested in some way. The below expressions are # minimal, that is, removing any part of it destroys the effect. # # Without the 'static', there would be one more message like this: # Undefined variable "${:U:OX" # #.MAKEFLAGS: -dv IND= ${:OX} -# expect+6: while evaluating "${:U:OX:U${IND}} ${:U:OX:U${IND}}" with value "": Bad modifier ":OX" -# expect+5: while evaluating "${:U:OX:U${IND}}" with value "": Bad modifier ":OX" -# expect+4: Undefined variable "${:U:OX" -# expect+3: while evaluating variable "IND" with value "${:OX}": while evaluating "${:OX}" with value "": Bad modifier ":OX" -# expect+2: Undefined variable "${:U:OX" -# expect+1: while evaluating variable "IND" with value "${:OX}": while evaluating "${:OX}" with value "": Bad modifier ":OX" +# expect+4: Bad modifier ":OX" +# expect+3: Bad modifier ":OX" +# expect+2: Bad modifier ":OX" +# expect+1: Bad modifier ":OX" _:= ${:U:OX:U${IND}} ${:U:OX:U${IND}} #.MAKEFLAGS: -d0 # Before var.c 1.032 from 2022-08-24, make complained about 'Unknown modifier' # or 'Bad modifier' when in fact the modifier was entirely correct, it was # just not delimited by either ':' or '}' but instead by '\0'. -# expect+1: while evaluating "${:U:Q" with value "": Unclosed expression, expecting '}' for modifier "Q" +# expect+1: Unclosed expression, expecting '}' for modifier "Q" UNCLOSED:= ${:U:Q -# expect+1: while evaluating "${:U:sh" with value "": Unclosed expression, expecting '}' for modifier "sh" +# expect+1: Unclosed expression, expecting '}' for modifier "sh" UNCLOSED:= ${:U:sh -# expect+1: while evaluating "${:U:tA" with value "": Unclosed expression, expecting '}' for modifier "tA" +# expect+1: Unclosed expression, expecting '}' for modifier "tA" UNCLOSED:= ${:U:tA -# expect+1: while evaluating "${:U:tsX" with value "": Unclosed expression, expecting '}' for modifier "tsX" +# expect+1: Unclosed expression, expecting '}' for modifier "tsX" UNCLOSED:= ${:U:tsX -# expect+1: while evaluating "${:U:ts" with value "": Unclosed expression, expecting '}' for modifier "ts" +# expect+1: Unclosed expression, expecting '}' for modifier "ts" UNCLOSED:= ${:U:ts -# expect+1: while evaluating "${:U:ts\040" with value "": Unclosed expression, expecting '}' for modifier "ts\040" +# expect+1: Unclosed expression, expecting '}' for modifier "ts\040" UNCLOSED:= ${:U:ts\040 -# expect+1: while evaluating "${:U:u" with value "": Unclosed expression, expecting '}' for modifier "u" +# expect+1: Unclosed expression, expecting '}' for modifier "u" UNCLOSED:= ${:U:u -# expect+1: while evaluating "${:U:H" with value ".": Unclosed expression, expecting '}' for modifier "H" +# expect+1: Unclosed expression, expecting '}' for modifier "H" UNCLOSED:= ${:U:H -# expect+1: while evaluating "${:U:[1]" with value "": Unclosed expression, expecting '}' for modifier "[1]" +# expect+1: Unclosed expression, expecting '}' for modifier "[1]" UNCLOSED:= ${:U:[1] -# expect+1: while evaluating "${:U:hash" with value "b2af338b": Unclosed expression, expecting '}' for modifier "hash" +# expect+1: Unclosed expression, expecting '}' for modifier "hash" UNCLOSED:= ${:U:hash -# expect+1: while evaluating "${:U:range" with value "1": Unclosed expression, expecting '}' for modifier "range" +# expect+1: Unclosed expression, expecting '}' for modifier "range" UNCLOSED:= ${:U:range -# expect+1: while evaluating "${:U:_" with value "": Unclosed expression, expecting '}' for modifier "_" +# expect+1: Unclosed expression, expecting '}' for modifier "_" UNCLOSED:= ${:U:_ -# expect+1: while evaluating "${:U:gmtime" with value "": Unclosed expression, expecting '}' for modifier "gmtime" +# expect+1: Unclosed expression, expecting '}' for modifier "gmtime" UNCLOSED:= ${:U:gmtime -# expect+1: while evaluating "${:U:localtime" with value "": Unclosed expression, expecting '}' for modifier "localtime" +# expect+1: Unclosed expression, expecting '}' for modifier "localtime" UNCLOSED:= ${:U:localtime diff --git a/contrib/bmake/var.c b/contrib/bmake/var.c index 3d611bdd9b5d..eed451779673 100644 --- a/contrib/bmake/var.c +++ b/contrib/bmake/var.c @@ -1,4870 +1,4843 @@ -/* $NetBSD: var.c,v 1.1135 2024/07/09 17:07:23 rillig Exp $ */ +/* $NetBSD: var.c,v 1.1144 2025/01/11 21:21:33 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Handling of variables and the expressions formed from them. * * Variables are set using lines of the form VAR=value. Both the variable * name and the value can contain references to other variables, by using * expressions like ${VAR}, ${VAR:Modifiers}, ${${VARNAME}} or ${VAR:${MODS}}. * * Interface: * Var_Set * Var_SetExpand Set the value of the variable, creating it if * necessary. * * Var_Append * Var_AppendExpand * Append more characters to the variable, creating it if * necessary. A space is placed between the old value and * the new one. * * Var_Exists * Var_ExistsExpand * See if a variable exists. * * Var_Value Return the unexpanded value of a variable, or NULL if * the variable is undefined. * * Var_Subst Substitute all expressions in a string. * * Var_Parse Parse an expression such as ${VAR:Mpattern}. * * Var_Delete Delete a variable. * * Var_ReexportVars * Export some or even all variables to the environment * of this process and its child processes. * * Var_Export Export the variable to the environment of this process * and its child processes. * * Var_UnExport Don't export the variable anymore. * * Debugging: * Var_Stats Print out hashing statistics if in -dh mode. * * Var_Dump Print out all variables defined in the given scope. */ #include #include #include "make.h" #include #ifdef HAVE_REGEX_H #include #endif #ifdef HAVE_INTTYPES_H #include #endif #ifdef HAVE_STDINT_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #include #include "dir.h" #include "job.h" #include "metachar.h" #ifndef SIZE_MAX #define SIZE_MAX 0xffffffffUL #endif /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.1135 2024/07/09 17:07:23 rillig Exp $"); +MAKE_RCSID("$NetBSD: var.c,v 1.1144 2025/01/11 21:21:33 rillig Exp $"); /* * Variables are defined using one of the VAR=value assignments. Their * value can be queried by expressions such as $V, ${VAR}, or with modifiers * such as ${VAR:S,from,to,g:Q}. * * There are 3 kinds of variables: scope variables, environment variables, * undefined variables. * * Scope variables are stored in GNode.vars. The only way to undefine * a scope variable is using the .undef directive. In particular, it must * not be possible to undefine a variable during the evaluation of an * expression, or Var.name might point nowhere. (There is another, * unintended way to undefine a scope variable, see varmod-loop-delete.mk.) * * Environment variables are short-lived. They are returned by VarFind, and * after using them, they must be freed using VarFreeShortLived. * * Undefined variables occur during evaluation of expressions such * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers. */ typedef struct Var { /* * The name of the variable, once set, doesn't change anymore. * For scope variables, it aliases the corresponding HashEntry name. * For environment and undefined variables, it is allocated. */ FStr name; /* The unexpanded value of the variable. */ Buffer val; /* The variable came from the command line. */ bool fromCmd:1; /* * The variable is short-lived. * These variables are not registered in any GNode, therefore they * must be freed after use. */ bool shortLived:1; /* * The variable comes from the environment. * Appending to its value depends on the scope, see var-op-append.mk. */ bool fromEnvironment:1; /* * The variable value cannot be changed anymore, and the variable * cannot be deleted. Any attempts to do so are silently ignored, * they are logged with -dv though. * Use .[NO]READONLY: to adjust. * * See VAR_SET_READONLY. */ bool readOnly:1; /* * The variable is read-only and immune to the .NOREADONLY special * target. Any attempt to modify it results in an error. */ bool readOnlyLoud:1; /* * The variable is currently being accessed by Var_Parse or Var_Subst. * This temporary marker is used to avoid endless recursion. */ bool inUse:1; /* * The variable is exported to the environment, to be used by child * processes. */ bool exported:1; /* * At the point where this variable was exported, it contained an * unresolved reference to another variable. Before any child * process is started, it needs to be actually exported, resolving * the referenced variable just in time. */ bool reexport:1; } Var; /* * Exporting variables is expensive and may leak memory, so skip it if we * can. */ typedef enum VarExportedMode { VAR_EXPORTED_NONE, VAR_EXPORTED_SOME, VAR_EXPORTED_ALL } VarExportedMode; typedef enum UnexportWhat { /* Unexport the variables given by name. */ UNEXPORT_NAMED, /* * Unexport all globals previously exported, but keep the environment * inherited from the parent. */ UNEXPORT_ALL, /* * Unexport all globals previously exported and clear the environment * inherited from the parent. */ UNEXPORT_ENV } UnexportWhat; /* Flags for pattern matching in the :S and :C modifiers */ typedef struct PatternFlags { bool subGlobal:1; /* 'g': replace as often as possible */ bool subOnce:1; /* '1': replace only once */ bool anchorStart:1; /* '^': match only at start of word */ bool anchorEnd:1; /* '$': match only at end of word */ } PatternFlags; /* SepBuf builds a string from words interleaved with separators. */ typedef struct SepBuf { Buffer buf; bool needSep; /* Usually ' ', but see the ':ts' modifier. */ char sep; } SepBuf; typedef enum { VSK_TARGET, VSK_VARNAME, VSK_COND, VSK_COND_THEN, VSK_COND_ELSE, VSK_EXPR, VSK_EXPR_PARSE } EvalStackElementKind; typedef struct { EvalStackElementKind kind; const char *str; const FStr *value; } EvalStackElement; typedef struct { EvalStackElement *elems; size_t len; size_t cap; - Buffer details; } EvalStack; /* Whether we have replaced the original environ (which we cannot free). */ char **savedEnv = NULL; /* * Special return value for Var_Parse, indicating a parse error. It may be * caused by an undefined variable, a syntax error in a modifier or * something entirely different. */ char var_Error[] = ""; /* * Special return value for Var_Parse, indicating an undefined variable in * a case where VARE_EVAL_DEFINED is not set. This undefined variable is * typically a dynamic variable such as ${.TARGET}, whose expansion needs to * be deferred until it is defined in an actual target. * * See VARE_EVAL_KEEP_UNDEFINED. */ static char varUndefined[] = ""; /* * Traditionally this make consumed $$ during := like any other expansion. * Other make's do not, and this make follows straight since 2016-01-09. * * This knob allows controlling the behavior: * false to consume $$ during := assignment. * true to preserve $$ during := assignment. */ #define MAKE_SAVE_DOLLARS ".MAKE.SAVE_DOLLARS" static bool save_dollars = false; /* * A scope collects variable names and their values. * * The main scope is SCOPE_GLOBAL, which contains the variables that are set * in the makefiles. SCOPE_INTERNAL acts as a fallback for SCOPE_GLOBAL and * contains some internal make variables. These internal variables can thus * be overridden, they can also be restored by undefining the overriding * variable. * * SCOPE_CMDLINE contains variables from the command line arguments. These * override variables from SCOPE_GLOBAL. * * There is no scope for environment variables, these are generated on-the-fly * whenever they are referenced. * * Each target has its own scope, containing the 7 target-local variables * .TARGET, .ALLSRC, etc. Variables set on dependency lines also go in * this scope. */ GNode *SCOPE_CMDLINE; GNode *SCOPE_GLOBAL; GNode *SCOPE_INTERNAL; static VarExportedMode var_exportedVars = VAR_EXPORTED_NONE; static const char VarEvalMode_Name[][32] = { "parse", "parse-balanced", "eval", + "eval-defined-loud", "eval-defined", "eval-keep-undefined", "eval-keep-dollar-and-undefined", }; static EvalStack evalStack; static void EvalStack_Push(EvalStackElementKind kind, const char *str, const FStr *value) { if (evalStack.len >= evalStack.cap) { evalStack.cap = 16 + 2 * evalStack.cap; evalStack.elems = bmake_realloc(evalStack.elems, evalStack.cap * sizeof(*evalStack.elems)); } evalStack.elems[evalStack.len].kind = kind; evalStack.elems[evalStack.len].str = str; evalStack.elems[evalStack.len].value = value; evalStack.len++; } static void EvalStack_Pop(void) { assert(evalStack.len > 0); evalStack.len--; } -const char * -EvalStack_Details(void) +void +EvalStack_PrintDetails(void) { size_t i; - Buffer *buf = &evalStack.details; - - buf->len = 0; - for (i = 0; i < evalStack.len; i++) { + for (i = evalStack.len; i > 0; i--) { static const char descr[][42] = { "in target", "while evaluating variable", "while evaluating condition", "while evaluating then-branch of condition", "while evaluating else-branch of condition", "while evaluating", "while parsing", }; - EvalStackElement *elem = evalStack.elems + i; + EvalStackElement *elem = evalStack.elems + i - 1; EvalStackElementKind kind = elem->kind; - Buf_AddStr(buf, descr[kind]); - Buf_AddStr(buf, " \""); - Buf_AddStr(buf, elem->str); - if (elem->value != NULL - && (kind == VSK_VARNAME || kind == VSK_EXPR)) { - Buf_AddStr(buf, "\" with value \""); - Buf_AddStr(buf, elem->value->str); - } - Buf_AddStr(buf, "\": "); + const char* value = elem->value != NULL + && (kind == VSK_VARNAME || kind == VSK_EXPR) + ? elem->value->str : NULL; + + debug_printf("\t%s \"%s%s%s\"\n", descr[kind], elem->str, + value != NULL ? "\" with value \"" : "", + value != NULL ? value : ""); } - return buf->len > 0 ? buf->data : ""; } static Var * VarNew(FStr name, const char *value, bool shortLived, bool fromEnvironment, bool readOnly) { size_t value_len = strlen(value); Var *var = bmake_malloc(sizeof *var); var->name = name; Buf_InitSize(&var->val, value_len + 1); Buf_AddBytes(&var->val, value, value_len); var->fromCmd = false; var->shortLived = shortLived; var->fromEnvironment = fromEnvironment; var->readOnly = readOnly; var->readOnlyLoud = false; var->inUse = false; var->exported = false; var->reexport = false; return var; } static Substring CanonicalVarname(Substring name) { if (!(Substring_Length(name) > 0 && name.start[0] == '.')) return name; if (Substring_Equals(name, ".ALLSRC")) return Substring_InitStr(ALLSRC); if (Substring_Equals(name, ".ARCHIVE")) return Substring_InitStr(ARCHIVE); if (Substring_Equals(name, ".IMPSRC")) return Substring_InitStr(IMPSRC); if (Substring_Equals(name, ".MEMBER")) return Substring_InitStr(MEMBER); if (Substring_Equals(name, ".OODATE")) return Substring_InitStr(OODATE); if (Substring_Equals(name, ".PREFIX")) return Substring_InitStr(PREFIX); if (Substring_Equals(name, ".TARGET")) return Substring_InitStr(TARGET); /* GNU make has an additional alias $^ == ${.ALLSRC}. */ if (Substring_Equals(name, ".SHELL") && shellPath == NULL) Shell_Init(); return name; } static Var * GNode_FindVar(GNode *scope, Substring varname, unsigned int hash) { return HashTable_FindValueBySubstringHash(&scope->vars, varname, hash); } /* * Find the variable in the scope, and maybe in other scopes as well. * * Input: * name name to find, is not expanded any further * scope scope in which to look first * elsewhere true to look in other scopes as well * * Results: * The found variable, or NULL if the variable does not exist. * If the variable is short-lived (such as environment variables), it * must be freed using VarFreeShortLived after use. */ static Var * VarFindSubstring(Substring name, GNode *scope, bool elsewhere) { Var *var; unsigned int nameHash; /* Replace '.TARGET' with '@', likewise for other local variables. */ name = CanonicalVarname(name); nameHash = Hash_Substring(name); var = GNode_FindVar(scope, name, nameHash); if (!elsewhere) return var; if (var == NULL && scope != SCOPE_CMDLINE) var = GNode_FindVar(SCOPE_CMDLINE, name, nameHash); if (!opts.checkEnvFirst && var == NULL && scope != SCOPE_GLOBAL) { var = GNode_FindVar(SCOPE_GLOBAL, name, nameHash); if (var == NULL && scope != SCOPE_INTERNAL) { /* SCOPE_INTERNAL is subordinate to SCOPE_GLOBAL */ var = GNode_FindVar(SCOPE_INTERNAL, name, nameHash); } } if (var == NULL) { FStr envName = Substring_Str(name); const char *envValue = getenv(envName.str); if (envValue != NULL) return VarNew(envName, envValue, true, true, false); FStr_Done(&envName); if (opts.checkEnvFirst && scope != SCOPE_GLOBAL) { var = GNode_FindVar(SCOPE_GLOBAL, name, nameHash); if (var == NULL && scope != SCOPE_INTERNAL) var = GNode_FindVar(SCOPE_INTERNAL, name, nameHash); return var; } return NULL; } return var; } static Var * VarFind(const char *name, GNode *scope, bool elsewhere) { return VarFindSubstring(Substring_InitStr(name), scope, elsewhere); } /* If the variable is short-lived, free it, including its value. */ static void VarFreeShortLived(Var *v) { if (!v->shortLived) return; FStr_Done(&v->name); Buf_Done(&v->val); free(v); } static const char * ValueDescription(const char *value) { if (value[0] == '\0') return "# (empty)"; if (ch_isspace(value[strlen(value) - 1])) return "# (ends with space)"; return ""; } /* Add a new variable of the given name and value to the given scope. */ static Var * VarAdd(const char *name, const char *value, GNode *scope, VarSetFlags flags) { HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL); Var *v = VarNew(FStr_InitRefer(/* aliased to */ he->key), value, false, false, (flags & VAR_SET_READONLY) != 0); HashEntry_Set(he, v); DEBUG4(VAR, "%s: %s = %s%s\n", scope->name, name, value, ValueDescription(value)); return v; } /* * Remove a variable from a scope, freeing all related memory as well. * The variable name is kept as-is, it is not expanded. */ void Var_Delete(GNode *scope, const char *varname) { HashEntry *he = HashTable_FindEntry(&scope->vars, varname); Var *v; if (he == NULL) { DEBUG2(VAR, "%s: ignoring delete '%s' as it is not found\n", scope->name, varname); return; } v = he->value; if (v->readOnlyLoud) { Parse_Error(PARSE_FATAL, "Cannot delete \"%s\" as it is read-only", v->name.str); return; } if (v->readOnly) { DEBUG2(VAR, "%s: ignoring delete '%s' as it is read-only\n", scope->name, varname); return; } if (v->inUse) { Parse_Error(PARSE_FATAL, "Cannot delete variable \"%s\" while it is used", v->name.str); return; } DEBUG2(VAR, "%s: delete %s\n", scope->name, varname); if (v->exported) unsetenv(v->name.str); if (strcmp(v->name.str, ".MAKE.EXPORTED") == 0) var_exportedVars = VAR_EXPORTED_NONE; assert(v->name.freeIt == NULL); HashTable_DeleteEntry(&scope->vars, he); Buf_Done(&v->val); free(v); } #ifdef CLEANUP void Var_DeleteAll(GNode *scope) { HashIter hi; HashIter_Init(&hi, &scope->vars); while (HashIter_Next(&hi)) { Var *v = hi.entry->value; Buf_Done(&v->val); free(v); } } #endif /* * Undefine one or more variables from the global scope. * The argument is expanded exactly once and then split into words. */ void Var_Undef(const char *arg) { char *expanded; Words varnames; size_t i; if (arg[0] == '\0') { Parse_Error(PARSE_FATAL, "The .undef directive requires an argument"); return; } expanded = Var_Subst(arg, SCOPE_GLOBAL, VARE_EVAL); if (expanded == var_Error) { /* TODO: Make this part of the code reachable. */ Parse_Error(PARSE_FATAL, "Error in variable names to be undefined"); return; } varnames = Str_Words(expanded, false); if (varnames.len == 1 && varnames.words[0][0] == '\0') varnames.len = 0; for (i = 0; i < varnames.len; i++) { const char *varname = varnames.words[i]; Global_Delete(varname); } Words_Free(varnames); free(expanded); } static bool MayExport(const char *name) { if (name[0] == '.') return false; /* skip internals */ if (name[0] == '-') return false; /* skip misnamed variables */ if (name[1] == '\0') { /* * A single char. * If it is one of the variables that should only appear in * local scope, skip it, else we can get Var_Subst * into a loop. */ switch (name[0]) { case '@': case '%': case '*': case '!': return false; } } return true; } static bool ExportVarEnv(Var *v, GNode *scope) { const char *name = v->name.str; char *val = v->val.data; char *expr; if (v->exported && !v->reexport) return false; /* nothing to do */ if (strchr(val, '$') == NULL) { if (!v->exported) setenv(name, val, 1); return true; } if (v->inUse) return false; /* see EMPTY_SHELL in directive-export.mk */ /* XXX: name is injected without escaping it */ expr = str_concat3("${", name, "}"); val = Var_Subst(expr, scope, VARE_EVAL); if (scope != SCOPE_GLOBAL) { /* we will need to re-export the global version */ v = VarFind(name, SCOPE_GLOBAL, false); if (v != NULL) v->exported = false; } /* TODO: handle errors */ setenv(name, val, 1); free(val); free(expr); return true; } static bool ExportVarPlain(Var *v) { if (strchr(v->val.data, '$') == NULL) { setenv(v->name.str, v->val.data, 1); v->exported = true; v->reexport = false; return true; } /* * Flag the variable as something we need to re-export. * No point actually exporting it now though, * the child process can do it at the last minute. * Avoid calling setenv more often than necessary since it can leak. */ v->exported = true; v->reexport = true; return true; } static bool ExportVarLiteral(Var *v) { if (v->exported && !v->reexport) return false; if (!v->exported) setenv(v->name.str, v->val.data, 1); return true; } /* * Mark a single variable to be exported later for subprocesses. * * Internal variables are not exported. */ static bool ExportVar(const char *name, GNode *scope, VarExportMode mode) { Var *v; if (!MayExport(name)) return false; v = VarFind(name, scope, false); if (v == NULL && scope != SCOPE_GLOBAL) v = VarFind(name, SCOPE_GLOBAL, false); if (v == NULL) return false; if (mode == VEM_ENV) return ExportVarEnv(v, scope); else if (mode == VEM_PLAIN) return ExportVarPlain(v); else return ExportVarLiteral(v); } /* * Actually export the variables that have been marked as needing to be * re-exported. */ void Var_ReexportVars(GNode *scope) { char *xvarnames; /* * Several make implementations support this sort of mechanism for * tracking recursion - but each uses a different name. * We allow the makefiles to update MAKELEVEL and ensure * children see a correctly incremented value. */ char level_buf[21]; snprintf(level_buf, sizeof level_buf, "%d", makelevel + 1); setenv(MAKE_LEVEL_ENV, level_buf, 1); if (var_exportedVars == VAR_EXPORTED_NONE) return; if (var_exportedVars == VAR_EXPORTED_ALL) { HashIter hi; /* Ouch! Exporting all variables at once is crazy. */ HashIter_Init(&hi, &SCOPE_GLOBAL->vars); while (HashIter_Next(&hi)) { Var *var = hi.entry->value; ExportVar(var->name.str, scope, VEM_ENV); } return; } xvarnames = Var_Subst("${.MAKE.EXPORTED:O:u}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ if (xvarnames[0] != '\0') { Words varnames = Str_Words(xvarnames, false); size_t i; for (i = 0; i < varnames.len; i++) ExportVar(varnames.words[i], scope, VEM_ENV); Words_Free(varnames); } free(xvarnames); } static void ExportVars(const char *varnames, bool isExport, VarExportMode mode) /* TODO: try to combine the parameters 'isExport' and 'mode'. */ { Words words = Str_Words(varnames, false); size_t i; if (words.len == 1 && words.words[0][0] == '\0') words.len = 0; for (i = 0; i < words.len; i++) { const char *varname = words.words[i]; if (!ExportVar(varname, SCOPE_GLOBAL, mode)) continue; if (var_exportedVars == VAR_EXPORTED_NONE) var_exportedVars = VAR_EXPORTED_SOME; if (isExport && mode == VEM_PLAIN) Global_Append(".MAKE.EXPORTED", varname); } Words_Free(words); } static void ExportVarsExpand(const char *uvarnames, bool isExport, VarExportMode mode) { char *xvarnames = Var_Subst(uvarnames, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ ExportVars(xvarnames, isExport, mode); free(xvarnames); } /* Export the named variables, or all variables. */ void Var_Export(VarExportMode mode, const char *varnames) { if (mode == VEM_ALL) { var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */ return; } else if (mode == VEM_PLAIN && varnames[0] == '\0') { Parse_Error(PARSE_WARNING, ".export requires an argument."); return; } ExportVarsExpand(varnames, true, mode); } void Var_ExportVars(const char *varnames) { ExportVarsExpand(varnames, false, VEM_PLAIN); } static void ClearEnv(void) { const char *level; char **newenv; level = getenv(MAKE_LEVEL_ENV); /* we should preserve this */ if (environ == savedEnv) { /* we have been here before! */ newenv = bmake_realloc(environ, 2 * sizeof(char *)); } else { if (savedEnv != NULL) { free(savedEnv); savedEnv = NULL; } newenv = bmake_malloc(2 * sizeof(char *)); } /* Note: we cannot safely free() the original environ. */ environ = savedEnv = newenv; newenv[0] = NULL; newenv[1] = NULL; if (level != NULL && *level != '\0') setenv(MAKE_LEVEL_ENV, level, 1); } static void GetVarnamesToUnexport(bool isEnv, const char *arg, FStr *out_varnames, UnexportWhat *out_what) { UnexportWhat what; FStr varnames = FStr_InitRefer(""); if (isEnv) { if (arg[0] != '\0') { Parse_Error(PARSE_FATAL, "The directive .unexport-env does not take " "arguments"); /* continue anyway */ } what = UNEXPORT_ENV; } else { what = arg[0] != '\0' ? UNEXPORT_NAMED : UNEXPORT_ALL; if (what == UNEXPORT_NAMED) varnames = FStr_InitRefer(arg); } if (what != UNEXPORT_NAMED) { char *expanded = Var_Subst("${.MAKE.EXPORTED:O:u}", SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ varnames = FStr_InitOwn(expanded); } *out_varnames = varnames; *out_what = what; } static void UnexportVar(Substring varname, UnexportWhat what) { Var *v = VarFindSubstring(varname, SCOPE_GLOBAL, false); if (v == NULL) { DEBUG2(VAR, "Not unexporting \"%.*s\" (not found)\n", (int)Substring_Length(varname), varname.start); return; } DEBUG2(VAR, "Unexporting \"%.*s\"\n", (int)Substring_Length(varname), varname.start); if (what != UNEXPORT_ENV && v->exported && !v->reexport) unsetenv(v->name.str); v->exported = false; v->reexport = false; if (what == UNEXPORT_NAMED) { /* Remove the variable names from .MAKE.EXPORTED. */ /* XXX: v->name is injected without escaping it */ char *expr = str_concat3( "${.MAKE.EXPORTED:N", v->name.str, "}"); char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_EVAL); /* TODO: handle errors */ Global_Set(".MAKE.EXPORTED", filtered); free(filtered); free(expr); } } static void -UnexportVars(FStr *varnames, UnexportWhat what) +UnexportVars(const char *varnames, UnexportWhat what) { size_t i; SubstringWords words; if (what == UNEXPORT_ENV) ClearEnv(); - words = Substring_Words(varnames->str, false); + words = Substring_Words(varnames, false); for (i = 0; i < words.len; i++) UnexportVar(words.words[i], what); SubstringWords_Free(words); if (what != UNEXPORT_NAMED) Global_Delete(".MAKE.EXPORTED"); } /* Handle the .unexport and .unexport-env directives. */ void Var_UnExport(bool isEnv, const char *arg) { UnexportWhat what; FStr varnames; GetVarnamesToUnexport(isEnv, arg, &varnames, &what); - UnexportVars(&varnames, what); + UnexportVars(varnames.str, what); FStr_Done(&varnames); } /* Set the variable to the value; the name is not expanded. */ void Var_SetWithFlags(GNode *scope, const char *name, const char *val, VarSetFlags flags) { Var *v; assert(val != NULL); if (name[0] == '\0') { DEBUG3(VAR, "%s: ignoring '%s = %s' as the variable name is empty\n", scope->name, name, val); return; } if (scope == SCOPE_GLOBAL && VarFind(name, SCOPE_CMDLINE, false) != NULL) { /* * The global variable would not be visible anywhere. * Therefore, there is no point in setting it at all. */ DEBUG3(VAR, "%s: ignoring '%s = %s' " "due to a command line variable of the same name\n", scope->name, name, val); return; } /* * Only look for a variable in the given scope since anything set * here will override anything in a lower scope, so there's not much * point in searching them all. */ v = VarFind(name, scope, false); if (v == NULL) { if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT)) { /* * This variable would normally prevent the same name * being added to SCOPE_GLOBAL, so delete it from * there if needed. Otherwise -V name may show the * wrong value. * * See ExistsInCmdline. */ - Var_Delete(SCOPE_GLOBAL, name); + Var *gl = VarFind(name, SCOPE_GLOBAL, false); + if (gl != NULL && gl->readOnlyLoud) + Parse_Error(PARSE_FATAL, + "Cannot override " + "read-only global variable \"%s\" " + "with a command line variable", name); + else + Var_Delete(SCOPE_GLOBAL, name); } if (strcmp(name, ".SUFFIXES") == 0) { /* special: treat as read-only */ DEBUG3(VAR, "%s: ignoring '%s = %s' as it is read-only\n", scope->name, name, val); return; } v = VarAdd(name, val, scope, flags); } else { if (v->readOnlyLoud) { Parse_Error(PARSE_FATAL, "Cannot overwrite \"%s\" as it is read-only", name); return; } if (v->readOnly && !(flags & VAR_SET_READONLY)) { DEBUG3(VAR, "%s: ignoring '%s = %s' as it is read-only\n", scope->name, name, val); return; } Buf_Clear(&v->val); Buf_AddStr(&v->val, val); DEBUG4(VAR, "%s: %s = %s%s\n", scope->name, name, val, ValueDescription(val)); if (v->exported) ExportVar(name, scope, VEM_PLAIN); } if (scope == SCOPE_CMDLINE) { v->fromCmd = true; /* * Any variables given on the command line are automatically * exported to the environment (as per POSIX standard), except * for internals. */ if (!(flags & VAR_SET_NO_EXPORT)) { /* * If requested, don't export these in the * environment individually. We still put * them in .MAKEOVERRIDES so that the * command-line settings continue to override * Makefile settings. */ if (!opts.varNoExportEnv && name[0] != '.') setenv(name, val, 1); if (!(flags & VAR_SET_INTERNAL)) Global_Append(".MAKEOVERRIDES", name); } } if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) save_dollars = ParseBoolean(val, save_dollars); if (v != NULL) VarFreeShortLived(v); } void Var_Set(GNode *scope, const char *name, const char *val) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); } /* * In the scope, expand the variable name once, then create the variable or * replace its value. */ void Var_SetExpand(GNode *scope, const char *name, const char *val) { FStr varname = FStr_InitRefer(name); assert(val != NULL); Var_Expand(&varname, scope, VARE_EVAL); if (varname.str[0] == '\0') { DEBUG4(VAR, "%s: ignoring '%s = %s' " "as the variable name '%s' expands to empty\n", scope->name, varname.str, val, name); } else Var_SetWithFlags(scope, varname.str, val, VAR_SET_NONE); FStr_Done(&varname); } void Global_Set(const char *name, const char *value) { Var_Set(SCOPE_GLOBAL, name, value); } void Global_Delete(const char *name) { Var_Delete(SCOPE_GLOBAL, name); } void Global_Set_ReadOnly(const char *name, const char *value) { Var_SetWithFlags(SCOPE_GLOBAL, name, value, VAR_SET_NONE); VarFind(name, SCOPE_GLOBAL, false)->readOnlyLoud = true; } /* * Append the value to the named variable. * * If the variable doesn't exist, it is created. Otherwise a single space * and the given value are appended. */ void Var_Append(GNode *scope, const char *name, const char *val) { Var *v; v = VarFind(name, scope, scope == SCOPE_GLOBAL); if (v == NULL) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); } else if (v->readOnlyLoud) { Parse_Error(PARSE_FATAL, "Cannot append to \"%s\" as it is read-only", name); return; } else if (v->readOnly) { DEBUG3(VAR, "%s: ignoring '%s += %s' as it is read-only\n", scope->name, name, val); } else if (scope == SCOPE_CMDLINE || !v->fromCmd) { Buf_AddByte(&v->val, ' '); Buf_AddStr(&v->val, val); DEBUG3(VAR, "%s: %s = %s\n", scope->name, name, v->val.data); if (v->fromEnvironment) { /* See VarAdd. */ HashEntry *he = HashTable_CreateEntry(&scope->vars, name, NULL); HashEntry_Set(he, v); FStr_Done(&v->name); v->name = FStr_InitRefer(/* aliased to */ he->key); v->shortLived = false; v->fromEnvironment = false; } } } /* * In the scope, expand the variable name once. If the variable exists in the * scope, add a space and the value, otherwise set the variable to the value. * * Appending to an environment variable only works in the global scope, that * is, for variable assignments in makefiles, but not inside conditions or the * commands of a target. */ void Var_AppendExpand(GNode *scope, const char *name, const char *val) { FStr xname = FStr_InitRefer(name); assert(val != NULL); Var_Expand(&xname, scope, VARE_EVAL); if (xname.str != name && xname.str[0] == '\0') DEBUG4(VAR, "%s: ignoring '%s += %s' " "as the variable name '%s' expands to empty\n", scope->name, xname.str, val, name); else Var_Append(scope, xname.str, val); FStr_Done(&xname); } void Global_Append(const char *name, const char *value) { Var_Append(SCOPE_GLOBAL, name, value); } bool Var_Exists(GNode *scope, const char *name) { Var *v = VarFind(name, scope, true); if (v == NULL) return false; VarFreeShortLived(v); return true; } /* * See if the given variable exists, in the given scope or in other * fallback scopes. * * Input: * scope scope in which to start search * name name of the variable to find, is expanded once */ bool Var_ExistsExpand(GNode *scope, const char *name) { FStr varname = FStr_InitRefer(name); bool exists; Var_Expand(&varname, scope, VARE_EVAL); exists = Var_Exists(scope, varname.str); FStr_Done(&varname); return exists; } /* * Return the unexpanded value of the given variable in the given scope, * falling back to the command, global and environment scopes, in this order, * but see the -e option. * * Input: * name the name to find, is not expanded any further * * Results: * The value if the variable exists, NULL if it doesn't. * The value is valid until the next modification to any variable. */ FStr Var_Value(GNode *scope, const char *name) { Var *v = VarFind(name, scope, true); char *value; if (v == NULL) return FStr_InitRefer(NULL); if (!v->shortLived) return FStr_InitRefer(v->val.data); value = v->val.data; v->val.data = NULL; VarFreeShortLived(v); return FStr_InitOwn(value); } /* Set or clear the read-only attribute of the variable if it exists. */ void Var_ReadOnly(const char *name, bool bf) { Var *v; v = VarFind(name, SCOPE_GLOBAL, false); if (v == NULL) { DEBUG1(VAR, "Var_ReadOnly: %s not found\n", name); return; } v->readOnly = bf; DEBUG2(VAR, "Var_ReadOnly: %s %s\n", name, bf ? "true" : "false"); } /* * Return the unexpanded variable value from this node, without trying to look * up the variable in any other scope. */ const char * GNode_ValueDirect(GNode *gn, const char *name) { Var *v = VarFind(name, gn, false); return v != NULL ? v->val.data : NULL; } static VarEvalMode VarEvalMode_WithoutKeepDollar(VarEvalMode emode) { return emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED ? VARE_EVAL_KEEP_UNDEFINED : emode; } -static VarEvalMode -VarEvalMode_UndefOk(VarEvalMode emode) -{ - return emode == VARE_EVAL_DEFINED ? VARE_EVAL : emode; -} - static bool VarEvalMode_ShouldEval(VarEvalMode emode) { return emode != VARE_PARSE; } static bool VarEvalMode_ShouldKeepUndef(VarEvalMode emode) { return emode == VARE_EVAL_KEEP_UNDEFINED || emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED; } static bool VarEvalMode_ShouldKeepDollar(VarEvalMode emode) { return emode == VARE_EVAL_KEEP_DOLLAR_AND_UNDEFINED; } static void SepBuf_Init(SepBuf *buf, char sep) { Buf_InitSize(&buf->buf, 32); buf->needSep = false; buf->sep = sep; } static void SepBuf_Sep(SepBuf *buf) { buf->needSep = true; } static void SepBuf_AddBytes(SepBuf *buf, const char *mem, size_t mem_size) { if (mem_size == 0) return; if (buf->needSep && buf->sep != '\0') { Buf_AddByte(&buf->buf, buf->sep); buf->needSep = false; } Buf_AddBytes(&buf->buf, mem, mem_size); } static void SepBuf_AddRange(SepBuf *buf, const char *start, const char *end) { SepBuf_AddBytes(buf, start, (size_t)(end - start)); } static void SepBuf_AddStr(SepBuf *buf, const char *str) { SepBuf_AddBytes(buf, str, strlen(str)); } static void SepBuf_AddSubstring(SepBuf *buf, Substring sub) { SepBuf_AddRange(buf, sub.start, sub.end); } static char * SepBuf_DoneData(SepBuf *buf) { return Buf_DoneData(&buf->buf); } /* * This callback for ModifyWords gets a single word from an expression * and typically adds a modification of this word to the buffer. It may also * do nothing or add several words. * * For example, when evaluating the modifier ':M*b' in ${:Ua b c:M*b}, the * callback is called 3 times, once for "a", "b" and "c". * * Some ModifyWord functions assume that they are always passed a * null-terminated substring, which is currently guaranteed but may change in * the future. */ typedef void (*ModifyWordProc)(Substring word, SepBuf *buf, void *data); static void ModifyWord_Head(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { SepBuf_AddSubstring(buf, Substring_Dirname(word)); } static void ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { SepBuf_AddSubstring(buf, Substring_Basename(word)); } static void ModifyWord_Suffix(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *lastDot = Substring_FindLast(word, '.'); if (lastDot != NULL) SepBuf_AddRange(buf, lastDot + 1, word.end); } static void ModifyWord_Root(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *lastDot, *end; lastDot = Substring_FindLast(word, '.'); end = lastDot != NULL ? lastDot : word.end; SepBuf_AddRange(buf, word.start, end); } struct ModifyWord_SysVSubstArgs { GNode *scope; Substring lhsPrefix; bool lhsPercent; Substring lhsSuffix; const char *rhs; }; static void ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) { const struct ModifyWord_SysVSubstArgs *args = data; FStr rhs; const char *percent; if (Substring_IsEmpty(word)) return; if (!Substring_HasPrefix(word, args->lhsPrefix) || !Substring_HasSuffix(word, args->lhsSuffix)) { SepBuf_AddSubstring(buf, word); return; } rhs = FStr_InitRefer(args->rhs); Var_Expand(&rhs, args->scope, VARE_EVAL); percent = args->lhsPercent ? strchr(rhs.str, '%') : NULL; if (percent != NULL) SepBuf_AddRange(buf, rhs.str, percent); if (percent != NULL || !args->lhsPercent) SepBuf_AddRange(buf, word.start + Substring_Length(args->lhsPrefix), word.end - Substring_Length(args->lhsSuffix)); SepBuf_AddStr(buf, percent != NULL ? percent + 1 : rhs.str); FStr_Done(&rhs); } static const char * Substring_Find(Substring haystack, Substring needle) { size_t len, needleLen, i; len = Substring_Length(haystack); needleLen = Substring_Length(needle); for (i = 0; i + needleLen <= len; i++) if (memcmp(haystack.start + i, needle.start, needleLen) == 0) return haystack.start + i; return NULL; } struct ModifyWord_SubstArgs { Substring lhs; Substring rhs; PatternFlags pflags; bool matched; }; static void ModifyWord_Subst(Substring word, SepBuf *buf, void *data) { struct ModifyWord_SubstArgs *args = data; size_t wordLen, lhsLen; const char *match; wordLen = Substring_Length(word); if (args->pflags.subOnce && args->matched) goto nosub; lhsLen = Substring_Length(args->lhs); if (args->pflags.anchorStart) { if (wordLen < lhsLen || memcmp(word.start, args->lhs.start, lhsLen) != 0) goto nosub; if (args->pflags.anchorEnd && wordLen != lhsLen) goto nosub; /* :S,^prefix,replacement, or :S,^whole$,replacement, */ SepBuf_AddSubstring(buf, args->rhs); SepBuf_AddRange(buf, word.start + lhsLen, word.end); args->matched = true; return; } if (args->pflags.anchorEnd) { if (wordLen < lhsLen) goto nosub; if (memcmp(word.end - lhsLen, args->lhs.start, lhsLen) != 0) goto nosub; /* :S,suffix$,replacement, */ SepBuf_AddRange(buf, word.start, word.end - lhsLen); SepBuf_AddSubstring(buf, args->rhs); args->matched = true; return; } if (Substring_IsEmpty(args->lhs)) goto nosub; /* unanchored case, may match more than once */ while ((match = Substring_Find(word, args->lhs)) != NULL) { SepBuf_AddRange(buf, word.start, match); SepBuf_AddSubstring(buf, args->rhs); args->matched = true; word.start = match + lhsLen; if (Substring_IsEmpty(word) || !args->pflags.subGlobal) break; } nosub: SepBuf_AddSubstring(buf, word); } #ifdef HAVE_REGEX_H /* Print the error caused by a regcomp or regexec call. */ static void RegexError(int reerr, const regex_t *pat, const char *str) { size_t errlen = regerror(reerr, pat, NULL, 0); char *errbuf = bmake_malloc(errlen); regerror(reerr, pat, errbuf, errlen); Parse_Error(PARSE_FATAL, "%s: %s", str, errbuf); free(errbuf); } /* In the modifier ':C', replace a backreference from \0 to \9. */ static void RegexReplaceBackref(char ref, SepBuf *buf, const char *wp, const regmatch_t *m, size_t nsub) { unsigned int n = (unsigned)ref - '0'; if (n >= nsub) Parse_Error(PARSE_FATAL, "No subexpression \\%u", n); else if (m[n].rm_so == -1) { if (opts.strict) Error("No match for subexpression \\%u", n); } else { SepBuf_AddRange(buf, wp + (size_t)m[n].rm_so, wp + (size_t)m[n].rm_eo); } } /* * The regular expression matches the word; now add the replacement to the * buffer, taking back-references from 'wp'. */ static void RegexReplace(Substring replace, SepBuf *buf, const char *wp, const regmatch_t *m, size_t nsub) { const char *rp; for (rp = replace.start; rp != replace.end; rp++) { if (*rp == '\\' && rp + 1 != replace.end && (rp[1] == '&' || rp[1] == '\\')) SepBuf_AddBytes(buf, ++rp, 1); else if (*rp == '\\' && rp + 1 != replace.end && ch_isdigit(rp[1])) RegexReplaceBackref(*++rp, buf, wp, m, nsub); else if (*rp == '&') { SepBuf_AddRange(buf, wp + (size_t)m[0].rm_so, wp + (size_t)m[0].rm_eo); } else SepBuf_AddBytes(buf, rp, 1); } } struct ModifyWord_SubstRegexArgs { regex_t re; size_t nsub; Substring replace; PatternFlags pflags; bool matched; }; static void ModifyWord_SubstRegex(Substring word, SepBuf *buf, void *data) { struct ModifyWord_SubstRegexArgs *args = data; int xrv; const char *wp; int flags = 0; regmatch_t m[10]; assert(word.end[0] == '\0'); /* assume null-terminated word */ wp = word.start; if (args->pflags.subOnce && args->matched) goto no_match; again: xrv = regexec(&args->re, wp, args->nsub, m, flags); if (xrv == 0) goto ok; if (xrv != REG_NOMATCH) RegexError(xrv, &args->re, "Unexpected regex error"); no_match: SepBuf_AddRange(buf, wp, word.end); return; ok: args->matched = true; SepBuf_AddBytes(buf, wp, (size_t)m[0].rm_so); RegexReplace(args->replace, buf, wp, m, args->nsub); wp += (size_t)m[0].rm_eo; if (args->pflags.subGlobal) { flags |= REG_NOTBOL; if (m[0].rm_so == 0 && m[0].rm_eo == 0 && *wp != '\0') { SepBuf_AddBytes(buf, wp, 1); wp++; } if (*wp != '\0') goto again; } if (*wp != '\0') SepBuf_AddStr(buf, wp); } #endif struct ModifyWord_LoopArgs { GNode *scope; const char *var; /* name of the temporary variable */ const char *body; /* string to expand */ VarEvalMode emode; }; static void ModifyWord_Loop(Substring word, SepBuf *buf, void *data) { const struct ModifyWord_LoopArgs *args; char *s; if (Substring_IsEmpty(word)) return; args = data; assert(word.end[0] == '\0'); /* assume null-terminated word */ Var_SetWithFlags(args->scope, args->var, word.start, VAR_SET_NO_EXPORT); s = Var_Subst(args->body, args->scope, args->emode); /* TODO: handle errors */ DEBUG2(VAR, "ModifyWord_Loop: expand \"%s\" to \"%s\"\n", args->body, s); if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n')) buf->needSep = false; SepBuf_AddStr(buf, s); free(s); } /* * The :[first..last] modifier selects words from the expression. * It can also reverse the words. */ static char * VarSelectWords(const char *str, int first, int last, char sep, bool oneBigWord) { SubstringWords words; int len, start, end, step; int i; SepBuf buf; SepBuf_Init(&buf, sep); if (oneBigWord) { /* fake what Substring_Words() would do */ words.len = 1; words.words = bmake_malloc(sizeof(words.words[0])); words.freeIt = NULL; words.words[0] = Substring_InitStr(str); /* no need to copy */ } else { words = Substring_Words(str, false); } /* Convert -1 to len, -2 to (len - 1), etc. */ len = (int)words.len; if (first < 0) first += len + 1; if (last < 0) last += len + 1; if (first > last) { start = (first > len ? len : first) - 1; end = last < 1 ? 0 : last - 1; step = -1; } else { start = first < 1 ? 0 : first - 1; end = last > len ? len : last; step = 1; } for (i = start; (step < 0) == (i >= end); i += step) { SepBuf_AddSubstring(&buf, words.words[i]); SepBuf_Sep(&buf); } SubstringWords_Free(words); return SepBuf_DoneData(&buf); } static void ModifyWord_Realpath(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { struct stat st; char rbuf[MAXPATHLEN]; const char *rp; assert(word.end[0] == '\0'); /* assume null-terminated word */ rp = cached_realpath(word.start, rbuf); if (rp != NULL && *rp == '/' && stat(rp, &st) == 0) SepBuf_AddStr(buf, rp); else SepBuf_AddSubstring(buf, word); } static char * SubstringWords_JoinFree(SubstringWords words) { Buffer buf; size_t i; Buf_Init(&buf); for (i = 0; i < words.len; i++) { if (i != 0) { /* * XXX: Use ch->sep instead of ' ', for consistency. */ Buf_AddByte(&buf, ' '); } Buf_AddRange(&buf, words.words[i].start, words.words[i].end); } SubstringWords_Free(words); return Buf_DoneData(&buf); } /* * Quote shell meta-characters and space characters in the string. * If quoteDollar is set, also quote and double any '$' characters. */ static void QuoteShell(const char *str, bool quoteDollar, LazyBuf *buf) { const char *p; LazyBuf_Init(buf, str); for (p = str; *p != '\0'; p++) { if (*p == '\n') { const char *newline = Shell_GetNewline(); if (newline == NULL) newline = "\\\n"; LazyBuf_AddStr(buf, newline); continue; } if (ch_isspace(*p) || ch_is_shell_meta(*p)) LazyBuf_Add(buf, '\\'); LazyBuf_Add(buf, *p); if (quoteDollar && *p == '$') LazyBuf_AddStr(buf, "\\$"); } } /* * Compute the 32-bit hash of the given string, using the MurmurHash3 * algorithm. Output is encoded as 8 hex digits, in Little Endian order. */ static char * Hash(const char *str) { static const char hexdigits[16] = "0123456789abcdef"; const unsigned char *ustr = (const unsigned char *)str; uint32_t h = 0x971e137bU; uint32_t c1 = 0x95543787U; uint32_t c2 = 0x2ad7eb25U; size_t len2 = strlen(str); char *buf; size_t i; size_t len; for (len = len2; len != 0;) { uint32_t k = 0; switch (len) { default: k = ((uint32_t)ustr[3] << 24) | ((uint32_t)ustr[2] << 16) | ((uint32_t)ustr[1] << 8) | (uint32_t)ustr[0]; len -= 4; ustr += 4; break; case 3: k |= (uint32_t)ustr[2] << 16; /* FALLTHROUGH */ case 2: k |= (uint32_t)ustr[1] << 8; /* FALLTHROUGH */ case 1: k |= (uint32_t)ustr[0]; len = 0; } c1 = c1 * 5 + 0x7b7d159cU; c2 = c2 * 5 + 0x6bce6396U; k *= c1; k = (k << 11) ^ (k >> 21); k *= c2; h = (h << 13) ^ (h >> 19); h = h * 5 + 0x52dce729U; h ^= k; } h ^= (uint32_t)len2; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; buf = bmake_malloc(9); for (i = 0; i < 8; i++) { buf[i] = hexdigits[h & 0x0f]; h >>= 4; } buf[8] = '\0'; return buf; } static char * FormatTime(const char *fmt, time_t t, bool gmt) { char buf[BUFSIZ]; if (t == 0) time(&t); if (*fmt == '\0') fmt = "%c"; if (gmt && strchr(fmt, 's') != NULL) { /* strftime "%s" only works with localtime, not with gmtime. */ const char *prev_tz_env = getenv("TZ"); char *prev_tz = prev_tz_env != NULL ? bmake_strdup(prev_tz_env) : NULL; setenv("TZ", "UTC", 1); strftime(buf, sizeof buf, fmt, localtime(&t)); if (prev_tz != NULL) { setenv("TZ", prev_tz, 1); free(prev_tz); } else unsetenv("TZ"); } else strftime(buf, sizeof buf, fmt, (gmt ? gmtime : localtime)(&t)); buf[sizeof buf - 1] = '\0'; return bmake_strdup(buf); } /* * The ApplyModifier functions take an expression that is being evaluated. * Their task is to apply a single modifier to the expression. This involves * parsing the modifier, evaluating it and finally updating the value of the * expression. * * Parsing the modifier * * If parsing succeeds, the parsing position *pp is updated to point to the * first character following the modifier, which typically is either ':' or * ch->endc. The modifier doesn't have to check for this delimiter character, * this is done by ApplyModifiers. * * XXX: As of 2020-11-15, some modifiers such as :S, :C, :P, :L do not * need to be followed by a ':' or endc; this was an unintended mistake. * * If parsing fails because of a missing delimiter after a modifier part (as * in the :S, :C or :@ modifiers), return AMR_CLEANUP. * * If parsing fails because the modifier is unknown, return AMR_UNKNOWN to * try the SysV modifier ':from=to' as fallback. This should only be * done as long as there have been no side effects from evaluating nested * variables, to avoid evaluating them more than once. In this case, the * parsing position may or may not be updated. (XXX: Why not? The original * parsing position is well-known in ApplyModifiers.) * * If parsing fails and the SysV modifier ${VAR:from=to} should not be used * as a fallback, issue an error message using Parse_Error (preferred over * Error) and then return AMR_CLEANUP, which stops processing the expression. * (XXX: As of 2020-08-23, evaluation of the string continues nevertheless * after skipping a few bytes, which results in garbage.) * * Evaluating the modifier * * After parsing, the modifier is evaluated. The side effects from evaluating * nested expressions in the modifier text often already happen * during parsing though. For most modifiers this doesn't matter since their * only noticeable effect is that they update the value of the expression. * Some modifiers such as ':sh' or '::=' have noticeable side effects though. * * Evaluating the modifier usually takes the current value of the * expression from ch->expr->value, or the variable name from ch->var->name, * and stores the result back in ch->expr->value via Expr_SetValueOwn or * Expr_SetValueRefer. * * If evaluating fails, the fallback error message "Bad modifier" is printed. * TODO: Add proper error handling to Var_Subst, Var_Parse, ApplyModifiers and * ModifyWords. * * Some modifiers such as :D and :U turn undefined expressions into defined * expressions using Expr_Define. */ typedef enum ExprDefined { /* The expression is based on a regular, defined variable. */ DEF_REGULAR, /* The expression is based on an undefined variable. */ DEF_UNDEF, /* * The expression started as an undefined expression, but one * of the modifiers (such as ':D' or ':U') has turned the expression * from undefined to defined. */ DEF_DEFINED } ExprDefined; static const char ExprDefined_Name[][10] = { "regular", "undefined", "defined" }; #if __STDC_VERSION__ >= 199901L #define const_member const #else #define const_member /* no const possible */ #endif /* An expression based on a variable, such as $@ or ${VAR:Mpattern:Q}. */ typedef struct Expr { const char *name; FStr value; VarEvalMode const_member emode; GNode *const_member scope; ExprDefined defined; } Expr; /* * The status of applying a chain of modifiers to an expression. * * The modifiers of an expression are broken into chains of modifiers, * starting a new nested chain whenever an indirect modifier starts. There * are at most 2 nesting levels: the outer one for the direct modifiers, and * the inner one for the indirect modifiers. * * For example, the expression ${VAR:M*:${IND1}:${IND2}:O:u} has 3 chains of * modifiers: * * Chain 1 starts with the single modifier ':M*'. * Chain 2 starts with all modifiers from ${IND1}. * Chain 2 ends at the ':' between ${IND1} and ${IND2}. * Chain 3 starts with all modifiers from ${IND2}. * Chain 3 ends at the ':' after ${IND2}. * Chain 1 continues with the 2 modifiers ':O' and ':u'. * Chain 1 ends at the final '}' of the expression. * * After such a chain ends, its properties no longer have any effect. * * See varmod-indirect.mk. */ typedef struct ModChain { Expr *expr; /* '\0' or '{' or '(' */ char const_member startc; /* '\0' or '}' or ')' */ char const_member endc; /* Separator when joining words (see the :ts modifier). */ char sep; /* * Whether some modifiers that otherwise split the variable value * into words, like :S and :C, treat the variable value as a single * big word, possibly containing spaces. */ bool oneBigWord; } ModChain; static void Expr_Define(Expr *expr) { if (expr->defined == DEF_UNDEF) expr->defined = DEF_DEFINED; } static const char * Expr_Str(const Expr *expr) { return expr->value.str; } static SubstringWords Expr_Words(const Expr *expr) { return Substring_Words(Expr_Str(expr), false); } static void Expr_SetValue(Expr *expr, FStr value) { FStr_Done(&expr->value); expr->value = value; } static void Expr_SetValueOwn(Expr *expr, char *value) { Expr_SetValue(expr, FStr_InitOwn(value)); } static void Expr_SetValueRefer(Expr *expr, const char *value) { Expr_SetValue(expr, FStr_InitRefer(value)); } static bool Expr_ShouldEval(const Expr *expr) { return VarEvalMode_ShouldEval(expr->emode); } static bool ModChain_ShouldEval(const ModChain *ch) { return Expr_ShouldEval(ch->expr); } typedef enum ApplyModifierResult { /* Continue parsing */ AMR_OK, /* Not a match, try the ':from=to' modifier as well. */ AMR_UNKNOWN, /* Error out with "Bad modifier" message. */ AMR_BAD, /* Error out without the standard error message. */ AMR_CLEANUP } ApplyModifierResult; /* * Allow backslashes to escape the delimiter, $, and \, but don't touch other * backslashes. */ static bool IsEscapedModifierPart(const char *p, char delim, struct ModifyWord_SubstArgs *subst) { if (p[0] != '\\' || p[1] == '\0') return false; if (p[1] == delim || p[1] == '\\' || p[1] == '$') return true; return p[1] == '&' && subst != NULL; } /* * In a part of a modifier, parse a subexpression and evaluate it. */ static void ParseModifierPartExpr(const char **pp, LazyBuf *part, const ModChain *ch, VarEvalMode emode) { const char *p = *pp; FStr nested_val = Var_Parse(&p, ch->expr->scope, VarEvalMode_WithoutKeepDollar(emode)); /* TODO: handle errors */ if (VarEvalMode_ShouldEval(emode)) LazyBuf_AddStr(part, nested_val.str); else LazyBuf_AddSubstring(part, Substring_Init(*pp, p)); FStr_Done(&nested_val); *pp = p; } /* * In a part of a modifier, parse some text that looks like a subexpression. * If the text starts with '$(', any '(' and ')' must be balanced. * If the text starts with '${', any '{' and '}' must be balanced. * If the text starts with '$', that '$' is copied verbatim, it is not parsed * as a short-name expression. */ static void ParseModifierPartBalanced(const char **pp, LazyBuf *part) { const char *p = *pp; if (p[1] == '(' || p[1] == '{') { char startc = p[1]; int endc = startc == '(' ? ')' : '}'; int depth = 1; for (p += 2; *p != '\0' && depth > 0; p++) { if (p[-1] != '\\') { if (*p == startc) depth++; if (*p == endc) depth--; } } LazyBuf_AddSubstring(part, Substring_Init(*pp, p)); *pp = p; } else { LazyBuf_Add(part, *p); *pp = p + 1; } } /* * Parse a part of a modifier such as the "from" and "to" in :S/from/to/ or * the "var" or "replacement ${var}" in :@var@replacement ${var}@, up to and * including the next unescaped delimiter. The delimiter, as well as the * backslash or the dollar, can be escaped with a backslash. * * Return true if parsing succeeded, together with the parsed (and possibly * expanded) part. In that case, pp points right after the delimiter. The * delimiter is not included in the part though. */ static bool ParseModifierPart( /* The parsing position, updated upon return */ const char **pp, char end1, char end2, /* Mode for evaluating nested expressions. */ VarEvalMode emode, ModChain *ch, LazyBuf *part, /* * For the first part of the ':S' modifier, set anchorEnd if the last * character of the pattern is a $. */ PatternFlags *out_pflags, /* * For the second part of the ':S' modifier, allow ampersands to be * escaped and replace unescaped ampersands with subst->lhs. */ struct ModifyWord_SubstArgs *subst ) { const char *p = *pp; LazyBuf_Init(part, p); while (*p != '\0' && *p != end1 && *p != end2) { if (IsEscapedModifierPart(p, end2, subst)) { LazyBuf_Add(part, p[1]); p += 2; } else if (*p != '$') { /* Unescaped, simple text */ if (subst != NULL && *p == '&') LazyBuf_AddSubstring(part, subst->lhs); else LazyBuf_Add(part, *p); p++; } else if (p[1] == end2) { /* Unescaped '$' at end */ if (out_pflags != NULL) out_pflags->anchorEnd = true; else LazyBuf_Add(part, *p); p++; } else if (emode == VARE_PARSE_BALANCED) ParseModifierPartBalanced(&p, part); else ParseModifierPartExpr(&p, part, ch, emode); } *pp = p; if (*p != end1 && *p != end2) { Parse_Error(PARSE_FATAL, "Unfinished modifier ('%c' missing)", end2); LazyBuf_Done(part); return false; } if (end1 == end2) (*pp)++; { Substring sub = LazyBuf_Get(part); DEBUG2(VAR, "Modifier part: \"%.*s\"\n", (int)Substring_Length(sub), sub.start); } return true; } MAKE_INLINE bool IsDelimiter(char c, const ModChain *ch) { return c == ':' || c == ch->endc || c == '\0'; } /* Test whether mod starts with modname, followed by a delimiter. */ MAKE_INLINE bool ModMatch(const char *mod, const char *modname, const ModChain *ch) { size_t n = strlen(modname); return strncmp(mod, modname, n) == 0 && IsDelimiter(mod[n], ch); } /* Test whether mod starts with modname, followed by a delimiter or '='. */ MAKE_INLINE bool ModMatchEq(const char *mod, const char *modname, const ModChain *ch) { size_t n = strlen(modname); return strncmp(mod, modname, n) == 0 && (IsDelimiter(mod[n], ch) || mod[n] == '='); } static bool TryParseIntBase0(const char **pp, int *out_num) { char *end; long n; errno = 0; n = strtol(*pp, &end, 0); if (end == *pp) return false; if ((n == LONG_MIN || n == LONG_MAX) && errno == ERANGE) return false; if (n < INT_MIN || n > INT_MAX) return false; *pp = end; *out_num = (int)n; return true; } static bool TryParseSize(const char **pp, size_t *out_num) { char *end; unsigned long n; if (!ch_isdigit(**pp)) return false; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) return false; if (n > SIZE_MAX) return false; *pp = end; *out_num = (size_t)n; return true; } static bool TryParseChar(const char **pp, int base, char *out_ch) { char *end; unsigned long n; if (!ch_isalnum(**pp)) return false; errno = 0; n = strtoul(*pp, &end, base); if (n == ULONG_MAX && errno == ERANGE) return false; if (n > UCHAR_MAX) return false; *pp = end; *out_ch = (char)n; return true; } /* * Modify each word of the expression using the given function and place the * result back in the expression. */ static void ModifyWords(ModChain *ch, ModifyWordProc modifyWord, void *modifyWord_args, bool oneBigWord) { Expr *expr = ch->expr; const char *val = Expr_Str(expr); SepBuf result; SubstringWords words; size_t i; Substring word; if (!ModChain_ShouldEval(ch)) return; if (oneBigWord) { SepBuf_Init(&result, ch->sep); /* XXX: performance: Substring_InitStr calls strlen */ word = Substring_InitStr(val); modifyWord(word, &result, modifyWord_args); goto done; } words = Substring_Words(val, false); DEBUG3(VAR, "ModifyWords: split \"%s\" into %u %s\n", val, (unsigned)words.len, words.len != 1 ? "words" : "word"); SepBuf_Init(&result, ch->sep); for (i = 0; i < words.len; i++) { modifyWord(words.words[i], &result, modifyWord_args); if (result.buf.len > 0) SepBuf_Sep(&result); } SubstringWords_Free(words); done: Expr_SetValueOwn(expr, SepBuf_DoneData(&result)); } /* :@var@...${var}...@ */ static ApplyModifierResult ApplyModifier_Loop(const char **pp, ModChain *ch) { Expr *expr = ch->expr; struct ModifyWord_LoopArgs args; char prev_sep; LazyBuf tvarBuf, strBuf; FStr tvar, str; args.scope = expr->scope; (*pp)++; /* Skip the first '@' */ if (!ParseModifierPart(pp, '@', '@', VARE_PARSE, ch, &tvarBuf, NULL, NULL)) return AMR_CLEANUP; tvar = LazyBuf_DoneGet(&tvarBuf); args.var = tvar.str; if (strchr(args.var, '$') != NULL) { Parse_Error(PARSE_FATAL, "In the :@ modifier, the variable name \"%s\" " "must not contain a dollar", args.var); goto cleanup_tvar; } if (!ParseModifierPart(pp, '@', '@', VARE_PARSE_BALANCED, ch, &strBuf, NULL, NULL)) goto cleanup_tvar; str = LazyBuf_DoneGet(&strBuf); args.body = str.str; if (!Expr_ShouldEval(expr)) goto done; args.emode = VarEvalMode_WithoutKeepDollar(expr->emode); prev_sep = ch->sep; ch->sep = ' '; /* XXX: should be ch->sep for consistency */ ModifyWords(ch, ModifyWord_Loop, &args, ch->oneBigWord); ch->sep = prev_sep; /* XXX: Consider restoring the previous value instead of deleting. */ Var_Delete(expr->scope, args.var); done: FStr_Done(&tvar); FStr_Done(&str); return AMR_OK; cleanup_tvar: FStr_Done(&tvar); return AMR_CLEANUP; } static void ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval, LazyBuf *buf) { const char *p; p = *pp + 1; LazyBuf_Init(buf, p); while (!IsDelimiter(*p, ch)) { /* * XXX: This code is similar to the one in Var_Parse. See if * the code can be merged. See also ParseModifier_Match and * ParseModifierPart. */ /* See Buf_AddEscaped in for.c for the counterpart. */ if (*p == '\\') { char c = p[1]; if ((IsDelimiter(c, ch) && c != '\0') || c == '$' || c == '\\') { if (shouldEval) LazyBuf_Add(buf, c); p += 2; continue; } } if (*p == '$') { FStr val = Var_Parse(&p, ch->expr->scope, shouldEval ? ch->expr->emode : VARE_PARSE); /* TODO: handle errors */ if (shouldEval) LazyBuf_AddStr(buf, val.str); FStr_Done(&val); continue; } if (shouldEval) LazyBuf_Add(buf, *p); p++; } *pp = p; } /* :Ddefined or :Uundefined */ static ApplyModifierResult ApplyModifier_Defined(const char **pp, ModChain *ch) { Expr *expr = ch->expr; LazyBuf buf; bool shouldEval = Expr_ShouldEval(expr) && (**pp == 'D') == (expr->defined == DEF_REGULAR); ParseModifier_Defined(pp, ch, shouldEval, &buf); Expr_Define(expr); if (shouldEval) Expr_SetValue(expr, Substring_Str(LazyBuf_Get(&buf))); LazyBuf_Done(&buf); return AMR_OK; } /* :L */ static ApplyModifierResult ApplyModifier_Literal(const char **pp, ModChain *ch) { Expr *expr = ch->expr; (*pp)++; if (Expr_ShouldEval(expr)) { Expr_Define(expr); Expr_SetValueOwn(expr, bmake_strdup(expr->name)); } return AMR_OK; } static bool TryParseTime(const char **pp, time_t *out_time) { char *end; unsigned long n; if (!ch_isdigit(**pp)) return false; errno = 0; n = strtoul(*pp, &end, 10); if (n == ULONG_MAX && errno == ERANGE) return false; *pp = end; *out_time = (time_t)n; /* ignore possible truncation for now */ return true; } /* :gmtime and :localtime */ static ApplyModifierResult ApplyModifier_Time(const char **pp, ModChain *ch) { Expr *expr; time_t t; const char *args; const char *mod = *pp; bool gmt = mod[0] == 'g'; if (!ModMatchEq(mod, gmt ? "gmtime" : "localtime", ch)) return AMR_UNKNOWN; args = mod + (gmt ? 6 : 9); if (args[0] == '=') { const char *p = args + 1; LazyBuf buf; FStr arg; if (!ParseModifierPart(&p, ':', ch->endc, ch->expr->emode, ch, &buf, NULL, NULL)) return AMR_CLEANUP; arg = LazyBuf_DoneGet(&buf); if (ModChain_ShouldEval(ch)) { const char *arg_p = arg.str; if (!TryParseTime(&arg_p, &t) || *arg_p != '\0') { Parse_Error(PARSE_FATAL, "Invalid time value \"%s\"", arg.str); FStr_Done(&arg); return AMR_CLEANUP; } } else t = 0; FStr_Done(&arg); *pp = p; } else { t = 0; *pp = args; } expr = ch->expr; if (Expr_ShouldEval(expr)) Expr_SetValueOwn(expr, FormatTime(Expr_Str(expr), t, gmt)); return AMR_OK; } /* :hash */ static ApplyModifierResult ApplyModifier_Hash(const char **pp, ModChain *ch) { if (!ModMatch(*pp, "hash", ch)) return AMR_UNKNOWN; *pp += 4; if (ModChain_ShouldEval(ch)) Expr_SetValueOwn(ch->expr, Hash(Expr_Str(ch->expr))); return AMR_OK; } /* :P */ static ApplyModifierResult ApplyModifier_Path(const char **pp, ModChain *ch) { Expr *expr = ch->expr; GNode *gn; char *path; (*pp)++; if (!Expr_ShouldEval(expr)) return AMR_OK; Expr_Define(expr); gn = Targ_FindNode(expr->name); if (gn == NULL || gn->type & OP_NOPATH) path = NULL; else if (gn->path != NULL) path = bmake_strdup(gn->path); else { SearchPath *searchPath = Suff_FindPath(gn); path = Dir_FindFile(expr->name, searchPath); } if (path == NULL) path = bmake_strdup(expr->name); Expr_SetValueOwn(expr, path); return AMR_OK; } /* :!cmd! */ static ApplyModifierResult ApplyModifier_ShellCommand(const char **pp, ModChain *ch) { Expr *expr = ch->expr; LazyBuf cmdBuf; FStr cmd; (*pp)++; if (!ParseModifierPart(pp, '!', '!', expr->emode, ch, &cmdBuf, NULL, NULL)) return AMR_CLEANUP; cmd = LazyBuf_DoneGet(&cmdBuf); if (Expr_ShouldEval(expr)) { char *output, *error; output = Cmd_Exec(cmd.str, &error); Expr_SetValueOwn(expr, output); if (error != NULL) { Parse_Error(PARSE_WARNING, "%s", error); free(error); } } else Expr_SetValueRefer(expr, ""); FStr_Done(&cmd); Expr_Define(expr); return AMR_OK; } /* * The :range modifier generates an integer sequence as long as the words. * The :range=7 modifier generates an integer sequence from 1 to 7. */ static ApplyModifierResult ApplyModifier_Range(const char **pp, ModChain *ch) { size_t n; Buffer buf; size_t i; const char *mod = *pp; if (!ModMatchEq(mod, "range", ch)) return AMR_UNKNOWN; if (mod[5] == '=') { const char *p = mod + 6; if (!TryParseSize(&p, &n)) { Parse_Error(PARSE_FATAL, "Invalid number \"%s\" for ':range' modifier", mod + 6); return AMR_CLEANUP; } *pp = p; } else { n = 0; *pp = mod + 5; } if (!ModChain_ShouldEval(ch)) return AMR_OK; if (n == 0) { SubstringWords words = Expr_Words(ch->expr); n = words.len; SubstringWords_Free(words); } Buf_Init(&buf); for (i = 0; i < n; i++) { if (i != 0) { /* * XXX: Use ch->sep instead of ' ', for consistency. */ Buf_AddByte(&buf, ' '); } Buf_AddInt(&buf, 1 + (int)i); } Expr_SetValueOwn(ch->expr, Buf_DoneData(&buf)); return AMR_OK; } /* Parse a ':M' or ':N' modifier. */ static char * ParseModifier_Match(const char **pp, const ModChain *ch) { const char *mod = *pp; Expr *expr = ch->expr; bool copy = false; /* pattern should be, or has been, copied */ bool needSubst = false; const char *endpat; char *pattern; /* * In the loop below, ignore ':' unless we are at (or back to) the * original brace level. * XXX: This will likely not work right if $() and ${} are intermixed. */ /* * XXX: This code is similar to the one in Var_Parse. * See if the code can be merged. * See also ApplyModifier_Defined. */ int depth = 0; const char *p; for (p = mod + 1; *p != '\0' && !(*p == ':' && depth == 0); p++) { if (*p == '\\' && p[1] != '\0' && (IsDelimiter(p[1], ch) || p[1] == ch->startc)) { if (!needSubst) copy = true; p++; continue; } if (*p == '$') needSubst = true; if (*p == '(' || *p == '{') depth++; if (*p == ')' || *p == '}') { depth--; if (depth < 0) break; } } *pp = p; endpat = p; if (copy) { char *dst; const char *src; /* Compress the \:'s out of the pattern. */ pattern = bmake_malloc((size_t)(endpat - (mod + 1)) + 1); dst = pattern; src = mod + 1; for (; src < endpat; src++, dst++) { if (src[0] == '\\' && src + 1 < endpat && /* XXX: ch->startc is missing here; see above */ IsDelimiter(src[1], ch)) src++; *dst = *src; } *dst = '\0'; } else { pattern = bmake_strsedup(mod + 1, endpat); } if (needSubst) { char *old_pattern = pattern; /* * XXX: Contrary to ParseModifierPart, a dollar in a ':M' or * ':N' modifier must be escaped as '$$', not as '\$'. */ pattern = Var_Subst(pattern, expr->scope, expr->emode); /* TODO: handle errors */ free(old_pattern); } DEBUG2(VAR, "Pattern for ':%c' is \"%s\"\n", mod[0], pattern); return pattern; } struct ModifyWord_MatchArgs { const char *pattern; bool neg; bool error_reported; }; static void ModifyWord_Match(Substring word, SepBuf *buf, void *data) { struct ModifyWord_MatchArgs *args = data; StrMatchResult res; assert(word.end[0] == '\0'); /* assume null-terminated word */ res = Str_Match(word.start, args->pattern); if (res.error != NULL && !args->error_reported) { args->error_reported = true; Parse_Error(PARSE_FATAL, "%s in pattern '%s' of modifier '%s'", res.error, args->pattern, args->neg ? ":N" : ":M"); } if (res.matched != args->neg) SepBuf_AddSubstring(buf, word); } /* :Mpattern or :Npattern */ static ApplyModifierResult ApplyModifier_Match(const char **pp, ModChain *ch) { char mod = **pp; char *pattern; pattern = ParseModifier_Match(pp, ch); if (ModChain_ShouldEval(ch)) { struct ModifyWord_MatchArgs args; args.pattern = pattern; args.neg = mod == 'N'; args.error_reported = false; ModifyWords(ch, ModifyWord_Match, &args, ch->oneBigWord); } free(pattern); return AMR_OK; } struct ModifyWord_MtimeArgs { bool error; bool use_fallback; ApplyModifierResult rc; time_t fallback; }; static void ModifyWord_Mtime(Substring word, SepBuf *buf, void *data) { struct ModifyWord_MtimeArgs *args = data; struct stat st; char tbuf[21]; if (Substring_IsEmpty(word)) return; assert(word.end[0] == '\0'); /* assume null-terminated word */ if (stat(word.start, &st) < 0) { if (args->error) { Parse_Error(PARSE_FATAL, "Cannot determine mtime for '%s': %s", word.start, strerror(errno)); args->rc = AMR_CLEANUP; return; } if (args->use_fallback) st.st_mtime = args->fallback; else time(&st.st_mtime); } snprintf(tbuf, sizeof(tbuf), "%u", (unsigned)st.st_mtime); SepBuf_AddStr(buf, tbuf); } /* :mtime */ static ApplyModifierResult ApplyModifier_Mtime(const char **pp, ModChain *ch) { const char *p, *mod = *pp; struct ModifyWord_MtimeArgs args; if (!ModMatchEq(mod, "mtime", ch)) return AMR_UNKNOWN; *pp += 5; p = *pp; args.error = false; args.use_fallback = p[0] == '='; args.rc = AMR_OK; if (args.use_fallback) { p++; if (TryParseTime(&p, &args.fallback)) { } else if (strncmp(p, "error", 5) == 0) { p += 5; args.error = true; } else goto invalid_argument; if (!IsDelimiter(*p, ch)) goto invalid_argument; *pp = p; } ModifyWords(ch, ModifyWord_Mtime, &args, ch->oneBigWord); return args.rc; invalid_argument: Parse_Error(PARSE_FATAL, "Invalid argument '%.*s' for modifier ':mtime'", (int)strcspn(*pp + 1, ":{}()"), *pp + 1); return AMR_CLEANUP; } static void ParsePatternFlags(const char **pp, PatternFlags *pflags, bool *oneBigWord) { for (;; (*pp)++) { if (**pp == 'g') pflags->subGlobal = true; else if (**pp == '1') pflags->subOnce = true; else if (**pp == 'W') *oneBigWord = true; else break; } } MAKE_INLINE PatternFlags PatternFlags_None(void) { PatternFlags pflags = { false, false, false, false }; return pflags; } /* :S,from,to, */ static ApplyModifierResult ApplyModifier_Subst(const char **pp, ModChain *ch) { struct ModifyWord_SubstArgs args; bool oneBigWord; LazyBuf lhsBuf, rhsBuf; char delim = (*pp)[1]; if (delim == '\0') { Parse_Error(PARSE_FATAL, "Missing delimiter for modifier ':S'"); (*pp)++; return AMR_CLEANUP; } *pp += 2; args.pflags = PatternFlags_None(); args.matched = false; if (**pp == '^') { args.pflags.anchorStart = true; (*pp)++; } if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, ch, &lhsBuf, &args.pflags, NULL)) return AMR_CLEANUP; args.lhs = LazyBuf_Get(&lhsBuf); if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, ch, &rhsBuf, NULL, &args)) { LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; } args.rhs = LazyBuf_Get(&rhsBuf); oneBigWord = ch->oneBigWord; ParsePatternFlags(pp, &args.pflags, &oneBigWord); ModifyWords(ch, ModifyWord_Subst, &args, oneBigWord); LazyBuf_Done(&lhsBuf); LazyBuf_Done(&rhsBuf); return AMR_OK; } #ifdef HAVE_REGEX_H /* :C,from,to, */ static ApplyModifierResult ApplyModifier_Regex(const char **pp, ModChain *ch) { struct ModifyWord_SubstRegexArgs args; bool oneBigWord; int error; LazyBuf reBuf, replaceBuf; FStr re; char delim = (*pp)[1]; if (delim == '\0') { Parse_Error(PARSE_FATAL, "Missing delimiter for modifier ':C'"); (*pp)++; return AMR_CLEANUP; } *pp += 2; if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, ch, &reBuf, NULL, NULL)) return AMR_CLEANUP; re = LazyBuf_DoneGet(&reBuf); if (!ParseModifierPart(pp, delim, delim, ch->expr->emode, ch, &replaceBuf, NULL, NULL)) { FStr_Done(&re); return AMR_CLEANUP; } args.replace = LazyBuf_Get(&replaceBuf); args.pflags = PatternFlags_None(); args.matched = false; oneBigWord = ch->oneBigWord; ParsePatternFlags(pp, &args.pflags, &oneBigWord); if (!ModChain_ShouldEval(ch)) goto done; error = regcomp(&args.re, re.str, REG_EXTENDED); if (error != 0) { RegexError(error, &args.re, "Regex compilation error"); LazyBuf_Done(&replaceBuf); FStr_Done(&re); return AMR_CLEANUP; } args.nsub = args.re.re_nsub + 1; if (args.nsub > 10) args.nsub = 10; ModifyWords(ch, ModifyWord_SubstRegex, &args, oneBigWord); regfree(&args.re); done: LazyBuf_Done(&replaceBuf); FStr_Done(&re); return AMR_OK; } #endif /* :Q, :q */ static ApplyModifierResult ApplyModifier_Quote(const char **pp, ModChain *ch) { LazyBuf buf; bool quoteDollar; quoteDollar = **pp == 'q'; if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; (*pp)++; if (!ModChain_ShouldEval(ch)) return AMR_OK; QuoteShell(Expr_Str(ch->expr), quoteDollar, &buf); if (buf.data != NULL) Expr_SetValue(ch->expr, LazyBuf_DoneGet(&buf)); else LazyBuf_Done(&buf); return AMR_OK; } static void ModifyWord_Copy(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) { SepBuf_AddSubstring(buf, word); } /* :ts */ static ApplyModifierResult ApplyModifier_ToSep(const char **pp, ModChain *ch) { const char *sep = *pp + 2; /* * Even in parse-only mode, apply the side effects, since the side * effects are neither observable nor is there a performance penalty. * Checking for VARE_EVAL for every single piece of code in here * would make the code in this function too hard to read. */ /* ":ts" or ":ts:" */ if (sep[0] != ch->endc && IsDelimiter(sep[1], ch)) { *pp = sep + 1; ch->sep = sep[0]; goto ok; } /* ":ts" or ":ts:" */ if (IsDelimiter(sep[0], ch)) { *pp = sep; ch->sep = '\0'; /* no separator */ goto ok; } /* ":ts". */ if (sep[0] != '\\') { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; } /* ":ts\n" */ if (sep[1] == 'n') { *pp = sep + 2; ch->sep = '\n'; goto ok; } /* ":ts\t" */ if (sep[1] == 't') { *pp = sep + 2; ch->sep = '\t'; goto ok; } /* ":ts\x40" or ":ts\100" */ { const char *p = sep + 1; int base = 8; /* assume octal */ if (sep[1] == 'x') { base = 16; p++; } else if (!ch_isdigit(sep[1])) { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; /* ":ts". */ } if (!TryParseChar(&p, base, &ch->sep)) { Parse_Error(PARSE_FATAL, "Invalid character number at \"%s\"", p); return AMR_CLEANUP; } if (!IsDelimiter(*p, ch)) { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; } *pp = p; } ok: ModifyWords(ch, ModifyWord_Copy, NULL, ch->oneBigWord); return AMR_OK; } static char * str_totitle(const char *str) { size_t i, n = strlen(str) + 1; char *res = bmake_malloc(n); for (i = 0; i < n; i++) { if (i == 0 || ch_isspace(res[i - 1])) res[i] = ch_toupper(str[i]); else res[i] = ch_tolower(str[i]); } return res; } static char * str_toupper(const char *str) { size_t i, n = strlen(str) + 1; char *res = bmake_malloc(n); for (i = 0; i < n; i++) res[i] = ch_toupper(str[i]); return res; } static char * str_tolower(const char *str) { size_t i, n = strlen(str) + 1; char *res = bmake_malloc(n); for (i = 0; i < n; i++) res[i] = ch_tolower(str[i]); return res; } /* :tA, :tu, :tl, :ts, etc. */ static ApplyModifierResult ApplyModifier_To(const char **pp, ModChain *ch) { Expr *expr = ch->expr; const char *mod = *pp; assert(mod[0] == 't'); if (IsDelimiter(mod[1], ch)) { *pp = mod + 1; return AMR_BAD; /* Found ":t" or ":t:". */ } if (mod[1] == 's') return ApplyModifier_ToSep(pp, ch); if (!IsDelimiter(mod[2], ch)) { /* :t */ *pp = mod + 1; return AMR_BAD; } if (mod[1] == 'A') { /* :tA */ *pp = mod + 2; ModifyWords(ch, ModifyWord_Realpath, NULL, ch->oneBigWord); return AMR_OK; } if (mod[1] == 't') { /* :tt */ *pp = mod + 2; if (Expr_ShouldEval(expr)) Expr_SetValueOwn(expr, str_totitle(Expr_Str(expr))); return AMR_OK; } if (mod[1] == 'u') { /* :tu */ *pp = mod + 2; if (Expr_ShouldEval(expr)) Expr_SetValueOwn(expr, str_toupper(Expr_Str(expr))); return AMR_OK; } if (mod[1] == 'l') { /* :tl */ *pp = mod + 2; if (Expr_ShouldEval(expr)) Expr_SetValueOwn(expr, str_tolower(Expr_Str(expr))); return AMR_OK; } if (mod[1] == 'W' || mod[1] == 'w') { /* :tW, :tw */ *pp = mod + 2; ch->oneBigWord = mod[1] == 'W'; return AMR_OK; } /* Found ":t:" or ":t". */ *pp = mod + 1; /* XXX: unnecessary but observable */ return AMR_BAD; } /* :[#], :[1], :[-1..1], etc. */ static ApplyModifierResult ApplyModifier_Words(const char **pp, ModChain *ch) { Expr *expr = ch->expr; int first, last; const char *p; LazyBuf argBuf; FStr arg; (*pp)++; /* skip the '[' */ if (!ParseModifierPart(pp, ']', ']', expr->emode, ch, &argBuf, NULL, NULL)) return AMR_CLEANUP; arg = LazyBuf_DoneGet(&argBuf); p = arg.str; if (!IsDelimiter(**pp, ch)) goto bad_modifier; /* Found junk after ']' */ if (!ModChain_ShouldEval(ch)) goto ok; if (p[0] == '\0') goto bad_modifier; /* Found ":[]". */ if (strcmp(p, "#") == 0) { /* Found ":[#]" */ if (ch->oneBigWord) Expr_SetValueRefer(expr, "1"); else { Buffer buf; SubstringWords words = Expr_Words(expr); size_t ac = words.len; SubstringWords_Free(words); Buf_Init(&buf); Buf_AddInt(&buf, (int)ac); Expr_SetValueOwn(expr, Buf_DoneData(&buf)); } goto ok; } if (strcmp(p, "*") == 0) { /* ":[*]" */ ch->oneBigWord = true; goto ok; } if (strcmp(p, "@") == 0) { /* ":[@]" */ ch->oneBigWord = false; goto ok; } /* Expect ":[N]" or ":[start..end]" */ if (!TryParseIntBase0(&p, &first)) goto bad_modifier; if (p[0] == '\0') /* ":[N]" */ last = first; else if (strncmp(p, "..", 2) == 0) { p += 2; if (!TryParseIntBase0(&p, &last) || *p != '\0') goto bad_modifier; } else goto bad_modifier; if (first == 0 && last == 0) { /* ":[0]" or ":[0..0]" */ ch->oneBigWord = true; goto ok; } if (first == 0 || last == 0) /* ":[0..N]" or ":[N..0]" */ goto bad_modifier; Expr_SetValueOwn(expr, VarSelectWords(Expr_Str(expr), first, last, ch->sep, ch->oneBigWord)); ok: FStr_Done(&arg); return AMR_OK; bad_modifier: FStr_Done(&arg); return AMR_BAD; } #if __STDC_VERSION__ >= 199901L || defined(HAVE_LONG_LONG_INT) # define NUM_TYPE long long # define PARSE_NUM_TYPE strtoll #else # define NUM_TYPE long # define PARSE_NUM_TYPE strtol #endif static NUM_TYPE num_val(Substring s) { NUM_TYPE val; char *ep; val = PARSE_NUM_TYPE(s.start, &ep, 0); if (ep != s.start) { switch (*ep) { case 'K': case 'k': val <<= 10; break; case 'M': case 'm': val <<= 20; break; case 'G': case 'g': val <<= 30; break; } } return val; } static int SubNumAsc(const void *sa, const void *sb) { NUM_TYPE a, b; a = num_val(*((const Substring *)sa)); b = num_val(*((const Substring *)sb)); return a > b ? 1 : b > a ? -1 : 0; } static int SubNumDesc(const void *sa, const void *sb) { return SubNumAsc(sb, sa); } static int Substring_Cmp(Substring a, Substring b) { for (; a.start < a.end && b.start < b.end; a.start++, b.start++) if (a.start[0] != b.start[0]) return (unsigned char)a.start[0] - (unsigned char)b.start[0]; return (int)((a.end - a.start) - (b.end - b.start)); } static int SubStrAsc(const void *sa, const void *sb) { return Substring_Cmp(*(const Substring *)sa, *(const Substring *)sb); } static int SubStrDesc(const void *sa, const void *sb) { return SubStrAsc(sb, sa); } static void ShuffleSubstrings(Substring *strs, size_t n) { size_t i; for (i = n - 1; i > 0; i--) { size_t rndidx = (size_t)random() % (i + 1); Substring t = strs[i]; strs[i] = strs[rndidx]; strs[rndidx] = t; } } /* * :O order ascending * :Or order descending * :Ox shuffle * :On numeric ascending * :Onr, :Orn numeric descending */ static ApplyModifierResult ApplyModifier_Order(const char **pp, ModChain *ch) { const char *mod = *pp; SubstringWords words; int (*cmp)(const void *, const void *); if (IsDelimiter(mod[1], ch)) { cmp = SubStrAsc; (*pp)++; } else if (IsDelimiter(mod[2], ch)) { if (mod[1] == 'n') cmp = SubNumAsc; else if (mod[1] == 'r') cmp = SubStrDesc; else if (mod[1] == 'x') cmp = NULL; else goto bad; *pp += 2; } else if (IsDelimiter(mod[3], ch)) { if ((mod[1] == 'n' && mod[2] == 'r') || (mod[1] == 'r' && mod[2] == 'n')) cmp = SubNumDesc; else goto bad; *pp += 3; } else goto bad; if (!ModChain_ShouldEval(ch)) return AMR_OK; words = Expr_Words(ch->expr); if (cmp == NULL) ShuffleSubstrings(words.words, words.len); else { assert(words.words[0].end[0] == '\0'); qsort(words.words, words.len, sizeof(words.words[0]), cmp); } Expr_SetValueOwn(ch->expr, SubstringWords_JoinFree(words)); return AMR_OK; bad: (*pp)++; return AMR_BAD; } /* :? then : else */ static ApplyModifierResult ApplyModifier_IfElse(const char **pp, ModChain *ch) { Expr *expr = ch->expr; LazyBuf thenBuf; LazyBuf elseBuf; VarEvalMode then_emode = VARE_PARSE; VarEvalMode else_emode = VARE_PARSE; + int parseErrorsBefore = parseErrors, parseErrorsAfter = parseErrors; - CondResult cond_rc = CR_TRUE; /* just not CR_ERROR */ + CondResult cond_rc = CR_TRUE; /* anything other than CR_ERROR */ if (Expr_ShouldEval(expr)) { evalStack.elems[evalStack.len - 1].kind = VSK_COND; cond_rc = Cond_EvalCondition(expr->name); if (cond_rc == CR_TRUE) then_emode = expr->emode; if (cond_rc == CR_FALSE) else_emode = expr->emode; + parseErrorsAfter = parseErrors; } evalStack.elems[evalStack.len - 1].kind = VSK_COND_THEN; (*pp)++; /* skip past the '?' */ if (!ParseModifierPart(pp, ':', ':', then_emode, ch, &thenBuf, NULL, NULL)) return AMR_CLEANUP; evalStack.elems[evalStack.len - 1].kind = VSK_COND_ELSE; if (!ParseModifierPart(pp, ch->endc, ch->endc, else_emode, ch, &elseBuf, NULL, NULL)) { LazyBuf_Done(&thenBuf); return AMR_CLEANUP; } (*pp)--; /* Go back to the ch->endc. */ if (cond_rc == CR_ERROR) { evalStack.elems[evalStack.len - 1].kind = VSK_COND; - Parse_Error(PARSE_FATAL, "Bad condition"); + if (parseErrorsAfter == parseErrorsBefore) + Parse_Error(PARSE_FATAL, "Bad condition"); LazyBuf_Done(&thenBuf); LazyBuf_Done(&elseBuf); return AMR_CLEANUP; } if (!Expr_ShouldEval(expr)) { LazyBuf_Done(&thenBuf); LazyBuf_Done(&elseBuf); } else if (cond_rc == CR_TRUE) { Expr_SetValue(expr, LazyBuf_DoneGet(&thenBuf)); LazyBuf_Done(&elseBuf); } else { LazyBuf_Done(&thenBuf); Expr_SetValue(expr, LazyBuf_DoneGet(&elseBuf)); } Expr_Define(expr); return AMR_OK; } /* * The ::= modifiers are special in that they do not read the variable value * but instead assign to that variable. They always expand to an empty * string. * * Their main purpose is in supporting .for loops that generate shell commands * since an ordinary variable assignment at that point would terminate the * dependency group for these targets. For example: * * list-targets: .USE * .for i in ${.TARGET} ${.TARGET:R}.gz * @${t::=$i} * @echo 'The target is ${t:T}.' * .endfor * * ::= Assigns as the new value of variable. * ::?= Assigns as value of variable if * it was not already set. * ::+= Appends to variable. * ::!= Assigns output of as the new value of * variable. */ static ApplyModifierResult ApplyModifier_Assign(const char **pp, ModChain *ch) { Expr *expr = ch->expr; GNode *scope; FStr val; LazyBuf buf; const char *mod = *pp; const char *op = mod + 1; if (op[0] == '=') goto found_op; if ((op[0] == '+' || op[0] == '?' || op[0] == '!') && op[1] == '=') goto found_op; return AMR_UNKNOWN; /* "::" */ found_op: if (expr->name[0] == '\0') { *pp = mod + 1; return AMR_BAD; } *pp = mod + (op[0] != '=' ? 3 : 2); if (!ParseModifierPart(pp, ch->endc, ch->endc, expr->emode, ch, &buf, NULL, NULL)) return AMR_CLEANUP; val = LazyBuf_DoneGet(&buf); (*pp)--; /* Go back to the ch->endc. */ if (!Expr_ShouldEval(expr)) goto done; scope = expr->scope; /* scope where v belongs */ if (expr->defined == DEF_REGULAR && expr->scope != SCOPE_GLOBAL && VarFind(expr->name, expr->scope, false) == NULL) scope = SCOPE_GLOBAL; if (op[0] == '+') Var_Append(scope, expr->name, val.str); else if (op[0] == '!') { char *output, *error; output = Cmd_Exec(val.str, &error); if (error != NULL) { Parse_Error(PARSE_WARNING, "%s", error); free(error); } else Var_Set(scope, expr->name, output); free(output); } else if (op[0] == '?' && expr->defined == DEF_REGULAR) { /* Do nothing. */ } else Var_Set(scope, expr->name, val.str); Expr_SetValueRefer(expr, ""); done: FStr_Done(&val); return AMR_OK; } /* * :_=... * remember current value */ static ApplyModifierResult ApplyModifier_Remember(const char **pp, ModChain *ch) { Expr *expr = ch->expr; const char *mod = *pp; FStr name; if (!ModMatchEq(mod, "_", ch)) return AMR_UNKNOWN; name = FStr_InitRefer("_"); if (mod[1] == '=') { /* * XXX: This ad-hoc call to strcspn deviates from the usual * behavior defined in ParseModifierPart. This creates an * unnecessary and undocumented inconsistency in make. */ const char *arg = mod + 2; size_t argLen = strcspn(arg, ":)}"); *pp = arg + argLen; name = FStr_InitOwn(bmake_strldup(arg, argLen)); } else *pp = mod + 1; if (Expr_ShouldEval(expr)) Var_Set(SCOPE_GLOBAL, name.str, Expr_Str(expr)); FStr_Done(&name); return AMR_OK; } /* * Apply the given function to each word of the variable value, * for a single-letter modifier such as :H, :T. */ static ApplyModifierResult ApplyModifier_WordFunc(const char **pp, ModChain *ch, ModifyWordProc modifyWord) { if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; (*pp)++; ModifyWords(ch, modifyWord, NULL, ch->oneBigWord); return AMR_OK; } /* Remove adjacent duplicate words. */ static ApplyModifierResult ApplyModifier_Unique(const char **pp, ModChain *ch) { SubstringWords words; if (!IsDelimiter((*pp)[1], ch)) return AMR_UNKNOWN; (*pp)++; if (!ModChain_ShouldEval(ch)) return AMR_OK; words = Expr_Words(ch->expr); if (words.len > 1) { size_t di, si; di = 0; for (si = 1; si < words.len; si++) { if (!Substring_Eq(words.words[si], words.words[di])) { di++; if (di != si) words.words[di] = words.words[si]; } } words.len = di + 1; } Expr_SetValueOwn(ch->expr, SubstringWords_JoinFree(words)); return AMR_OK; } /* Test whether the modifier has the form '='. */ static bool IsSysVModifier(const char *p, char startc, char endc) { bool eqFound = false; int depth = 1; while (*p != '\0' && depth > 0) { if (*p == '=') /* XXX: should also test depth == 1 */ eqFound = true; else if (*p == endc) depth--; else if (*p == startc) depth++; if (depth > 0) p++; } return *p == endc && eqFound; } /* :from=to */ static ApplyModifierResult ApplyModifier_SysV(const char **pp, ModChain *ch) { Expr *expr = ch->expr; LazyBuf lhsBuf, rhsBuf; FStr rhs; struct ModifyWord_SysVSubstArgs args; Substring lhs; const char *lhsSuffix; const char *mod = *pp; if (!IsSysVModifier(mod, ch->startc, ch->endc)) return AMR_UNKNOWN; if (!ParseModifierPart(pp, '=', '=', expr->emode, ch, &lhsBuf, NULL, NULL)) return AMR_CLEANUP; if (!ParseModifierPart(pp, ch->endc, ch->endc, expr->emode, ch, &rhsBuf, NULL, NULL)) { LazyBuf_Done(&lhsBuf); return AMR_CLEANUP; } rhs = LazyBuf_DoneGet(&rhsBuf); (*pp)--; /* Go back to the ch->endc. */ /* Do not turn an empty expression into non-empty. */ if (lhsBuf.len == 0 && Expr_Str(expr)[0] == '\0') goto done; lhs = LazyBuf_Get(&lhsBuf); lhsSuffix = Substring_SkipFirst(lhs, '%'); args.scope = expr->scope; args.lhsPrefix = Substring_Init(lhs.start, lhsSuffix != lhs.start ? lhsSuffix - 1 : lhs.start); args.lhsPercent = lhsSuffix != lhs.start; args.lhsSuffix = Substring_Init(lhsSuffix, lhs.end); args.rhs = rhs.str; ModifyWords(ch, ModifyWord_SysVSubst, &args, ch->oneBigWord); done: LazyBuf_Done(&lhsBuf); FStr_Done(&rhs); return AMR_OK; } /* :sh */ static ApplyModifierResult ApplyModifier_SunShell(const char **pp, ModChain *ch) { Expr *expr = ch->expr; const char *p = *pp; if (!(p[1] == 'h' && IsDelimiter(p[2], ch))) return AMR_UNKNOWN; *pp = p + 2; if (Expr_ShouldEval(expr)) { char *output, *error; output = Cmd_Exec(Expr_Str(expr), &error); if (error != NULL) { Parse_Error(PARSE_WARNING, "%s", error); free(error); } Expr_SetValueOwn(expr, output); } return AMR_OK; } /* * In cases where the evaluation mode and the definedness are the "standard" * ones, don't log them, to keep the logs readable. */ static bool ShouldLogInSimpleFormat(const Expr *expr) { - return (expr->emode == VARE_EVAL || expr->emode == VARE_EVAL_DEFINED) + return (expr->emode == VARE_EVAL + || expr->emode == VARE_EVAL_DEFINED + || expr->emode == VARE_EVAL_DEFINED_LOUD) && expr->defined == DEF_REGULAR; } static void LogBeforeApply(const ModChain *ch, const char *mod) { const Expr *expr = ch->expr; bool is_single_char = mod[0] != '\0' && IsDelimiter(mod[1], ch); /* * At this point, only the first character of the modifier can * be used since the end of the modifier is not yet known. */ if (!Expr_ShouldEval(expr)) { debug_printf("Parsing modifier ${%s:%c%s}\n", expr->name, mod[0], is_single_char ? "" : "..."); return; } if (ShouldLogInSimpleFormat(expr)) { debug_printf( "Evaluating modifier ${%s:%c%s} on value \"%s\"\n", expr->name, mod[0], is_single_char ? "" : "...", Expr_Str(expr)); return; } debug_printf( "Evaluating modifier ${%s:%c%s} on value \"%s\" (%s, %s)\n", expr->name, mod[0], is_single_char ? "" : "...", Expr_Str(expr), VarEvalMode_Name[expr->emode], ExprDefined_Name[expr->defined]); } static void LogAfterApply(const ModChain *ch, const char *p, const char *mod) { const Expr *expr = ch->expr; const char *value = Expr_Str(expr); const char *quot = value == var_Error ? "" : "\""; if (ShouldLogInSimpleFormat(expr)) { debug_printf("Result of ${%s:%.*s} is %s%s%s\n", expr->name, (int)(p - mod), mod, quot, value == var_Error ? "error" : value, quot); return; } debug_printf("Result of ${%s:%.*s} is %s%s%s (%s, %s)\n", expr->name, (int)(p - mod), mod, quot, value == var_Error ? "error" : value, quot, VarEvalMode_Name[expr->emode], ExprDefined_Name[expr->defined]); } static ApplyModifierResult ApplyModifier(const char **pp, ModChain *ch) { switch (**pp) { case '!': return ApplyModifier_ShellCommand(pp, ch); case ':': return ApplyModifier_Assign(pp, ch); case '?': return ApplyModifier_IfElse(pp, ch); case '@': return ApplyModifier_Loop(pp, ch); case '[': return ApplyModifier_Words(pp, ch); case '_': return ApplyModifier_Remember(pp, ch); #ifdef HAVE_REGEX_H case 'C': return ApplyModifier_Regex(pp, ch); #endif case 'D': case 'U': return ApplyModifier_Defined(pp, ch); case 'E': return ApplyModifier_WordFunc(pp, ch, ModifyWord_Suffix); case 'g': case 'l': return ApplyModifier_Time(pp, ch); case 'H': return ApplyModifier_WordFunc(pp, ch, ModifyWord_Head); case 'h': return ApplyModifier_Hash(pp, ch); case 'L': return ApplyModifier_Literal(pp, ch); case 'M': case 'N': return ApplyModifier_Match(pp, ch); case 'm': return ApplyModifier_Mtime(pp, ch); case 'O': return ApplyModifier_Order(pp, ch); case 'P': return ApplyModifier_Path(pp, ch); case 'Q': case 'q': return ApplyModifier_Quote(pp, ch); case 'R': return ApplyModifier_WordFunc(pp, ch, ModifyWord_Root); case 'r': return ApplyModifier_Range(pp, ch); case 'S': return ApplyModifier_Subst(pp, ch); case 's': return ApplyModifier_SunShell(pp, ch); case 'T': return ApplyModifier_WordFunc(pp, ch, ModifyWord_Tail); case 't': return ApplyModifier_To(pp, ch); case 'u': return ApplyModifier_Unique(pp, ch); default: return AMR_UNKNOWN; } } static void ApplyModifiers(Expr *, const char **, char, char); typedef enum ApplyModifiersIndirectResult { /* The indirect modifiers have been applied successfully. */ AMIR_CONTINUE, /* Fall back to the SysV modifier. */ AMIR_SYSV, /* Error out. */ AMIR_OUT } ApplyModifiersIndirectResult; /* * While expanding an expression, expand and apply indirect modifiers, * such as in ${VAR:${M_indirect}}. * * All indirect modifiers of a group must come from a single * expression. ${VAR:${M1}} is valid but ${VAR:${M1}${M2}} is not. * * Multiple groups of indirect modifiers can be chained by separating them * with colons. ${VAR:${M1}:${M2}} contains 2 indirect modifiers. * * If the expression is not followed by ch->endc or ':', fall * back to trying the SysV modifier, such as in ${VAR:${FROM}=${TO}}. */ static ApplyModifiersIndirectResult ApplyModifiersIndirect(ModChain *ch, const char **pp) { Expr *expr = ch->expr; const char *p = *pp; FStr mods = Var_Parse(&p, expr->scope, expr->emode); /* TODO: handle errors */ if (mods.str[0] != '\0' && !IsDelimiter(*p, ch)) { FStr_Done(&mods); return AMIR_SYSV; } DEBUG3(VAR, "Indirect modifier \"%s\" from \"%.*s\"\n", mods.str, (int)(p - *pp), *pp); if (ModChain_ShouldEval(ch) && mods.str[0] != '\0') { const char *modsp = mods.str; ApplyModifiers(expr, &modsp, '\0', '\0'); if (Expr_Str(expr) == var_Error || *modsp != '\0') { FStr_Done(&mods); *pp = p; return AMIR_OUT; /* error already reported */ } } FStr_Done(&mods); if (*p == ':') p++; else if (*p == '\0' && ch->endc != '\0') { Parse_Error(PARSE_FATAL, "Unclosed expression after indirect modifier, " "expecting '%c'", ch->endc); *pp = p; return AMIR_OUT; } *pp = p; return AMIR_CONTINUE; } static ApplyModifierResult ApplySingleModifier(const char **pp, ModChain *ch) { ApplyModifierResult res; const char *mod = *pp; const char *p = *pp; if (DEBUG(VAR)) LogBeforeApply(ch, mod); res = ApplyModifier(&p, ch); if (res == AMR_UNKNOWN) { assert(p == mod); res = ApplyModifier_SysV(&p, ch); } if (res == AMR_UNKNOWN) { /* * Guess the end of the current modifier. * XXX: Skipping the rest of the modifier hides * errors and leads to wrong results. * Parsing should rather stop here. */ for (p++; !IsDelimiter(*p, ch); p++) continue; Parse_Error(PARSE_FATAL, "Unknown modifier \"%.*s\"", (int)(p - mod), mod); Expr_SetValueRefer(ch->expr, var_Error); } if (res == AMR_CLEANUP || res == AMR_BAD) { *pp = p; return res; } if (DEBUG(VAR)) LogAfterApply(ch, p, mod); if (*p == '\0' && ch->endc != '\0') { Parse_Error(PARSE_FATAL, "Unclosed expression, expecting '%c' for " "modifier \"%.*s\"", ch->endc, (int)(p - mod), mod); } else if (*p == ':') { p++; } else if (opts.strict && *p != '\0' && *p != ch->endc) { Parse_Error(PARSE_FATAL, "Missing delimiter ':' after modifier \"%.*s\"", (int)(p - mod), mod); /* * TODO: propagate parse error to the enclosing * expression */ } *pp = p; return AMR_OK; } #if __STDC_VERSION__ >= 199901L #define ModChain_Init(expr, startc, endc, sep, oneBigWord) \ (ModChain) { expr, startc, endc, sep, oneBigWord } #else MAKE_INLINE ModChain ModChain_Init(Expr *expr, char startc, char endc, char sep, bool oneBigWord) { ModChain ch; ch.expr = expr; ch.startc = startc; ch.endc = endc; ch.sep = sep; ch.oneBigWord = oneBigWord; return ch; } #endif /* Apply any modifiers (such as :Mpattern or :@var@loop@ or :Q or ::=value). */ static void ApplyModifiers( Expr *expr, const char **pp, /* the parsing position, updated upon return */ char startc, /* '(' or '{'; or '\0' for indirect modifiers */ char endc /* ')' or '}'; or '\0' for indirect modifiers */ ) { ModChain ch = ModChain_Init(expr, startc, endc, ' ', false); const char *p; const char *mod; assert(startc == '(' || startc == '{' || startc == '\0'); assert(endc == ')' || endc == '}' || endc == '\0'); assert(Expr_Str(expr) != NULL); p = *pp; if (*p == '\0' && endc != '\0') { Parse_Error(PARSE_FATAL, "Unclosed expression, expecting '%c'", ch.endc); goto cleanup; } while (*p != '\0' && *p != endc) { ApplyModifierResult res; if (*p == '$') { /* * TODO: Only evaluate the expression once, no matter * whether it's an indirect modifier or the initial * part of a SysV modifier. */ ApplyModifiersIndirectResult amir = ApplyModifiersIndirect(&ch, &p); if (amir == AMIR_CONTINUE) continue; if (amir == AMIR_OUT) break; } mod = p; res = ApplySingleModifier(&p, &ch); if (res == AMR_CLEANUP) goto cleanup; if (res == AMR_BAD) goto bad_modifier; } *pp = p; assert(Expr_Str(expr) != NULL); /* Use var_Error or varUndefined. */ return; bad_modifier: /* Take a guess at where the modifier ends. */ Parse_Error(PARSE_FATAL, "Bad modifier \":%.*s\"", (int)strcspn(mod, ":)}"), mod); cleanup: /* * TODO: Use p + strlen(p) instead, to stop parsing immediately. * * In the unit tests, this generates a few shell commands with * unbalanced quotes. Instead of producing these incomplete strings, * commands with evaluation errors should not be run at all. * * To make that happen, Var_Subst must report the actual errors * instead of returning the resulting string unconditionally. */ *pp = p; Expr_SetValueRefer(expr, var_Error); } /* * Only 4 of the 7 built-in local variables are treated specially as they are * the only ones that will be set when dynamic sources are expanded. */ static bool VarnameIsDynamic(Substring varname) { const char *name; size_t len; name = varname.start; len = Substring_Length(varname); if (len == 1 || (len == 2 && (name[1] == 'F' || name[1] == 'D'))) { switch (name[0]) { case '@': case '%': case '*': case '!': return true; } return false; } if ((len == 7 || len == 8) && name[0] == '.' && ch_isupper(name[1])) { return Substring_Equals(varname, ".TARGET") || Substring_Equals(varname, ".ARCHIVE") || Substring_Equals(varname, ".PREFIX") || Substring_Equals(varname, ".MEMBER"); } return false; } static const char * UndefinedShortVarValue(char varname, const GNode *scope) { if (scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL) { /* * If substituting a local variable in a non-local scope, * assume it's for dynamic source stuff. We have to handle * this specially and return the longhand for the variable * with the dollar sign escaped so it makes it back to the * caller. Only four of the local variables are treated * specially as they are the only four that will be set * when dynamic sources are expanded. */ switch (varname) { case '@': return "$(.TARGET)"; case '%': return "$(.MEMBER)"; case '*': return "$(.PREFIX)"; case '!': return "$(.ARCHIVE)"; } } return NULL; } /* * Parse a variable name, until the end character or a colon, whichever * comes first. */ static void ParseVarname(const char **pp, char startc, char endc, GNode *scope, VarEvalMode emode, LazyBuf *buf) { const char *p = *pp; int depth = 0; LazyBuf_Init(buf, p); while (*p != '\0') { if ((*p == endc || *p == ':') && depth == 0) break; if (*p == startc) depth++; if (*p == endc) depth--; if (*p == '$') { FStr nested_val = Var_Parse(&p, scope, emode); /* TODO: handle errors */ LazyBuf_AddStr(buf, nested_val.str); FStr_Done(&nested_val); } else { LazyBuf_Add(buf, *p); p++; } } *pp = p; } static bool IsShortVarnameValid(char varname, const char *start) { if (varname != '$' && varname != ':' && varname != '}' && varname != ')' && varname != '\0') return true; if (!opts.strict) return false; /* XXX: Missing error message */ if (varname == '$' && save_dollars) Parse_Error(PARSE_FATAL, "To escape a dollar, use \\$, not $$, at \"%s\"", start); else if (varname == '\0') Parse_Error(PARSE_FATAL, "Dollar followed by nothing"); else if (save_dollars) Parse_Error(PARSE_FATAL, "Invalid variable name '%c', at \"%s\"", varname, start); return false; } /* * Parse a single-character variable name such as in $V or $@. * Return whether to continue parsing. */ static bool ParseVarnameShort(char varname, const char **pp, GNode *scope, VarEvalMode emode, const char **out_false_val, Var **out_true_var) { char name[2]; Var *v; const char *val; if (!IsShortVarnameValid(varname, *pp)) { (*pp)++; /* only skip the '$' */ *out_false_val = var_Error; return false; } name[0] = varname; name[1] = '\0'; v = VarFind(name, scope, true); if (v != NULL) { /* No need to advance *pp, the calling code handles this. */ *out_true_var = v; return true; } *pp += 2; val = UndefinedShortVarValue(varname, scope); if (val == NULL) - val = emode == VARE_EVAL_DEFINED ? var_Error : varUndefined; + val = emode == VARE_EVAL_DEFINED + || emode == VARE_EVAL_DEFINED_LOUD + ? var_Error : varUndefined; - if (opts.strict && val == var_Error) { + if ((opts.strict || emode == VARE_EVAL_DEFINED_LOUD) + && val == var_Error) { Parse_Error(PARSE_FATAL, "Variable \"%s\" is undefined", name); } *out_false_val = val; return false; } /* Find variables like @F or ", varname.start[0]) == NULL) return NULL; v = VarFindSubstring(Substring_Init(varname.start, varname.start + 1), scope, false); if (v == NULL) return NULL; *out_extraModifiers = varname.start[1] == 'D' ? "H:" : "T:"; return v; } static FStr EvalUndefined(bool dynamic, const char *start, const char *p, Substring varname, VarEvalMode emode) { if (dynamic) return FStr_InitOwn(bmake_strsedup(start, p)); - if (emode == VARE_EVAL_DEFINED && opts.strict) { + if (emode == VARE_EVAL_DEFINED_LOUD + || (emode == VARE_EVAL_DEFINED && opts.strict)) { Parse_Error(PARSE_FATAL, "Variable \"%.*s\" is undefined", (int)Substring_Length(varname), varname.start); return FStr_InitRefer(var_Error); } return FStr_InitRefer( - emode == VARE_EVAL_DEFINED ? var_Error : varUndefined); + emode == VARE_EVAL_DEFINED_LOUD || emode == VARE_EVAL_DEFINED + ? var_Error : varUndefined); } /* * Parse a long variable name enclosed in braces or parentheses such as $(VAR) * or ${VAR}, up to the closing brace or parenthesis, or in the case of * ${VAR:Modifiers}, up to the ':' that starts the modifiers. * Return whether to continue parsing. */ static bool ParseVarnameLong( const char **pp, char startc, GNode *scope, VarEvalMode emode, + VarEvalMode nested_emode, const char **out_false_pp, FStr *out_false_val, char *out_true_endc, Var **out_true_v, bool *out_true_haveModifier, const char **out_true_extraModifiers, bool *out_true_dynamic, ExprDefined *out_true_exprDefined ) { LazyBuf varname; Substring name; Var *v; bool haveModifier; bool dynamic = false; const char *p = *pp; const char *start = p; char endc = startc == '(' ? ')' : '}'; p += 2; /* skip "${" or "$(" or "y(" */ - ParseVarname(&p, startc, endc, scope, emode, &varname); + ParseVarname(&p, startc, endc, scope, nested_emode, &varname); name = LazyBuf_Get(&varname); if (*p == ':') haveModifier = true; else if (*p == endc) haveModifier = false; else { Parse_Error(PARSE_FATAL, "Unclosed variable \"%.*s\"", (int)Substring_Length(name), name.start); LazyBuf_Done(&varname); *out_false_pp = p; *out_false_val = FStr_InitRefer(var_Error); return false; } v = VarFindSubstring(name, scope, true); /* * At this point, p points just after the variable name, either at * ':' or at endc. */ if (v == NULL && Substring_Equals(name, ".SUFFIXES")) { char *suffixes = Suff_NamesStr(); v = VarNew(FStr_InitRefer(".SUFFIXES"), suffixes, true, false, true); free(suffixes); } else if (v == NULL) v = FindLocalLegacyVar(name, scope, out_true_extraModifiers); if (v == NULL) { /* * Defer expansion of dynamic variables if they appear in * non-local scope since they are not defined there. */ dynamic = VarnameIsDynamic(name) && (scope == SCOPE_CMDLINE || scope == SCOPE_GLOBAL); if (!haveModifier) { p++; /* skip endc */ *out_false_pp = p; *out_false_val = EvalUndefined(dynamic, start, p, name, emode); LazyBuf_Done(&varname); return false; } /* * The expression is based on an undefined variable. * Nevertheless it needs a Var, for modifiers that access the * variable name, such as :L or :?. * * Most modifiers leave this expression in the "undefined" * state (DEF_UNDEF), only a few modifiers like :D, :U, :L, * :P turn this undefined expression into a defined * expression (DEF_DEFINED). * * In the end, after applying all modifiers, if the expression * is still undefined, Var_Parse will return an empty string * instead of the actually computed value. */ v = VarNew(LazyBuf_DoneGet(&varname), "", true, false, false); *out_true_exprDefined = DEF_UNDEF; } else LazyBuf_Done(&varname); *pp = p; *out_true_endc = endc; *out_true_v = v; *out_true_haveModifier = haveModifier; *out_true_dynamic = dynamic; return true; } #if __STDC_VERSION__ >= 199901L #define Expr_Init(name, value, emode, scope, defined) \ (Expr) { name, value, emode, scope, defined } #else MAKE_INLINE Expr Expr_Init(const char *name, FStr value, VarEvalMode emode, GNode *scope, ExprDefined defined) { Expr expr; expr.name = name; expr.value = value; expr.emode = emode; expr.scope = scope; expr.defined = defined; return expr; } #endif /* * Expressions of the form ${:U...} with a trivial value are often generated * by .for loops and are boring, so evaluate them without debug logging. */ static bool Var_Parse_U(const char **pp, VarEvalMode emode, FStr *out_value) { const char *p; p = *pp; if (!(p[0] == '$' && p[1] == '{' && p[2] == ':' && p[3] == 'U')) return false; p += 4; while (*p != '$' && *p != '{' && *p != ':' && *p != '\\' && *p != '}' && *p != '\0') p++; if (*p != '}') return false; *out_value = emode == VARE_PARSE ? FStr_InitRefer("") : FStr_InitOwn(bmake_strsedup(*pp + 4, p)); *pp = p + 1; return true; } /* * Given the start of an expression (such as $v, $(VAR), ${VAR:Mpattern}), * extract the variable name and the modifiers, if any. While parsing, apply * the modifiers to the value of the expression. * * Input: * *pp The string to parse. * When called from CondParser_FuncCallEmpty, it can * also point to the "y" of "empty(VARNAME:Modifiers)". * scope The scope for finding variables. * emode Controls the exact details of parsing and evaluation. * * Output: * *pp The position where to continue parsing. * TODO: After a parse error, the value of *pp is * unspecified. It may not have been updated at all, * point to some random character in the string, to the * location of the parse error, or at the end of the * string. * return The value of the expression, never NULL. * return var_Error if there was a parse error. * return var_Error if the base variable of the expression was * undefined, emode is VARE_EVAL_DEFINED, and none of * the modifiers turned the undefined expression into a * defined expression. * XXX: It is not guaranteed that an error message has * been printed. * return varUndefined if the base variable of the expression * was undefined, emode was not VARE_EVAL_DEFINED, * and none of the modifiers turned the undefined * expression into a defined expression. - * XXX: It is not guaranteed that an error message has - * been printed. */ FStr Var_Parse(const char **pp, GNode *scope, VarEvalMode emode) { const char *start, *p; bool haveModifier; /* true for ${VAR:...}, false for ${VAR} */ char startc; /* the actual '{' or '(' or '\0' */ char endc; /* the expected '}' or ')' or '\0' */ /* * true if the expression is based on one of the 7 predefined * variables that are local to a target, and the expression is * expanded in a non-local scope. The result is the text of the * expression, unaltered. This is needed to support dynamic sources. */ bool dynamic; const char *extramodifiers; Var *v; - Expr expr = Expr_Init(NULL, FStr_InitRefer(NULL), emode, + Expr expr = Expr_Init(NULL, FStr_InitRefer(NULL), + emode == VARE_EVAL_DEFINED || emode == VARE_EVAL_DEFINED_LOUD + ? VARE_EVAL : emode, scope, DEF_REGULAR); FStr val; if (Var_Parse_U(pp, emode, &val)) return val; p = *pp; start = p; DEBUG2(VAR, "Var_Parse: %s (%s)\n", start, VarEvalMode_Name[emode]); val = FStr_InitRefer(NULL); extramodifiers = NULL; /* extra modifiers to apply first */ dynamic = false; endc = '\0'; /* Appease GCC. */ startc = p[1]; if (startc != '(' && startc != '{') { if (!ParseVarnameShort(startc, pp, scope, emode, &val.str, &v)) return val; haveModifier = false; p++; } else { - if (!ParseVarnameLong(&p, startc, scope, emode, + if (!ParseVarnameLong(&p, startc, scope, emode, expr.emode, pp, &val, &endc, &v, &haveModifier, &extramodifiers, &dynamic, &expr.defined)) return val; } expr.name = v->name.str; if (v->inUse && VarEvalMode_ShouldEval(emode)) { - if (scope->fname != NULL) { - fprintf(stderr, "In a command near "); - PrintLocation(stderr, false, scope); - } - Fatal("Variable %s is recursive.", v->name.str); + Parse_Error(PARSE_FATAL, "Variable %s is recursive.", + v->name.str); + FStr_Done(&val); + if (*p != '\0') + p++; + *pp = p; + return FStr_InitRefer(var_Error); } /* * FIXME: This assignment creates an alias to the current value of the * variable. This means that as long as the value of the expression * stays the same, the value of the variable must not change, and the * variable must not be deleted. Using the ':@' modifier, it is * possible (since var.c 1.212 from 2017-02-01) to delete the variable * while its value is still being used: * * VAR= value * _:= ${VAR:${:U:@VAR@@}:S,^,prefix,} * * The same effect might be achievable using the '::=' or the ':_' * modifiers. * * At the bottom of this function, the resulting value is compared to * the then-current value of the variable. This might also invoke * undefined behavior. */ expr.value = FStr_InitRefer(v->val.data); if (!VarEvalMode_ShouldEval(emode)) EvalStack_Push(VSK_EXPR_PARSE, start, NULL); else if (expr.name[0] != '\0') EvalStack_Push(VSK_VARNAME, expr.name, &expr.value); else EvalStack_Push(VSK_EXPR, start, &expr.value); /* * Before applying any modifiers, expand any nested expressions from * the variable value. */ if (VarEvalMode_ShouldEval(emode) && strchr(Expr_Str(&expr), '$') != NULL) { char *expanded; - VarEvalMode nested_emode = emode; - if (opts.strict) - nested_emode = VarEvalMode_UndefOk(nested_emode); v->inUse = true; - expanded = Var_Subst(Expr_Str(&expr), scope, nested_emode); + expanded = Var_Subst(Expr_Str(&expr), scope, expr.emode); v->inUse = false; /* TODO: handle errors */ Expr_SetValueOwn(&expr, expanded); } if (extramodifiers != NULL) { const char *em = extramodifiers; ApplyModifiers(&expr, &em, '\0', '\0'); } if (haveModifier) { p++; /* Skip initial colon. */ ApplyModifiers(&expr, &p, startc, endc); } if (*p != '\0') /* Skip past endc if possible. */ p++; *pp = p; if (expr.defined == DEF_UNDEF) { if (dynamic) Expr_SetValueOwn(&expr, bmake_strsedup(start, p)); else { - /* - * The expression is still undefined, therefore - * discard the actual value and return an error marker - * instead. - */ Expr_SetValueRefer(&expr, emode == VARE_EVAL_DEFINED + || emode == VARE_EVAL_DEFINED_LOUD ? var_Error : varUndefined); } } if (v->shortLived) { if (expr.value.str == v->val.data) { /* move ownership */ expr.value.freeIt = v->val.data; v->val.data = NULL; } VarFreeShortLived(v); } EvalStack_Pop(); return expr.value; } static void VarSubstDollarDollar(const char **pp, Buffer *res, VarEvalMode emode) { /* A dollar sign may be escaped with another dollar sign. */ if (save_dollars && VarEvalMode_ShouldKeepDollar(emode)) Buf_AddByte(res, '$'); Buf_AddByte(res, '$'); *pp += 2; } static void -VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, - VarEvalMode emode, bool *inout_errorReported) +VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, VarEvalMode emode) { const char *p = *pp; const char *nested_p = p; FStr val = Var_Parse(&nested_p, scope, emode); /* TODO: handle errors */ if (val.str == var_Error || val.str == varUndefined) { - if (!VarEvalMode_ShouldKeepUndef(emode)) { - p = nested_p; - } else if (val.str == var_Error) { - - /* - * FIXME: The condition 'val.str == var_Error' doesn't - * mean there was an undefined variable. It could - * equally well be a parse error; see - * unit-tests/varmod-order.mk. - */ - - /* - * If variable is undefined, complain and skip the - * variable. The complaint will stop us from doing - * anything when the file is parsed. - */ - if (!*inout_errorReported) { - Parse_Error(PARSE_FATAL, - "Undefined variable \"%.*s\"", - (int)(nested_p - p), p); - *inout_errorReported = true; - } + if (!VarEvalMode_ShouldKeepUndef(emode) + || val.str == var_Error) { p = nested_p; } else { /* * Copy the initial '$' of the undefined expression, * thereby deferring expansion of the expression, but * expand nested expressions if already possible. See * unit-tests/varparse-undef-partial.mk. */ Buf_AddByte(buf, *p); p++; } } else { p = nested_p; Buf_AddStr(buf, val.str); } FStr_Done(&val); *pp = p; } /* * Skip as many characters as possible -- either to the end of the string, * or to the next dollar sign, which may start an expression. */ static void VarSubstPlain(const char **pp, Buffer *res) { const char *p = *pp; const char *start = p; for (p++; *p != '$' && *p != '\0'; p++) continue; Buf_AddRange(res, start, p); *pp = p; } /* * Expand all expressions like $V, ${VAR}, $(VAR:Modifiers) in the * given string. * * Input: * str The string in which the expressions are expanded. * scope The scope in which to start searching for variables. * The other scopes are searched as well. * emode The mode for parsing or evaluating subexpressions. */ char * Var_Subst(const char *str, GNode *scope, VarEvalMode emode) { const char *p = str; Buffer res; - /* - * Set true if an error has already been reported, to prevent a - * plethora of messages when recursing - */ - static bool errorReported; - Buf_Init(&res); - errorReported = false; while (*p != '\0') { if (p[0] == '$' && p[1] == '$') VarSubstDollarDollar(&p, &res, emode); else if (p[0] == '$') - VarSubstExpr(&p, &res, scope, emode, &errorReported); + VarSubstExpr(&p, &res, scope, emode); else VarSubstPlain(&p, &res); } return Buf_DoneData(&res); } char * Var_SubstInTarget(const char *str, GNode *scope) { char *res; EvalStack_Push(VSK_TARGET, scope->name, NULL); res = Var_Subst(str, scope, VARE_EVAL); EvalStack_Pop(); return res; } void Var_Expand(FStr *str, GNode *scope, VarEvalMode emode) { char *expanded; if (strchr(str->str, '$') == NULL) return; expanded = Var_Subst(str->str, scope, emode); /* TODO: handle errors */ FStr_Done(str); *str = FStr_InitOwn(expanded); } void Var_Stats(void) { HashTable_DebugStats(&SCOPE_GLOBAL->vars, "Global variables"); } static int StrAsc(const void *sa, const void *sb) { return strcmp( *((const char *const *)sa), *((const char *const *)sb)); } /* Print all variables in a scope, sorted by name. */ void Var_Dump(GNode *scope) { Vector /* of const char * */ vec; HashIter hi; size_t i; const char **varnames; Vector_Init(&vec, sizeof(const char *)); HashIter_Init(&hi, &scope->vars); while (HashIter_Next(&hi)) *(const char **)Vector_Push(&vec) = hi.entry->key; varnames = vec.items; qsort(varnames, vec.len, sizeof varnames[0], StrAsc); for (i = 0; i < vec.len; i++) { const char *varname = varnames[i]; const Var *var = HashTable_FindValue(&scope->vars, varname); debug_printf("%-16s = %s%s\n", varname, var->val.data, ValueDescription(var->val.data)); } Vector_Done(&vec); } diff --git a/usr.bin/bmake/Makefile b/usr.bin/bmake/Makefile index b243cd0275b6..43e1ef99b635 100644 --- a/usr.bin/bmake/Makefile +++ b/usr.bin/bmake/Makefile @@ -1,180 +1,203 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # -SRCTOP?= ${.CURDIR:H:H} +SRCTOP ?= ${.CURDIR:H:H} # look here first for config.h -CFLAGS+= -I${.CURDIR} +CFLAGS += -I${.CURDIR} # for after-import -CLEANDIRS+= FreeBSD -CLEANFILES+= bootstrap +CLEANDIRS += FreeBSD +CLEANFILES += bootstrap -# $Id: Makefile,v 1.127 2024/03/19 16:03:23 sjg Exp $ +# $Id: Makefile,v 1.132 2024/09/22 19:56:26 sjg Exp $ -PROG?= ${.CURDIR:T} +PROG ?= ${.CURDIR:T} -SRCS= \ +SRCS = \ arch.c \ buf.c \ compat.c \ cond.c \ dir.c \ for.c \ hash.c \ job.c \ lst.c \ main.c \ make.c \ make_malloc.c \ meta.c \ metachar.c \ parse.c \ str.c \ suff.c \ targ.c \ trace.c \ util.c \ var.c -.sinclude "Makefile.inc" +.MAIN: all + +MAN = ${PROG}.1 +SRCS.${MAN} = ${srcdir}/make.1 + +.-include "Makefile.inc" # this file gets generated by configure -.sinclude "Makefile.config" +.-include "Makefile.config" .if !empty(LIBOBJS) -SRCS+= ${LIBOBJS:T:.o=.c} +SRCS += ${LIBOBJS:T:.o =.c} .endif # just in case -prefix?= /usr -srcdir?= ${.CURDIR} +prefix ?= /usr +srcdir ?= ${.PARSEDIR} +srcdir : = ${srcdir} -DEFAULT_SYS_PATH?= ${prefix}/share/mk +DEFAULT_SYS_PATH ?= ${prefix}/share/mk -CPPFLAGS+= -DUSE_META -CFLAGS+= ${CPPFLAGS} -CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\" -CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE -CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} -COPTS.main.c+= "-DMAKE_VERSION=\"${_MAKE_VERSION}\"" +CPPFLAGS += -DUSE_META +CFLAGS += ${CPPFLAGS} +CFLAGS += -D_PATH_DEFSYSPATH =\"${DEFAULT_SYS_PATH}\" +CFLAGS += -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE +CFLAGS += ${COPTS.${.ALLSRC:M*.c:T:u}} +COPTS.main.c += "-DMAKE_VERSION =\"${_MAKE_VERSION}\"" .for x in FORCE_MAKE_OS FORCE_MACHINE FORCE_MACHINE_ARCH .ifdef $x -COPTS.main.c+= "-D$x=\"${$x}\"" +COPTS.main.c += "-D$x =\"${$x}\"" .endif .endfor # meta mode can be useful even without filemon # should be set by now USE_FILEMON ?= no -.if ${USE_FILEMON:tl} != "no" +.if ${USE_FILEMON:tl} ! = "no" .PATH: ${srcdir}/filemon -SRCS+= filemon_${USE_FILEMON}.c -COPTS.meta.c+= -DUSE_FILEMON -DUSE_FILEMON_${USE_FILEMON:tu} -COPTS.job.c+= ${COPTS.meta.c} +SRCS += filemon_${USE_FILEMON}.c +COPTS.meta.c += -DUSE_FILEMON -DUSE_FILEMON_${USE_FILEMON:tu} +COPTS.job.c += ${COPTS.meta.c} -.if ${USE_FILEMON} == "dev" +.if ${USE_FILEMON} = = "dev" FILEMON_H ?= /usr/include/dev/filemon/filemon.h -.if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" +.if exists(${FILEMON_H}) && ${FILEMON_H:T} = = "filemon.h" COPTS.filemon_dev.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} .endif -.elif ${USE_FILEMON} == "ktrace" -COPTS.filemon_ktrace.c += -Wno-error=unused-parameter +.elif ${USE_FILEMON} = = "ktrace" +COPTS.filemon_ktrace.c += -Wno-error =unused-parameter .endif .endif # USE_FILEMON .PATH: ${srcdir} + .if make(obj) || make(clean) -SUBDIR+= unit-tests +SUBDIR.${MK_TESTS} += unit-tests .endif +MAN1 = ${MAN} + +.if ${MK_GEN_MAN:Uno} = = "yes" + +# we use this to generate ${MAN} +.include <${srcdir}/mk/genfiles.mk> -MAN= ${PROG}.1 -MAN1= ${MAN} +.if ${PROG} ! = "make" +CLEANFILES += my.history +SED_CMDS.${MAN} += \ + -e '/^.Dt/s/MAKE/${PROG:tu}/' \ + -e '/^.Nm/s/make/${PROG}/' \ + +.endif -.if ${PROG} != "make" -CLEANFILES+= my.history -.if make(${MAN}) || !exists(${srcdir}/${MAN}) +.if ${CLEANFILES:U:Mmy.history} ! = "" +${MAN}: my.history my.history: @(echo ".Nm"; \ echo "is derived from NetBSD"; \ echo ".Xr make 1 ."; \ echo "It uses autoconf to facilitate portability to other platforms."; \ echo ".Pp") > $@ -.NOPATH: ${MAN} -${MAN}: make.1 my.history - @echo making $@ - @sed \ - -e '/^.Dt/s/MAKE/${PROG:tu}/' \ - -e 's/^.Nx/NetBSD/' \ - -e '/^.Nm/s/make/${PROG}/' \ +SED_CMDS.${MAN} += \ -e '/^.Sh HISTORY/rmy.history' \ - -e '/^.Sh HISTORY/,$$s,^.Nm,make,' ${srcdir}/make.1 > $@ + -e '/^.Sh HISTORY/,/BUGS/s,^.Nm,make,' \ + +.endif -all beforeinstall: ${MAN} -_mfromdir=. +.if ${.MAKE.OS:N*BSD} ! = "" +# assume .Nx is not supported +SED_CMDS.${MAN} += -e 's/^\.Nx/NetBSD/' .endif + +# watch out for a late change of PROG +.if !empty(SRCS.${MAN}) +.NOPATH: ${MAN} +${MAN}: ${SRCS.${MAN}} _GENFILES_USE + +all man beforeinstall: ${MAN} +_mfromdir =. .endif +.endif # MK_GEN_MAN -MANTARGET?= cat -MANDEST?= ${MANDIR}/${MANTARGET}1 +MANTARGET ?= cat +MANDEST ?= ${MANDIR}/${MANTARGET}1 -.if ${MANTARGET} == "cat" -_mfromdir=${srcdir} +.if ${MANTARGET} = = "cat" +_mfromdir = ${srcdir} .endif .include -CPPFLAGS+= -DMAKE_NATIVE -DHAVE_CONFIG_H +CPPFLAGS += -DMAKE_NATIVE -DHAVE_CONFIG_H COPTS.var.c += -Wno-cast-qual COPTS.job.c += -Wno-format-nonliteral COPTS.parse.c += -Wno-format-nonliteral COPTS.var.c += -Wno-format-nonliteral # Force these -SHAREDIR= ${SHAREDIR.bmake:U${prefix}/share} -BINDIR= ${BINDIR.bmake:U${prefix}/bin} -MANDIR= ${MANDIR.bmake:U${SHAREDIR}/man} +SHAREDIR = ${SHAREDIR.bmake:U${prefix}/share} +BINDIR = ${BINDIR.bmake:U${prefix}/bin} +MANDIR = ${MANDIR.bmake:U${SHAREDIR}/man} ${OBJS}: config.h # A simple unit-test driver to help catch regressions TEST_MAKE ?= ${.OBJDIR}/${PROG:T} accept test: .NOMETA cd ${.CURDIR}/unit-tests && \ - MAKEFLAGS= ${TEST_MAKE} -r -m / ${.TARGET} ${TESTS:DTESTS=${TESTS:Q}} + MAKEFLAGS = ${TEST_MAKE} -r -m / ${.TARGET} ${TESTS:DTESTS =${TESTS:Q}} -.if make(test) && ${MK_AUTO_OBJ} == "yes" +.if make(test) && ${MK_AUTO_OBJ} = = "yes" # The test target above visits unit-tests with -r -m / # which prevents MK_AUTO_OBJ doing its job # so do it here -.if defined(MAKEOBJDIRPREFIX) || ${MAKEOBJDIR:U:M*/*} != "" +.if defined(MAKEOBJDIRPREFIX) || ${MAKEOBJDIR:U:M*/*} ! = "" _utobj = ${.OBJDIR}/unit-tests .else _utobj = ${.CURDIR}/unit-tests/${MAKEOBJDIR:Uobj} .endif utobj: .NOMETA @test -d ${_utobj} && exit 0; \ echo "[Creating ${_utobj}...]"; \ umask ${OBJDIR_UMASK:U002}; \ mkdir -p ${_utobj} test: utobj .endif # override some simple things -BINDIR= /usr/bin -MANDIR= /usr/share/man/man +BINDIR = /usr/bin +MANDIR = /usr/share/man/man # make sure we get this -CFLAGS+= ${COPTS.${.IMPSRC:T}} +CFLAGS += ${COPTS.${.IMPSRC:T}} after-import: ${SRCTOP}/contrib/bmake/bsd.after-import.mk cd ${.CURDIR} && ${.MAKE} -f ${SRCTOP}/contrib/bmake/bsd.after-import.mk diff --git a/usr.bin/bmake/Makefile.config b/usr.bin/bmake/Makefile.config index 75ceb4a50e15..9ca3691cbb2a 100644 --- a/usr.bin/bmake/Makefile.config +++ b/usr.bin/bmake/Makefile.config @@ -1,28 +1,28 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # SRCTOP?= ${.CURDIR:H:H} # things set by configure -_MAKE_VERSION?=20240711 +_MAKE_VERSION?=20250125 prefix?= /usr srcdir= ${SRCTOP}/contrib/bmake CC?= cc MAKE_OS?= DEFAULT_SYS_PATH?= .../share/mk:/usr/share/mk EGREP = egrep CPPFLAGS+= CFLAGS+= ${CPPFLAGS} -DHAVE_CONFIG_H LDFLAGS+= LIBOBJS+= ${LIBOBJDIR}stresep$U.o LDADD+= USE_META?= yes USE_FILEMON?= dev FILEMON_H?= /usr/include/dev/filemon/filemon.h BMAKE_PATH_MAX?= 1024 # used if MAXPATHLEN not defined CPPFLAGS+= -DBMAKE_PATH_MAX=${BMAKE_PATH_MAX} diff --git a/usr.bin/bmake/unit-tests/Makefile b/usr.bin/bmake/unit-tests/Makefile index d70c0f87e459..7011ab754b9e 100644 --- a/usr.bin/bmake/unit-tests/Makefile +++ b/usr.bin/bmake/unit-tests/Makefile @@ -1,911 +1,918 @@ # This is a generated file, do NOT edit! # See contrib/bmake/bsd.after-import.mk # -# $Id: Makefile,v 1.224 2024/07/13 05:27:35 sjg Exp $ +# $Id: Makefile,v 1.230 2025/01/30 18:04:20 sjg Exp $ # -# $NetBSD: Makefile,v 1.350 2024/07/07 09:37:00 rillig Exp $ +# $NetBSD: Makefile,v 1.355 2025/01/25 22:46:30 sjg Exp $ # # Unit tests for make(1) # # The main targets are: # # all: # run all the tests # test: # run 'all', and compare to expected results # accept: # move generated output to expected results # # Settable variables # # TEST_MAKE # The make program to be tested. # # # Adding a test case # # Each feature should get its own set of tests in its own suitably # named makefile (*.mk), with its own set of expected results (*.exp), # and it should be added to the TESTS list. # .MAIN: all # we use these below but we might be an older make .MAKE.OS?= ${uname -s:L:sh} .MAKE.UID?= ${id -u:L:sh} # for many tests we need a TMPDIR that will not collide # with other users. .if ${.OBJDIR} != ${.CURDIR} # easy TMPDIR:= ${.OBJDIR}/tmp .elif defined(TMPDIR) TMPDIR:= ${TMPDIR}/uid${.MAKE.UID} .else TMPDIR:= /tmp/uid${.MAKE.UID} .endif # make sure it exists .if !exist(${TMPDIR}) _!= mkdir -p ${TMPDIR} .endif +# and clean it up - outside the context of +# any target that might be using it. +.END: rm-tmpdir +rm-tmpdir: .NOMETA + @rm -rf ${TMPDIR} # Each test is in a sub-makefile. # Keep the list sorted. # Any test that is commented out must be ignored in # src/tests/usr.bin/make/t_make.sh as well. #TESTS+= archive #TESTS+= archive-suffix TESTS+= cmd-errors TESTS+= cmd-errors-jobs TESTS+= cmd-errors-lint TESTS+= cmd-interrupt TESTS+= cmdline TESTS+= cmdline-redirect-stdin TESTS+= cmdline-undefined TESTS+= comment TESTS+= compat-error TESTS+= cond-cmp-numeric TESTS+= cond-cmp-numeric-eq TESTS+= cond-cmp-numeric-ge TESTS+= cond-cmp-numeric-gt TESTS+= cond-cmp-numeric-le TESTS+= cond-cmp-numeric-lt TESTS+= cond-cmp-numeric-ne TESTS+= cond-cmp-string TESTS+= cond-cmp-unary TESTS+= cond-eof TESTS+= cond-func TESTS+= cond-func-commands TESTS+= cond-func-defined TESTS+= cond-func-empty TESTS+= cond-func-exists TESTS+= cond-func-make TESTS+= cond-func-make-main TESTS+= cond-func-target TESTS+= cond-late TESTS+= cond-op TESTS+= cond-op-and TESTS+= cond-op-and-lint TESTS+= cond-op-not TESTS+= cond-op-or TESTS+= cond-op-or-lint TESTS+= cond-op-parentheses TESTS+= cond-short TESTS+= cond-token-number TESTS+= cond-token-plain TESTS+= cond-token-string TESTS+= cond-token-var TESTS+= cond-undef-lint TESTS+= counter TESTS+= counter-append TESTS+= dep TESTS+= dep-colon TESTS+= dep-colon-bug-cross-file TESTS+= dep-double-colon TESTS+= dep-double-colon-indep TESTS+= dep-duplicate TESTS+= dep-exclam TESTS+= dep-none TESTS+= dep-op-missing TESTS+= dep-percent TESTS+= dep-var TESTS+= dep-wildcards TESTS+= depsrc TESTS+= depsrc-end TESTS+= depsrc-exec TESTS+= depsrc-ignore TESTS+= depsrc-made TESTS+= depsrc-make TESTS+= depsrc-meta TESTS+= depsrc-nometa TESTS+= depsrc-nometa_cmp TESTS+= depsrc-nopath TESTS+= depsrc-notmain TESTS+= depsrc-optional TESTS+= depsrc-phony TESTS+= depsrc-precious TESTS+= depsrc-recursive TESTS+= depsrc-silent TESTS+= depsrc-use TESTS+= depsrc-usebefore TESTS+= depsrc-usebefore-double-colon TESTS+= depsrc-wait TESTS+= deptgt TESTS+= deptgt-begin TESTS+= deptgt-begin-fail TESTS+= deptgt-begin-fail-indirect TESTS+= deptgt-default TESTS+= deptgt-delete_on_error TESTS+= deptgt-end TESTS+= deptgt-end-fail TESTS+= deptgt-end-fail-all TESTS+= deptgt-end-fail-indirect TESTS+= deptgt-end-jobs TESTS+= deptgt-error TESTS+= deptgt-ignore TESTS+= deptgt-interrupt TESTS+= deptgt-main TESTS+= deptgt-makeflags TESTS+= deptgt-no_parallel TESTS+= deptgt-nopath TESTS+= deptgt-notparallel TESTS+= deptgt-objdir TESTS+= deptgt-order TESTS+= deptgt-path TESTS+= deptgt-path-suffix TESTS+= deptgt-phony TESTS+= deptgt-posix TESTS+= deptgt-precious TESTS+= deptgt-shell TESTS+= deptgt-silent TESTS+= deptgt-silent-jobs TESTS+= deptgt-stale TESTS+= deptgt-suffixes TESTS+= dir TESTS+= dir-expand-path TESTS+= directive TESTS+= directive-dinclude TESTS+= directive-elif TESTS+= directive-elifdef TESTS+= directive-elifmake TESTS+= directive-elifndef TESTS+= directive-elifnmake TESTS+= directive-else TESTS+= directive-endfor TESTS+= directive-endif TESTS+= directive-error TESTS+= directive-export TESTS+= directive-export-env TESTS+= directive-export-impl TESTS+= directive-export-gmake TESTS+= directive-export-literal TESTS+= directive-for TESTS+= directive-for-break TESTS+= directive-for-empty TESTS+= directive-for-errors TESTS+= directive-for-escape TESTS+= directive-for-generating-endif TESTS+= directive-for-if TESTS+= directive-for-lines TESTS+= directive-for-null TESTS+= directive-hyphen-include TESTS+= directive-if TESTS+= directive-if-nested TESTS+= directive-ifdef TESTS+= directive-ifmake TESTS+= directive-ifndef TESTS+= directive-ifnmake TESTS+= directive-include TESTS+= directive-include-fatal TESTS+= directive-include-guard TESTS+= directive-info TESTS+= directive-misspellings TESTS+= directive-sinclude TESTS+= directive-undef TESTS+= directive-unexport TESTS+= directive-unexport-env TESTS+= directive-warning TESTS+= dollar TESTS+= doterror TESTS+= dotwait TESTS+= error TESTS+= # escape # broken by reverting POSIX changes TESTS+= export TESTS+= export-all TESTS+= export-env TESTS+= export-variants TESTS+= gnode-submake TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main TESTS+= job-flags TESTS+= job-output-long-lines TESTS+= job-output-null TESTS+= jobs-empty-commands TESTS+= jobs-empty-commands-error TESTS+= jobs-error-indirect TESTS+= jobs-error-nested TESTS+= jobs-error-nested-make TESTS+= lint TESTS+= make-exported TESTS+= meta-cmd-cmp TESTS+= moderrs TESTS+= modmisc .if ${.MAKE.UID} > 0 TESTS+= objdir-writable .endif TESTS+= opt TESTS+= opt-backwards TESTS+= opt-chdir TESTS+= opt-debug TESTS+= opt-debug-all TESTS+= opt-debug-archive TESTS+= opt-debug-curdir TESTS+= opt-debug-cond TESTS+= opt-debug-dir TESTS+= opt-debug-errors TESTS+= opt-debug-errors-jobs TESTS+= opt-debug-file TESTS+= opt-debug-for TESTS+= opt-debug-graph1 TESTS+= opt-debug-graph2 TESTS+= opt-debug-graph3 TESTS+= opt-debug-hash TESTS+= opt-debug-jobs TESTS+= opt-debug-lint TESTS+= opt-debug-loud TESTS+= opt-debug-meta TESTS+= opt-debug-making TESTS+= opt-debug-no-rm TESTS+= opt-debug-parse TESTS+= opt-debug-suff TESTS+= opt-debug-targets TESTS+= opt-debug-varraw TESTS+= opt-debug-var TESTS+= opt-debug-x-trace TESTS+= opt-define TESTS+= opt-env TESTS+= opt-file TESTS+= opt-ignore TESTS+= opt-include-dir TESTS+= opt-jobs TESTS+= opt-jobs-internal TESTS+= opt-jobs-no-action TESTS+= opt-keep-going TESTS+= opt-keep-going-indirect TESTS+= opt-keep-going-multiple TESTS+= opt-m-include-dir TESTS+= opt-no-action TESTS+= opt-no-action-at-all TESTS+= opt-no-action-runflags TESTS+= opt-no-action-touch TESTS+= opt-query TESTS+= opt-raw TESTS+= opt-silent TESTS+= opt-touch TESTS+= opt-touch-jobs TESTS+= opt-tracefile TESTS+= opt-var-expanded TESTS+= opt-var-literal TESTS+= opt-version TESTS+= opt-warnings-as-errors TESTS+= opt-where-am-i TESTS+= opt-x-reduce-exported TESTS+= order TESTS+= parse TESTS+= parse-var TESTS+= phony-end TESTS+= posix TESTS+= # posix1 # broken by reverting POSIX changes TESTS+= recursive TESTS+= sh TESTS+= sh-dots TESTS+= sh-errctl TESTS+= sh-flags TESTS+= sh-jobs TESTS+= sh-jobs-error TESTS+= sh-leading-at TESTS+= sh-leading-hyphen TESTS+= sh-leading-plus TESTS+= sh-meta-chars TESTS+= sh-multi-line TESTS+= sh-single-line TESTS+= shell-csh TESTS+= shell-custom .if exists(/bin/ksh) TESTS+= shell-ksh .endif TESTS+= shell-sh +TESTS+= suff TESTS+= suff-add-later TESTS+= suff-clear-regular TESTS+= suff-clear-single TESTS+= suff-incomplete TESTS+= suff-lookup TESTS+= suff-main TESTS+= suff-main-several TESTS+= suff-phony TESTS+= suff-rebuild TESTS+= suff-self TESTS+= suff-transform-debug TESTS+= suff-transform-endless TESTS+= suff-transform-expand TESTS+= suff-transform-select TESTS+= suff-use TESTS+= sunshcmd TESTS+= ternary TESTS+= unexport TESTS+= unexport-env TESTS+= use-inference TESTS+= var-readonly TESTS+= var-scope TESTS+= var-scope-cmdline TESTS+= var-scope-env TESTS+= var-scope-global TESTS+= var-scope-local TESTS+= var-scope-local-legacy TESTS+= var-eval-short TESTS+= var-op TESTS+= var-op-append TESTS+= var-op-assign TESTS+= var-op-default TESTS+= var-op-expand TESTS+= var-op-shell TESTS+= var-op-sunsh TESTS+= var-recursive TESTS+= varcmd TESTS+= vardebug TESTS+= varfind TESTS+= varmisc TESTS+= varmod TESTS+= varmod-assign TESTS+= varmod-assign-shell TESTS+= varmod-defined TESTS+= varmod-edge TESTS+= varmod-exclam-shell TESTS+= varmod-extension TESTS+= varmod-gmtime TESTS+= varmod-hash TESTS+= varmod-head TESTS+= varmod-ifelse TESTS+= varmod-indirect TESTS+= varmod-l-name-to-value TESTS+= varmod-localtime TESTS+= varmod-loop TESTS+= varmod-loop-delete TESTS+= varmod-loop-varname TESTS+= varmod-match TESTS+= varmod-match-escape TESTS+= varmod-mtime TESTS+= varmod-no-match TESTS+= varmod-order TESTS+= varmod-order-numeric TESTS+= varmod-order-reverse TESTS+= varmod-order-shuffle TESTS+= varmod-order-string TESTS+= varmod-path TESTS+= varmod-quote TESTS+= varmod-quote-dollar TESTS+= varmod-range TESTS+= varmod-remember TESTS+= varmod-root TESTS+= varmod-select-words TESTS+= varmod-shell TESTS+= varmod-subst TESTS+= varmod-subst-regex TESTS+= varmod-sun-shell TESTS+= varmod-sysv TESTS+= varmod-tail TESTS+= varmod-to-abs TESTS+= varmod-to-lower TESTS+= varmod-to-many-words TESTS+= varmod-to-one-word TESTS+= varmod-to-separator TESTS+= varmod-to-title TESTS+= varmod-to-upper TESTS+= varmod-undefined TESTS+= varmod-unique TESTS+= varname TESTS+= varname-dollar TESTS+= varname-dot-alltargets TESTS+= varname-dot-curdir TESTS+= varname-dot-includes TESTS+= varname-dot-includedfromdir TESTS+= varname-dot-includedfromfile TESTS+= varname-dot-libs TESTS+= varname-dot-make-dependfile TESTS+= varname-dot-make-expand_variables TESTS+= varname-dot-make-exported TESTS+= varname-dot-make-jobs TESTS+= varname-dot-make-jobs-prefix TESTS+= varname-dot-make-level TESTS+= varname-dot-make-makefile_preference TESTS+= varname-dot-make-makefiles TESTS+= varname-dot-make-meta-bailiwick TESTS+= varname-dot-make-meta-created TESTS+= varname-dot-make-meta-files .if ${.MAKE.PATH_FILEMON:Uno:Nktrace:N/dev*} == "" && ${TMPDIR:N/tmp*:N/var/tmp*} != "" # these tests will not work if TMPDIR is or is a subdir of # /tmp or /var/tmp .if ${.MAKE.PATH_FILEMON:N/dev/*} != "" || exists(${.MAKE.PATH_FILEMON}) TESTS+= varname-dot-make-meta-ignore_filter TESTS+= varname-dot-make-meta-ignore_paths TESTS+= varname-dot-make-meta-ignore_patterns TESTS+= varname-dot-make-path_filemon .else .warning Skipping tests that require ${.MAKE.PATH_FILEMON} .endif .endif TESTS+= varname-dot-make-meta-prefix TESTS+= varname-dot-make-mode TESTS+= varname-dot-make-pid TESTS+= varname-dot-make-ppid TESTS+= varname-dot-make-save_dollars TESTS+= varname-dot-makeflags TESTS+= varname-dot-makeoverrides TESTS+= varname-dot-newline TESTS+= varname-dot-objdir TESTS+= varname-dot-parsedir TESTS+= varname-dot-parsefile TESTS+= varname-dot-path TESTS+= varname-dot-shell TESTS+= varname-dot-suffixes TESTS+= varname-dot-targets TESTS+= varname-empty TESTS+= varname-make TESTS+= varname-make_print_var_on_error TESTS+= varname-make_print_var_on_error-jobs TESTS+= varname-makefile TESTS+= varname-makeflags TESTS+= varname-pwd TESTS+= varname-vpath TESTS+= varparse-dynamic TESTS+= varparse-errors TESTS+= varparse-mod TESTS+= varparse-undef-partial # some shells have quirks _shell := ${.SHELL:tA:T} .if ${_shell} == "dash" # dash fails -x output BROKEN_TESTS+= opt-debug-x-trace .elif ${_shell:N*ksh*} == "" BROKEN_TESTS+= \ deptgt-silent-jobs \ job-flags \ job-output-long-lines \ opt-debug-x-trace \ sh-flags \ var-op-shell \ .if ${_shell:Nmksh} == "" # more broken that pdksh BROKEN_TESTS+= \ opt-jobs-no-action \ sh-errctl \ sh-leading-hyphen \ .endif .endif .if ${UTC_1:Uno} == "" # this will not work if UTC_1 is set empty BROKEN_TESTS+= varmod-localtime .endif .if ${.MAKE.OS:NDarwin} == "" BROKEN_TESTS+= shell-ksh .endif .if ${.MAKE.OS:NIRIX*} == "" BROKEN_TESTS+= \ cmd-interrupt \ deptgt-interrupt \ job-output-null \ opt-chdir \ opt-debug-x-trace \ sh-leading-hyphen \ .endif .if ${.MAKE.OS} == "SCO_SV" BROKEN_TESTS+= \ opt-debug-graph[23] \ varmod-localtime \ varmod-to-separator \ .if ${_shell} == "bash" BROKEN_TESTS+= job-output-null .else BROKEN_TESTS+= \ cmd-interrupt \ job-flags \ .endif .endif # Some tests just do not work on some platforms or environments # so allow for some filtering. .if !empty(BROKEN_TESTS) .warning Skipping broken tests: ${BROKEN_TESTS:O:u} TESTS:= ${TESTS:${BROKEN_TESTS:S,^,N,:ts:}} .endif # Ideas for more tests: # char-0020-space.mk # char-005C-backslash.mk # escape-cond-str.mk # escape-cond-func-arg.mk # escape-varmod.mk # escape-varmod-define.mk # escape-varmod-match.mk # escape-varname.mk # escape-varassign-varname.mk # escape-varassign-varname-cmdline.mk # escape-varassign-value.mk # escape-varassign-value-cmdline.mk # escape-dependency-source.mk # escape-dependency-target.mk # escape-for-varname.mk # escape-for-item.mk # posix-*.mk (see posix.mk and posix1.mk) # Additional environment variables for some of the tests. # The base environment is -i PATH="$PATH". ENV.depsrc-optional+= TZ=UTC ENV.deptgt-phony+= MAKESYSPATH=. ENV.directive-undef= ENV_VAR=env-value ENV.opt-env= FROM_ENV=value-from-env ENV.opt-m-include-dir= ${MAKEOBJDIR:DMAKEOBJDIR=${MAKEOBJDIR}} ENV.varmisc= FROM_ENV=env ENV.varmisc+= FROM_ENV_BEFORE=env ENV.varmisc+= FROM_ENV_AFTER=env ENV.varmod-localtime+= TZ=${UTC_1:UEurope/Berlin} ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 # Override make flags for some of the tests; default is -k. # If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of # settings FLAGS.test=-dv here, since that is closer to the test code. FLAGS.cond-func-make= via-cmdline FLAGS.doterror= # none, especially not -k FLAGS.jobs-error-indirect= # none, especially not -k FLAGS.jobs-error-nested= # none, especially not -k FLAGS.jobs-error-nested-make= # none, especially not -k FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' # Some tests need extra postprocessing. SED_CMDS.deptgt-phony= ${STD_SED_CMDS.dd} SED_CMDS.dir= ${STD_SED_CMDS.dd} SED_CMDS.directive-include-guard= \ -e '/\.MAKEFLAGS/d' \ - -e '/^Parsing line/d' \ + -e '/^Parsing .*:[1-9][0-9]*:/d' \ -e '/^SetFilenameVars:/d' \ -e '/^ParseDependency/d' \ -e '/^ParseEOF:/d' SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d' .if ${.MAKE.OS:NCygwin} == "" SED_CMDS.export+= -e '/^WINDIR=/d' -e '/^SYSTEMROOT=/d' .endif SED_CMDS.export-all= ${SED_CMDS.export} SED_CMDS.export-env= ${SED_CMDS.export} SED_CMDS.cmdline= -e 's,uid${.MAKE.UID}/,,' SED_CMDS.job-output-long-lines= \ ${:D Job separators on their own line are ok. } \ -e '/^--- job-[ab] ---$$/d' \ ${:D Plain output lines are ok as well. } \ ${:D They may come in multiples of 1024 or as 10000. } \ -e '/^aa*$$/d' \ -e '/^bb*$$/d' \ ${:D The following lines should rather not occur since the job } \ ${:D marker should always be at the beginning of the line. } \ -e '/^aa*--- job-b ---$$/d' \ -e '/^bb*--- job-a ---$$/d' SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \ -e '/name/s,file,File,' \ -e 's,no such,No such,' \ -e 's,Filename,File name,' # meta line numbers can vary based on filemon implementation SED_CMDS.meta-ignore= -e 's,\(\.meta:\) [1-9][0-9]*:,\1 :,' SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1} SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2} SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3} SED_CMDS.opt-debug-hash= -e 's,\(entries\)=[1-9][0-9],\1=,' SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(),' SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid ,' SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process ,' SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: ,' SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: ,' # The "-q" may be there or not, see jobs.c, variable shells. SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: \) -q,\1,' SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex} SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output} SED_CMDS.opt-where-am-i= -e '/usr.obj/d' # For Compat_RunCommand, useShell == false. SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' # For Compat_RunCommand, useShell == true. SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,,' SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1,' SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj} SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output} SED_CMDS.shell-csh= ${STD_SED_CMDS.white-space} SED_CMDS.sh-leading-hyphen= ${STD_SED_CMDS.shell} SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1} SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1} SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1} SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell} SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,' SED_CMDS.var-op-shell+= ${STD_SED_CMDS.white-space} SED_CMDS.vardebug+= -e 's,${.SHELL},,' SED_CMDS.varmod-mtime+= -e "s,\(.*\)': .*,\1': ," SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex} SED_CMDS.varparse-errors+= ${STD_SED_CMDS.timestamp} SED_CMDS.varname-dot-make-meta-ignore_filter+= ${SED_CMDS.meta-ignore} SED_CMDS.varname-dot-make-meta-ignore_paths+= ${SED_CMDS.meta-ignore} SED_CMDS.varname-dot-make-meta-ignore_patterns+= ${SED_CMDS.meta-ignore} SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: ",' SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g' SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' SED_CMDS.varname-empty= ${.OBJDIR .PARSEDIR .PATH .SHELL .SYSPATH:L:@v@-e '/\\$v/d'@} # Some tests need an additional round of postprocessing. POSTPROC.depsrc-wait= sed -e '/^---/d' -e 's,^\(: Making 3[abc]\)[123]$$,\1,' POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/' POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' POSTPROC.varname-dot-make-mode= sed 's,^\(: Making [abc]\)[123]$$,\1,' # Some tests reuse other tests, which makes them unnecessarily fragile. export-all.rawout: export.mk unexport.rawout: export.mk unexport-env.rawout: export.mk # End of the configuration section. # Some standard sed commands, to be used in the SED_CMDS above. # In tests that use the debugging option -dd, ignore debugging output that is # only logged in -DCLEANUP mode. STD_SED_CMDS.dd= -e '/^OpenDirs_Done:/d' STD_SED_CMDS.dd+= -e '/^CachedDir /d' STD_SED_CMDS.dd+= -e 's, ${DEFSYSPATH:U/usr/share/mk} , ,' # Omit details such as process IDs from the output of the -dg1 option. STD_SED_CMDS.dg1= -e '/\#.* \.$$/d' STD_SED_CMDS.dg1+= -e '/\.MAKE.PATH_FILEMON/d' STD_SED_CMDS.dg1+= -e '/^\#.*\/mk/d' STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, ,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.[A-Z_]* *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.JOBS\.C *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(MACHINE[_ARCH]* *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(MAKE *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e 's,^\(\.SHELL *=\) .*,\1
,' STD_SED_CMDS.dg1+= -e '/\.SYSPATH/d' STD_SED_CMDS.dg2= ${STD_SED_CMDS.dg1} STD_SED_CMDS.dg2+= -e 's,\(last modified\) ..:..:.. ... ..\, ....,\1 ,' STD_SED_CMDS.dg3= ${STD_SED_CMDS.dg2} # Omit details such as process IDs from the output of the -dj option. STD_SED_CMDS.dj= \ -e '/Process/d;/JobFinish:/d' \ -e 's,^\(Job_TokenWithdraw\)([0-9]*),\1(),' \ -e 's,^([0-9][0-9]*) \(withdrew token\),() \1,' \ -e 's, \(pid\) [0-9][0-9]*, \1 ,' \ -e 's,^\( Command:\) .*,\1 ,' # Reduce the noise for tests running with the -n option, since there is no # other way to suppress the echoing of the commands. STD_SED_CMDS.hide-from-output= \ -e '/^echo hide-from-output/d' \ -e 's,hide-from-output ,,' \ -e 's,hide-from-output,,' # Normalize the output for error messages from the shell. # # $shell -c '...' # NetBSD sh ...: not found # NetBSD ksh ksh: ...: not found # bash 5.0.18 bash: ...: command not found # bash 5.1.0 bash: line 1: ...: command not found # dash dash: 1: ...: not found # # $shell -c '< /nonexistent' # NetBSD sh sh: cannot open /nonexistent: no such file # NetBSD ksh ksh: cannot open /nonexistent: No such file or directory # bash 5.0.18 bash: /nonexistent: No such file or directory # bash 5.1.0 bash: line 1: /nonexistent: No such file or directory # dash dash: 1: cannot open /nonexistent: No such file # # echo '< /nonexistent' | $shell # NetBSD sh sh: cannot open /nonexistent: no such file # NetBSD ksh ksh: [1]: cannot open /nonexistent: No such file or directory # bash 5.0.18 bash: line 1: /nonexistent: No such file or directory # bash 5.1.0 bash: line 1: /nonexistent: No such file or directory # dash dash: 1: cannot open /nonexistent: No such file # STD_SED_CMDS.shell+= -e 's,^${.SHELL},${.SHELL:T},' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,' STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,' STD_SED_CMDS.shell+= -e 's,: command not found,: not found,' STD_SED_CMDS.white-space= -e 's, *, ,g' -e 's, *$$,,' # The actual error messages for a failed regcomp or regexec differ between the # implementations. STD_SED_CMDS.regex= \ -e 's,\(Regex compilation error:\).*,\1 (details omitted),' # Normalize timestamps from ':gmtime' or ':localtime' to ''. # See STD_SED_CMDS.dg2 for timestamps from the debug log. STD_SED_CMDS.timestamp= \ -e 's,[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [12][0-9][0-9][0-9],,' # End of the configuration helpers section. -.sinclude "Makefile.inc" -.sinclude "Makefile.config" +.-include "Makefile.inc" +.-include "Makefile.config" UNIT_TESTS:= ${srcdir} .PATH: ${UNIT_TESTS} .if ${USE_ABSOLUTE_TESTNAMES:Uno} == yes OUTFILES= ${TESTS:@test@${.CURDIR:tA}/${test}.out@} .else OUTFILES= ${TESTS:=.out} .endif all: ${OUTFILES} CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp CLEANFILES+= obj*.[och] lib*.a # posix1.mk CLEANFILES+= issue* .[ab]* # suffixes.mk CLEANDIRS= dir dummy *.tmp # posix1.mk clean: rm -rf ${CLEANDIRS} rm -f ${CLEANFILES} TEST_MAKE?= ${.MAKE} TOOL_SED?= sed TOOL_TR?= tr TOOL_DIFF?= diff DIFF_FLAGS?= -u # ensure consistent results from sort(1) LC_ALL= C LANG= C .export LANG LC_ALL .if ${.MAKE.MODE:Unormal:Mmeta} != "" # we don't need the noise _MKMSG_TEST= : .endif # Some Linux systems such as Fedora have deprecated egrep in favor of grep -E. .if ${.MAKE.OS:NLinux} == "" EGREP= grep -E .endif # Keep the classical definition for all other systems. Just as the bmake code # is kept compatible with C90, the tests are kept compatible with systems that # are several decades old and don't follow modern POSIX standards. EGREP?= egrep MAKE_TEST_ENV= EGREP="${EGREP}" MAKE_TEST_ENV+= MALLOC_OPTIONS="JA" # for jemalloc 100 MAKE_TEST_ENV+= MALLOC_CONF="junk:true" # for jemalloc 510 MAKE_TEST_ENV+= TMPDIR=${TMPDIR} .if ${.MAKE.OS} == "NetBSD" LIMIT_RESOURCES?= ulimit -v 300000 .endif LIMIT_RESOURCES?= : # Each test is run in a sub-make, to keep the tests from interfering with # each other, and because they use different environment variables and # command line options. .SUFFIXES: .mk .rawout .out .mk.rawout: @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX} @set -eu; \ ${LIMIT_RESOURCES}; \ cd ${.OBJDIR}; \ env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \ ${TEST_MAKE} \ -r -C ${.CURDIR} -f ${.IMPSRC} \ ${FLAGS.${.PREFIX:T}:U-k} \ > ${.TARGET}.tmp 2>&1 \ && status=$$? || status=$$?; \ echo $$status > ${.TARGET:R}.status @mv ${.TARGET}.tmp ${.TARGET} # Postprocess the test output to make the output platform-independent. # # Replace anything after 'stopped in' with unit-tests _SED_CMDS+= -e '/stopped/s, in /.*, in unit-tests,' # Allow the test files to be placed anywhere. _SED_CMDS+= -e 's,\(\.PARSEDIR}\) = `'"/[^']*'"',\1 = ,' _SED_CMDS+= -e 's,\(\.INCLUDEDFROMDIR}\) = `'"/[^']*'"',\1 = ,' _SED_CMDS+= -e 's,${TMPDIR},,g' -e 's,${TMPDIR:tA},,g' # canonicalize ${.OBJDIR} and ${.CURDIR} _SED_CMDS+= -e 's,${.CURDIR},,g' .if ${.OBJDIR} != ${.CURDIR} # yes this is inaccurate but none of the tests expect anywhere # which we get depending on how MAKEOBJDIR is set. _SED_CMDS+= -e 's,${.OBJDIR},,g' -e 's,${.OBJDIR:tA},,g' .endif # always pretend .MAKE was called 'make' _SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' _SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' _SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' _SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}\(\[[1-9][0-9]*\]:\),make\1,' _SED_CMDS+= -e 's,/,,g' _SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' _SED_CMDS+= -e '/MAKE_VERSION/d' _SED_CMDS+= -e '/EGREP=/d' # on AT&T derived systems: false exits 255 not 1 .if ${.MAKE.OS:N*BSD} != "" _SED_CMDS+= -e 's,\(Error code\) 255,\1 1,' .endif .if ${_shell:N*ksh*} == "" _SED_CMDS+= -e '/^set [+-]v/d' SED_CMDS.opt-debug-jobs+= -e 's,Command: ksh -v,Command: ,' SED_CMDS.opt-debug-jobs+= -e 's,Command: -v,Command: ,' .endif .rawout.out: @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \ - < ${.IMPSRC} > ${.TARGET}.tmp1 - @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 - @rm ${.TARGET}.tmp1 - @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2 - @mv ${.TARGET}.tmp2 ${.TARGET} + < ${.IMPSRC} > ${.TARGET}.tmp + @${POSTPROC.${.PREFIX:T}:D \ + ${POSTPROC.${.PREFIX:T}} < ${.TARGET}.tmp > ${.TARGET}.post \ + && mv ${.TARGET}.post ${.TARGET}.tmp} + @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} .if empty(DIFF_FLAGS) DIFF_ECHO= echo .else DIFF_ECHO= : .endif # Compare all output files test: ${OUTFILES} .PHONY @failed= ; \ for test in ${TESTS}; do \ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out && continue || \ ${DIFF_ECHO} diff ${UNIT_TESTS}/$${test}.exp $${test}.out; \ ${TOOL_DIFF} ${DIFF_FLAGS} ${UNIT_TESTS}/$${test}.exp $${test}.out \ || failed="$${failed}$${failed:+ }$${test}" ; \ done ; \ if [ -n "$${failed}" ]; then \ echo "Failed tests: $${failed}" ; false ; \ else \ echo "All tests passed" ; \ lua=${LUA:Ulua} ; \ have_lua=$$("$$lua" -e 'print "yes"' 2>&1) ; \ if [ "$$have_lua" = "yes" -a -s ${.CURDIR}/check-expect.lua ]; then \ (cd ${.CURDIR} && "$$lua" ./check-expect.lua *.mk); \ fi; \ fi accept: @for test in ${TESTS}; do \ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \ || { echo "Replacing $${test}.exp" ; \ cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \ done .if exists(${TEST_MAKE}) ${TESTS:=.rawout}: ${TEST_MAKE} # in meta mode, we *know* if a target script is impacted # by a makefile change. .if ${.MAKE.MODE:Unormal:Mmeta} == "" ${TESTS:=.rawout}: ${.PARSEDIR}/Makefile .endif .endif -.sinclude +.-include