From 7a05a7153af649605e1ebde33aac94a14ed2a4cd Mon Sep 17 00:00:00 2001 From: "Simon J. Gerraty" Date: Sat, 13 Jan 2024 17:16:25 -0800 Subject: [PATCH] Import bmake-20240108 Interesting/relevant changes since bmake-20230909 * 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 * 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 * 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. * 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 * 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() * 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. * 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 * 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. * VERSION (_MAKE_VERSION): 20231208 Merge with NetBSD make, pick up o var.c: ensure fromCmd is set correctly for variables set on command line. * 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 * bsd.after-import.mk (ECHO_TAG): FreeBSD no longer uses $FreeBSD$ tag, so avoid adding it. mk/ChangeLog since bmake-20230909 * dirdeps.mk: for MAKE_VERSION 20240105 we do not have the same limits on command line length, so skip export of lists to env. * jobs.mk: avoid C suffix in JOB_MAX_C if factor is floating point. This keeps JOB_MAX numeric incase another makefile does comparisons. * gendirdeps.mk: if META_XTRAS is passed to us, add to META_FILES --- ChangeLog | 102 +++ README | 17 +- VERSION | 2 +- arch.c | 10 +- bmake.1 | 6 +- bmake.cat1 | 6 +- boot-strap | 16 +- bsd.after-import.mk | 17 +- buf.c | 6 +- buf.h | 4 +- compat.c | 65 +- cond.c | 111 +-- configure | 34 +- configure.in | 16 +- dir.c | 330 +++------ for.c | 14 +- hash.c | 28 +- hash.h | 21 +- job.c | 139 ++-- job.h | 6 +- lst.c | 22 +- lst.h | 8 +- main.c | 156 ++-- make.1 | 6 +- make.c | 28 +- make.h | 5 +- meta.c | 6 +- mk/ChangeLog | 42 ++ mk/compiler.mk | 4 +- mk/dirdeps.mk | 17 +- mk/dpadd.mk | 14 +- mk/gendirdeps.mk | 6 +- mk/host-target.mk | 7 +- mk/init.mk | 8 +- mk/install-mk | 4 +- mk/jobs.mk | 5 +- mk/lib.mk | 25 +- mk/man.mk | 95 ++- mk/meta.autodep.mk | 4 +- mk/own.mk | 3 +- mk/prog.mk | 5 +- mk/rst2htm.mk | 3 +- os.sh | 0 parse.c | 279 +++---- str.c | 83 +-- str.h | 31 +- suff.c | 168 ++--- unit-tests/Makefile | 9 +- unit-tests/cmd-errors-jobs.exp | 2 +- unit-tests/cmd-errors-lint.exp | 2 +- unit-tests/cmd-errors.exp | 2 +- unit-tests/cmdline-undefined.mk | 4 +- unit-tests/comment.mk | 4 +- unit-tests/cond-cmp-string.mk | 14 +- unit-tests/cond-cmp-unary.mk | 8 +- unit-tests/cond-eof.mk | 4 +- unit-tests/cond-func-defined.exp | 2 +- unit-tests/cond-func-defined.mk | 8 +- unit-tests/cond-func-empty.mk | 16 +- unit-tests/cond-func-exists.mk | 6 +- unit-tests/cond-func.mk | 4 +- unit-tests/cond-late.exp | 2 +- unit-tests/cond-late.mk | 6 +- unit-tests/cond-op-and.mk | 8 +- unit-tests/cond-op-or.mk | 8 +- unit-tests/cond-short.exp | 2 +- unit-tests/cond-short.mk | 23 +- unit-tests/cond-token-number.mk | 6 +- unit-tests/cond-token-plain.exp | 4 +- unit-tests/cond-token-plain.mk | 16 +- unit-tests/cond-token-string.exp | 2 +- unit-tests/cond-token-string.mk | 10 +- unit-tests/cond-token-var.mk | 16 +- unit-tests/dep-var.exp | 2 +- unit-tests/dep-var.mk | 10 +- unit-tests/depsrc-ignore.exp | 2 +- unit-tests/deptgt-delete_on_error.exp | 2 +- unit-tests/deptgt-makeflags.exp | 2 +- unit-tests/deptgt-makeflags.mk | 6 +- unit-tests/deptgt.mk | 6 +- unit-tests/dir.mk | 4 +- unit-tests/directive-else.mk | 4 +- unit-tests/directive-export-gmake.exp | 1 + unit-tests/directive-export-gmake.mk | 25 +- unit-tests/directive-export.exp | 6 +- unit-tests/directive-export.mk | 10 +- unit-tests/directive-for-empty.mk | 12 +- unit-tests/directive-for-errors.mk | 14 +- unit-tests/directive-for-escape.exp | 4 +- unit-tests/directive-for-escape.mk | 12 +- unit-tests/directive-for-if.mk | 4 +- unit-tests/directive-for.exp | 19 +- unit-tests/directive-for.mk | 27 +- unit-tests/directive-ifmake.mk | 4 +- unit-tests/directive-ifndef.mk | 36 +- unit-tests/directive-include-guard.exp | 16 + unit-tests/directive-include-guard.mk | 188 +++-- unit-tests/directive-warning.mk | 4 +- unit-tests/directive.mk | 4 +- unit-tests/escape.exp | 36 +- unit-tests/escape.mk | 7 +- unit-tests/hanoi-include.mk | 7 +- unit-tests/jobs-error-indirect.exp | 2 +- unit-tests/jobs-error-nested-make.exp | 2 +- unit-tests/jobs-error-nested.exp | 4 +- unit-tests/lint.mk | 4 +- unit-tests/moderrs.exp | 20 +- unit-tests/moderrs.mk | 6 +- unit-tests/opt-debug-file.mk | 4 +- unit-tests/opt-debug-jobs.mk | 4 +- unit-tests/opt-debug-lint.mk | 6 +- unit-tests/opt-debug-loud.mk | 6 +- unit-tests/opt-debug-var.mk | 4 +- unit-tests/parse-var.mk | 6 +- unit-tests/recursive.exp | 4 +- unit-tests/recursive.mk | 15 +- unit-tests/sh-dots.mk | 6 +- unit-tests/sh-leading-hyphen.exp | 2 +- unit-tests/shell-sh.mk | 4 +- unit-tests/unexport.mk | 4 +- unit-tests/var-eval-short.mk | 4 +- unit-tests/var-op-append.mk | 33 +- unit-tests/var-op-assign.mk | 4 +- unit-tests/var-op-default.mk | 8 +- unit-tests/var-op-expand.mk | 6 +- unit-tests/var-op-shell.mk | 20 +- unit-tests/var-readonly.exp | 3 + unit-tests/var-readonly.mk | 4 +- unit-tests/var-recursive.exp | 8 +- unit-tests/var-recursive.mk | 4 +- unit-tests/var-scope-cmdline.mk | 4 +- unit-tests/var-scope-local-legacy.exp | 2 +- unit-tests/var-scope-local-legacy.mk | 22 +- unit-tests/var-scope-local.exp | 4 +- unit-tests/var-scope-local.mk | 12 +- unit-tests/vardebug.exp | 20 +- unit-tests/vardebug.mk | 29 +- unit-tests/varmisc.exp | 5 +- unit-tests/varmisc.mk | 28 +- unit-tests/varmod-assign.exp | 32 + unit-tests/varmod-assign.mk | 71 +- unit-tests/varmod-defined.exp | 2 +- unit-tests/varmod-defined.mk | 10 +- unit-tests/varmod-edge.exp | 2 +- unit-tests/varmod-edge.mk | 8 +- unit-tests/varmod-gmtime.mk | 15 +- unit-tests/varmod-ifelse.exp | 20 +- unit-tests/varmod-ifelse.mk | 36 +- unit-tests/varmod-indirect.mk | 24 +- unit-tests/varmod-l-name-to-value.mk | 4 +- unit-tests/varmod-localtime.mk | 4 +- unit-tests/varmod-loop-varname.mk | 4 +- unit-tests/varmod-loop.mk | 6 +- unit-tests/varmod-match-escape.mk | 20 +- unit-tests/varmod-match.exp | 33 +- unit-tests/varmod-match.mk | 349 +++++---- unit-tests/varmod-mtime.exp | 16 +- unit-tests/varmod-mtime.mk | 45 +- unit-tests/varmod-order.exp | 6 +- unit-tests/varmod-range.exp | 21 +- unit-tests/varmod-range.mk | 26 +- unit-tests/varmod-subst-regex.mk | 57 +- unit-tests/varmod-subst.mk | 46 +- unit-tests/varmod-sysv.mk | 10 +- unit-tests/varmod-to-separator.mk | 4 +- unit-tests/varmod-undefined.mk | 6 +- unit-tests/varmod.mk | 6 +- unit-tests/varname-dot-shell.exp | 10 +- unit-tests/varname-dot-suffixes.exp | 20 +- unit-tests/varname-dot-suffixes.mk | 19 +- unit-tests/varname-empty.exp | 24 +- unit-tests/varname-empty.mk | 4 +- .../varname-make_print_var_on_error-jobs.mk | 4 +- unit-tests/varname.mk | 4 +- unit-tests/varparse-dynamic.mk | 4 +- unit-tests/varparse-errors.exp | 28 +- unit-tests/varparse-errors.mk | 8 +- unit-tests/varparse-mod.mk | 6 +- unit-tests/varparse-undef-partial.mk | 13 +- util.c | 96 ++- var.c | 696 ++++++++---------- 181 files changed, 2598 insertions(+), 2190 deletions(-) mode change 100644 => 100755 mk/install-mk mode change 100644 => 100755 os.sh diff --git a/ChangeLog b/ChangeLog index ec0be0f4027c..4bd41562fb30 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,105 @@ +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 diff --git a/README b/README index a782f6dfc5b5..bf59107db2d3 100644 --- a/README +++ b/README @@ -6,12 +6,12 @@ Since 1993 I have run it on AIX, BSDi, Darwin, FreeBSD, HP-UX, IRIX, Linux, Minix, OSF, Solaris, SunOS and even UTS. Others have run it on many more systems. -Currently each release is tested on NetBSD, FreeBSD, Solaris and Linux. +Currently each release is tested on Darwin, NetBSD, FreeBSD and Linux. Since 2003 bmake switched to a date based version (first was 20030714) which generally represents the date it was last merged with NetBSD's make. Since then, NetBSD's make is imported within a week of any -interesting changes, so that bmake tracks it very closely. +*interesting* changes, so that bmake tracks it very closely. Building ======== @@ -33,20 +33,27 @@ the GNU standard process of:: ./configure; make; make install +This will *not* work on Darwin or any other system with a case +insensitive filesystem. It depends on a generated ``makefile`` which +is disabled by default on Darwin. + To make much use of bmake you will need the bsd.*.mk macros or my portable *.mk macros which are included with bmake since 20121212 and separately available from -http://www.crufty.net/ftp/pub/sjg/mk.tar.gz -which will be links to the latest versions. +https://www.crufty.net/ftp/pub/sjg/mk.tar.gz +both that and +https://www.crufty.net/ftp/pub/sjg/bmake.tar.gz +will be links to the latest versions. Porting ======= If you encounter a system that bmake does not build or work on *out of the box*, I welcome patches. +Even a report of unit tests which fail is appreciated. If you can provide access to a suitable machine - even better. -More info can be found at http://www.crufty.net/help/sjg/bmake.htm +More info can be found at https://www.crufty.net/help/sjg/bmake.htm --sjg diff --git a/VERSION b/VERSION index 0cc67de7b22d..ca636f3ab1e2 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ # keep this compatible with sh and make -_MAKE_VERSION=20230909 +_MAKE_VERSION=20240108 diff --git a/arch.c b/arch.c index 6d2c6e0f1875..b90452149c99 100644 --- a/arch.c +++ b/arch.c @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.213 2023/02/14 21:08:00 rillig Exp $ */ +/* $NetBSD: arch.c,v 1.214 2023/11/19 22:50:11 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -147,7 +147,7 @@ struct ar_hdr { #include "dir.h" /* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: arch.c,v 1.213 2023/02/14 21:08:00 rillig Exp $"); +MAKE_RCSID("$NetBSD: arch.c,v 1.214 2023/11/19 22:50:11 rillig Exp $"); typedef struct List ArchList; typedef struct ListNode ArchListNode; @@ -253,7 +253,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) 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 variable + bool expandLib; /* Whether the parsed lib contains * expressions that need to be expanded */ spec = *pp; @@ -262,7 +262,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) for (cp = lib.str; *cp != '(' && *cp != '\0';) { if (*cp == '$') { - /* Expand nested variable expressions. */ + /* Expand nested expressions. */ /* XXX: This code can probably be shortened. */ const char *nested_p = cp; FStr result; @@ -299,7 +299,7 @@ Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) mem = FStr_InitRefer(cp); while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { if (*cp == '$') { - /* Expand nested variable expressions. */ + /* Expand nested expressions. */ /* * XXX: This code can probably be shortened. */ diff --git a/bmake.1 b/bmake.1 index a658eab6700e..55d910b3b204 100644 --- a/bmake.1 +++ b/bmake.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.371 2023/09/10 21:52:36 rillig Exp $ +.\" $NetBSD: make.1,v 1.372 2023/12/24 16:48:30 sjg Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd September 9, 2023 +.Dd December 24, 2023 .Dt BMAKE 1 .Os .Sh NAME @@ -2523,7 +2523,7 @@ set the read-only attribute on the global variables specified as sources. .It Ic .SHELL Sets the shell that .Nm -uses to execute commands in jobs mode. +uses to execute commands. The sources are a set of .Ar field\| Ns Cm \&= Ns Ar value pairs. diff --git a/bmake.cat1 b/bmake.cat1 index bdd48dda11db..61138ea0f2b6 100644 --- a/bmake.cat1 +++ b/bmake.cat1 @@ -1599,8 +1599,8 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) set the read-only attribute on the global variables specified as sources. - .SHELL Sets the shell that bmake uses to execute commands in jobs mode. - The sources are a set of field=value pairs. + .SHELL Sets the shell that bmake uses to execute commands. The sources + are a set of field=value pairs. name This is the minimal specification, used to select one of the built-in shell specs; sh, ksh, and csh. @@ -1759,4 +1759,4 @@ BMAKE(1) FreeBSD General Commands Manual BMAKE(1) attempt to suppress a cascade of unnecessary errors, can result in a seemingly unexplained `*** Error code 6' -FreeBSD 13.0 September 9, 2023 FreeBSD 13.0 +FreeBSD 13.0 December 24, 2023 FreeBSD 13.0 diff --git a/boot-strap b/boot-strap index f47f0c4fdecd..00fa04a0e08b 100755 --- a/boot-strap +++ b/boot-strap @@ -82,9 +82,23 @@ # # Possibly useful configure_args: # +# --without-makefile +# do not generate 'makefile'. +# +# 'makefile' is used to enable the classic +# './configure; make; make install' dance, but on +# systems with case insensitive filesystems it can lead +# to infinite recursion. +# +# It is disabled by default on Darwin, and Cygwin. +# # --without-meta # disable use of meta mode. # +# Even without filemon(9) meta mode is very useful +# both for debugging build and improving reliability of +# update builds. +# # --without-filemon # disable use of filemon(9) which is currently only # available for NetBSD and FreeBSD. @@ -119,7 +133,7 @@ # Simon J. Gerraty # RCSid: -# $Id: boot-strap,v 1.58 2023/06/27 21:02:19 sjg Exp $ +# $Id: boot-strap,v 1.59 2023/11/26 18:19:43 sjg Exp $ # # @(#) Copyright (c) 2001 Simon J. Gerraty # diff --git a/bsd.after-import.mk b/bsd.after-import.mk index fec42c9deeed..418caeaa58a6 100644 --- a/bsd.after-import.mk +++ b/bsd.after-import.mk @@ -1,4 +1,4 @@ -# $Id: bsd.after-import.mk,v 1.17 2021/10/22 06:31:32 sjg Exp $ +# $Id: bsd.after-import.mk,v 1.18 2023/09/18 05:29:23 sjg Exp $ # This makefile is for use when integrating bmake into a BSD build # system. Use this makefile after importing bmake. @@ -68,18 +68,25 @@ MAKEFILE_SED = sed -e '/^MACHINE/d' \ # These are the simple files we want to capture configured_files= config.h Makefile.config unit-tests/Makefile.config +# FreeBSD has dropped their tag with svn +.if ${HOST_OS:NFreeBSD} == "" +ECHO_TAG= : +.else +ECHO_TAG?= echo +.endif + after-import: bootstrap ${MAKEFILE} .for f in ${configured_files:M*.[ch]} @echo Capturing $f @mkdir -p ${${.CURDIR}/$f:L:H} - @(echo '/* $$${HOST_OS}$$ */'; cat ${HOST_OS}/$f) > ${.CURDIR}/$f + @(${ECHO_TAG} '/* $$${HOST_OS}$$ */'; cat ${HOST_OS}/$f) > ${.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 '# $$${HOST_OS}$$'; echo; \ + 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 @@ -89,7 +96,7 @@ _makefile: bootstrap ${MAKEFILE} @echo Generating ${.CURDIR}/Makefile @(echo '# This is a generated file, do NOT edit!'; \ echo '# See ${_this:S,${SRCTOP}/,,}'; \ - echo '#'; echo '# $$${HOST_OS}$$'; \ + 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; \ @@ -115,7 +122,7 @@ _utmakefile: bootstrap ${MAKEFILE} @mkdir -p ${.CURDIR}/unit-tests @(echo '# This is a generated file, do NOT edit!'; \ echo '# See ${_this:S,${SRCTOP}/,,}'; \ - echo '#'; echo '# $$${HOST_OS}$$'; \ + echo '#'; ${ECHO_TAG} '# $$${HOST_OS}$$'; \ ${MAKEFILE_SED} \ -e '/^UNIT_TESTS/s,=.*,= $${srcdir},' \ ${BMAKE_SRC}/unit-tests/Makefile ) > ${.TARGET} diff --git a/buf.c b/buf.c index 845a68c46330..fdc6c8ec2b60 100644 --- a/buf.c +++ b/buf.c @@ -1,4 +1,4 @@ -/* $NetBSD: buf.c,v 1.56 2023/06/01 07:44:10 rillig Exp $ */ +/* $NetBSD: buf.c,v 1.57 2023/12/19 19:33:39 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,13 +69,13 @@ * SUCH DAMAGE. */ -/* Automatically-expanding null-terminated character buffers. */ +/* Automatically growing null-terminated buffers of characters. */ #include #include "make.h" /* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: buf.c,v 1.56 2023/06/01 07:44:10 rillig Exp $"); +MAKE_RCSID("$NetBSD: buf.c,v 1.57 2023/12/19 19:33:39 rillig Exp $"); /* Make space in the buffer for adding at least 16 more bytes. */ void diff --git a/buf.h b/buf.h index ccd7d513b211..c5e7d539de9e 100644 --- a/buf.h +++ b/buf.h @@ -1,4 +1,4 @@ -/* $NetBSD: buf.h,v 1.48 2023/06/01 07:44:10 rillig Exp $ */ +/* $NetBSD: buf.h,v 1.49 2023/12/19 19:33:39 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -96,7 +96,7 @@ Buf_Clear(Buffer *buf) buf->data[0] = '\0'; } -/* Buf_AddByte adds a single byte to a buffer. */ +/* Adds a single byte to a buffer. */ MAKE_INLINE void Buf_AddByte(Buffer *buf, char byte) { diff --git a/compat.c b/compat.c index 221eb64959e6..6f55880cbc54 100644 --- a/compat.c +++ b/compat.c @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.247 2023/05/04 22:31:17 sjg Exp $ */ +/* $NetBSD: compat.c,v 1.252 2024/01/05 23:22:06 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -94,7 +94,7 @@ #include "pathnames.h" /* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: compat.c,v 1.247 2023/05/04 22:31:17 sjg Exp $"); +MAKE_RCSID("$NetBSD: compat.c,v 1.252 2024/01/05 23:22:06 rillig Exp $"); static GNode *curTarg = NULL; static pid_t compatChild; @@ -110,10 +110,8 @@ 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) { + if (!opts.noExecute && unlink_file(file) == 0) Error("*** %s removed", file); - } } } @@ -132,14 +130,11 @@ CompatInterrupt(int signo) CompatDeleteTarget(curTarg); if (curTarg != NULL && !GNode_IsPrecious(curTarg)) { - /* - * Run .INTERRUPT only if hit with interrupt signal - */ + /* Run .INTERRUPT only if hit with interrupt signal. */ if (signo == SIGINT) { GNode *gn = Targ_FindNode(".INTERRUPT"); - if (gn != NULL) { + if (gn != NULL) Compat_Make(gn, gn); - } } } @@ -280,11 +275,9 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) silent = !DEBUG(LOUD); else if (*cmd == '-') errCheck = false; - else if (*cmd == '+') { + else if (*cmd == '+') doIt = true; - if (shellName == NULL) /* we came here from jobs */ - Shell_Init(); - } else if (!ch_isspace(*cmd)) + else if (!ch_isspace(*cmd)) /* Ignore whitespace for compatibility with gnu make */ break; cmd++; @@ -292,37 +285,25 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) while (ch_isspace(*cmd)) cmd++; - - /* - * If we did not end up with a command, just skip it. - */ if (cmd[0] == '\0') return true; useShell = UseShell(cmd); - /* - * Print the command before echoing if we're not supposed to be quiet - * for this one. We also print the command if -n given. - */ + if (!silent || !GNode_ShouldExecute(gn)) { printf("%s\n", cmd); fflush(stdout); } - /* - * If we're not supposed to execute any commands, this is as far as - * we go... - */ if (!doIt && !GNode_ShouldExecute(gn)) return true; DEBUG1(JOB, "Execute: '%s'\n", cmd); + if (useShell && shellPath == NULL) + Shell_Init(); /* we need shellPath */ + if (useShell) { - /* - * We need to pass the command off to the shell, typically - * because the command contains a "meta" character. - */ static const char *shargv[5]; /* The following work for any of the builtin shell specs. */ @@ -337,11 +318,6 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) bp = NULL; mav = NULL; } else { - /* - * No meta-characters, so no need to exec a shell. Break the - * command into words to form an argument vector we can - * execute. - */ Words words = Str_Words(cmd, false); mav = words.words; bp = words.freeIt; @@ -380,24 +356,21 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) meta_compat_parent(cpid); #endif - /* - * The child is off and running. Now all we can do is wait... - */ + /* The child is off and running. Now all we can do is wait... */ while ((retstat = wait(&reason)) != cpid) { if (retstat > 0) JobReapChild(retstat, reason, false); /* not ours? */ - if (retstat == -1 && errno != EINTR) { + if (retstat == -1 && errno != EINTR) break; - } } if (retstat < 0) Fatal("error in wait: %d: %s", retstat, strerror(errno)); if (WIFSTOPPED(reason)) { - status = WSTOPSIG(reason); /* stopped */ + status = WSTOPSIG(reason); } else if (WIFEXITED(reason)) { - status = WEXITSTATUS(reason); /* exited */ + status = WEXITSTATUS(reason); #if defined(USE_META) && defined(USE_FILEMON_ONCE) if (useMeta) meta_cmd_finish(NULL); @@ -408,7 +381,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) printf("*** Error code %d", status); } } else { - status = WTERMSIG(reason); /* signaled */ + status = WTERMSIG(reason); printf("*** Signal %d", status); } @@ -439,6 +412,7 @@ Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) printf(" (ignored)\n"); status = 0; } + fflush(stdout); } free(cmdStart); @@ -594,10 +568,6 @@ MakeUnmade(GNode *gn, GNode *pgn) gn->type |= OP_SILENT; if (Job_CheckCommands(gn, Fatal)) { - /* - * Our commands are ok, but we still have to worry about - * the -t flag. - */ if (!opts.touch || (gn->type & OP_MAKE)) { curTarg = gn; #ifdef USE_META @@ -781,7 +751,6 @@ Compat_MakeAll(GNodeList *targs) errorNode = gn; } - /* If the user has defined a .END target, run its commands. */ if (errorNode == NULL) { GNode *endNode = Targ_GetEndNode(); Compat_Make(endNode, endNode); diff --git a/cond.c b/cond.c index 4ed15ff26184..843abc9a92f8 100644 --- a/cond.c +++ b/cond.c @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.354 2023/08/11 04:56:31 rillig Exp $ */ +/* $NetBSD: cond.c,v 1.359 2023/12/29 12:59:43 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -81,8 +81,7 @@ * 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 + * Cond_EndFile At the end of reading a makefile, ensure that the * conditional directives are well-balanced. */ @@ -92,7 +91,7 @@ #include "dir.h" /* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: cond.c,v 1.354 2023/08/11 04:56:31 rillig Exp $"); +MAKE_RCSID("$NetBSD: cond.c,v 1.359 2023/12/29 12:59:43 rillig Exp $"); /* * Conditional expressions conform to this grammar: @@ -154,10 +153,10 @@ typedef struct CondParser { * 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 a variable expression or a plain word. + * been an expression or a plain word. * * In conditional directives like '.if', the left-hand side must - * either be a variable expression, a quoted string or a number. + * either be an expression, a quoted string or a number. */ bool leftUnquotedOK; @@ -166,9 +165,7 @@ typedef struct CondParser { /* * Whether an error message has already been printed for this - * condition. The first available error message is usually the most - * specific one, therefore it makes sense to suppress the standard - * "Malformed conditional" message. + * condition. */ bool printedError; } CondParser; @@ -212,16 +209,16 @@ ParseWord(const char **pp, bool doEval) { const char *p = *pp; Buffer word; - int paren_depth; + int depth; - Buf_InitSize(&word, 16); + Buf_Init(&word); - paren_depth = 0; + depth = 0; for (;;) { char ch = *p; if (ch == '\0' || ch == ' ' || ch == '\t') break; - if ((ch == '&' || ch == '|') && paren_depth == 0) + if ((ch == '&' || ch == '|') && depth == 0) break; if (ch == '$') { VarEvalMode emode = doEval @@ -238,8 +235,8 @@ ParseWord(const char **pp, bool doEval) continue; } if (ch == '(') - paren_depth++; - else if (ch == ')' && --paren_depth < 0) + depth++; + else if (ch == ')' && --depth < 0) break; Buf_AddByte(&word, ch); p++; @@ -258,7 +255,7 @@ ParseFuncArg(CondParser *par, const char **pp, bool doEval, const char *func) const char *p = *pp; char *res; - p++; /* Skip opening '(' - verified by caller */ + p++; /* skip the '(' */ cpp_skip_hspace(&p); res = ParseWord(&p, doEval); cpp_skip_hspace(&p); @@ -383,7 +380,7 @@ is_separator(char ch) } /* - * In a quoted or unquoted string literal or a number, parse a variable + * 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. @@ -429,7 +426,7 @@ CondParser_StringExpr(CondParser *par, const char *start, } /* - * Parse a string from a variable expression or an optionally quoted string, + * Parse a string from an expression or an optionally quoted string, * on the left-hand and right-hand sides of comparisons. * * Results: @@ -487,10 +484,6 @@ CondParser_Leaf(CondParser *par, bool doEval, bool unquotedOK, default: if (!unquotedOK && !quoted && *start != '$' && !ch_isdigit(*start)) { - /* - * The left-hand side must be quoted, - * a variable expression or a number. - */ str = FStr_InitRefer(NULL); goto return_str; } @@ -635,7 +628,6 @@ CondParser_Comparison(CondParser *par, bool doEval) CondParser_SkipWhitespace(par); if (!CondParser_ComparisonOp(par, &op)) { - /* Unknown operator, compare against an empty string or 0. */ t = ToToken(doEval && EvalTruthy(par, lhs.str, lhsQuoted)); goto done_lhs; } @@ -667,19 +659,19 @@ done_lhs: static bool CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) { - const char *cp = par->p; + const char *p = par->p; Token tok; FStr val; - if (!skip_string(&cp, "empty")) + if (!skip_string(&p, "empty")) return false; - cpp_skip_whitespace(&cp); - if (*cp != '(') + cpp_skip_whitespace(&p); + if (*p != '(') return false; - cp--; /* Make cp[1] point to the '('. */ - val = Var_Parse(&cp, SCOPE_CMDLINE, + p--; /* Make p[1] point to the '('. */ + val = Var_Parse(&p, SCOPE_CMDLINE, doEval ? VARE_WANTRES : VARE_PARSE_ONLY); /* TODO: handle errors */ @@ -692,7 +684,7 @@ CondParser_FuncCallEmpty(CondParser *par, bool doEval, Token *out_token) FStr_Done(&val); *out_token = tok; - par->p = cp; + par->p = p; return true; } @@ -734,7 +726,7 @@ CondParser_FuncCall(CondParser *par, bool doEval, Token *out_token) /* * 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, a variable expression or a string literal. + * operator, which is a number, an expression or a string literal. * * TODO: Can this be merged into CondParser_Comparison? */ @@ -743,37 +735,36 @@ CondParser_ComparisonOrLeaf(CondParser *par, bool doEval) { Token t; char *arg; - const char *cp; + const char *p; - /* Push anything numeric through the compare expression */ - cp = par->p; - if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[0] == '+') + p = par->p; + if (ch_isdigit(p[0]) || p[0] == '-' || p[0] == '+') return CondParser_Comparison(par, doEval); /* - * Most likely we have a naked token 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. + * 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, a variable expression may be evaluated twice, + * XXX: In edge cases, an expression may be evaluated twice, * see cond-token-plain.mk, keyword 'twice'. */ - arg = ParseWord(&cp, doEval); + arg = ParseWord(&p, doEval); assert(arg[0] != '\0'); - if (*cp == '=' || *cp == '!' || *cp == '<' || *cp == '>') + if (*p == '=' || *p == '!' || *p == '<' || *p == '>') return CondParser_Comparison(par, doEval); - par->p = cp; + 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 a variable expansion. + * be empty - even if it contained an expression. */ t = ToToken(doEval && par->evalBare(arg) != par->negateEvalBare); free(arg); @@ -961,7 +952,7 @@ CondParser_Eval(CondParser *par) } /* - * Evaluate the condition, including any side effects from the variable + * Evaluate the condition, including any side effects from the * expressions in the condition. The condition consists of &&, ||, !, * function(arg), comparisons and parenthetical groupings thereof. */ @@ -1034,12 +1025,6 @@ DetermineKindOfConditional(const char **pp, bool *out_plain, return true; unknown_directive: - /* - * TODO: Add error message about unknown directive, since there is no - * other known directive that starts with 'el' or 'if'. - * - * Example: .elifx 123 - */ return false; } @@ -1068,7 +1053,7 @@ unknown_directive: * conditional (when evaluates to true) * CR_FALSE to skip the lines after the conditional * (when evaluates to false, or when a previous - * branch has already been taken) + * 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 @@ -1115,7 +1100,7 @@ Cond_EvalLine(const char *line) p++; /* skip the leading '.' */ cpp_skip_hspace(&p); - if (IsEndif(p)) { /* It is an '.endif'. */ + if (IsEndif(p)) { if (p[5] != '\0') { Parse_Error(PARSE_FATAL, "The .endif directive does not take arguments"); @@ -1135,14 +1120,8 @@ Cond_EvalLine(const char *line) /* Parse the name of the directive, such as 'if', 'elif', 'endif'. */ if (p[0] == 'e') { - if (p[1] != 'l') { - /* - * Unknown directive. It might still be a - * transformation rule like '.err.txt', - * therefore no error message here. - */ + if (p[1] != 'l') return CR_ERROR; - } /* Quite likely this is 'else' or 'elif' */ p += 2; @@ -1176,13 +1155,8 @@ Cond_EvalLine(const char *line) } else isElif = false; - if (p[0] != 'i' || p[1] != 'f') { - /* - * Unknown directive. It might still be a transformation rule - * like '.elisp.scm', therefore no error message here. - */ - return CR_ERROR; /* Not an ifxxx or elifxxx line */ - } + if (p[0] != 'i' || p[1] != 'f') + return CR_ERROR; if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) return CR_ERROR; @@ -1219,16 +1193,11 @@ Cond_EvalLine(const char *line) state = cond_states[cond_depth]; cond_depth++; if (!(state & IFS_ACTIVE)) { - /* - * If we aren't parsing the data, - * treat as always false. - */ cond_states[cond_depth] = IFS_WAS_ACTIVE; return CR_FALSE; } } - /* And evaluate the conditional expression */ res = CondEvalExpression(p, plain, evalBare, negate, true, false); if (res == CR_ERROR) { /* Syntax error, error message already output. */ diff --git a/configure b/configure index 9d6130f66c84..2898a27bb98e 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for bmake 20230723. +# Generated by GNU Autoconf 2.71 for bmake 20240101. # # Report bugs to . # @@ -610,8 +610,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='bmake' PACKAGE_TARNAME='bmake' -PACKAGE_VERSION='20230723' -PACKAGE_STRING='bmake 20230723' +PACKAGE_VERSION='20240101' +PACKAGE_STRING='bmake 20240101' PACKAGE_BUGREPORT='sjg@NetBSD.org' PACKAGE_URL='' @@ -724,6 +724,7 @@ with_defshell with_makefile with_meta with_filemon +with_bmake_strftime with_machine with_force_machine with_force_machine_arch @@ -1290,7 +1291,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bmake 20230723 to adapt to many kinds of systems. +\`configure' configures bmake 20240101 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1352,7 +1353,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bmake 20230723:";; + short | recursive ) echo "Configuration of bmake 20240101:";; esac cat <<\_ACEOF @@ -1374,6 +1375,7 @@ Optional Packages: --without-makefile disable use of generated makefile --without-meta disable use of meta-mode --with-filemon={no,dev,ktrace,path/filemon.h} indicate filemon method for meta-mode. Path to filemon.h implies dev + --with-bmake-strftime force use of bmake strftime --with-machine=MACHINE explicitly set MACHINE --with-force-machine=MACHINE set FORCE_MACHINE --with-force-machine-arch=MACHINE set FORCE_MACHINE_ARCH @@ -1461,7 +1463,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bmake configure 20230723 +bmake configure 20240101 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1968,7 +1970,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bmake $as_me 20230723, which was +It was created by bmake $as_me 20240101, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2783,7 +2785,7 @@ esac fi case "$OS" in -CYGWIN*|MINGW*) use_makefile=no;; +CYGWIN*|Darwin|MINGW*) use_makefile=no;; *) use_makefile=yes;; esac @@ -2846,6 +2848,15 @@ esac fi + +# Check whether --with-bmake_strftime was given. +if test ${with_bmake_strftime+y} +then : + withval=$with_bmake_strftime; case "${withval}" in +yes|no) bmake_strftime=$withval;; +esac +fi + case "$use_meta" in yes) case "$use_filemon" in @@ -4897,6 +4908,9 @@ if test $bmake_path_max -gt 1024; then # this is all we expect bmake_path_max=1024 fi +if test ${bmake_strftime:-no} = yes; then + CPPFLAGS="${CPPFLAGS} -DFORCE_BMAKE_STRFTIME" +fi echo "Using: BMAKE_PATH_MAX=$bmake_path_max" >&6 # if type does not work which(1) had better! # note we cannot rely on type returning non-zero on failure @@ -7574,7 +7588,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bmake $as_me 20230723, which was +This file was extended by bmake $as_me 20240101, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -7638,7 +7652,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -bmake config.status 20230723 +bmake config.status 20240101 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.in b/configure.in index 5b97bc1bed52..ad652e53024b 100644 --- a/configure.in +++ b/configure.in @@ -1,11 +1,11 @@ dnl dnl RCSid: -dnl $Id: configure.in,v 1.97 2023/07/25 20:12:32 sjg Exp $ +dnl $Id: configure.in,v 1.101 2024/01/04 23:04:07 sjg Exp $ dnl dnl Process this file with autoconf to produce a configure script dnl AC_PREREQ([2.71]) -AC_INIT([bmake],[20230723],[sjg@NetBSD.org]) +AC_INIT([bmake],[20240101],[sjg@NetBSD.org]) AC_CONFIG_HEADERS(config.h) dnl make srcdir absolute @@ -62,7 +62,7 @@ no) ;; esac]) dnl case "$OS" in -CYGWIN*|MINGW*) use_makefile=no;; +CYGWIN*|Darwin|MINGW*) use_makefile=no;; *) use_makefile=yes;; esac AC_ARG_WITH(makefile, @@ -114,6 +114,13 @@ dev) ;; *) filemon_h=no;; esac ]) +dnl some systems have broken/incomplete strftime +AC_ARG_WITH(bmake_strftime, +[ --with-bmake-strftime force use of bmake strftime], +[case "${withval}" in +yes|no) bmake_strftime=$withval;; +esac]) +dnl dnl echo "Note: use_meta=$use_meta use_filemon=$use_filemon filemon_h=$filemon_h" >&6 case "$use_meta" in yes) @@ -189,6 +196,9 @@ if test $bmake_path_max -gt 1024; then # this is all we expect bmake_path_max=1024 fi +if test ${bmake_strftime:-no} = yes; then + CPPFLAGS="${CPPFLAGS} -DFORCE_BMAKE_STRFTIME" +fi echo "Using: BMAKE_PATH_MAX=$bmake_path_max" >&6 AC_SUBST(bmake_path_max)dnl dnl diff --git a/dir.c b/dir.c index 1da9e7d8fc13..5c11d6c794d7 100644 --- a/dir.c +++ b/dir.c @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.282 2023/06/23 04:56:54 rillig Exp $ */ +/* $NetBSD: dir.c,v 1.286 2023/12/29 18:53:24 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -83,7 +83,7 @@ * * Dir_End Clean up the module. * - * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. + * Dir_SetPATH Set ${.PATH} to reflect the state of dirSearchPath. * * Dir_HasWildcards * Returns true if the name given it needs to @@ -94,13 +94,12 @@ * from the search path. * * Dir_FindFile Searches for a file on a given search path. - * If it exists, the entire path is returned. - * Otherwise NULL is returned. + * If it exists, returns the entire path, otherwise NULL. * * Dir_FindHereOrAbove - * Search for a path in the current directory and - * then all the directories above it in turn until - * the path is found or we reach the root ("/"). + * Search for a path in the current directory and then + * all the directories above it in turn, until the path + * is found or the root directory ("/") is reached. * * Dir_UpdateMTime * Update the modification time and path of a node with @@ -114,11 +113,6 @@ * preceded by the command flag and all of them * separated by a space. * - * Dir_Destroy Destroy an element of a search path. Frees up all - * things that can be freed for the element as long - * as the element is no longer referenced by any other - * search path. - * * SearchPath_Clear * Resets a search path to the empty list. * @@ -138,7 +132,7 @@ #include "job.h" /* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ -MAKE_RCSID("$NetBSD: dir.c,v 1.282 2023/06/23 04:56:54 rillig Exp $"); +MAKE_RCSID("$NetBSD: dir.c,v 1.286 2023/12/29 18:53:24 rillig Exp $"); /* * A search path is a list of CachedDir structures. A CachedDir has in it the @@ -152,26 +146,21 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.282 2023/06/23 04:56:54 rillig Exp $"); * All previously-read directories are kept in openDirs, which is checked * first before a directory is opened. * - * The need for the caching of whole directories is brought about by the - * multi-level transformation code in suff.c, which tends to search for far - * more files than regular make does. In the initial implementation, the - * amount of time spent performing "stat" calls was truly astronomical. - * The problem with caching at the start is, of course, that pmake doesn't - * then detect changes to these directories during the course of the make. - * Three possibilities suggest themselves: + * This cache is used by the multi-level transformation code in suff.c, which + * tends to search for far more files than in regular explicit targets. After + * a directory has been cached, any later changes to that directory are not + * reflected in the cache. To keep the cache up to date, there are several + * ideas: * * 1) just use stat to test for a file's existence. As mentioned above, - * this is very inefficient due to the number of checks engendered by + * this is very inefficient due to the number of checks performed by * the multi-level transformation code. * - * 2) use readdir() and company to search the directories, keeping them - * open between checks. I have tried this and while it didn't slow down - * the process too much, it could severely affect the amount of - * parallelism available as each directory open would take another file - * descriptor out of play for handling I/O for another job. Given that - * it is only recently (as of 1993 or earlier) that UNIX OS's have taken - * to allowing more than 20 or 32 file descriptors for a process, this - * doesn't seem acceptable to me. + * 2) use readdir() to search the directories, keeping them open between + * checks. Around 1993 or earlier, this didn't slow down the process too + * much, but it consumed one file descriptor per open directory, which + * was critical on the then-current operating systems, as many limited + * the number of open file descriptors to 20 or 32. * * 3) record the mtime of the directory in the CachedDir structure and * verify the directory hasn't changed since the contents were cached. @@ -184,9 +173,9 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.282 2023/06/23 04:56:54 rillig Exp $"); * number of reloadings and if the number goes over a (small) limit, * resort to using stat in its place. * - * An additional thing to consider is that pmake is used primarily to create - * C programs and until recently (as of 1993 or earlier) pcc-based compilers - * refused to allow you to specify where the resulting object file should be + * An additional thing to consider is that make is used primarily to create + * C programs and until recently (as of 1993 or earlier), pcc-based compilers + * didn't have an option to specify where the resulting object file should be * placed. This forced all objects to be created in the current directory. * This isn't meant as a full excuse, just an explanation of some of the * reasons for the caching used here. @@ -212,7 +201,7 @@ MAKE_RCSID("$NetBSD: dir.c,v 1.282 2023/06/23 04:56:54 rillig Exp $"); /* A cache for the filenames in a directory. */ struct CachedDir { /* - * Name of directory, either absolute or relative to the current + * Name of the directory, either absolute or relative to the current * directory. The name is not normalized in any way, that is, "." * and "./." are different. * @@ -238,8 +227,6 @@ struct CachedDir { typedef List CachedDirList; typedef ListNode CachedDirListNode; -typedef ListNode SearchPathNode; - /* A list of cached directories, with fast lookup by directory name. */ typedef struct OpenDirs { CachedDirList list; @@ -273,8 +260,7 @@ static CachedDir *dotLast = NULL; * * XXX: If this is done way early, there's a chance other rules will have * already updated the file, in which case we'll update it again. Generally, - * there won't be two rules to update a single file, so this should be ok, - * but... + * there won't be two rules to update a single file, so this should be ok. */ static HashTable mtimes; @@ -338,7 +324,7 @@ CachedDir_Unref(CachedDir *dir) free(dir); } -/* Update the value of the CachedDir variable, updating the reference counts. */ +/* Update the value of 'var', updating the reference counts. */ static void CachedDir_Assign(CachedDir **var, CachedDir *dir) { @@ -475,9 +461,7 @@ Dir_Init(void) CachedDir_Assign(&dotLast, CachedDir_New(".DOTLAST")); } -/* - * Called by Dir_InitDir and whenever .CURDIR is assigned to. - */ +/* Called by Dir_InitDir and whenever .CURDIR is assigned to. */ void Dir_InitCur(const char *newCurdir) { @@ -487,7 +471,7 @@ Dir_InitCur(const char *newCurdir) return; /* - * Our build directory is not the same as our source directory. + * The build directory is not the same as the source directory. * Keep this one around too. */ dir = SearchPath_Add(NULL, newCurdir); @@ -498,7 +482,7 @@ Dir_InitCur(const char *newCurdir) } /* - * (Re)initialize "dot" (current/object directory) path hash. + * (Re)initialize "dot" (the current/object directory). * Some directories may be cached. */ void @@ -599,8 +583,6 @@ Dir_SetSYSPATH(void) * XXX: This code is not 100% correct ([^]] fails etc.). I really don't think * that make(1) should be expanding patterns, because then you have to set a * mechanism for escaping the expansion! - * - * Return true if the word should be expanded, false otherwise. */ bool Dir_HasWildcards(const char *name) @@ -637,20 +619,14 @@ Dir_HasWildcards(const char *name) } /* - * See if any files match the pattern and add their names to the 'expansions' - * list if they do. + * See if any files as seen from 'dir' match 'pattern', and add their names + * to 'expansions' if they do. * - * This is incomplete -- wildcards are only expanded in the final path - * component, but not in directories like src/lib*c/file*.c, but it - * will do for now (now being 1993 until at least 2020). To expand these, + * Wildcards are only expanded in the final path component, but not in + * directories like src/lib*c/file*.c. To expand these wildcards, * delegate the work to the shell, using the '!=' variable assignment * operator, the ':sh' variable modifier or the ':!...!' variable modifier, * such as in ${:!echo src/lib*c/file*.c!}. - * - * Input: - * pattern Pattern to look for - * dir Directory to search - * expansion Place to store the results */ static void DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) @@ -696,21 +672,18 @@ DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) } } -/* - * Find the next closing brace in the string, taking nested braces into - * account. - */ +/* Find the next closing brace in 'p', taking nested braces into account. */ static const char * closing_brace(const char *p) { - int nest = 0; + int depth = 0; while (*p != '\0') { - if (*p == '}' && nest == 0) + if (*p == '}' && depth == 0) break; if (*p == '{') - nest++; + depth++; if (*p == '}') - nest--; + depth--; p++; } return p; @@ -723,14 +696,14 @@ closing_brace(const char *p) static const char * separator_comma(const char *p) { - int nest = 0; + int depth = 0; while (*p != '\0') { - if ((*p == '}' || *p == ',') && nest == 0) + if ((*p == '}' || *p == ',') && depth == 0) break; if (*p == '{') - nest++; + depth++; if (*p == '}') - nest--; + depth--; p++; } return p; @@ -787,7 +760,7 @@ DirExpandCurly(const char *word, const char *brace, SearchPath *path, const char *prefix, *middle, *piece, *middle_end, *suffix; size_t prefix_len, suffix_len; - /* Split the word into prefix '{' middle '}' suffix. */ + /* Split the word into prefix, '{', middle, '}' and suffix. */ middle = brace + 1; middle_end = closing_brace(middle); @@ -824,11 +797,11 @@ DirExpandCurly(const char *word, const char *brace, SearchPath *path, } -/* Expand the pattern in each of the directories from the path. */ +/* Expand 'pattern' in each of the directories from 'path'. */ static void DirExpandPath(const char *pattern, SearchPath *path, StringList *expansions) { - SearchPathNode *ln; + CachedDirListNode *ln; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; DirMatchFiles(pattern, dir, expansions); @@ -860,11 +833,6 @@ SearchPath_ExpandMiddle(SearchPath *path, const char *pattern, SearchPath *partPath; prefix = bmake_strsedup(pattern, wildcardComponent + 1); - /* - * XXX: Check the "the directory is added to the path" part. - * It is probably surprising that the directory before a - * wildcard gets added to the path. - */ /* * XXX: Only the first match of the prefix in the path is * taken, any others are ignored. The expectation may be @@ -880,7 +848,7 @@ SearchPath_ExpandMiddle(SearchPath *path, const char *pattern, * path contains ../Etc/Object and we're looking for Etc, it won't * be found. Ah well. Probably not important. * - * XXX: Check whether the above comment is still true. + * TODO: Check whether the above comment is still true. */ if (dirpath == NULL) return; @@ -921,15 +889,9 @@ SearchPath_Expand(SearchPath *path, const char *pattern, StringList *expansions) goto done; } - /* At this point, the pattern does not contain '{'. */ - slash = strchr(pattern, '/'); if (slash == NULL) { - /* The pattern has no directory component. */ - - /* First the files in dot. */ DirMatchFiles(pattern, dot, expansions); - /* Then the files in every other directory on the path. */ DirExpandPath(pattern, path, expansions); goto done; } @@ -972,13 +934,13 @@ done: } /* - * Find if the file with the given name exists in the given path. + * Find if 'base' exists in 'dir'. * Return the freshly allocated path to the file, or NULL. */ static char * DirLookup(CachedDir *dir, const char *base) { - char *file; /* the current filename to check */ + char *file; DEBUG1(DIR, " %s ...\n", dir->name); @@ -994,7 +956,7 @@ DirLookup(CachedDir *dir, const char *base) /* - * Find if the file with the given name exists in the given directory. + * Find if 'name' exists in 'dir'. * Return the freshly allocated path to the file, or NULL. */ static char * @@ -1016,12 +978,12 @@ DirLookupSubdir(CachedDir *dir, const char *name) } /* - * Find if the file with the given name exists in the given path. - * Return the freshly allocated path to the file, the empty string, or NULL. - * Returning the empty string means that the search should be terminated. + * Find if 'name' (which has basename 'base') exists in 'dir'. + * Return the freshly allocated path to the file, an empty string, or NULL. + * Returning an empty string means that the search should be terminated. */ static char * -DirLookupAbs(CachedDir *dir, const char *name, const char *cp) +DirLookupAbs(CachedDir *dir, const char *name, const char *base) { const char *dnp; /* pointer into dir->name */ const char *np; /* pointer into name */ @@ -1037,10 +999,10 @@ DirLookupAbs(CachedDir *dir, const char *name, const char *cp) for (dnp = dir->name, np = name; *dnp != '\0' && *dnp == *np; dnp++, np++) continue; - if (*dnp != '\0' || np != cp - 1) + if (*dnp != '\0' || np != base - 1) return NULL; - if (!HashSet_Contains(&dir->files, cp)) { + if (!HashSet_Contains(&dir->files, base)) { DEBUG0(DIR, " must be here but isn't -- returning\n"); return bmake_strdup(""); /* to terminate the search */ } @@ -1080,7 +1042,7 @@ static bool FindFileRelative(SearchPath *path, bool seenDotLast, const char *name, char **out_file) { - SearchPathNode *ln; + CachedDirListNode *ln; bool checkedDot = false; char *file; @@ -1090,11 +1052,11 @@ FindFileRelative(SearchPath *path, bool seenDotLast, if (dot != NULL) { checkedDot = true; if ((file = DirLookupSubdir(dot, name)) != NULL) - goto found; + goto done; } if (cur != NULL && (file = DirLookupSubdir(cur, name)) != NULL) - goto found; + goto done; } for (ln = path->dirs.first; ln != NULL; ln = ln->next) { @@ -1107,18 +1069,18 @@ FindFileRelative(SearchPath *path, bool seenDotLast, checkedDot = true; } if ((file = DirLookupSubdir(dir, name)) != NULL) - goto found; + goto done; } if (seenDotLast) { if (dot != NULL && !checkedDot) { checkedDot = true; if ((file = DirLookupSubdir(dot, name)) != NULL) - goto found; + goto done; } if (cur != NULL && (file = DirLookupSubdir(cur, name)) != NULL) - goto found; + goto done; } if (checkedDot) { @@ -1128,12 +1090,12 @@ FindFileRelative(SearchPath *path, bool seenDotLast, */ DEBUG0(DIR, " Checked . already, returning NULL\n"); file = NULL; - goto found; + goto done; } return false; -found: +done: *out_file = file; return true; } @@ -1143,18 +1105,8 @@ FindFileAbsolute(SearchPath *path, bool seenDotLast, const char *name, const char *base, char **out_file) { char *file; - SearchPathNode *ln; + CachedDirListNode *ln; - /* - * For absolute names, compare directory path prefix against - * the the directory path of each member on the search path - * for an exact match. If we have an exact match on any member - * of the search path, use the cached contents of that member - * to lookup the final file component. If that lookup fails we - * can safely assume that the file does not exist at all. - * This is signified by DirLookupAbs() returning an empty - * string. - */ DEBUG0(DIR, " Trying exact path matches...\n"); if (!seenDotLast && cur != NULL && @@ -1187,13 +1139,6 @@ found: /* * Find the file with the given name along the given search path. * - * If the file is found in a directory that is not on the path - * already (either 'name' is absolute or it is a relative path - * [ dir1/.../dirn/file ] which exists below one of the directories - * already on the search path), its directory is added to the end - * of the path, on the assumption that there will be more files in - * that directory later on. Sometimes this is true. Sometimes not. - * * Input: * name the file to find * path the directories to search, or NULL @@ -1206,7 +1151,7 @@ Dir_FindFile(const char *name, SearchPath *path) { char *file; /* the current filename to check */ bool seenDotLast = false; /* true if we should search dot last */ - struct cached_stat cst; /* Buffer for stat, if necessary */ + struct cached_stat cst; const char *trailing_dot = "."; const char *base = str_basename(name); @@ -1233,21 +1178,19 @@ Dir_FindFile(const char *name, SearchPath *path) * of each of the directories on the search path. */ if (base == name || (base - name == 2 && *name == '.')) { - SearchPathNode *ln; + CachedDirListNode *ln; /* - * We look through all the directories on the path seeking one + * Look through all the directories on the path seeking one * which contains the final component of the given name. If - * such a file is found, we concatenate the directory name - * and the final component and return the resulting string. - * If we don't find any such thing, we go on to phase two. + * such a file is found, return its pathname. + * If there is no such file, go on to phase two. * - * No matter what, we always look for the file in the current - * directory before anywhere else (unless we found the magic - * DOTLAST path, in which case we search it last) and we *do - * not* add the ./ to it if it exists. + * No matter what, always look for the file in the current + * directory before anywhere else (unless the path contains + * the magic '.DOTLAST', in which case search it last). * This is so there are no conflicts between what the user - * specifies (fish.c) and what pmake finds (./fish.c). + * specifies (fish.c) and what make finds (./fish.c). */ if (!seenDotLast && (file = DirFindDot(name, base)) != NULL) return file; @@ -1264,30 +1207,14 @@ Dir_FindFile(const char *name, SearchPath *path) return file; } - /* - * We didn't find the file on any directory in the search path. - * If the name doesn't contain a slash, that means it doesn't exist. - * If it *does* contain a slash, however, there is still hope: it - * could be in a subdirectory of one of the members of the search - * path. (eg. /usr/include and sys/types.h. The above search would - * fail to turn up types.h in /usr/include, but it *is* in - * /usr/include/sys/types.h). - * [ This no longer applies: If we find such a file, we assume there - * will be more (what else can we assume?) and add all but the last - * component of the resulting name onto the search path (at the - * end).] - * This phase is only performed if the file is *not* absolute. - */ if (base == name) { DEBUG0(DIR, " failed.\n"); misses++; return NULL; } - if (*base == '\0') { - /* we were given a trailing "/" */ - base = trailing_dot; - } + if (*base == '\0') + base = trailing_dot; /* we were given a trailing "/" */ if (name[0] != '/') { if (FindFileRelative(path, seenDotLast, name, &file)) @@ -1298,16 +1225,7 @@ Dir_FindFile(const char *name, SearchPath *path) } /* - * Didn't find it that way, either. Sigh. Phase 3. Add its directory - * onto the search path in any case, just in case, then look for the - * thing in the hash table. If we find it, grand. We return a new - * copy of the name. Otherwise we sadly return a NULL pointer. Sigh. - * Note that if the directory holding the file doesn't exist, this - * will do an extra search of the final directory on the path. Unless - * something weird happens, this search won't succeed and life will - * be groovy. - * - * Sigh. We cannot add the directory onto the search path because + * We cannot add the directory onto the search path because * of this amusing case: * $(INSTALLDIR)/$(FILE): $(FILE) * @@ -1315,75 +1233,40 @@ Dir_FindFile(const char *name, SearchPath *path) * When searching for $(FILE), we will find it in $(INSTALLDIR) * b/c we added it here. This is not good... */ -#if 0 - { - CachedDir *dir; - char *prefix; - if (base == trailing_dot) { - base = strrchr(name, '/'); - base++; - } - prefix = bmake_strsedup(name, base - 1); - (void)SearchPath_Add(path, prefix); - free(prefix); - - bigmisses++; - if (path->last == NULL) - return NULL; - - dir = path->last->datum; - if (HashSet_Contains(&dir->files, base)) - return bmake_strdup(name); - return NULL; - } -#else DEBUG1(DIR, " Looking for \"%s\" ...\n", name); bigmisses++; - if (cached_stat(name, &cst) == 0) { + if (cached_stat(name, &cst) == 0) return bmake_strdup(name); - } DEBUG0(DIR, " failed. Returning NULL\n"); return NULL; -#endif } /* - * Search for a path starting at a given directory and then working our way - * up towards the root. - * - * Input: - * here starting directory - * search_path the relative path we are looking for - * - * Results: - * The found path, or NULL. + * Search for 'needle' starting at the directory 'here' and then working our + * way up towards the root directory. Return the allocated path, or NULL. */ char * -Dir_FindHereOrAbove(const char *here, const char *search_path) +Dir_FindHereOrAbove(const char *here, const char *needle) { struct cached_stat cst; char *dirbase, *dirbase_end; char *try, *try_end; - /* copy out our starting point */ dirbase = bmake_strdup(here); dirbase_end = dirbase + strlen(dirbase); - /* loop until we determine a result */ for (;;) { - - /* try and stat(2) it ... */ - try = str_concat3(dirbase, "/", search_path); + try = str_concat3(dirbase, "/", needle); if (cached_stat(try, &cst) != -1) { - /* - * success! if we found a file, chop off - * the filename so we return a directory. - */ if ((cst.cst_mode & S_IFMT) != S_IFDIR) { + /* + * Chop off the filename, to return a + * directory. + */ try_end = try + strlen(try); while (try_end > try && *try_end != '/') try_end--; @@ -1396,16 +1279,10 @@ Dir_FindHereOrAbove(const char *here, const char *search_path) } free(try); - /* - * nope, we didn't find it. if we used up dirbase we've - * reached the root and failed. - */ if (dirbase_end == dirbase) break; /* failed! */ - /* - * truncate dirbase from the end to move up a dir - */ + /* Truncate dirbase from the end to move up a dir. */ while (dirbase_end > dirbase && *dirbase_end != '/') dirbase_end--; *dirbase_end = '\0'; /* chop! */ @@ -1476,10 +1353,10 @@ ResolveFullName(GNode *gn) } /* - * Search gn along dirSearchPath and store its modification time in gn->mtime. - * If no file is found, store 0 instead. + * Search 'gn' along 'dirSearchPath' and store its modification time in + * 'gn->mtime'. If no file is found, store 0 instead. * - * The found file is stored in gn->path, unless the node already had a path. + * The found file is stored in 'gn->path', unless the node already had a path. */ void Dir_UpdateMTime(GNode *gn, bool forceRefresh) @@ -1562,14 +1439,14 @@ CacheNewDir(const char *name, SearchPath *path) } /* - * Read the list of filenames in the directory and store the result - * in openDirs. + * Read the list of filenames in the directory 'name' and store the result + * in 'openDirs'. * - * If a path is given, append the directory to that path. + * If a search path is given, append the directory to that path. * * Input: * path The path to which the directory should be - * added, or NULL to only add the directory to openDirs + * added, or NULL to only add the directory to openDirs. * name The name of the directory to add. * The name is not normalized in any way. * Output: @@ -1584,7 +1461,7 @@ SearchPath_Add(SearchPath *path, const char *name) { if (path != NULL && strcmp(name, ".DOTLAST") == 0) { - SearchPathNode *ln; + CachedDirListNode *ln; /* XXX: Linear search gets slow with thousands of entries. */ for (ln = path->dirs.first; ln != NULL; ln = ln->next) { @@ -1617,7 +1494,7 @@ SearchPath * Dir_CopyDirSearchPath(void) { SearchPath *path = SearchPath_New(); - SearchPathNode *ln; + CachedDirListNode *ln; for (ln = dirSearchPath.dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; Lst_Append(&path->dirs, CachedDir_Ref(dir)); @@ -1628,22 +1505,14 @@ Dir_CopyDirSearchPath(void) /* * Make a string by taking all the directories in the given search path and * preceding them by the given flag. Used by the suffix module to create - * variables for compilers based on suffix search paths. - * - * Input: - * flag flag which should precede each directory - * path list of directories - * - * Results: - * The string mentioned above. Note that there is no space between the - * given flag and each directory. The empty string is returned if things - * don't go well. + * variables for compilers based on suffix search paths. Note that there is no + * space between the given flag and each directory. */ char * SearchPath_ToFlags(SearchPath *path, const char *flag) { Buffer buf; - SearchPathNode *ln; + CachedDirListNode *ln; Buf_Init(&buf); @@ -1663,7 +1532,7 @@ SearchPath_ToFlags(SearchPath *path, const char *flag) void SearchPath_Free(SearchPath *path) { - SearchPathNode *ln; + CachedDirListNode *ln; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; @@ -1694,7 +1563,7 @@ SearchPath_Clear(SearchPath *path) void SearchPath_AddAll(SearchPath *dst, SearchPath *src) { - SearchPathNode *ln; + CachedDirListNode *ln; for (ln = src->dirs.first; ln != NULL; ln = ln->next) { CachedDir *dir = ln->datum; @@ -1709,7 +1578,6 @@ percentage(int num, int den) return den != 0 ? num * 100 / den : 0; } -/********** DEBUG INFO **********/ void Dir_PrintDirectories(void) { @@ -1732,7 +1600,7 @@ Dir_PrintDirectories(void) void SearchPath_Print(const SearchPath *path) { - SearchPathNode *ln; + CachedDirListNode *ln; for (ln = path->dirs.first; ln != NULL; ln = ln->next) { const CachedDir *dir = ln->datum; diff --git a/for.c b/for.c index 38fd1c447206..b8e8099816b0 100644 --- a/for.c +++ b/for.c @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.176 2023/06/01 09:02:14 rillig Exp $ */ +/* $NetBSD: for.c,v 1.177 2023/11/19 22:50:11 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -45,7 +45,7 @@ * * After reaching the .endfor, the values from the .for line are grouped * according to the number of variables. For each such group, the unexpanded - * body is scanned for variable expressions, and those that match the + * body is scanned for expressions, and those that match the * variable names are replaced with expressions of the form ${:U...}. After * that, the body is treated like a file from an .include directive. * @@ -58,7 +58,7 @@ #include "make.h" /* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: for.c,v 1.176 2023/06/01 09:02:14 rillig Exp $"); +MAKE_RCSID("$NetBSD: for.c,v 1.177 2023/11/19 22:50:11 rillig Exp $"); typedef struct ForLoop { @@ -298,8 +298,8 @@ For_Accum(const char *line, int *forLevel) /* * When the body of a '.for i' loop is prepared for an iteration, each * occurrence of $i in the body is replaced with ${:U...}, inserting the - * value of the item. If this item contains a '$', it may be the start of a - * variable expression. This expression is copied verbatim, its length is + * value of the item. If this item contains a '$', it may be the start of an + * expression. This expression is copied verbatim, its length is * determined here, in a rather naive way, ignoring escape characters and * funny delimiters in modifiers like ':S}from}to}'. */ @@ -428,7 +428,7 @@ ForLoop_SubstVarLong(ForLoop *f, unsigned int firstItem, Buffer *body, /* * While expanding the body of a .for loop, replace single-character - * variable expressions like $i with their ${:U...} expansion. + * expressions like $i with their ${:U...} expansion. */ static void ForLoop_SubstVarShort(ForLoop *f, unsigned int firstItem, Buffer *body, @@ -464,7 +464,7 @@ found: * Compute the body for the current iteration by copying the unexpanded body, * replacing the expressions for the iteration variables on the way. * - * Using variable expressions ensures that the .for loop can't generate + * Using expressions ensures that the .for loop can't generate * syntax, and that the later parsing will still see an expression. * This code assumes that the variable with the empty name is never defined, * see unit-tests/varname-empty.mk. diff --git a/hash.c b/hash.c index b1796c859390..88d41c2c0f6f 100644 --- a/hash.c +++ b/hash.c @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.72 2022/02/09 21:09:24 rillig Exp $ */ +/* $NetBSD: hash.c,v 1.74 2023/12/19 19:33:39 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,12 +69,12 @@ * SUCH DAMAGE. */ -/* Hash tables with string keys. */ +/* Hash tables with string keys and pointer values. */ #include "make.h" /* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: hash.c,v 1.72 2022/02/09 21:09:24 rillig Exp $"); +MAKE_RCSID("$NetBSD: hash.c,v 1.74 2023/12/19 19:33:39 rillig Exp $"); /* * The ratio of # entries to # buckets at which we rebuild the table to @@ -113,7 +113,7 @@ Hash_Substring(Substring key) static HashEntry * HashTable_Find(HashTable *t, Substring key, unsigned int h) { - HashEntry *e; + HashEntry *he; unsigned int chainlen = 0; size_t keyLen = Substring_Length(key); @@ -122,18 +122,18 @@ HashTable_Find(HashTable *t, Substring key, unsigned int h) t, h, (int)keyLen, key.start); #endif - for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { + for (he = t->buckets[h & t->bucketsMask]; he != NULL; he = he->next) { chainlen++; - if (e->key_hash == h && - strncmp(e->key, key.start, keyLen) == 0 && - e->key[keyLen] == '\0') + if (he->hash == h && + strncmp(he->key, key.start, keyLen) == 0 && + he->key[keyLen] == '\0') break; } if (chainlen > t->maxchain) t->maxchain = chainlen; - return e; + return he; } /* Set up the hash table. */ @@ -207,7 +207,7 @@ HashTable_FindValueBySubstringHash(HashTable *t, Substring key, unsigned int h) /* * Make the hash table larger. Any bucket numbers from the old table become - * invalid; the hash codes stay valid though. + * invalid; the hash values stay valid though. */ static void HashTable_Enlarge(HashTable *t) @@ -226,8 +226,8 @@ HashTable_Enlarge(HashTable *t) HashEntry *he = oldBuckets[i]; while (he != NULL) { HashEntry *next = he->next; - he->next = newBuckets[he->key_hash & newMask]; - newBuckets[he->key_hash & newMask] = he; + he->next = newBuckets[he->hash & newMask]; + newBuckets[he->hash & newMask] = he; he = next; } } @@ -264,7 +264,7 @@ HashTable_CreateEntry(HashTable *t, const char *key, bool *out_isNew) he = bmake_malloc(sizeof *he + (size_t)(keyEnd - key)); he->value = NULL; - he->key_hash = h; + he->hash = h; memcpy(he->key, key, (size_t)(keyEnd - key) + 1); he->next = t->buckets[h & t->bucketsMask]; @@ -287,7 +287,7 @@ HashTable_Set(HashTable *t, const char *key, void *value) void HashTable_DeleteEntry(HashTable *t, HashEntry *he) { - HashEntry **ref = &t->buckets[he->key_hash & t->bucketsMask]; + HashEntry **ref = &t->buckets[he->hash & t->bucketsMask]; HashEntry *p; for (; (p = *ref) != NULL; ref = &p->next) { diff --git a/hash.h b/hash.h index 016108c39060..2bee685b7ebb 100644 --- a/hash.h +++ b/hash.h @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.46 2022/01/31 22:58:26 rillig Exp $ */ +/* $NetBSD: hash.h,v 1.48 2023/12/19 19:33:39 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -72,7 +72,7 @@ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93 */ -/* Hash tables with strings as keys and arbitrary pointers as values. */ +/* Hash tables with string keys and pointer values. */ #ifndef MAKE_HASH_H #define MAKE_HASH_H @@ -82,18 +82,17 @@ typedef struct HashEntry { struct HashEntry *next; /* Used to link together all the entries * associated with the same bucket. */ void *value; - unsigned int key_hash; /* hash value of the key */ + unsigned int hash; /* hash value of the key */ char key[1]; /* key string, variable length */ } HashEntry; /* The hash table containing the entries. */ typedef struct HashTable { - HashEntry **buckets; /* Pointers to HashEntry, one for each bucket - * in the table. */ + HashEntry **buckets; unsigned int bucketsSize; - unsigned int numEntries; /* Number of entries in the table. */ + unsigned int numEntries; unsigned int bucketsMask; /* Used to select the bucket for a hash. */ - unsigned int maxchain; /* max length of chain detected */ + unsigned int maxchain; /* Maximum length of chain seen. */ } HashTable; /* State of an iteration over all entries in a table. */ @@ -109,15 +108,15 @@ typedef struct HashSet { } HashSet; MAKE_INLINE void * MAKE_ATTR_USE -HashEntry_Get(HashEntry *h) +HashEntry_Get(HashEntry *he) { - return h->value; + return he->value; } MAKE_INLINE void -HashEntry_Set(HashEntry *h, void *datum) +HashEntry_Set(HashEntry *he, void *datum) { - h->value = datum; + he->value = datum; } /* Set things up for iterating over all entries in the hash table. */ diff --git a/job.c b/job.c index a2e6eeb83090..21b119ed6865 100644 --- a/job.c +++ b/job.c @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.459 2023/02/15 06:52:58 rillig Exp $ */ +/* $NetBSD: job.c,v 1.465 2024/01/07 11:39:04 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -70,12 +70,11 @@ */ /* - * job.c -- - * handle the creation etc. of our child processes. + * 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 + * 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. @@ -155,7 +154,7 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.459 2023/02/15 06:52:58 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.465 2024/01/07 11:39:04 rillig Exp $"); /* * A shell defines how the commands are run. All commands for a target are @@ -258,9 +257,7 @@ typedef struct ShellWriter { } ShellWriter; -/* - * error handling variables - */ +/* error handling variables */ static int job_errors = 0; /* number of errors reported */ static enum { /* Why is the make aborting? */ ABORT_NONE, @@ -270,9 +267,7 @@ static enum { /* Why is the make aborting? */ } aborting = ABORT_NONE; #define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ -/* - * this tracks the number of tokens currently "out" to build jobs. - */ +/* Tracks the number of tokens currently "out" to build jobs. */ int jobTokensRunning = 0; typedef enum JobStartResult { @@ -301,15 +296,15 @@ typedef enum JobStartResult { #define DEFSHELL_INDEX_SH 1 #define DEFSHELL_INDEX_KSH 2 #define DEFSHELL_INDEX_CSH 3 -#else /* !DEFSHELL_CUSTOM */ +#else #define DEFSHELL_INDEX_SH 0 #define DEFSHELL_INDEX_KSH 1 #define DEFSHELL_INDEX_CSH 2 -#endif /* !DEFSHELL_CUSTOM */ +#endif #ifndef DEFSHELL_INDEX #define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */ -#endif /* !DEFSHELL_INDEX */ +#endif static Shell shells[] = { #ifdef DEFSHELL_CUSTOM @@ -425,7 +420,7 @@ 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; /* we want a token */ +static unsigned int wantToken; static bool lurking_children = false; static bool make_suspended = false; /* Whether we've seen a SIGTSTP (etc) */ @@ -560,7 +555,7 @@ JobCreatePipe(Job *job, int minfd) Punt("Cannot create pipe: %s", strerror(errno)); for (i = 0; i < 2; i++) { - /* Avoid using low numbered fds */ + /* Avoid using low-numbered fds */ fd = fcntl(pipe_fds[i], F_DUPFD, minfd); if (fd != -1) { close(pipe_fds[i]); @@ -571,7 +566,6 @@ JobCreatePipe(Job *job, int minfd) job->inPipe = pipe_fds[0]; job->outPipe = pipe_fds[1]; - /* Set close-on-exec flag for both */ 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) @@ -694,10 +688,10 @@ JobPassSig_suspend(int signo) /* * We've been continued. * - * A whole host of signals continue to happen! + * 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 alseep. - * The SIGCONT that actually caused us to wakeup. + * 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 @@ -746,7 +740,7 @@ ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags) else if (*p == '+') out_cmdFlags->always = true; else if (!ch_isspace(*p)) - /* Ignore whitespace for compatibility with gnu make */ + /* Ignore whitespace for compatibility with GNU make */ break; p++; } @@ -876,13 +870,9 @@ static void JobWriteSpecials(Job *job, ShellWriter *wr, const char *escCmd, bool run, CommandFlags *inout_cmdFlags, const char **inout_cmdTemplate) { - if (!run) { - /* - * If there is no command to run, there is no need to switch - * error checking off and on again for nothing. - */ + if (!run) inout_cmdFlags->ignerr = false; - } else if (shell->hasErrCtl) + 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, @@ -951,12 +941,10 @@ JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) escCmd = shell->hasErrCtl ? NULL : EscapeShellDblQuot(xcmd); if (!cmdFlags.echo) { - if (job->echo && run && shell->hasEchoCtl) { + if (job->echo && run && shell->hasEchoCtl) ShellWriter_EchoOff(wr); - } else { - if (shell->hasErrCtl) - cmdFlags.echo = true; - } + else if (shell->hasErrCtl) + cmdFlags.echo = true; } if (cmdFlags.ignerr) { @@ -1404,7 +1392,7 @@ Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) return false; } - abortProc("%s: don't know how to make %s. Stop", progname, gn->name); + abortProc("don't know how to make %s. Stop", gn->name); return false; } @@ -1431,7 +1419,7 @@ JobExec(Job *job, char **argv) } /* - * Some jobs produce no output and it's disconcerting to have + * 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. @@ -1483,9 +1471,7 @@ JobExec(Job *job, char **argv) execDie("lseek to 0", "stdin"); if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { - /* - * Pass job token pipe to submakes. - */ + /* Pass job token pipe to submakes. */ if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) execDie("clear close-on-exec", "tokenWaitJob.inPipe"); @@ -1781,12 +1767,12 @@ JobStart(GNode *gn, bool special) * itself. */ static char * -PrintFilteredOutput(char *cp, char *endp) /* XXX: should all be const */ +PrintFilteredOutput(char *p, char *endp) /* XXX: should all be const */ { - char *ecp; /* XXX: should be const */ + char *ep; /* XXX: should be const */ if (shell->noPrint == NULL || shell->noPrint[0] == '\0') - return cp; + return p; /* * XXX: What happens if shell->noPrint occurs on the boundary of @@ -1794,9 +1780,9 @@ PrintFilteredOutput(char *cp, char *endp) /* XXX: should all be const */ * be a proper stream filter instead of doing string matching on * selected chunks of the output. */ - while ((ecp = strstr(cp, shell->noPrint)) != NULL) { - if (ecp != cp) { - *ecp = '\0'; /* XXX: avoid writing to the buffer */ + 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. @@ -1804,16 +1790,16 @@ PrintFilteredOutput(char *cp, char *endp) /* XXX: should all be const */ * there must be a newline, so we don't print one. */ /* XXX: What about null bytes in the output? */ - (void)fprintf(stdout, "%s", cp); + (void)fprintf(stdout, "%s", p); (void)fflush(stdout); } - cp = ecp + shell->noPrintLen; - if (cp == endp) + p = ep + shell->noPrintLen; + if (p == endp) break; - cp++; /* skip over the (XXX: assumed) newline */ - pp_skip_whitespace(&cp); + p++; /* skip over the (XXX: assumed) newline */ + pp_skip_whitespace(&p); } - return cp; + return p; } /* @@ -1908,7 +1894,7 @@ again: */ job->outBuf[i] = '\0'; if (i >= job->curPos) { - char *cp; + char *p; /* * FIXME: SwitchOutputTo should be here, according to @@ -1916,23 +1902,23 @@ again: * do anything in the default shell, this bug has gone * unnoticed until now. */ - cp = PrintFilteredOutput(job->outBuf, &job->outBuf[i]); + 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 (*cp != '\0') { + if (*p != '\0') { if (!opts.silent) SwitchOutputTo(job->node); #ifdef USE_META if (useMeta) { - meta_job_output(job, cp, + meta_job_output(job, p, gotNL ? "\n" : ""); } #endif - (void)fprintf(stdout, "%s%s", cp, + (void)fprintf(stdout, "%s%s", p, gotNL ? "\n" : ""); (void)fflush(stdout); } @@ -2212,11 +2198,10 @@ Shell_GetNewline(void) void Job_SetPrefix(void) { - if (targPrefix != NULL) { + if (targPrefix != NULL) free(targPrefix); - } else if (!Var_Exists(SCOPE_GLOBAL, ".MAKE.JOB.PREFIX")) { + else if (!Var_Exists(SCOPE_GLOBAL, ".MAKE.JOB.PREFIX")) Global_Set(".MAKE.JOB.PREFIX", "---"); - } targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}", SCOPE_GLOBAL, VARE_WANTRES); @@ -2283,9 +2268,7 @@ Job_Init(void) watchfd(&childExitJob); sigemptyset(&caught_signals); - /* - * Install a SIGCHLD handler. - */ + /* Install a SIGCHLD handler. */ (void)bmake_signal(SIGCHLD, JobChildSig); sigaddset(&caught_signals, SIGCHLD); @@ -2416,9 +2399,7 @@ Job_ParseShell(char *line) memset(&newShell, 0, sizeof newShell); - /* - * Parse the specification by keyword - */ + /* Parse the specification by keyword. */ wordsList = Str_Words(line, true); words = wordsList.words; argc = wordsList.len; @@ -2513,25 +2494,9 @@ Job_ParseShell(char *line) } } } else { - /* - * The user provided a path. If s/he gave nothing else - * (fullSpec is false), try and find a matching shell in the - * ones we know of. Else we just take the specification at - * its word and copy it to a new location. In either case, - * we need to record the path the user gave for the shell. - */ shellPath = path; - path = strrchr(path, '/'); - if (path == NULL) { - path = UNCONST(shellPath); - } else { - path++; - } - if (newShell.name != NULL) { - shellName = newShell.name; - } else { - shellName = path; - } + shellName = newShell.name != NULL ? newShell.name + : str_basename(path); if (!fullSpec) { if ((sh = FindShellByName(shellName)) == NULL) { Parse_Error(PARSE_WARNING, @@ -2628,11 +2593,10 @@ Job_Finish(void) GNode *endNode = Targ_GetEndNode(); if (!Lst_IsEmpty(&endNode->commands) || !Lst_IsEmpty(&endNode->children)) { - if (job_errors != 0) { + if (job_errors != 0) Error("Errors reported so .END ignored"); - } else { + else JobRun(endNode); - } } return job_errors; } @@ -2772,9 +2736,7 @@ clearfd(Job *job) fdsLen--; } #endif - /* - * Move last job in table into hole made by dead job. - */ + /* Move last job in table into hole made by dead job. */ if (fdsLen != i) { fds[i] = fds[fdsLen]; jobByFdIndex[i] = jobByFdIndex[fdsLen]; @@ -2905,9 +2867,8 @@ Job_TokenWithdraw(void) if (count == 0) Fatal("eof on job pipe!"); if (count < 0 && jobTokensRunning != 0) { - if (errno != EAGAIN) { + if (errno != EAGAIN) Fatal("job pipe read: %s", strerror(errno)); - } DEBUG1(JOB, "(%d) blocked for token\n", getpid()); wantToken = 1; return false; diff --git a/job.h b/job.h index 574c12ed16a0..26185ba84a7d 100644 --- a/job.h +++ b/job.h @@ -1,4 +1,4 @@ -/* $NetBSD: job.h,v 1.77 2021/12/15 12:58:01 rillig Exp $ */ +/* $NetBSD: job.h,v 1.78 2023/12/19 19:33:39 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -73,9 +73,7 @@ * from: @(#)job.h 8.1 (Berkeley) 6/6/93 */ -/* - * Running of jobs in parallel mode. - */ +/* Run jobs in parallel mode. */ #ifndef MAKE_JOB_H #define MAKE_JOB_H diff --git a/lst.c b/lst.c index 09a6ef10c76e..7a7c08200947 100644 --- a/lst.c +++ b/lst.c @@ -1,4 +1,4 @@ -/* $NetBSD: lst.c,v 1.106 2022/02/26 11:57:21 rillig Exp $ */ +/* $NetBSD: lst.c,v 1.107 2023/12/29 20:43:58 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -34,7 +34,7 @@ #include "make.h" -MAKE_RCSID("$NetBSD: lst.c,v 1.106 2022/02/26 11:57:21 rillig Exp $"); +MAKE_RCSID("$NetBSD: lst.c,v 1.107 2023/12/29 20:43:58 rillig Exp $"); static ListNode * LstNodeNew(ListNode *prev, ListNode *next, void *datum) @@ -48,15 +48,6 @@ LstNodeNew(ListNode *prev, ListNode *next, void *datum) return ln; } -/* Create and initialize a new, empty list. */ -List * -Lst_New(void) -{ - List *list = bmake_malloc(sizeof *list); - Lst_Init(list); - return list; -} - void Lst_Done(List *list) { @@ -80,15 +71,6 @@ Lst_DoneCall(List *list, LstFreeProc freeProc) } } -/* Free a list and all its nodes. The node data are not freed though. */ -void -Lst_Free(List *list) -{ - - Lst_Done(list); - free(list); -} - /* Insert a new node with the datum before the given node. */ void Lst_InsertBefore(List *list, ListNode *ln, void *datum) diff --git a/lst.h b/lst.h index a5170b420b7a..59f8f201192f 100644 --- a/lst.h +++ b/lst.h @@ -1,4 +1,4 @@ -/* $NetBSD: lst.h,v 1.103 2022/03/03 19:55:27 rillig Exp $ */ +/* $NetBSD: lst.h,v 1.104 2023/12/29 20:43:58 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -106,16 +106,10 @@ struct List { /* Free the datum of a node, called before freeing the node itself. */ typedef void LstFreeProc(void *); -/* Create or destroy a list */ - -/* Create a new list. */ -List *Lst_New(void) MAKE_ATTR_USE; /* Free the list nodes, but not the list itself. */ void Lst_Done(List *); /* Free the list nodes, freeing the node data using the given function. */ void Lst_DoneCall(List *, LstFreeProc); -/* Free the list, leaving the node data unmodified. */ -void Lst_Free(List *); #define LST_INIT { NULL, NULL } diff --git a/main.c b/main.c index b0ce265d34a6..79e2e5e0b4e7 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.599 2023/09/10 21:52:36 rillig Exp $ */ +/* $NetBSD: main.c,v 1.609 2024/01/07 01:33:57 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -111,8 +111,8 @@ #include "trace.h" /* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: main.c,v 1.599 2023/09/10 21:52:36 rillig Exp $"); -#if defined(MAKE_NATIVE) && !defined(lint) +MAKE_RCSID("$NetBSD: main.c,v 1.609 2024/01/07 01:33:57 sjg Exp $"); +#if defined(MAKE_NATIVE) __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " "The Regents of the University of California. " "All rights reserved."); @@ -125,7 +125,7 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " CmdOpts opts; time_t now; /* Time at start of make */ GNode *defaultNode; /* .DEFAULT node */ -bool allPrecious; /* .PRECIOUS given on line by itself */ +bool allPrecious; /* .PRECIOUS given on a line by itself */ bool deleteOnError; /* .DELETE_ON_ERROR: set */ static int maxJobTokens; /* -j argument */ @@ -152,7 +152,7 @@ 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'. + * all the options without '-', convert 'flags' to '-f -l -a -g -s '. */ static char * explode(const char *flags) @@ -226,8 +226,7 @@ MainParseArgDebugFile(const char *arg) opts.debug_file = fopen(fname, mode); if (opts.debug_file == NULL) { - fprintf(stderr, "Cannot open debug file \"%s\"\n", - fname); + fprintf(stderr, "Cannot open debug file \"%s\"\n", fname); exit(2); } free(fname); @@ -407,19 +406,19 @@ MainParseArgJobs(const char *arg) forceJobs = true; opts.maxJobs = (int)strtol(arg, &end, 0); - p = arg + (end - arg); + p = end; #ifdef _SC_NPROCESSORS_ONLN if (*p != '\0') { double d; - if (*p == 'C') { + if (*p == 'C') d = (opts.maxJobs > 0) ? opts.maxJobs : 1; - } else if (*p == '.') { + else if (*p == '.') { d = strtod(arg, &end); - p = arg + (end - arg); + p = end; } else - d = 0; - if (d > 0) { + d = 0.0; + if (d > 0.0) { p = ""; opts.maxJobs = (int)sysconf(_SC_NPROCESSORS_ONLN); opts.maxJobs = (int)(d * (double)opts.maxJobs); @@ -443,8 +442,7 @@ MainParseArgJobs(const char *arg) static void MainParseArgSysInc(const char *argvalue) { - /* look for magic parent directory search string */ - if (strncmp(".../", argvalue, 4) == 0) { + if (strncmp(argvalue, ".../", 4) == 0) { char *found_path = Dir_FindHereOrAbove(curdir, argvalue + 4); if (found_path == NULL) return; @@ -480,7 +478,7 @@ MainParseOption(char c, const char *argvalue) Global_Append(MAKEFLAGS, argvalue); break; case 'I': - Parse_AddIncludeDir(argvalue); + SearchPath_Add(parseIncPath, argvalue); Global_Append(MAKEFLAGS, "-I"); Global_Append(MAKEFLAGS, argvalue); break; @@ -691,13 +689,13 @@ Main_ParseArgLine(const char *line) { Words words; char *buf; + const char *p; if (line == NULL) return; - /* XXX: don't use line as an iterator variable */ - for (; *line == ' '; line++) + for (p = line; *p == ' '; p++) continue; - if (line[0] == '\0') + if (p[0] == '\0') return; #ifndef POSIX @@ -716,7 +714,7 @@ Main_ParseArgLine(const char *line) #endif { FStr argv0 = Var_Value(SCOPE_GLOBAL, ".MAKE"); - buf = str_concat3(argv0.str, " ", line); + buf = str_concat3(argv0.str, " ", p); FStr_Done(&argv0); } @@ -791,22 +789,17 @@ SetVarObjdir(bool writable, const char *var, const char *suffix) } /* - * Splits str into words, adding them to the list. + * Splits str into words (in-place, modifying it), adding them to the list. * The string must be kept alive as long as the list. */ -int -str2Lst_Append(StringList *lp, char *str) +void +AppendWords(StringList *lp, char *str) { - char *cp; - int n; - + char *p; const char *sep = " \t"; - for (n = 0, cp = strtok(str, sep); cp != NULL; cp = strtok(NULL, sep)) { - Lst_Append(lp, cp); - n++; - } - return n; + for (p = strtok(str, sep); p != NULL; p = strtok(NULL, sep)) + Lst_Append(lp, p); } #ifdef SIGINFO @@ -1042,20 +1035,14 @@ InitVarMachineArch(void) #ifndef NO_PWD_OVERRIDE /* - * All this code is so that we know where we are when we start up - * on a different machine with pmake. - * - * XXX: Make no longer has "local" and "remote" mode. Is this code still - * necessary? - * * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX * since the value of curdir can vary depending on how we got - * here. Ie sitting at a shell prompt (shell that provides $PWD) - * or via subdir.mk in which case its likely a shell which does + * 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 a variable expression. + * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains an expression. */ static void HandlePWD(const struct stat *curdir_st) @@ -1189,7 +1176,7 @@ static void InitDefSysIncPath(char *syspath) { static char defsyspath[] = _PATH_DEFSYSPATH; - char *start, *cp; + char *start, *p; /* * If no user-supplied system path was given (through the -m option) @@ -1201,11 +1188,11 @@ InitDefSysIncPath(char *syspath) else syspath = bmake_strdup(syspath); - for (start = syspath; *start != '\0'; start = cp) { - for (cp = start; *cp != '\0' && *cp != ':'; cp++) + for (start = syspath; *start != '\0'; start = p) { + for (p = start; *p != '\0' && *p != ':'; p++) continue; - if (*cp == ':') - *cp++ = '\0'; + if (*p == ':') + *p++ = '\0'; /* look for magic parent directory search string */ if (strncmp(start, ".../", 4) == 0) { @@ -1296,17 +1283,17 @@ InitVpath(void) /* TODO: handle errors */ path = vpath; do { - char *cp; + char *p; /* skip to end of directory */ - for (cp = path; *cp != ':' && *cp != '\0'; cp++) + for (p = path; *p != ':' && *p != '\0'; p++) continue; /* Save terminator character so know when to stop */ - savec = *cp; - *cp = '\0'; + savec = *p; + *p = '\0'; /* Add directory to search path */ (void)SearchPath_Add(&dirSearchPath, path); - *cp = savec; - path = cp + 1; + *p = savec; + path = p + 1; } while (savec == ':'); free(vpath); } @@ -1332,7 +1319,7 @@ ReadFirstDefaultMakefile(void) SCOPE_CMDLINE, VARE_WANTRES); /* TODO: handle errors */ - (void)str2Lst_Append(&makefiles, prefs); + AppendWords(&makefiles, prefs); for (ln = makefiles.first; ln != NULL; ln = ln->next) if (ReadMakefile(ln->datum)) @@ -1378,22 +1365,12 @@ main_Init(int argc, char **argv) exit(2); } - /* - * Get the name of this type of MACHINE from utsname - * so we can share an executable for similar machines. - * (i.e. m68k: amiga hp300, mac68k, sun3, ...) - * - * Note that both MACHINE and MACHINE_ARCH are decided at - * run-time. - */ machine = InitVarMachine(&utsname); machine_arch = InitVarMachineArch(); myPid = getpid(); /* remember this for vFork() */ - /* - * Just in case MAKEOBJDIR wants us to do something tricky. - */ + /* Just in case MAKEOBJDIR wants us to do something tricky. */ Targ_Init(); Var_Init(); Global_Set_ReadOnly(".MAKE.OS", utsname.sysname); @@ -1402,7 +1379,7 @@ main_Init(int argc, char **argv) #ifdef MAKE_VERSION Global_Set("MAKE_VERSION", MAKE_VERSION); #endif - Global_Set_ReadOnly(".newline", "\n"); /* handy for :@ loops */ + Global_Set_ReadOnly(".newline", "\n"); #ifndef MAKEFILE_PREFERENCE_LIST /* This is the traditional preference for makefiles. */ # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" @@ -1757,16 +1734,40 @@ Cmd_Exec(const char *cmd, char **error) Buffer buf; /* buffer to store the result */ ssize_t bytes_read; char *output; - char *cp; + char *p; int saved_errno; + char cmd_file[MAXPATHLEN]; + size_t cmd_len; + int cmd_fd = -1; - if (shellName == NULL) + if (shellPath == NULL) Shell_Init(); + cmd_len = strlen(cmd); + if (cmd_len > 1000) { + cmd_fd = mkTempFile(NULL, cmd_file, sizeof(cmd_file)); + 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; + } + } + } + args[0] = shellName; - args[1] = "-c"; - args[2] = cmd; - args[3] = NULL; + if (cmd_fd >= 0) { + args[1] = cmd_file; + args[2] = NULL; + } else { + cmd_file[0] = '\0'; + args[1] = "-c"; + args[2] = cmd; + args[3] = NULL; + } DEBUG1(VAR, "Capturing the output of command \"%s\"\n", cmd); if (pipe(pipefds) == -1) { @@ -1815,9 +1816,9 @@ Cmd_Exec(const char *cmd, char **error) buf.data[buf.len - 1] = '\0'; output = Buf_DoneData(&buf); - for (cp = output; *cp != '\0'; cp++) - if (*cp == '\n') - *cp = ' '; + for (p = output; *p != '\0'; p++) + if (*p == '\n') + *p = ' '; if (WIFSIGNALED(status)) *error = str_concat3("\"", cmd, "\" exited on a signal"); @@ -1829,6 +1830,8 @@ Cmd_Exec(const char *cmd, char **error) "Couldn't read shell's output for \"", cmd, "\""); else *error = NULL; + if (cmd_file[0] != '\0') + unlink(cmd_file); return output; } @@ -1880,6 +1883,7 @@ Fatal(const char *fmt, ...) Job_Wait(); (void)fflush(stdout); + fprintf(stderr, "%s: ", progname); va_start(ap, fmt); (void)vfprintf(stderr, fmt, ap); va_end(ap); @@ -2003,13 +2007,13 @@ execDie(const char *af, const char *av) static void purge_relative_cached_realpaths(void) { - HashEntry *he, *nhe; + HashEntry *he, *next; HashIter hi; HashIter_Init(&hi, &cached_realpaths); he = HashIter_Next(&hi); while (he != NULL) { - nhe = HashIter_Next(&hi); + next = HashIter_Next(&hi); if (he->key[0] != '/') { DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); HashTable_DeleteEntry(&cached_realpaths, he); @@ -2018,7 +2022,7 @@ purge_relative_cached_realpaths(void) * free them or document why they cannot be freed. */ } - he = nhe; + he = next; } } @@ -2183,7 +2187,7 @@ getTmpdir(void) /* * Create and open a temp file using "pattern". - * If out_fname is provided, set it to a copy of the filename created. + * If tfile is provided, set it to a copy of the filename created. * Otherwise unlink the file once open. */ int diff --git a/make.1 b/make.1 index e70252f1b73e..0be0f9c8d64f 100644 --- a/make.1 +++ b/make.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.371 2023/09/10 21:52:36 rillig Exp $ +.\" $NetBSD: make.1,v 1.372 2023/12/24 16:48:30 sjg Exp $ .\" .\" Copyright (c) 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -29,7 +29,7 @@ .\" .\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 .\" -.Dd September 9, 2023 +.Dd December 24, 2023 .Dt MAKE 1 .Os .Sh NAME @@ -2523,7 +2523,7 @@ set the read-only attribute on the global variables specified as sources. .It Ic .SHELL Sets the shell that .Nm -uses to execute commands in jobs mode. +uses to execute commands. The sources are a set of .Ar field\| Ns Cm \&= Ns Ar value pairs. diff --git a/make.c b/make.c index b636800d33cb..53f7b35754ba 100644 --- a/make.c +++ b/make.c @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.259 2023/02/14 21:38:31 rillig Exp $ */ +/* $NetBSD: make.c,v 1.262 2024/01/05 23:22:06 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -104,7 +104,7 @@ #include "job.h" /* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ -MAKE_RCSID("$NetBSD: make.c,v 1.259 2023/02/14 21:38:31 rillig Exp $"); +MAKE_RCSID("$NetBSD: make.c,v 1.262 2024/01/05 23:22:06 rillig Exp $"); /* Sequence # to detect recursion. */ static unsigned int checked_seqno = 1; @@ -132,7 +132,7 @@ GNodeType_ToString(GNodeType type, void **freeIt) { Buffer buf; - Buf_InitSize(&buf, 32); + Buf_Init(&buf); #define ADD(flag) Buf_AddFlag(&buf, (type & (flag)) != OP_NONE, #flag) ADD(OP_DEPENDS); ADD(OP_FORCE); @@ -174,7 +174,7 @@ GNodeFlags_ToString(GNodeFlags flags, void **freeIt) { Buffer buf; - Buf_InitSize(&buf, 32); + Buf_Init(&buf); Buf_AddFlag(&buf, flags.remake, "REMAKE"); Buf_AddFlag(&buf, flags.childMade, "CHILDMADE"); Buf_AddFlag(&buf, flags.force, "FORCE"); @@ -330,13 +330,12 @@ GNode_IsOODate(GNode *gn) * out-of-date. */ if (DEBUG(MAKE)) { - if (gn->type & OP_FORCE) { + if (gn->type & OP_FORCE) debug_printf("! operator..."); - } else if (gn->type & OP_PHONY) { + else if (gn->type & OP_PHONY) debug_printf(".PHONY node..."); - } else { + else debug_printf(".EXEC node..."); - } } oodate = true; } else if (IsOODateRegular(gn)) { @@ -440,11 +439,10 @@ Make_HandleUse(GNode *cgn, GNode *pgn) * We don't need to do this for commands. * They get expanded properly when we execute. */ - if (gn->uname == NULL) { + if (gn->uname == NULL) gn->uname = gn->name; - } else { + else free(gn->name); - } gn->name = Var_Subst(gn->uname, pgn, VARE_WANTRES); /* TODO: handle errors */ if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) { @@ -546,9 +544,8 @@ Make_Recheck(GNode *gn) * depend on FRC to be made, so we have to check for gn->children * being empty as well. */ - if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { + if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) gn->mtime = now; - } #else /* * This is what Make does and it's actually a good thing, as it @@ -689,9 +686,8 @@ Make_Update(GNode *cgn) * now -- some rules won't actually update the file. If the file * still doesn't exist, make its mtime now. */ - if (cgn->made != UPTODATE) { + if (cgn->made != UPTODATE) mtime = Make_Recheck(cgn); - } /* * If this is a `::' node, we must consult its first instance @@ -1224,7 +1220,7 @@ MakePrintStatusList(GNodeList *gnodes, int *errors) static void ExamineLater(GNodeList *examine, GNodeList *toBeExamined) { - ListNode *ln; + GNodeListNode *ln; for (ln = toBeExamined->first; ln != NULL; ln = ln->next) { GNode *gn = ln->datum; diff --git a/make.h b/make.h index dab619c95b80..1b9147b383b0 100644 --- a/make.h +++ b/make.h @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.325 2023/09/10 11:52:29 rillig Exp $ */ +/* $NetBSD: make.h,v 1.327 2023/12/17 09:02:26 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -882,7 +882,6 @@ 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_AddIncludeDir(const char *); void Parse_File(const char *, int); void Parse_PushInput(const char *, unsigned, unsigned, Buffer, struct ForLoop *); @@ -1064,7 +1063,7 @@ 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; -int str2Lst_Append(StringList *, char *); +void AppendWords(StringList *, char *); void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); bool GNode_ShouldExecute(GNode *gn) MAKE_ATTR_USE; diff --git a/meta.c b/meta.c index 89f1a9fa1588..b9103a620e49 100644 --- a/meta.c +++ b/meta.c @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.206 2023/08/19 00:09:17 sjg Exp $ */ +/* $NetBSD: meta.c,v 1.207 2023/12/17 09:02:26 rillig Exp $ */ /* * Implement 'meta' mode. @@ -620,7 +620,7 @@ meta_mode_init(const char *make_mode) metaBailiwickStr = Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", SCOPE_GLOBAL, VARE_WANTRES); /* TODO: handle errors */ - str2Lst_Append(&metaBailiwick, metaBailiwickStr); + AppendWords(&metaBailiwick, metaBailiwickStr); /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ @@ -629,7 +629,7 @@ meta_mode_init(const char *make_mode) metaIgnorePathsStr = Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", SCOPE_GLOBAL, VARE_WANTRES); /* TODO: handle errors */ - str2Lst_Append(&metaIgnorePaths, metaIgnorePathsStr); + AppendWords(&metaIgnorePaths, metaIgnorePathsStr); /* * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} diff --git a/mk/ChangeLog b/mk/ChangeLog index 26be14aae809..447c8cf9c955 100644 --- a/mk/ChangeLog +++ b/mk/ChangeLog @@ -1,3 +1,45 @@ +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 diff --git a/mk/compiler.mk b/mk/compiler.mk index ce394c3275bf..4f942d1f9f7e 100644 --- a/mk/compiler.mk +++ b/mk/compiler.mk @@ -1,4 +1,4 @@ -# $Id: compiler.mk,v 1.11 2022/09/09 17:44:29 sjg Exp $ +# $Id: compiler.mk,v 1.12 2023/10/03 18:47:48 sjg Exp $ # # @(#) Copyright (c) 2019, Simon J. Gerraty # @@ -32,7 +32,7 @@ COMPILER_TYPE = gcc .endif .endif .if empty(COMPILER_VERSION) -COMPILER_VERSION != echo "${_v:M[1-9].[0-9]*}:[1]" | \ +COMPILER_VERSION != echo "${_v:M[1-9][0-9]*.[0-9]*}:[1]" | \ awk -F. '{print $$1 * 10000 + $$2 * 100 + $$3;}' .endif .undef _v diff --git a/mk/dirdeps.mk b/mk/dirdeps.mk index 6ed271db4df0..b3b34145e8e9 100644 --- a/mk/dirdeps.mk +++ b/mk/dirdeps.mk @@ -1,4 +1,4 @@ -# $Id: dirdeps.mk,v 1.165 2023/08/19 17:35:32 sjg Exp $ +# $Id: dirdeps.mk,v 1.166 2024/01/05 23:16:34 sjg Exp $ # SPDX-License-Identifier: BSD-2-Clause # @@ -754,7 +754,6 @@ _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}/,,} -.export _cache_xtra_deps _new_dirdeps .if !empty(DIRDEPS_EXPORT_VARS) || !empty(DEP_EXPORT_VARS) # Discouraged, but there are always exceptions. # Handle it here rather than explain how. @@ -804,15 +803,27 @@ ${_this_dir}.$m: ${_build_dirs:M*.$q} .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 -# anything in _{build,env}_xtra_dirs is hooked to dirdeps: only +.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} diff --git a/mk/dpadd.mk b/mk/dpadd.mk index 07528f9e926c..a4c73c1854fc 100644 --- a/mk/dpadd.mk +++ b/mk/dpadd.mk @@ -1,6 +1,6 @@ -# $Id: dpadd.mk,v 1.30 2021/12/08 05:56:50 sjg Exp $ +# $Id: dpadd.mk,v 1.31 2023/11/25 01:07:49 sjg Exp $ # -# @(#) Copyright (c) 2004, Simon J. Gerraty +# @(#) Copyright (c) 2004-2023, Simon J. Gerraty # # This file is provided in the hope that it will # be of use. There is absolutely NO WARRANTY. @@ -75,6 +75,10 @@ # and -L${STAGE_OBJTOP}/usr/lib are sufficient, and we should # have no need of anything else. # +# Sometimes things are more complicated so allow for +# DPLIBS to be qualified with each of the variables in +# DPLIBS_QUALIFIER_LIST (default is VAR_QUALIFIER_LIST same as +# init.mk) .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .NOTMAIN @@ -110,7 +114,9 @@ CXXFLAGS_LAST += ${CXXFLAGS_DEBUG_XTRA} .-include # DPLIBS helps us ensure we keep DPADD and LDADD in sync -DPLIBS+= ${DPLIBS_LAST} +DPLIBS_QUALIFIER_LIST ?= ${VAR_QUALIFIER_LIST} +DPLIBS += ${DPLIBS_QUALIFIER_LIST:u:@Q@${DPLIBS.$Q:U}@} +DPLIBS+= ${DPLIBS_LAST} ${DPLIBS_QUALIFIER_LIST:u:@Q@${DPLIBS_LAST.$Q:U}@} DPADD+= ${DPLIBS:N-*} .for __lib in ${DPLIBS} .if "${__lib:M-*}" != "" @@ -131,7 +137,7 @@ __dpadd_libs := ${DPADD:M*/lib*} # dups will be dealt with later. # Note: libfoo_pic uses DPLIBS_libfoo __ldadd_all_xtras= -.for __lib in ${__dpadd_libs:@d@${DPLIBS_${d:T:R:S,_pic,,}}@} +.for __lib in ${__dpadd_libs:@d@${DPLIBS_${d:T:R:S,_pic,,}} ${DPLIBS_QUALIFIER_LIST:u:@Q@${DPLIBS_${d:T:R:S,_pic,,}.$Q:U}@}@} __ldadd_all_xtras+= ${LDADD_${__lib}:U${__lib:T:R:S/lib/-l/:C/\.so.*//}} .if "${DPADD:M${__lib}}" == "" DPADD+= ${__lib} diff --git a/mk/gendirdeps.mk b/mk/gendirdeps.mk index ec4e188cdf78..5d54aa232152 100644 --- a/mk/gendirdeps.mk +++ b/mk/gendirdeps.mk @@ -1,4 +1,4 @@ -# $Id: gendirdeps.mk,v 1.49 2023/04/20 17:45:03 sjg Exp $ +# $Id: gendirdeps.mk,v 1.50 2023/11/04 16:47:34 sjg Exp $ # SPDX-License-Identifier: BSD-2-Clause # @@ -102,6 +102,10 @@ _DEPENDFILE := ${_CURDIR}/${.MAKE.DEPENDFILE:T} # 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) diff --git a/mk/host-target.mk b/mk/host-target.mk index f9f22027c00f..d8abea17fdac 100644 --- a/mk/host-target.mk +++ b/mk/host-target.mk @@ -1,5 +1,5 @@ # RCSid: -# $Id: host-target.mk,v 1.18 2023/05/22 23:08:31 sjg Exp $ +# $Id: host-target.mk,v 1.19 2023/09/21 06:44:53 sjg Exp $ # Host platform information; may be overridden .if !target(__${.PARSEFILE}__) @@ -48,9 +48,14 @@ HOST_TARGET := ${host_os:S,/,,g}${HOST_OSMAJOR}-${_HOST_ARCH} # sometimes we want HOST_TARGET32 MACHINE32.amd64 = i386 MACHINE32.x86_64 = i386 +.if !defined(_HOST_ARCH32) _HOST_ARCH32 := ${MACHINE32.${_HOST_ARCH}:U${_HOST_ARCH:S,64$,,}} +.export _HOST_ARCH32 +.endif HOST_TARGET32 := ${host_os:S,/,,g}${HOST_OSMAJOR}-${_HOST_ARCH32} +.export HOST_TARGET HOST_TARGET32 + # tr is insanely non-portable, accommodate the lowest common denominator TR ?= tr toLower = ${TR} 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' diff --git a/mk/init.mk b/mk/init.mk index b740378cd69a..b0d365754d86 100644 --- a/mk/init.mk +++ b/mk/init.mk @@ -1,4 +1,4 @@ -# $Id: init.mk,v 1.27 2022/01/01 17:32:18 sjg Exp $ +# $Id: init.mk,v 1.30 2023/10/03 16:25:01 sjg Exp $ # # @(#) Copyright (c) 2002, Simon J. Gerraty # @@ -32,6 +32,11 @@ _this_mk_dir := ${.PARSEDIR} # should have been set by sys.mk CXX_SUFFIXES?= .cc .cpp .cxx .C +.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 @@ -51,6 +56,7 @@ QUALIFIED_VAR_LIST += \ CPPFLAGS \ CPUFLAGS \ LDFLAGS \ + SRCS \ # a final :U avoids errors if someone uses := .for V in ${QUALIFIED_VAR_LIST:O:u:@q@$q $q_LAST@} diff --git a/mk/install-mk b/mk/install-mk old mode 100644 new mode 100755 index b042f99671a8..357afd171e8c --- a/mk/install-mk +++ b/mk/install-mk @@ -59,7 +59,7 @@ # Simon J. Gerraty # RCSid: -# $Id: install-mk,v 1.242 2023/09/09 16:00:16 sjg Exp $ +# $Id: install-mk,v 1.244 2024/01/05 23:16:34 sjg Exp $ # # @(#) Copyright (c) 1994-2023 Simon J. Gerraty # @@ -74,7 +74,7 @@ # sjg@crufty.net # -MK_VERSION=20230909 +MK_VERSION=20240105 OWNER= GROUP= MODE=444 diff --git a/mk/jobs.mk b/mk/jobs.mk index 0643e6481082..404ce6c3dadd 100644 --- a/mk/jobs.mk +++ b/mk/jobs.mk @@ -1,4 +1,4 @@ -# $Id: jobs.mk,v 1.14 2023/09/11 16:52:44 sjg Exp $ +# $Id: jobs.mk,v 1.16 2023/11/29 15:59:50 sjg Exp $ # # @(#) Copyright (c) 2012-2023, Simon J. Gerraty # @@ -78,7 +78,8 @@ JOB_MAX = ${.MAKE.JOBS} # This should be derrived from number of cpu's .if ${.MAKE.JOBS.C:Uno} == "yes" # 1.2 - 1.5 times nCPU works well on most machines that support -jC -JOB_MAX_C ?= 1.33C +# if the factor is floating point, the C suffix isn't needed +JOB_MAX_C ?= 1.33 JOB_MAX ?= ${JOB_MAX_C} .endif JOB_MAX ?= 8 diff --git a/mk/lib.mk b/mk/lib.mk index 2f97fc93e5c6..9c1c2b446d41 100644 --- a/mk/lib.mk +++ b/mk/lib.mk @@ -1,4 +1,4 @@ -# $Id: lib.mk,v 1.75 2023/09/11 05:20:23 sjg Exp $ +# $Id: lib.mk,v 1.81 2023/10/03 18:18:57 sjg Exp $ .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .NOTMAIN @@ -62,6 +62,10 @@ META_NOECHO?= echo # (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 @@ -111,7 +115,7 @@ APICFLAGS?= -k # Platform-independent linker flags for ELF shared libraries .if ${OBJECT_FMT} == "ELF" SHLIB_SOVERSION= ${SHLIB_MAJOR} -SHLIB_SHFLAGS= -soname lib${LIB}.so.${SHLIB_SOVERSION} +SHLIB_SHFLAGS= -Wl,-soname,lib${LIB}.so.${SHLIB_SOVERSION} SHLIB_LDSTARTFILE?= /usr/lib/crtbeginS.o SHLIB_LDENDFILE?= /usr/lib/crtendS.o .endif @@ -125,7 +129,7 @@ LD_shared=${SHLIB_SHFLAGS} .if ${TARGET_OSNAME} == "FreeBSD" .if ${OBJECT_FMT} == "ELF" SHLIB_SOVERSION= ${SHLIB_MAJOR} -SHLIB_SHFLAGS= -soname lib${LIB}.so.${SHLIB_SOVERSION} +SHLIB_SHFLAGS= -Wl,-soname,lib${LIB}.so.${SHLIB_SOVERSION} .else SHLIB_SHFLAGS= -assert pure-text .endif @@ -168,7 +172,6 @@ AR_cq= -cqs .elif ${TARGET_OSNAME} == "FreeBSD" LD_solib= lib${LIB}_pic.a .elif ${TARGET_OSNAME} == "Linux" -SHLIB_LD = ${CC} # 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 @@ -180,7 +183,6 @@ LD_pobjs = ${POBJS} LD_sobjs = ${SOBJS} .endif .elif ${TARGET_OSNAME} == "Darwin" -SHLIB_LD = ${CC} SHLIB_INSTALL_VERSION ?= ${SHLIB_MAJOR} SHLIB_COMPATABILITY_VERSION ?= ${SHLIB_MAJOR}.${SHLIB_MINOR:U0} SHLIB_COMPATABILITY ?= \ @@ -213,8 +215,6 @@ PICFLAG ?= -fPIC -fno-common RANLIB = : .endif -SHLIB_LD ?= ${LD} - .if !empty(SHLIB_MAJOR) .if ${NEED_SOLINKS} && empty(SHLIB_LINKS) .if ${MK_LINKLIB} != "no" @@ -441,8 +441,6 @@ lib${LIB}_pic.a: ${SOBJS} @${AR} ${AR_cq} ${.TARGET} ${LD_sobjs} ${RANLIB} ${.TARGET} -#SHLIB_LDADD?= ${LDADD} - # bound to be non-portable... # this is known to work for NetBSD 1.6 and FreeBSD 4.2 lib${LIB}.${LD_so}: ${SOLIB} ${DPADD} @@ -450,10 +448,9 @@ lib${LIB}.${LD_so}: ${SOLIB} ${DPADD} @rm -f ${.TARGET} .if ${TARGET_OSNAME:NFreeBSD:NNetBSD} == "" .if ${OBJECT_FMT} == "ELF" - ${SHLIB_LD} -x -shared ${SHLIB_SHFLAGS} ${LDFLAGS} -o ${.TARGET} \ - ${SHLIB_LDSTARTFILE} \ - --whole-archive ${SOLIB} --no-whole-archive \ - ${LDADD} ${SHLIB_LDADD} ${SHLIB_LDENDFILE} + ${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} @@ -480,7 +477,7 @@ 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}.so.*.* ${SOBJS} + 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} diff --git a/mk/man.mk b/mk/man.mk index ce4380fedc55..be1b7d98cd04 100644 --- a/mk/man.mk +++ b/mk/man.mk @@ -1,4 +1,4 @@ -# $Id: man.mk,v 1.25 2021/10/31 03:03:14 sjg Exp $ +# $Id: man.mk,v 1.26 2023/12/30 02:10:38 sjg Exp $ .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .NOTMAIN @@ -16,13 +16,13 @@ OPTIONS_DEFAULT_NO += CMT2DOC # so we have to use sed(1). # set MANTARGET=cat for formatted pages -MANTARGET?= man +MANTARGET ?= man # set this to .0 for same behavior as bsd.man.mk -MCATEXT?= +MCATEXT ?= -NROFF?= nroff -MANDIR?= /usr/share/man -MANDOC?= man +NROFF ?= nroff +MANDIR ?= /usr/share/man +MANDOC ?= man MAN_SUFFIXES?= .1 .2 .3 .4 .5 .6 .7 .8 .9 .SUFFIXES: ${MAN_SUFFIXES} @@ -35,8 +35,24 @@ ${MAN_SUFFIXES:@s@$s${s:S,.,.cat,}@}: @${NROFF} -${MANDOC} ${.IMPSRC} > ${.TARGET:T}.new && \ mv ${.TARGET:T}.new ${.TARGET:T} + +.if !empty(MANOWN) +MAN_INSTALL_OWN ?= -o ${MANOWN} -g ${MANGRP} +MAN_CHOWN ?= chown +.else +MAN_CHOWN = : +.endif + +MINSTALL = ${INSTALL} ${COPY} ${MAN_INSTALL_OWN} -m ${MANMODE} + .if defined(MAN) && !empty(MAN) +.if ${MANTARGET} == "cat" +MANALL ?= ${MAN:T:@p@${p:R}.cat${p:E}@} +.else +MANALL ?= ${MAN} +.endif + .if ${MK_CMT2DOC} == "yes" # use cmt2doc.py to extract manpages from source CMT2DOC?= cmt2doc.py @@ -52,36 +68,65 @@ ${CMT2DOC_SUFFIXES:@s@${MAN_SUFFIXES:@m@$s$m@}@}: .endif -_mandir=${DESTDIR}${MANDIR}/${MANTARGET}`echo $$page | sed -e 's/.*\.cat/./' -e 's/.*\.//'` +# none of this is relevant unless doing maninstall +.if make(*install) +_mandir = ${DESTDIR}${MANDIR}/${MANTARGET}`echo $$page | sed -e 's/.*\.cat/./' -e 's/.*\.//'` .if ${MANTARGET} == "cat" -_mfromdir?=. -MANALL= ${MAN:${MAN_SUFFIXES:S,.,,:@m@S/.$m/.cat$m/@:ts:}} +_mfromdir ?= . .if ${MCATEXT} == "" -_minstpage=`echo $$page | sed 's/\.cat/./'` +_minstpage = `echo $$page | sed 's/\.cat/./'` .else -_minstpage=`echo $$page | sed 's/\.cat.*//'`${MCATEXT} +_minstpage = `echo $$page | sed 's/\.cat.*//'`${MCATEXT} .endif .endif .if target(${MAN:[1]}) -_mfromdir?=. +_mfromdir ?= . .endif -_mfromdir?=${.CURDIR} -MANALL?= ${MAN} -_minstpage?=$${page} +_mfromdir ?= ${.CURDIR} +_minstpage ?= $${page} .endif -.if !empty(MANOWN) -MAN_INSTALL_OWN ?= -o ${MANOWN} -g ${MANGRP} -MAN_CHOWN ?= chown -.else -MAN_CHOWN = : -.endif - -MINSTALL= ${INSTALL} ${COPY} ${MAN_INSTALL_OWN} -m ${MANMODE} .if defined(MANZ) # chown and chmod are done afterward automatically -MCOMPRESS= gzip -cf -MCOMPRESSSUFFIX= .gz +MCOMPRESS_CMD ?= gzip -cf +MCOMPRESS_EXT ?= .gz + +_MANZ_USE: .USE + @${MCOMPRESS_CMD} ${.ALLSRC} > ${.TARGET} + +.for _page in ${MANALL} +${_page:T}${MCOMPRESS_EXT}: ${_page} _MANZ_USE +.endfor +.endif + +.if ${MK_STAGING_MAN} == "yes" +_mansets := ${MAN:E:O:u:M*[1-9]:@s@man$s@} +.if ${MANTARGET} == "cat" +STAGE_AS_SETS += ${_mansets} +_stage_man = stage_as +.else +STAGE_SETS += ${_mansets} +_stage_man = stage_files +.endif +STAGE_TARGETS += ${_stage_man} +.for _page _as in ${MANALL:@x@$x ${x:T:S/.cat/./}@} +${_stage_man}.man${_as:E}: ${_page} +.if target(${_page:T}${MCOMPRESS_EXT}) +${_man_stage}.man${_as:E}: ${_page:T}${MCOMPRESS_EXT} +.endif +STAGE_DIR.man${_as:E} ?= ${STAGE_OBJTOP}${MANDIR}/${MANTARGET}${_as:E}${MANSUBDIR} +.if ${MANTARGET} == "cat" +STAGE_AS_${_page} = ${_as} +.endif +.endfor +.if !defined(NO_MLINKS) && !empty(MLINKS) +STAGE_SETS += mlinks +STAGE_TARGETS += stage_links +STAGE_LINKS.mlinks := ${MLINKS:M*.[1-9]:@f@${f:S,^,${MANDIR}/${MANTARGET}${f:E}${MANSUBDIR}/,}@} +stage_links.mlinks: ${_mansets:@s@stage_files.$s@} +.endif +.endif + .endif maninstall: diff --git a/mk/meta.autodep.mk b/mk/meta.autodep.mk index 6785d2ebf874..11b5f17f4edc 100644 --- a/mk/meta.autodep.mk +++ b/mk/meta.autodep.mk @@ -1,4 +1,4 @@ -# $Id: meta.autodep.mk,v 1.59 2023/08/19 17:35:32 sjg Exp $ +# $Id: meta.autodep.mk,v 1.60 2024/01/09 23:42:22 sjg Exp $ # # @(#) Copyright (c) 2010, Simon J. Gerraty @@ -305,7 +305,7 @@ ${_DEPENDFILE}: .PRECIOUS CLEANFILES += *.meta filemon.* *.db # these make it easy to gather some stats -now_utc = ${%s:L:localtime} +now_utc ?= ${%s:L:localtime} start_utc := ${now_utc} meta_stats= meta=${empty(.MAKE.META.FILES):?0:${.MAKE.META.FILES:[#]}} \ diff --git a/mk/own.mk b/mk/own.mk index 7b0d74caba8a..5f0eb0d09f2a 100644 --- a/mk/own.mk +++ b/mk/own.mk @@ -1,4 +1,4 @@ -# $Id: own.mk,v 1.44 2021/12/08 05:56:50 sjg Exp $ +# $Id: own.mk,v 1.45 2023/12/30 02:10:38 sjg Exp $ .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .NOTMAIN @@ -119,6 +119,7 @@ OPTIONS_DEFAULT_DEPENDENT+= \ PICINSTALL/LINKLIB \ PICLIB/PIC \ PROFILE/LINKLIB \ + STAGING_MAN/STAGING \ STAGING_PROG/STAGING \ .include diff --git a/mk/prog.mk b/mk/prog.mk index e01a92ba9a0e..9afa908d507f 100644 --- a/mk/prog.mk +++ b/mk/prog.mk @@ -1,4 +1,4 @@ -# $Id: prog.mk,v 1.39 2023/04/20 23:45:56 sjg Exp $ +# $Id: prog.mk,v 1.40 2023/10/02 21:35:43 sjg Exp $ .if !target(__${.PARSEFILE}__) __${.PARSEFILE}__: .NOTMAIN @@ -75,12 +75,9 @@ ${CXX_SUFFIXES:%=%.o}: .if defined(PROG_CXX) PROG= ${PROG_CXX} -_CCLINK= ${CXX} _SUPCXX?= -lstdc++ -lm .endif -_CCLINK?= ${CC} - .if defined(PROG) BINDIR ?= ${prefix}/bin diff --git a/mk/rst2htm.mk b/mk/rst2htm.mk index 0a26abb9126e..d7e98d087fd1 100644 --- a/mk/rst2htm.mk +++ b/mk/rst2htm.mk @@ -1,4 +1,4 @@ -# $Id: rst2htm.mk,v 1.13 2023/09/13 18:55:42 sjg Exp $ +# $Id: rst2htm.mk,v 1.14 2023/09/19 22:27:28 sjg Exp $ # # @(#) Copyright (c) 2009, Simon J. Gerraty # @@ -44,6 +44,7 @@ RST_SUFFIXES ?= .rst .txt CLEANFILES += ${HTMFILES} ${PDFFILES} html: ${HTMFILES} +pdf: ${PDFFILES} .SUFFIXES: ${RST_SUFFIXES} .htm .pdf diff --git a/os.sh b/os.sh old mode 100644 new mode 100755 diff --git a/parse.c b/parse.c index 85af2ced7a85..824def432753 100644 --- a/parse.c +++ b/parse.c @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.706 2023/08/19 11:09:02 rillig Exp $ */ +/* $NetBSD: parse.c,v 1.716 2024/01/07 11:39:04 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -121,7 +121,7 @@ #include "pathnames.h" /* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: parse.c,v 1.706 2023/08/19 11:09:02 rillig Exp $"); +MAKE_RCSID("$NetBSD: parse.c,v 1.716 2024/01/07 11:39:04 rillig Exp $"); /* Detects a multiple-inclusion guard in a makefile. */ typedef enum { @@ -131,9 +131,7 @@ typedef enum { GS_NO /* the file is not guarded */ } GuardState; -/* - * A file being read. - */ +/* A file being parsed. */ typedef struct IncludedFile { FStr name; /* absolute or relative to the cwd */ unsigned lineno; /* 1-based */ @@ -251,7 +249,7 @@ static StringList targCmds = LST_INIT; */ static GNode *order_pred; -static int parseErrors = 0; +static int parseErrors; /* * The include chain of makefiles. At index 0 is the top-level makefile from @@ -328,6 +326,23 @@ 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) { @@ -335,7 +350,7 @@ GetInclude(size_t i) return Vector_Get(&includes, i); } -/* The makefile that is currently being read. */ +/* The makefile or the body of a .for loop that is currently being read. */ static IncludedFile * CurFile(void) { @@ -382,7 +397,7 @@ LoadFile(const char *path, int fd) } assert(buf.len <= buf.cap); - if (!Buf_EndsWith(&buf, '\n')) + if (buf.len > 0 && !Buf_EndsWith(&buf, '\n')) Buf_AddByte(&buf, '\n'); return buf; /* may not be null-terminated */ @@ -442,8 +457,8 @@ IsEscaped(const char *line, const char *p) } /* - * Add the filename and lineno to the GNode so that we remember where its - * last command was added or where it was mentioned in a .depend file. + * 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) @@ -566,7 +581,7 @@ ParseErrorInternal(const GNode *gn, } /* - * Print a parse error message, including location information. + * 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). @@ -621,8 +636,7 @@ HandleMessage(ParseErrorLevel level, const char *levelName, const char *umsg) /* * Add the child to the parent's children, and for non-special targets, vice - * versa. Special targets such as .END do not need to be informed once the - * child target has been made. + * versa. */ static void LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) @@ -633,7 +647,10 @@ LinkSource(GNode *pgn, GNode *cgn, bool isSpecial) Lst_Append(&pgn->children, cgn); pgn->unmade++; - /* Special targets like .END don't need any children. */ + /* + * Special targets like .END do not need to be informed once the child + * target has been made. + */ if (!isSpecial) Lst_Append(&cgn->parents, pgn); @@ -672,10 +689,9 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { /* * If the node was on the left-hand side of a '::' operator, - * we need to create a new instance of it 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. + * 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 @@ -694,7 +710,7 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) if (doing_depend) RememberLocation(cohort); /* - * Make the cohort invisible as well to avoid duplicating it + * 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. @@ -709,11 +725,7 @@ TryApplyDependencyOperator(GNode *gn, GNodeType op) snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", (unsigned int)gn->unmade_cohorts % 1000000); } else { - /* - * We don't want to nuke any previous flags (whatever they - * were) so we just OR the new operator into the old. - */ - gn->type |= op; + gn->type |= op; /* preserve any previous flags */ } return true; @@ -730,12 +742,12 @@ ApplyDependencyOperator(GNodeType op) } /* - * We add a .WAIT node in the dependency list. After any dynamic dependencies + * 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. * - * We give each .WAIT node a unique name (mainly for diagnostics). + * Give each .WAIT node a unique name (mainly for diagnostics). */ static void ApplyDependencySourceWait(bool isSpecial) @@ -819,9 +831,7 @@ ApplyDependencySourceOrder(const char *src) Targ_PrintNode(gn, 0); } } - /* - * The current source now becomes the predecessor for the next one. - */ + /* The current source now becomes the predecessor for the next one. */ order_pred = gn; } @@ -879,7 +889,8 @@ MaybeUpdateMainTarget(void) 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); + DEBUG1(MAKE, "Setting main node to \"%s\"\n", + gn->name); mainNode = gn; return; } @@ -901,38 +912,31 @@ InvalidLineType(const char *line, const char *unexpanded_line) } 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'", + Parse_Error(PARSE_FATAL, + "Invalid line '%s', expanded to '%s'", unexpanded_line, line); } static void ParseDependencyTargetWord(char **pp, const char *lstart) { - const char *cp = *pp; + const char *p = *pp; - while (*cp != '\0') { - if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || - *cp == '(') && - !IsEscaped(lstart, cp)) + while (*p != '\0') { + if ((ch_isspace(*p) || *p == '!' || *p == ':' || *p == '(') + && !IsEscaped(lstart, p)) break; - if (*cp == '$') { - /* - * Must be a dynamic source (would have been expanded - * otherwise). - * - * There should be no errors in this, as they would - * have been discovered in the initial Var_Subst and - * we wouldn't be here. - */ - FStr val = Var_Parse(&cp, SCOPE_CMDLINE, + if (*p == '$') { + FStr val = Var_Parse(&p, SCOPE_CMDLINE, VARE_PARSE_ONLY); + /* TODO: handle errors */ FStr_Done(&val); } else - cp++; + p++; } - *pp += cp - *pp; + *pp += p - *pp; } /* @@ -1359,6 +1363,7 @@ ParseDependencySourceSpecial(ParseSpecial special, const char *word, Suff_AddSuffix(word); break; case SP_PATH: + case SP_SYSPATH: AddToPaths(word, paths); break; case SP_INCLUDES: @@ -1379,9 +1384,6 @@ ParseDependencySourceSpecial(ParseSpecial special, const char *word, case SP_READONLY: Var_ReadOnly(word, true); break; - case SP_SYSPATH: - AddToPaths(word, paths); - break; default: break; } @@ -1392,7 +1394,7 @@ ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special, GNodeType *inout_targetAttr, SearchPathList **inout_paths) { - char savec = *nameEnd; + char savedNameEnd = *nameEnd; *nameEnd = '\0'; if (!HandleDependencyTarget(name, inout_special, @@ -1404,7 +1406,7 @@ ApplyDependencyTarget(char *name, char *nameEnd, ParseSpecial *inout_special, else if (*inout_special == SP_PATH && *name != '.' && *name != '\0') Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", name); - *nameEnd = savec; + *nameEnd = savedNameEnd; return true; } @@ -1466,17 +1468,17 @@ static void ParseDependencySourcesSpecial(char *start, ParseSpecial special, SearchPathList *paths) { - char savec; while (*start != '\0') { + char savedEnd; char *end = start; while (*end != '\0' && !ch_isspace(*end)) end++; - savec = *end; + savedEnd = *end; *end = '\0'; ParseDependencySourceSpecial(special, start, paths); - *end = savec; - if (savec != '\0') + *end = savedEnd; + if (savedEnd != '\0') end++; pp_skip_whitespace(&end); start = end; @@ -1505,17 +1507,13 @@ ParseDependencySourcesMundane(char *start, * rest of the line is the value. */ if (Parse_IsVar(start, &var)) { - /* - * Check if this makefile has disabled - * setting local variables. - */ - bool target_vars = GetBooleanExpr( + bool targetVarsEnabled = GetBooleanExpr( "${.MAKE.TARGET_LOCAL_VARIABLES}", true); - if (target_vars) + if (targetVarsEnabled) LinkVarToTargets(&var); free(var.varname); - if (target_vars) + if (targetVarsEnabled) return true; } @@ -1594,7 +1592,6 @@ ParseDependencySources(char *p, GNodeType targetAttr, return; } - /* Now go for the sources. */ switch (special) { case SP_INCLUDES: case SP_LIBS: @@ -1766,10 +1763,7 @@ Parse_IsVar(const char *p, VarAssign *out_var) nameStart = p; firstSpace = NULL; - /* - * Scan for one of the assignment operators outside a variable - * expansion. - */ + /* Scan for one of the assignment operators outside an expression. */ while (*p != '\0') { char ch = *p++; if (ch == '(' || ch == '{') { @@ -1821,14 +1815,14 @@ Parse_IsVar(const char *p, VarAssign *out_var) * Check for syntax errors such as unclosed expressions or unknown modifiers. */ static void -VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope) +VarCheckSyntax(VarAssignOp op, const char *uvalue, GNode *scope) { if (opts.strict) { - if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { - char *expandedValue = Var_Subst(uvalue, + if (op != VAR_SUBST && strchr(uvalue, '$') != NULL) { + char *parsedValue = Var_Subst(uvalue, scope, VARE_PARSE_ONLY); /* TODO: handle errors */ - free(expandedValue); + free(parsedValue); } } } @@ -1841,7 +1835,7 @@ VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, char *evalue; /* - * make sure that we set the variable the first time to nothing + * 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, @@ -1952,7 +1946,7 @@ Parse_Var(VarAssign *var, GNode *scope) /* - * See if the command possibly calls a sub-make by using the variable + * See if the command possibly calls a sub-make by using the * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ static bool @@ -1991,16 +1985,10 @@ MaybeSubMake(const char *cmd) return false; } -/* - * Append the command to the target node. - * - * The node may be marked as a submake node if the command is determined to - * be that. - */ +/* Append the command to the target node. */ static void GNode_AddCommand(GNode *gn, char *cmd) { - /* Add to last (ie current) cohort for :: targets */ if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL) gn = gn->cohorts.last->datum; @@ -2011,35 +1999,15 @@ GNode_AddCommand(GNode *gn, char *cmd) gn->type |= OP_SUBMAKE; RememberLocation(gn); } else { -#if 0 - /* XXX: We cannot do this until we fix the tree */ - Lst_Append(&gn->commands, cmd); - Parse_Error(PARSE_WARNING, - "overriding commands for target \"%s\"; " - "previous commands defined at %s: %u ignored", - gn->name, gn->fname, gn->lineno); -#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); -#endif } } -/* - * Add a directory to the path searched for included makefiles bracketed - * by double-quotes. - */ -void -Parse_AddIncludeDir(const char *dir) -{ - (void)SearchPath_Add(parseIncPath, dir); -} - - /* * Parse a directive like '.include' or '.-include'. * @@ -2063,13 +2031,9 @@ ParseInclude(char *directive) return; } - if (*p++ == '<') - endc = '>'; - else - endc = '"'; + endc = *p++ == '<' ? '>' : '"'; file = FStr_InitRefer(p); - /* Skip to matching delimiter */ while (*p != '\0' && *p != endc) p++; @@ -2281,7 +2245,7 @@ IsSysVInclude(const char *line) static void ParseTraditionalInclude(char *line) { - char *cp; /* current position in file spec */ + char *p; /* current position in file spec */ bool done = false; bool silent = line[0] != 'i'; char *file = line + (silent ? 8 : 7); @@ -2294,13 +2258,13 @@ ParseTraditionalInclude(char *line) all_files = Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES); /* TODO: handle errors */ - for (file = all_files; !done; file = cp + 1) { + for (file = all_files; !done; file = p + 1) { /* Skip to end of line or next whitespace */ - for (cp = file; *cp != '\0' && !ch_isspace(*cp); cp++) + for (p = file; *p != '\0' && !ch_isspace(*p); p++) continue; - if (*cp != '\0') - *cp = '\0'; + if (*p != '\0') + *p = '\0'; else done = true; @@ -2345,9 +2309,8 @@ ParseGmakeExport(char *line) #endif /* - * Called when EOF is reached in the current file. If we were reading an - * include file or a .for loop, the includes stack is popped and things set - * up to go back to reading the previous file at the previous location. + * 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 @@ -2610,23 +2573,9 @@ SkipIrrelevantBranches(void) { const char *line; - while ((line = ReadLowLevelLine(LK_DOT)) != NULL) { + while ((line = ReadLowLevelLine(LK_DOT)) != NULL) if (Cond_EvalLine(line) == CR_TRUE) return true; - /* - * TODO: Check for typos in .elif directives such as .elsif - * or .elseif. - * - * This check will probably duplicate some of the code in - * ParseLine. Most of the code there cannot apply, only - * ParseVarassign and ParseDependencyLine can, and to prevent - * code duplication, these would need to be called with a - * flag called onlyCheckSyntax. - * - * See directive-elif.mk for details. - */ - } - return false; } @@ -2665,9 +2614,9 @@ ParseForLoop(const char *line) /* * Read an entire line from the input file. * - * Empty lines, .if and .for are completely handled by this function, - * leaving only variable assignments, other directives, dependency lines - * and shell commands to the caller. + * 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. @@ -2800,26 +2749,26 @@ HandleBreak(const char *arg) static bool ParseDirective(char *line) { - char *cp = line + 1; + char *p = line + 1; const char *arg; Substring dir; - pp_skip_whitespace(&cp); - if (IsInclude(cp, false)) { - ParseInclude(cp); + pp_skip_whitespace(&p); + if (IsInclude(p, false)) { + ParseInclude(p); return true; } - dir.start = cp; - while (ch_islower(*cp) || *cp == '-') - cp++; - dir.end = cp; + dir.start = p; + while (ch_islower(*p) || *p == '-') + p++; + dir.end = p; - if (*cp != '\0' && !ch_isspace(*cp)) + if (*p != '\0' && !ch_isspace(*p)) return false; - pp_skip_whitespace(&cp); - arg = cp; + pp_skip_whitespace(&p); + arg = p; if (Substring_Equals(dir, "break")) HandleBreak(arg); @@ -2880,7 +2829,7 @@ Parse_GuardEndif(void) static char * FindSemicolon(char *p) { - int level = 0; + int depth = 0; for (; *p != '\0'; p++) { if (*p == '\\' && p[1] != '\0') { @@ -2889,19 +2838,15 @@ FindSemicolon(char *p) } if (*p == '$' && (p[1] == '(' || p[1] == '{')) - level++; - else if (level > 0 && (*p == ')' || *p == '}')) - level--; - else if (level == 0 && *p == ';') + depth++; + else if (depth > 0 && (*p == ')' || *p == '}')) + depth--; + else if (depth == 0 && *p == ';') break; } return p; } -/* - * dependency -> [target...] op [source...] [';' command] - * op -> ':' | '::' | '!' - */ static void ParseDependencyLine(char *line) { @@ -2909,11 +2854,6 @@ ParseDependencyLine(char *line) char *expanded_line; const char *shellcmd = NULL; - /* - * For some reason - probably to make the parser impossible - - * a ';' can be used to separate commands from dependencies. - * Attempt to skip over ';' inside substitution patterns. - */ { char *semicolon = FindSemicolon(line); if (*semicolon != '\0') { @@ -2924,7 +2864,7 @@ ParseDependencyLine(char *line) } /* - * We now know it's a dependency line so it needs to have all + * 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 @@ -2935,7 +2875,7 @@ ParseDependencyLine(char *line) * as well. * * Parsing the line first would also prevent that targets - * generated from variable expressions are interpreted as the + * 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. */ @@ -2975,13 +2915,6 @@ ParseDependencyLine(char *line) static void ParseLine(char *line) { - /* - * Lines that begin with '.' can be pretty much anything: - * - directives like '.include' or '.if', - * - suffix rules like '.c.o:', - * - dependencies for filenames that start with '.', - * - variable assignments like '.tmp=value'. - */ if (line[0] == '.' && ParseDirective(line)) return; @@ -2992,9 +2925,6 @@ ParseLine(char *line) #ifdef SYSVINCLUDE if (IsSysVInclude(line)) { - /* - * It's an S3/S5-style "include". - */ ParseTraditionalInclude(line); return; } @@ -3003,9 +2933,6 @@ ParseLine(char *line) #ifdef GMAKEEXPORT if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && strchr(line, ':') == NULL) { - /* - * It's a Gmake "export". - */ ParseGmakeExport(line); return; } @@ -3019,10 +2946,7 @@ ParseLine(char *line) ParseDependencyLine(line); } -/* - * Parse a top-level makefile, incorporating its content into the global - * dependency graph. - */ +/* Interpret a top-level makefile. */ void Parse_File(const char *name, int fd) { @@ -3043,7 +2967,6 @@ Parse_File(const char *name, int fd) CurFile()->lineno, line); ParseLine(line); } - /* Reached EOF, but it may be just EOF of an include file. */ } while (ParseEOF()); FinishDependencyGroup(); diff --git a/str.c b/str.c index 2797e5567e92..1349831af2f1 100644 --- a/str.c +++ b/str.c @@ -1,4 +1,4 @@ -/* $NetBSD: str.c,v 1.99 2023/06/23 05:03:04 rillig Exp $ */ +/* $NetBSD: str.c,v 1.102 2024/01/05 23:22:06 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -71,7 +71,7 @@ #include "make.h" /* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ -MAKE_RCSID("$NetBSD: str.c,v 1.99 2023/06/23 05:03:04 rillig Exp $"); +MAKE_RCSID("$NetBSD: str.c,v 1.102 2024/01/05 23:22:06 rillig Exp $"); static HashTable interned_strings; @@ -107,6 +107,10 @@ str_concat3(const char *s1, const char *s2, const char *s3) * Fracture a string into an array of words (as delineated by tabs or spaces) * taking quotation marks into account. * + * A string that is empty or only contains whitespace nevertheless results in + * a single word. This is unexpected in many places, and the caller needs to + * correct for this edge case. + * * If expand is true, quotes are removed and escape sequences such as \r, \t, * etc... are expanded. In this case, return NULL on parse errors. * @@ -322,17 +326,13 @@ StrMatchResult Str_Match(const char *str, const char *pat) { StrMatchResult res = { NULL, false }; - const char *fixed_str, *fixed_pat; - bool asterisk, matched; - - asterisk = false; - fixed_str = str; - fixed_pat = pat; + bool asterisk = false; + const char *fixed_str = str; + const char *fixed_pat = pat; match_fixed_length: str = fixed_str; pat = fixed_pat; - matched = false; for (; *pat != '\0' && *pat != '*'; str++, pat++) { if (*str == '\0') return res; @@ -350,7 +350,7 @@ match_fixed_length: if (*pat == ']' || *pat == '\0') { if (neg) goto end_of_char_list; - goto match_done; + goto no_match; } if (*pat == *str) goto end_of_char_list; @@ -369,7 +369,7 @@ match_fixed_length: end_of_char_list: if (neg && *pat != ']' && *pat != '\0') - goto match_done; + goto no_match; while (*pat != ']' && *pat != '\0') pat++; if (*pat == '\0') @@ -379,43 +379,40 @@ match_fixed_length: if (*pat == '\\') /* match the next character exactly */ pat++; - if (*pat != *str) - goto match_done; - } - matched = true; - -match_done: - if (!asterisk) { - if (!matched) - return res; - if (*pat == '\0') { - res.matched = *str == '\0'; - return res; - } - asterisk = true; - } else { - if (!matched) { - fixed_str++; - goto match_fixed_length; - } - if (*pat == '\0') { - if (*str == '\0') { - res.matched = true; - return res; + if (*pat != *str) { + if (asterisk && str == fixed_str) { + while (*str != '\0' && *str != *pat) + str++; + fixed_str = str; + goto match_fixed_length; } - fixed_str += strlen(str); - goto match_fixed_length; + goto no_match; } } - while (*pat == '*') - pat++; - if (*pat == '\0') { - res.matched = true; - return res; + if (*pat == '*') { + asterisk = true; + while (*pat == '*') + pat++; + if (*pat == '\0') { + res.matched = true; + return res; + } + fixed_str = str; + fixed_pat = pat; + goto match_fixed_length; } - fixed_str = str; - fixed_pat = pat; + if (asterisk && *str != '\0') { + fixed_str += strlen(str); + goto match_fixed_length; + } + res.matched = *str == '\0'; + return res; + +no_match: + if (!asterisk) + return res; + fixed_str++; goto match_fixed_length; } diff --git a/str.h b/str.h index 71fd6bd15944..6bdfbf4d497f 100644 --- a/str.h +++ b/str.h @@ -1,4 +1,4 @@ -/* $NetBSD: str.h,v 1.17 2023/06/23 04:56:54 rillig Exp $ */ +/* $NetBSD: str.h,v 1.19 2024/01/05 21:56:55 rillig Exp $ */ /* Copyright (c) 2021 Roland Illig @@ -76,27 +76,24 @@ typedef struct StrMatchResult { } StrMatchResult; -MAKE_INLINE FStr -FStr_Init(const char *str, void *freeIt) -{ - FStr fstr; - fstr.str = str; - fstr.freeIt = freeIt; - return fstr; -} - /* Return a string that is the sole owner of str. */ MAKE_INLINE FStr FStr_InitOwn(char *str) { - return FStr_Init(str, str); + FStr fstr; + fstr.str = str; + fstr.freeIt = str; + return fstr; } /* Return a string that refers to the shared str. */ MAKE_INLINE FStr FStr_InitRefer(const char *str) { - return FStr_Init(str, NULL); + FStr fstr; + fstr.str = str; + fstr.freeIt = NULL; + return fstr; } MAKE_INLINE void @@ -154,14 +151,6 @@ Substring_Eq(Substring sub, Substring str) memcmp(sub.start, str.start, len) == 0; } -MAKE_STATIC Substring -Substring_Sub(Substring sub, size_t start, size_t end) -{ - assert(start <= Substring_Length(sub)); - assert(end <= Substring_Length(sub)); - return Substring_Init(sub.start + start, sub.start + end); -} - MAKE_STATIC bool Substring_HasPrefix(Substring sub, Substring prefix) { @@ -198,7 +187,7 @@ Substring_SkipFirst(Substring sub, char ch) } MAKE_STATIC const char * -Substring_LastIndex(Substring sub, char ch) +Substring_FindLast(Substring sub, char ch) { const char *p; diff --git a/suff.c b/suff.c index 01ae43cb21b4..e5f6ee83e86a 100644 --- a/suff.c +++ b/suff.c @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.368 2023/02/14 21:38:31 rillig Exp $ */ +/* $NetBSD: suff.c,v 1.377 2024/01/05 23:22:06 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -115,7 +115,7 @@ #include "dir.h" /* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ -MAKE_RCSID("$NetBSD: suff.c,v 1.368 2023/02/14 21:38:31 rillig Exp $"); +MAKE_RCSID("$NetBSD: suff.c,v 1.377 2024/01/05 23:22:06 rillig Exp $"); typedef List SuffixList; typedef ListNode SuffixListNode; @@ -142,8 +142,6 @@ static GNodeList transforms = LST_INIT; */ static int sNum = 0; -typedef List SuffixListList; - /* * A suffix such as ".c" or ".o" that may be used in suffix transformation * rules such as ".c.o:". @@ -185,14 +183,6 @@ typedef struct Suffix { SuffixList parents; /* Suffixes we have a transformation from */ SuffixList children; - /* - * Lists in which this suffix is referenced. - * - * XXX: These lists are used nowhere, they are just appended to, for - * no apparent reason. They do have the side effect of increasing - * refCount though. - */ - SuffixListList ref; } Suffix; /* @@ -374,7 +364,6 @@ SuffixList_Unref(SuffixList *list, Suffix *suff) } } -/* Free up all memory associated with the given suffix structure. */ static void Suffix_Free(Suffix *suff) { @@ -392,7 +381,6 @@ Suffix_Free(Suffix *suff) suff->name, suff->refCount); #endif - Lst_Done(&suff->ref); Lst_Done(&suff->children); Lst_Done(&suff->parents); SearchPath_Free(suff->searchPath); @@ -401,12 +389,6 @@ Suffix_Free(Suffix *suff) free(suff); } -static void -SuffFree(void *p) -{ - Suffix_Free(p); -} - /* Remove the suffix from the list, and free if it is otherwise unused. */ static void SuffixList_Remove(SuffixList *list, Suffix *suff) @@ -416,7 +398,7 @@ SuffixList_Remove(SuffixList *list, Suffix *suff) /* XXX: can lead to suff->refCount == -1 */ SuffixList_Unref(&sufflist, suff); DEBUG1(SUFF, "Removing suffix \"%s\"\n", suff->name); - SuffFree(suff); + Suffix_Free(suff); } } @@ -440,12 +422,10 @@ SuffixList_Insert(SuffixList *list, Suffix *suff) DEBUG2(SUFF, "inserting \"%s\" (%d) at end of list\n", suff->name, suff->sNum); Lst_Append(list, Suffix_Ref(suff)); - Lst_Append(&suff->ref, list); } 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)); - Lst_Append(&suff->ref, list); } else { DEBUG2(SUFF, "\"%s\" (%d) is already there\n", suff->name, suff->sNum); @@ -469,7 +449,6 @@ Suffix_New(const char *name) suff->searchPath = SearchPath_New(); Lst_Init(&suff->children); Lst_Init(&suff->parents); - Lst_Init(&suff->ref); suff->sNum = sNum++; suff->include = false; suff->library = false; @@ -496,7 +475,7 @@ Suff_ClearSuffixes(void) Lst_Init(&sufflist); sNum = 0; if (nullSuff != NULL) - SuffFree(nullSuff); + Suffix_Free(nullSuff); emptySuff = nullSuff = Suffix_New(""); SearchPath_AddAll(nullSuff->searchPath, &dirSearchPath); @@ -821,19 +800,7 @@ UpdateTargets(Suffix *suff) } } -/* - * Add the suffix to the end of the list of known suffixes. - * Should we restructure the suffix graph? Make doesn't. - * - * A GNode is created for the suffix (XXX: this sounds completely wrong) and - * a Suffix structure is created and added to the suffixes list unless the - * suffix was already known. - * The mainNode passed can be modified if a target mutated into a - * transform and that target happened to be the main target. - * - * Input: - * name the name of the suffix to add - */ +/* Add the suffix to the end of the list of known suffixes. */ void Suff_AddSuffix(const char *name) { @@ -1247,9 +1214,7 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) if (!Dir_HasWildcards(cgn->name)) return; - /* - * Expand the word along the chosen path - */ + /* Expand the word along the chosen path. */ Lst_Init(&expansions); SearchPath_Expand(Suff_FindPath(cgn), cgn->name, &expansions); @@ -1258,10 +1223,10 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) /* * Fetch next expansion off the list and find its GNode */ - char *cp = Lst_Dequeue(&expansions); + char *name = Lst_Dequeue(&expansions); - DEBUG1(SUFF, "%s...", cp); - gn = Targ_GetNode(cp); + DEBUG1(SUFF, "%s...", name); + gn = Targ_GetNode(name); /* Insert gn before the original child. */ Lst_InsertBefore(&pgn->children, cln, gn); @@ -1274,8 +1239,8 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) DEBUG0(SUFF, "\n"); /* - * Now the source is expanded, remove it from the list of children to - * keep it from being processed. + * 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); @@ -1287,57 +1252,56 @@ ExpandWildcards(GNodeListNode *cln, GNode *pgn) * add those nodes to the members list. * * Unfortunately, we can't use Str_Words because it doesn't understand about - * variable expressions with spaces in them. + * expressions with spaces in them. */ static void -ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members) +ExpandChildrenRegular(char *p, GNode *pgn, GNodeList *members) { char *start; - pp_skip_hspace(&cp); - start = cp; - while (*cp != '\0') { - if (*cp == ' ' || *cp == '\t') { + 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. */ - *cp++ = '\0'; + *p++ = '\0'; gn = Targ_GetNode(start); Lst_Append(members, gn); - pp_skip_hspace(&cp); + pp_skip_hspace(&p); /* Continue at the next non-space. */ - start = cp; - } else if (*cp == '$') { - /* Skip over the variable expression. */ - const char *nested_p = cp; + start = p; + } else if (*p == '$') { + /* Skip over the expression. */ + const char *nested_p = p; FStr junk = Var_Parse(&nested_p, pgn, VARE_PARSE_ONLY); /* TODO: handle errors */ if (junk.str == var_Error) { Parse_Error(PARSE_FATAL, - "Malformed variable expression at \"%s\"", - cp); - cp++; + "Malformed expression at \"%s\"", p); + p++; } else { - cp += nested_p - cp; + p += nested_p - p; } FStr_Done(&junk); - } else if (cp[0] == '\\' && cp[1] != '\0') { + } 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. */ - cp += 2; + p += 2; } else { - cp++; + p++; } } - if (cp != start) { + if (p != start) { /* * Stuff left over -- add it to the list too */ @@ -1347,7 +1311,7 @@ ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members) } /* - * Expand the names of any children of a given node that contain variable + * 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 @@ -1361,7 +1325,7 @@ static void ExpandChildren(GNodeListNode *cln, GNode *pgn) { GNode *cgn = cln->datum; - char *cp; /* Expanded value */ + char *expanded; if (!Lst_IsEmpty(&cgn->order_pred) || !Lst_IsEmpty(&cgn->order_succ)) /* It is all too hard to process the result of .ORDER */ @@ -1383,7 +1347,7 @@ ExpandChildren(GNodeListNode *cln, GNode *pgn) } DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); - cp = Var_Subst(cgn->name, pgn, VARE_UNDEFERR); + expanded = Var_Subst(cgn->name, pgn, VARE_UNDEFERR); /* TODO: handle errors */ { @@ -1395,39 +1359,32 @@ ExpandChildren(GNodeListNode *cln, GNode *pgn) * call on the Arch module to find the nodes for us, * expanding variables in the parent's scope. */ - char *p = cp; - (void)Arch_ParseArchive(&p, &members, pgn); + char *ap = expanded; + (void)Arch_ParseArchive(&ap, &members, pgn); } else { - ExpandChildrenRegular(cp, pgn, &members); + ExpandChildrenRegular(expanded, pgn, &members); } - /* - * Add all elements of the members list to the parent node. - */ + /* Add all members to the parent node. */ while (!Lst_IsEmpty(&members)) { GNode *gn = Lst_Dequeue(&members); DEBUG1(SUFF, "%s...", gn->name); - /* - * Add gn to the parents child list before the - * original child. - */ Lst_InsertBefore(&pgn->children, cln, gn); Lst_Append(&gn->parents, pgn); pgn->unmade++; - /* Expand wildcards on new node */ ExpandWildcards(cln->prev, pgn); } Lst_Done(&members); - free(cp); + free(expanded); } DEBUG0(SUFF, "\n"); /* - * Now the source is expanded, remove it from the list of children to - * keep it from being processed. + * 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); @@ -1446,16 +1403,10 @@ ExpandAllChildren(GNode *gn) } /* - * Find a path along which to expand the node. + * Find a path along which to search or expand the node. * - * If the node has a known suffix, use that path. - * If it has no known suffix, use the default system search path. - * - * Input: - * gn Node being examined - * - * Results: - * The appropriate path to search for the GNode. + * If the node has a known suffix, use that path, + * otherwise use the default system search path. */ SearchPath * Suff_FindPath(GNode *gn) @@ -1529,7 +1480,7 @@ ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) /* Apply the rule. */ Make_HandleUse(gn, tgn); - /* Deal with wildcards and variables in any acquired sources. */ + /* Deal with wildcards and expressions in any acquired sources. */ ln = ln != NULL ? ln->next : NULL; while (ln != NULL) { GNodeListNode *nln = ln->next; @@ -1556,7 +1507,7 @@ ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) static void ExpandMember(GNode *gn, const char *eoarch, GNode *mem, Suffix *memSuff) { - GNodeListNode *ln; + SuffixListNode *ln; size_t nameLen = (size_t)(eoarch - gn->name); /* Use first matching suffix... */ @@ -1565,7 +1516,6 @@ ExpandMember(GNode *gn, const char *eoarch, GNode *mem, Suffix *memSuff) break; if (ln != NULL) { - /* Got one -- apply it */ Suffix *suff = ln->datum; if (!ApplyTransform(gn, mem, suff, memSuff)) { DEBUG2(SUFF, "\tNo transformation from %s -> %s\n", @@ -1579,9 +1529,6 @@ static void FindDeps(GNode *, CandidateSearcher *); /* * Locate dependencies for an OP_ARCHV node. * - * Input: - * gn Node for which to locate dependencies - * * Side Effects: * Same as Suff_FindDeps */ @@ -1595,7 +1542,7 @@ FindDepsArchive(GNode *gn, CandidateSearcher *cs) const char *name; /* Start of member's name */ /* - * The node is an archive(member) pair. so we must find a + * The node is an 'archive(member)' pair, so we must find a * suffix for both of them. */ eoarch = strchr(gn->name, '('); @@ -1969,8 +1916,7 @@ FindDepsRegular(GNode *gn, CandidateSearcher *cs) if (targ->node == NULL) targ->node = Targ_GetNode(targ->file); - ApplyTransform(targ->node, src->node, - targ->suff, src->suff); + ApplyTransform(targ->node, src->node, targ->suff, src->suff); if (targ->node != gn) { /* @@ -2068,9 +2014,6 @@ FindDeps(GNode *gn, CandidateSearcher *cs) * * Need to handle the changing of the null suffix gracefully so the old * transformation rules don't just go away. - * - * Input: - * name Name of null suffix */ void Suff_SetNull(const char *name) @@ -2102,16 +2045,21 @@ Suff_Init(void) Suff_ClearSuffixes(); } - /* Clean up the suffixes module. */ void Suff_End(void) { #ifdef CLEANUP - Lst_DoneCall(&sufflist, SuffFree); - Lst_DoneCall(&suffClean, SuffFree); + 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) - SuffFree(nullSuff); + Suffix_Free(nullSuff); Lst_Done(&transforms); #endif } @@ -2135,7 +2083,7 @@ Suffix_Print(const Suffix *suff) { Buffer buf; - Buf_InitSize(&buf, 16); + Buf_Init(&buf); Buf_AddFlag(&buf, suff->include, "SUFF_INCLUDE"); Buf_AddFlag(&buf, suff->library, "SUFF_LIBRARY"); Buf_AddFlag(&buf, suff->isNull, "SUFF_NULL"); @@ -2191,7 +2139,7 @@ Suff_NamesStr(void) SuffixListNode *ln; Suffix *suff; - Buf_InitSize(&buf, 16); + Buf_Init(&buf); for (ln = sufflist.first; ln != NULL; ln = ln->next) { suff = ln->datum; if (ln != sufflist.first) diff --git a/unit-tests/Makefile b/unit-tests/Makefile index 910bed094afd..111e6325a667 100644 --- a/unit-tests/Makefile +++ b/unit-tests/Makefile @@ -1,6 +1,6 @@ -# $Id: Makefile,v 1.207 2023/09/09 16:44:03 sjg Exp $ +# $Id: Makefile,v 1.210 2024/01/08 18:28:08 sjg Exp $ # -# $NetBSD: Makefile,v 1.341 2023/09/09 16:41:04 sjg Exp $ +# $NetBSD: Makefile,v 1.342 2024/01/07 02:07:44 sjg Exp $ # # Unit tests for make(1) # @@ -217,7 +217,7 @@ TESTS+= hanoi-include TESTS+= impsrc TESTS+= include-main TESTS+= job-flags -#TESTS+= job-output-long-lines +TESTS+= job-output-long-lines TESTS+= job-output-null TESTS+= jobs-empty-commands TESTS+= jobs-empty-commands-error @@ -249,7 +249,7 @@ TESTS+= opt-debug-graph1 TESTS+= opt-debug-graph2 TESTS+= opt-debug-graph3 TESTS+= opt-debug-hash -#TESTS+= opt-debug-jobs +TESTS+= opt-debug-jobs TESTS+= opt-debug-lint TESTS+= opt-debug-loud TESTS+= opt-debug-meta @@ -821,6 +821,7 @@ _SED_CMDS+= -e 's,${.OBJDIR},,g' -e 's,${.OBJDIR:tA},,g' _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' diff --git a/unit-tests/cmd-errors-jobs.exp b/unit-tests/cmd-errors-jobs.exp index c6baacfe0ed7..d0a6e51eb84d 100644 --- a/unit-tests/cmd-errors-jobs.exp +++ b/unit-tests/cmd-errors-jobs.exp @@ -1,7 +1,7 @@ : undefined--eol make: Unclosed variable "UNCLOSED" : unclosed-variable- -make: Unclosed variable expression (expecting '}') for "UNCLOSED" +make: Unclosed expression, expecting '}' for "UNCLOSED" : unclosed-modifier- make: Unknown modifier "Z" : unknown-modifier--eol diff --git a/unit-tests/cmd-errors-lint.exp b/unit-tests/cmd-errors-lint.exp index 90b63bbcb08e..bdf4ae1a17e8 100644 --- a/unit-tests/cmd-errors-lint.exp +++ b/unit-tests/cmd-errors-lint.exp @@ -1,7 +1,7 @@ : undefined make: Unclosed variable "UNCLOSED" : unclosed-variable -make: Unclosed variable expression (expecting '}') for "UNCLOSED" +make: Unclosed expression, expecting '}' for "UNCLOSED" : unclosed-modifier make: Unknown modifier "Z" : unknown-modifier diff --git a/unit-tests/cmd-errors.exp b/unit-tests/cmd-errors.exp index c6baacfe0ed7..d0a6e51eb84d 100644 --- a/unit-tests/cmd-errors.exp +++ b/unit-tests/cmd-errors.exp @@ -1,7 +1,7 @@ : undefined--eol make: Unclosed variable "UNCLOSED" : unclosed-variable- -make: Unclosed variable expression (expecting '}') for "UNCLOSED" +make: Unclosed expression, expecting '}' for "UNCLOSED" : unclosed-modifier- make: Unknown modifier "Z" : unknown-modifier--eol diff --git a/unit-tests/cmdline-undefined.mk b/unit-tests/cmdline-undefined.mk index 062449f80df2..81b44518dd41 100644 --- a/unit-tests/cmdline-undefined.mk +++ b/unit-tests/cmdline-undefined.mk @@ -1,6 +1,6 @@ -# $NetBSD: cmdline-undefined.mk,v 1.3 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cmdline-undefined.mk,v 1.4 2023/11/19 21:47:52 rillig Exp $ # -# Tests for undefined variable expressions in the command line. +# Tests for undefined expressions in the command line. all: # When the command line is parsed, variable assignments using the diff --git a/unit-tests/comment.mk b/unit-tests/comment.mk index 792350f6223a..a3bf781b9a67 100644 --- a/unit-tests/comment.mk +++ b/unit-tests/comment.mk @@ -1,4 +1,4 @@ -# $NetBSD: comment.mk,v 1.5 2022/05/08 06:51:27 rillig Exp $ +# $NetBSD: comment.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ # # Demonstrate how comments are written in makefiles. @@ -55,7 +55,7 @@ VAR= \# # Both in the assignment. # Since 2012-03-24 the variable modifier :[#] does not need to be escaped. # To keep the parsing code simple, any "[#" does not start a comment, even -# outside of a variable expression. +# outside of an expression. WORDS= ${VAR:[#]} [# .if ${WORDS} != "1 [#" . error diff --git a/unit-tests/cond-cmp-string.mk b/unit-tests/cond-cmp-string.mk index 44d8beceacdd..7b13ebf2212b 100644 --- a/unit-tests/cond-cmp-string.mk +++ b/unit-tests/cond-cmp-string.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-cmp-string.mk,v 1.17 2023/03/28 14:38:29 rillig Exp $ +# $NetBSD: cond-cmp-string.mk,v 1.18 2023/11/19 21:47:52 rillig Exp $ # # Tests for string comparisons in .if conditions. @@ -20,11 +20,11 @@ . error .endif -# The left-hand side of the comparison requires that any variable expression +# The left-hand side of the comparison requires that any expression # is defined. # # The variable named "" is never defined, nevertheless it can be used as a -# starting point for variable expressions. Applying the :U modifier to such +# starting point for expressions. Applying the :U modifier to such # an undefined expression turns it into a defined expression. # # See ApplyModifier_Defined and DEF_DEFINED. @@ -63,13 +63,13 @@ . error .endif -# A variable expression can be enclosed in double quotes. +# 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 a variable expression resulted in a +# characters " \t!=><" directly after an expression resulted in a # "Malformed conditional", even though the string was well-formed. .if ${:Uword } != "${:Uword} " . error @@ -89,12 +89,12 @@ . error .endif -# Adding another variable expression to the string literal works though. +# Adding another expression to the string literal works though. .if ${:Uword} != "${:Uwo}${:Urd}" . error .endif -# Adding a space at the beginning of the quoted variable expression works +# Adding a space at the beginning of the quoted expression works # though. .if ${:U word } != " ${:Uword} " . error diff --git a/unit-tests/cond-cmp-unary.mk b/unit-tests/cond-cmp-unary.mk index 58927e3b4944..80626a279358 100755 --- a/unit-tests/cond-cmp-unary.mk +++ b/unit-tests/cond-cmp-unary.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-cmp-unary.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-cmp-unary.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ # # Tests for unary comparisons in .if conditions, that is, comparisons with # a single operand. If the operand is a number, it is compared to zero, @@ -24,7 +24,7 @@ . error .endif -# The empty string may come from a variable expression. +# The empty string may come from an expression. # # XXX: As of 2023-06-01, this empty string is interpreted "as a number" in # EvalTruthy, which is plain wrong. The bug is in TryParseNumber. @@ -32,13 +32,13 @@ . error .endif -# A variable expression that is not surrounded by quotes is interpreted +# An expression that is not surrounded by quotes is interpreted # as a number if possible, otherwise as a string. .if ${:U0} . error .endif -# A non-zero number from a variable expression evaluates to true. +# A non-zero number from an expression evaluates to true. .if !${:U12345} . error .endif diff --git a/unit-tests/cond-eof.mk b/unit-tests/cond-eof.mk index 25c6ec5de708..a5f7ce95f89e 100644 --- a/unit-tests/cond-eof.mk +++ b/unit-tests/cond-eof.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-eof.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-eof.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ # # Tests for parsing the end of '.if' conditions, which are represented as the # token TOK_EOF. @@ -9,7 +9,7 @@ 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 variable expression. +# 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. diff --git a/unit-tests/cond-func-defined.exp b/unit-tests/cond-func-defined.exp index c2fc78c1a35a..d556f3b982b3 100644 --- a/unit-tests/cond-func-defined.exp +++ b/unit-tests/cond-func-defined.exp @@ -1,6 +1,6 @@ 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 47: In .for loops, variable expressions for the loop variables are +make: "cond-func-defined.mk" line 47: In .for loops, expressions for the loop variables are make: "cond-func-defined.mk" line 49: substituted at evaluation time. There is no actual variable make: "cond-func-defined.mk" line 51: involved, even if it feels like it. make: Fatal errors encountered -- cannot continue diff --git a/unit-tests/cond-func-defined.mk b/unit-tests/cond-func-defined.mk index 10adfde292ed..14597398bc62 100644 --- a/unit-tests/cond-func-defined.mk +++ b/unit-tests/cond-func-defined.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-defined.mk,v 1.10 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-func-defined.mk,v 1.11 2023/11/19 21:47:52 rillig Exp $ # # Tests for the defined() function in .if conditions. @@ -25,7 +25,7 @@ ${:UA B}= variable name with spaces . error .endif -# If necessary, the whitespace can be generated by a variable expression. +# If necessary, the whitespace can be generated by an expression. .if !defined(${:UA B}) . error .endif @@ -43,8 +43,8 @@ ${:UA B}= variable name with spaces . if defined(var) . error . else -# expect+1: In .for loops, variable expressions for the loop variables are -. info In .for loops, variable expressions for the loop variables are +# expect+1: In .for loops, expressions for the loop variables are +. info In .for loops, expressions for the loop variables are # expect+1: substituted at evaluation time. There is no actual variable . info substituted at evaluation time. There is no actual variable # expect+1: involved, even if it feels like it. diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk index 2ad1c16edbcc..057b175a7693 100644 --- a/unit-tests/cond-func-empty.mk +++ b/unit-tests/cond-func-empty.mk @@ -1,9 +1,9 @@ -# $NetBSD: cond-func-empty.mk,v 1.22 2023/08/11 05:01:12 rillig Exp $ +# $NetBSD: cond-func-empty.mk,v 1.24 2023/12/19 19:33:40 rillig Exp $ # -# Tests for the empty() function in .if conditions, which tests a variable +# 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 a variable +# Note that the argument in the parentheses is a variable name, not an # expression. That name may be followed by ':...' modifiers. # @@ -120,7 +120,7 @@ ${:U }= space . error .endif -# The :L modifier creates a variable expression that has the same value as +# 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) @@ -138,7 +138,7 @@ ${:U }= space . error .endif -# Ensure that variable expressions that appear as part of the function call +# 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. # @@ -188,20 +188,20 @@ ${:U WORD }= variable name with spaces # 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 variable expression, in +# "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 variable expression was expanded though, and this was wrong. The +# The expression was expanded, and this was wrong. The # expansion was done without VARE_WANTRES (called VARF_WANTRES back then) # though. This had the effect that the ${:U1} from the value of VARNAME # expanded to an empty string. This in turn created the seemingly recursive # definition VARNAME=${VARNAME}, and that definition was never meant to be # expanded. # -# This was fixed by expanding nested variable expressions in the variable name +# This was fixed by expanding nested expressions in the variable name # only if the flag VARE_WANTRES is given. VARNAME= ${VARNAME${:U1}} .if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) diff --git a/unit-tests/cond-func-exists.mk b/unit-tests/cond-func-exists.mk index 48d7e727dc3f..40228cd44902 100644 --- a/unit-tests/cond-func-exists.mk +++ b/unit-tests/cond-func-exists.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func-exists.mk,v 1.6 2020/11/30 20:12:29 rillig Exp $ +# $NetBSD: cond-func-exists.mk,v 1.7 2023/11/19 21:47:52 rillig Exp $ # # Tests for the exists() function in .if conditions. @@ -17,7 +17,7 @@ .endif # The only way to escape characters that would otherwise influence the parser -# is to enclose them in a variable expression. For function arguments, +# is to enclose them in an expression. For function arguments, # neither the backslash nor the dollar sign act as escape character. .if exists(\.) . error @@ -27,7 +27,7 @@ . error .endif -# The argument to the function can have several variable expressions. +# 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 diff --git a/unit-tests/cond-func.mk b/unit-tests/cond-func.mk index 2e8a6785b0e8..aabd31b4db46 100644 --- a/unit-tests/cond-func.mk +++ b/unit-tests/cond-func.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-func.mk,v 1.13 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-func.mk,v 1.14 2023/11/19 21:47:52 rillig Exp $ # # Tests for those parts of the functions in .if conditions that are common # among several functions. @@ -38,7 +38,7 @@ ${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces . error .endif -# If necessary, the whitespace can be generated by a variable expression. +# If necessary, the whitespace can be generated by an expression. .if !defined(${:UA B}) . error .endif diff --git a/unit-tests/cond-late.exp b/unit-tests/cond-late.exp index e179e8c74cc4..703677da634c 100644 --- a/unit-tests/cond-late.exp +++ b/unit-tests/cond-late.exp @@ -1,4 +1,4 @@ -make: Bad conditional expression ' != "no"' in ' != "no"?:' +make: Bad conditional expression ' != "no"' before '?:' yes no exit status 0 diff --git a/unit-tests/cond-late.mk b/unit-tests/cond-late.mk index 1cfaaa2ee4e9..8e3d41f60001 100644 --- a/unit-tests/cond-late.mk +++ b/unit-tests/cond-late.mk @@ -1,6 +1,6 @@ -# $NetBSD: cond-late.mk,v 1.4 2023/05/10 15:53:32 rillig Exp $ +# $NetBSD: cond-late.mk,v 1.6 2023/12/10 20:12:28 rillig Exp $ # -# Using the :? modifier, variable expressions can contain conditional +# 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 @@ -31,6 +31,6 @@ cond-literal: VAR= ${${UNDEF} != "no":?:} # expect-reset -# expect: make: Bad conditional expression ' != "no"' in ' != "no"?:' +# expect: make: Bad conditional expression ' != "no"' before '?:' .if empty(VAR:Mpattern) .endif diff --git a/unit-tests/cond-op-and.mk b/unit-tests/cond-op-and.mk index 6fbcbdc4b2db..19a0672ba44e 100644 --- a/unit-tests/cond-op-and.mk +++ b/unit-tests/cond-op-and.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-op-and.mk,v 1.8 2023/08/15 21:27:09 rillig Exp $ +# $NetBSD: cond-op-and.mk,v 1.9 2023/12/17 09:44:00 rillig Exp $ # # Tests for the && operator in .if conditions. @@ -33,15 +33,15 @@ # 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: Malformed conditional (0 || (${DEF} && ${UNDEF})) .if 0 || (${DEF} && ${UNDEF}) .endif .if 0 || (!${DEF} && ${UNDEF}) .endif -# expect+1: Malformed conditional (0 || (${UNDEF} && ${UNDEF}) +# expect+1: Malformed conditional (0 || (${UNDEF} && ${UNDEF})) .if 0 || (${UNDEF} && ${UNDEF}) .endif -# expect+1: Malformed conditional (0 || (!${UNDEF} && ${UNDEF}) +# expect+1: Malformed conditional (0 || (!${UNDEF} && ${UNDEF})) .if 0 || (!${UNDEF} && ${UNDEF}) .endif .if 1 || (${DEF} && ${UNDEF}) diff --git a/unit-tests/cond-op-or.mk b/unit-tests/cond-op-or.mk index d66fa5af640f..165408f3c130 100644 --- a/unit-tests/cond-op-or.mk +++ b/unit-tests/cond-op-or.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-op-or.mk,v 1.10 2023/08/15 21:27:09 rillig Exp $ +# $NetBSD: cond-op-or.mk,v 1.11 2023/12/17 09:44:00 rillig Exp $ # # Tests for the || operator in .if conditions. @@ -43,13 +43,13 @@ DEF= defined .endif .if 1 && (${DEF} || ${UNDEF}) .endif -# expect+1: Malformed conditional (1 && (!${DEF} || ${UNDEF}) +# expect+1: Malformed conditional (1 && (!${DEF} || ${UNDEF})) .if 1 && (!${DEF} || ${UNDEF}) .endif -# expect+1: Malformed conditional (1 && (${UNDEF} || ${UNDEF}) +# expect+1: Malformed conditional (1 && (${UNDEF} || ${UNDEF})) .if 1 && (${UNDEF} || ${UNDEF}) .endif -# expect+1: Malformed conditional (1 && (!${UNDEF} || ${UNDEF}) +# expect+1: Malformed conditional (1 && (!${UNDEF} || ${UNDEF})) .if 1 && (!${UNDEF} || ${UNDEF}) .endif diff --git a/unit-tests/cond-short.exp b/unit-tests/cond-short.exp index 745d7e912c2b..44681b57ebc1 100644 --- a/unit-tests/cond-short.exp +++ b/unit-tests/cond-short.exp @@ -7,7 +7,7 @@ expected M pattern expected or expected or exists expected or empty -make: "cond-short.mk" line 214: Comparison with '<' requires both operands '' and '42' to be numeric +make: "cond-short.mk" line 231: Comparison with '<' requires both operands '' and '42' to be numeric make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk index 525ff7d0f2ab..bcdf372ca6e6 100644 --- a/unit-tests/cond-short.mk +++ b/unit-tests/cond-short.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-short.mk,v 1.20 2023/03/04 13:42:36 rillig Exp $ +# $NetBSD: cond-short.mk,v 1.23 2023/11/19 22:32:44 rillig Exp $ # # Demonstrates that in conditions, the right-hand side of an && or || # is only evaluated if it can actually influence the result. @@ -9,9 +9,9 @@ # Before 2020-06-28, the right-hand side of an && or || operator was always # evaluated, which was wrong. In cond.c 1.69 and var.c 1.197 on 2015-10-11, # Var_Parse got a new parameter named 'wantit'. Since then it would have been -# possible to skip evaluation of irrelevant variable expressions and only +# possible to skip evaluation of irrelevant expressions and only # parse them. They were still evaluated though, the only difference to -# relevant variable expressions was that in the irrelevant variable +# relevant expressions was that in the irrelevant # expressions, undefined variables were allowed. This allowed for conditions # like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an # error message 'Malformed conditional', but the irrelevant expression was @@ -178,6 +178,23 @@ INDIR_UNDEF= ${UNDEF} . error .endif + +# Since cond.c 1.76 from 2020.06.28 and before var.c 1.225 from 2020.07.01, +# the following snippet resulted in the error message 'Variable VAR is +# recursive'. The condition '0' evaluated to false, which made the right-hand +# side of the '&&' irrelevant. Back then, irrelevant condition parts were +# still evaluated, but in "irrelevant mode", which allowed undefined variables +# to occur in expressions. In this mode, the variable name 'VAR' was +# unnecessarily evaluated, resulting in the expression '${VAR${:U1}}'. In +# this expression, the variable name was 'VAR${:U1}', and of this variable +# name, only the fixed part 'VAR' was evaluated, without the part '${:U1}'. +# This partial evaluation led to the wrong error message about 'VAR' being +# recursive. +VAR= ${VAR${:U1}} +.if 0 && !empty(VAR) +.endif + + # Enclosing the expression in double quotes changes how that expression is # evaluated. In irrelevant expressions that are enclosed in double quotes, # expressions based on undefined variables are allowed and evaluate to an diff --git a/unit-tests/cond-token-number.mk b/unit-tests/cond-token-number.mk index 6cec21cf6c44..7e73f8b76f94 100644 --- a/unit-tests/cond-token-number.mk +++ b/unit-tests/cond-token-number.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-number.mk,v 1.9 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-token-number.mk,v 1.10 2023/11/19 21:47:52 rillig Exp $ # # Tests for number tokens in .if conditions. # @@ -52,13 +52,13 @@ . error .endif -# When the number comes from a variable expression though, it may be signed. +# 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 a variable expression though, it may be signed. +# When the number comes from an expression though, it may be signed. # XXX: This is inconsistent. .if !${:U+1} . error diff --git a/unit-tests/cond-token-plain.exp b/unit-tests/cond-token-plain.exp index a14f69581658..8044f3bac826 100644 --- a/unit-tests/cond-token-plain.exp +++ b/unit-tests/cond-token-plain.exp @@ -35,9 +35,9 @@ 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 variable expressions. +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 variable expressions. +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: == "" diff --git a/unit-tests/cond-token-plain.mk b/unit-tests/cond-token-plain.mk index b6444e8ac1d3..79fcc771a855 100644 --- a/unit-tests/cond-token-plain.mk +++ b/unit-tests/cond-token-plain.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-plain.mk,v 1.18 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-token-plain.mk,v 1.19 2023/11/19 21:47:52 rillig Exp $ # # Tests for plain tokens (that is, string literals without quotes) # in .if conditions. These are also called bare words. @@ -115,7 +115,7 @@ VAR= defined . error .endif -# Bare words may be intermixed with variable expressions. +# Bare words may be intermixed with expressions. .if V${:UA}R # expect+1: ok . info ok @@ -135,13 +135,13 @@ VAR= defined .if 0${:Ux00} . error .else -# expect+1: Numbers can be composed from literals and variable expressions. -. info Numbers can be composed from literals and variable expressions. +# 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 variable expressions. -. info Numbers can be composed from literals and variable expressions. +# expect+1: Numbers can be composed from literals and expressions. +. info Numbers can be composed from literals and expressions. .else . error .endif @@ -205,7 +205,7 @@ ${:U\\\\}= backslash # expect+1: Malformed conditional (left == right) .if left == right .endif -# Before cond.c 1.276 from 2021-09-21, a variable expression containing the +# 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) @@ -245,7 +245,7 @@ ${:U\\\\}= backslash # 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 a variable expression to the +# 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. diff --git a/unit-tests/cond-token-string.exp b/unit-tests/cond-token-string.exp index 959effba0908..db07adcb2d09 100644 --- a/unit-tests/cond-token-string.exp +++ b/unit-tests/cond-token-string.exp @@ -10,7 +10,7 @@ make: "cond-token-string.mk" line 61: The string literal " " is not empty, even 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. CondParser_Eval: "${:Uvalue}" -make: "cond-token-string.mk" line 77: A nonempty variable expression evaluates to true. +make: "cond-token-string.mk" line 77: A nonempty expression evaluates to true. CondParser_Eval: "${:U}" make: "cond-token-string.mk" line 86: An empty variable evaluates to false. CondParser_Eval: ("${VAR}") diff --git a/unit-tests/cond-token-string.mk b/unit-tests/cond-token-string.mk index 7ecf3059e300..d13c68da134a 100644 --- a/unit-tests/cond-token-string.mk +++ b/unit-tests/cond-token-string.mk @@ -1,4 +1,4 @@ -# $NetBSD: cond-token-string.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-token-string.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ # # Tests for quoted string literals in .if conditions. # @@ -26,7 +26,7 @@ .endif # The 'x' produces a "Malformed conditional" since the left-hand side of a -# comparison in an .if directive must be either a variable expression, 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} == "") .if x${:Uvalue} == "" @@ -73,8 +73,8 @@ .endif .if "${:Uvalue}" -# expect+1: A nonempty variable expression evaluates to true. -. info A nonempty variable expression evaluates to true. +# expect+1: A nonempty expression evaluates to true. +. info A nonempty expression evaluates to true. .else . error .endif @@ -87,7 +87,7 @@ .endif # A non-empty string evaluates to true, no matter if it's a literal string or -# if it contains variable expressions. The parentheses are not necessary for +# 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 diff --git a/unit-tests/cond-token-var.mk b/unit-tests/cond-token-var.mk index b50c0439c127..c6471756a3dd 100644 --- a/unit-tests/cond-token-var.mk +++ b/unit-tests/cond-token-var.mk @@ -1,11 +1,11 @@ -# $NetBSD: cond-token-var.mk,v 1.7 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: cond-token-var.mk,v 1.8 2023/11/19 21:47:52 rillig Exp $ # -# Tests for variable expressions in .if conditions. +# Tests for expressions in .if conditions. # -# Note the fine distinction between a variable and a variable expression. -# A variable has a name and a value. To access the value, one writes a -# variable expression of the form ${VAR}. This is a simple variable -# expression. Variable expressions can get more complicated by adding +# 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 @@ -49,7 +49,7 @@ DEF= defined .if ${UNDEF:U} .endif -# If the value of the variable expression is a number, it is compared against +# If the value of the expression is a number, it is compared against # zero. .if ${:U0} . error @@ -58,7 +58,7 @@ DEF= defined . error .endif -# If the value of the variable expression is not a number, any non-empty +# 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 diff --git a/unit-tests/dep-var.exp b/unit-tests/dep-var.exp index 4e38057bf6b9..cc229d32e6d4 100755 --- a/unit-tests/dep-var.exp +++ b/unit-tests/dep-var.exp @@ -22,7 +22,7 @@ Var_Parse: $): (parse-only) 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 variable expression at "$)" +make: Malformed expression at "$)" def2 a-def2-b 1-2-NDIRECT_2-2-1 diff --git a/unit-tests/dep-var.mk b/unit-tests/dep-var.mk index 16f7498fd5cc..8c1636bbdec2 100755 --- a/unit-tests/dep-var.mk +++ b/unit-tests/dep-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: dep-var.mk,v 1.8 2023/05/10 15:53:32 rillig Exp $ +# $NetBSD: dep-var.mk,v 1.11 2023/12/19 19:33:40 rillig Exp $ # # Tests for variable references in dependency declarations. # @@ -9,11 +9,11 @@ # expect: Var_Parse: ${UNDEF1} (eval-defined) # Even though undefined expressions should lead to errors, no error message is -# generated for this line. The variable expression ${UNDEF1} simply expands +# generated for this line. The expression ${UNDEF1} simply expands # to an empty string. all: ${UNDEF1} -# Using a double dollar in order to circumvent immediate variable expansion +# 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. # @@ -25,7 +25,7 @@ all: $${DEF2} a-$${DEF2}-b # 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 variable expression ${UNDEF3} simply expands to an empty string. +# The expression ${UNDEF3} simply expands to an empty string. all: $${UNDEF3} # Try out how many levels of indirection are really expanded in dependency @@ -63,7 +63,7 @@ INDIRECT_3= indirect UNDEF1= undef1 DEF2= def2 -# Cover the code in SuffExpandChildren that deals with malformed variable +# 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 diff --git a/unit-tests/depsrc-ignore.exp b/unit-tests/depsrc-ignore.exp index 162f10ddc17b..608671f58ed8 100644 --- a/unit-tests/depsrc-ignore.exp +++ b/unit-tests/depsrc-ignore.exp @@ -1,8 +1,8 @@ ignore-errors begin false ignore-errors +*** Error code 1 (ignored) ignore-errors end all begin -*** Error code 1 (ignored) false all *** Error code 1 (continuing) diff --git a/unit-tests/deptgt-delete_on_error.exp b/unit-tests/deptgt-delete_on_error.exp index 75fbbe12472f..e60aa01351e2 100644 --- a/unit-tests/deptgt-delete_on_error.exp +++ b/unit-tests/deptgt-delete_on_error.exp @@ -16,6 +16,7 @@ make: *** deptgt-delete_on_error-regular-delete removed Stop. make: stopped in unit-tests +*** Error code 1 (ignored) Parallel mode > deptgt-delete_on_error-regular; false @@ -45,5 +46,4 @@ make: stopped in unit-tests make: stopped in unit-tests *** Error code 1 (ignored) -*** Error code 1 (ignored) exit status 0 diff --git a/unit-tests/deptgt-makeflags.exp b/unit-tests/deptgt-makeflags.exp index ac8ffc83470d..ea29f76ad464 100644 --- a/unit-tests/deptgt-makeflags.exp +++ b/unit-tests/deptgt-makeflags.exp @@ -1,4 +1,4 @@ -Global: delete DOLLAR (not found) +Global: ignoring delete 'DOLLAR' as it is not found Command: DOLLAR = $$$$ Global: .MAKEOVERRIDES = VAR DOLLAR CondParser_Eval: ${DOLLAR} != "\$\$" diff --git a/unit-tests/deptgt-makeflags.mk b/unit-tests/deptgt-makeflags.mk index 26f3f5794354..2f8b00743e3f 100644 --- a/unit-tests/deptgt-makeflags.mk +++ b/unit-tests/deptgt-makeflags.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt-makeflags.mk,v 1.7 2021/11/29 00:17:10 rillig Exp $ +# $NetBSD: deptgt-makeflags.mk,v 1.9 2023/11/19 22:32:44 rillig Exp $ # # Tests for the special target .MAKEFLAGS in dependency declarations, # which adds command line options later, at parse time. @@ -65,9 +65,9 @@ .endif # Next try at defining another newline variable. Since whitespace around the -# variable value is trimmed, two empty variable expressions ${:U} surround the +# variable value is trimmed, two empty expressions ${:U} surround the # literal newline now. This prevents the newline from being skipped during -# parsing. The ':=' assignment operator expands the empty variable +# parsing. The ':=' assignment operator expands the empty # expressions, leaving only the newline as the variable value. # # This is one of the very few ways (maybe even the only one) to inject literal diff --git a/unit-tests/deptgt.mk b/unit-tests/deptgt.mk index 67c47e4909e6..30b8399191bd 100644 --- a/unit-tests/deptgt.mk +++ b/unit-tests/deptgt.mk @@ -1,4 +1,4 @@ -# $NetBSD: deptgt.mk,v 1.14 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: deptgt.mk,v 1.16 2023/12/17 09:44:00 rillig Exp $ # # Tests for special targets like .BEGIN or .SUFFIXES in dependency # declarations. @@ -30,7 +30,7 @@ VAR=value # 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 a variable +# It doesn't matter whether the empty string is generated by an # expression or whether it is just omitted. .MAKEFLAGS: -dp ${:U}: empty-source @@ -54,7 +54,7 @@ $$$$$$$${:U:Z}: # expect+1: warning: Extra target (ordinary) ignored .PATH ordinary: -# expect+1: Special and mundane targets don't mix. Mundane ones ignored +# expect+1: warning: Special and mundane targets don't mix. Mundane ones ignored ordinary .PATH: all: diff --git a/unit-tests/dir.mk b/unit-tests/dir.mk index d3c75c8cb11d..956285393489 100644 --- a/unit-tests/dir.mk +++ b/unit-tests/dir.mk @@ -1,4 +1,4 @@ -# $NetBSD: dir.mk,v 1.10 2023/01/24 00:24:02 sjg Exp $ +# $NetBSD: dir.mk,v 1.11 2023/12/19 19:33:40 rillig Exp $ # # Tests for dir.c. @@ -67,7 +67,7 @@ fetch fetch-post extract extract-post: # The expansions may have duplicates. # When the source of the dependency line is expanded later, each of the -# expanded words will be the same. +# expanded words resolves to the same node. all: dup-{1,1,1,1,1,1,1} dup-1: diff --git a/unit-tests/directive-else.mk b/unit-tests/directive-else.mk index 41b38a14a6cf..cda671907217 100644 --- a/unit-tests/directive-else.mk +++ b/unit-tests/directive-else.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-else.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-else.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ # # Tests for the .else directive. # @@ -45,7 +45,7 @@ .else # comment .endif -# A variable expression does count as an argument, even if it is empty. +# An expression does count as an argument, even if it is empty. .if 0 # expect+1: The .else directive does not take arguments .else ${:U} diff --git a/unit-tests/directive-export-gmake.exp b/unit-tests/directive-export-gmake.exp index cf08a0443acc..c37d3b2d8591 100644 --- a/unit-tests/directive-export-gmake.exp +++ b/unit-tests/directive-export-gmake.exp @@ -1,5 +1,6 @@ make: "directive-export-gmake.mk" line 71: Invalid line 'export VAR=${:U1}', expanded to 'export VAR=1' make: "directive-export-gmake.mk" line 85: 16:00:00 +make: "directive-export-gmake.mk" line 92: Variable/Value missing from "export" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-export-gmake.mk b/unit-tests/directive-export-gmake.mk index f7a617ab7553..de79470bf305 100644 --- a/unit-tests/directive-export-gmake.mk +++ b/unit-tests/directive-export-gmake.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export-gmake.mk,v 1.7 2023/08/20 20:48:32 rillig Exp $ +# $NetBSD: directive-export-gmake.mk,v 1.9 2023/12/17 09:44:00 rillig Exp $ # # Tests for the export directive (without leading dot), as in GNU make. @@ -67,7 +67,7 @@ export VAR=an ${UNDEF} variable .for value in 1 # XXX: The ':' in this line is inside an expression and should thus not be # interpreted as a dependency operator. -# expect+1: Invalid line 'export VAR=${:U1}' +# expect+1: Invalid line 'export VAR=${:U1}', expanded to 'export VAR=1' export VAR=${value} .endfor @@ -83,3 +83,24 @@ INDIRECT_TZ= ${:UAmerica/Los_Angeles} export TZ=${INDIRECT_TZ} # expect+1: 16:00:00 .info ${%T:L:localtime=86400} + + +# The '=' must be present in the unexpanded line, it cannot be generated by +# an expression. +EQ= = +# expect+1: Variable/Value missing from "export" +export EQ_VAR${EQ}eq-value +.if ${:!env!:MEQ_VAR=*} +. error +.endif + + +# The variable name must be given directly, it is not expanded. The name of +# the exported variable thus starts with a '$', and that name may be filtered +# out by the platform. +INDIRECT_NAME= I_NAME +INDIRECT_VALUE= indirect value +export ${INDIRECT_NAME}=${INDIRECT_VALUE} +.if ${:!env!:MI_NAME=*} +. error +.endif diff --git a/unit-tests/directive-export.exp b/unit-tests/directive-export.exp index a5d706e58f32..774a814570e3 100644 --- a/unit-tests/directive-export.exp +++ b/unit-tests/directive-export.exp @@ -1,4 +1,4 @@ -make: "directive-export.mk" line 50: 00:00:00 -make: "directive-export.mk" line 55: 00:00:00 -make: "directive-export.mk" line 58: 16:00:00 +make: "directive-export.mk" line 56: 00:00:00 +make: "directive-export.mk" line 61: 00:00:00 +make: "directive-export.mk" line 64: 16:00:00 exit status 0 diff --git a/unit-tests/directive-export.mk b/unit-tests/directive-export.mk index d46b1dc01f27..08109814bcfd 100644 --- a/unit-tests/directive-export.mk +++ b/unit-tests/directive-export.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-export.mk,v 1.9 2023/08/20 20:48:32 rillig Exp $ +# $NetBSD: directive-export.mk,v 1.10 2023/11/19 09:45:19 rillig Exp $ # # Tests for the .export directive. # @@ -35,7 +35,13 @@ VAR= value $$ ${INDIRECT} .export ${:U} -# Trigger the "This isn't going to end well" in ExportVarEnv. +# Before a child process is started, whether for the '!=' assignment operator +# or for the ':sh' modifier, all variables that were marked for being exported +# are expanded and then exported. If expanding such a variable requires +# running a child command, the marked-as-exported variables would need to be +# exported first, ending in an endless loop. To avoid this endless loop, +# don't export the variables while preparing a child process, see +# ExportVarEnv. EMPTY_SHELL= ${:sh} .export EMPTY_SHELL # only marked for export at this point _!= :;: # Force the variable to be actually exported. diff --git a/unit-tests/directive-for-empty.mk b/unit-tests/directive-for-empty.mk index 52a65b39beeb..1c4cb0f1ad27 100644 --- a/unit-tests/directive-for-empty.mk +++ b/unit-tests/directive-for-empty.mk @@ -1,12 +1,12 @@ -# $NetBSD: directive-for-empty.mk,v 1.2 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-for-empty.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $ # # Tests for .for loops containing conditions of the form 'empty(var:...)'. # -# When a .for loop is expanded, variable expressions in the body of the loop +# When a .for loop is expanded, expressions in the body of the loop # are replaced with expressions containing the variable values. This # replacement is a bit naive but covers most of the practical cases. The one # popular exception is the condition 'empty(var:Modifiers)', which does not -# look like a variable expression and is thus not replaced. +# look like an expression and is thus not replaced. # # See also: # https://gnats.netbsd.org/43821 @@ -24,7 +24,7 @@ .endfor -# In conditions, the function call to 'empty' does not look like a variable +# In conditions, the function call to 'empty' does not look like an # expression, therefore it is not replaced. Since there is no global variable # named 'i', this expression makes for a leaky abstraction. If the .for # variables were real variables, calling 'empty' would work on them as well. @@ -51,8 +51,8 @@ # loop would be naive and require many special cases, as there are many cases # that need to be considered when deciding whether the token 'empty' is a # function call or not, as demonstrated by the following examples. For -# variable expressions like '${i:Modifiers}', this is simpler as a single -# dollar almost always starts a variable expression. For counterexamples and +# expressions like '${i:Modifiers}', this is simpler as a single +# dollar almost always starts an expression. For counterexamples and # edge cases, see directive-for-escape.mk. Adding another such tricky detail # is out of the question. .MAKEFLAGS: -df diff --git a/unit-tests/directive-for-errors.mk b/unit-tests/directive-for-errors.mk index 0af65350e643..94362847cdfd 100644 --- a/unit-tests/directive-for-errors.mk +++ b/unit-tests/directive-for-errors.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-errors.mk,v 1.6 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-for-errors.mk,v 1.9 2023/12/19 19:33:40 rillig Exp $ # # Tests for error handling in .for loops. @@ -9,7 +9,7 @@ .fori in 1 2 3 . warning <${i}> .endfor -# expect-2: <> +# expect-2: warning: <> # expect-2: for-less endfor @@ -35,7 +35,7 @@ # # 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 variable +# 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 @@ -67,7 +67,7 @@ ${:U\\}= backslash # see whether the "variable" '\' is local # The list of values after the 'in' may be empty, no matter if this emptiness -# comes from an empty expansion or even from a syntactically empty line. +# 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 @@ -89,6 +89,6 @@ ${:U\\}= backslash # see whether the "variable" '\' is local .for i in 1 2 ${:U3:Z} 4 . warning Should not be reached. .endfor -# expect-2: Should not be reached. -# expect-3: Should not be reached. -# expect-4: Should not be reached. +# expect-2: warning: Should not be reached. +# expect-3: warning: Should not be reached. +# expect-4: warning: Should not be reached. diff --git a/unit-tests/directive-for-escape.exp b/unit-tests/directive-for-escape.exp index dc63776c3764..6c84b7780e84 100644 --- a/unit-tests/directive-for-escape.exp +++ b/unit-tests/directive-for-escape.exp @@ -1,12 +1,12 @@ For: end for 1 For: loop body with chars = !"#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~: . info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: Unclosed variable expression, expecting '}' for modifier "U!"" of variable "" with value "!"" +make: Unclosed expression, expecting '}' for modifier "U!"" of variable "" with value "!"" make: "directive-for-escape.mk" line 19: !" For: end for 1 For: loop body with chars = !"\\#$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~: . info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} -make: Unclosed variable expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\" +make: Unclosed expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\" make: "directive-for-escape.mk" line 30: !"\\ For: end for 1 For: loop body with i = $: diff --git a/unit-tests/directive-for-escape.mk b/unit-tests/directive-for-escape.mk index 05eac84d4d6b..16df5b1db4e3 100644 --- a/unit-tests/directive-for-escape.mk +++ b/unit-tests/directive-for-escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-escape.mk,v 1.21 2023/06/23 06:11:06 rillig Exp $ +# $NetBSD: directive-for-escape.mk,v 1.23 2023/11/19 22:32:44 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 @@ -69,7 +69,7 @@ VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) # Try to cover the code for nested '{}' in ExprLen, without success. # -# The value of the variable VALUES is not meant to be a variable expression. +# 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} @@ -128,7 +128,7 @@ ${:U\\}= backslash # 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 -# variable expressions, braces and parentheses. (Compare ':M', ':S', and +# 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 @@ -147,7 +147,7 @@ VALUES= begin<$${UNDEF:Ufallback:N{{{}}}}>end # expect-2: $ # Before for.c 1.173 from 2023-05-08, the name of the iteration variable -# could contain colons, which affected variable expressions having this exact +# 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 @@ -156,7 +156,7 @@ NUMBERS= one two three .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 variable +# could contain braces, which allowed to replace sequences of # expressions. This possibility was neither intended nor documented. BASENAME= one EXT= .c @@ -203,7 +203,7 @@ i,= comma . info eight ${$}${$}${$}${$} and no cents. .endfor # Outside a .for loop, '${$}' is interpreted differently. The outer '$' starts -# a variable expression. The inner '$' is followed by a '}' and is thus a +# 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. diff --git a/unit-tests/directive-for-if.mk b/unit-tests/directive-for-if.mk index f2d0b550c693..f5a20279cc97 100644 --- a/unit-tests/directive-for-if.mk +++ b/unit-tests/directive-for-if.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for-if.mk,v 1.2 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-for-if.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $ # # Test for a .for directive that contains an .if directive. # @@ -71,7 +71,7 @@ _!= echo "${var}" 1>&2; echo # In 2005, '.info' was not invented yet. .endfor # Before for.c 1.39 from 2008-12-21, a common workaround was to surround the -# variable expression from the .for loop with '"'. Such a string literal +# expression from the .for loop with '"'. Such a string literal # has been allowed since cond.c 1.23 from 2004-04-13. Between that commit and # the one from 2008, the parser would still get confused if the value from the # .for loop contained '"', which was effectively a code injection. diff --git a/unit-tests/directive-for.exp b/unit-tests/directive-for.exp index 7f37da4ef364..0d0313c4e7b0 100755 --- a/unit-tests/directive-for.exp +++ b/unit-tests/directive-for.exp @@ -17,14 +17,15 @@ make: "directive-for.mk" line 146: }{ }{ }{ 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 196: Unknown modifier "Z" -make: "directive-for.mk" line 197: XXX: Not reached word1 -make: "directive-for.mk" line 197: XXX: Not reached word3 -make: "directive-for.mk" line 204: no iteration variables in for -make: "directive-for.mk" line 230: 1 open conditional -make: "directive-for.mk" line 246: for-less endfor -make: "directive-for.mk" line 247: if-less endif -make: "directive-for.mk" line 255: if-less endif +make: "directive-for.mk" line 210: Unknown modifier "Z" +make: "directive-for.mk" line 211: XXX: Should not reach word1 +make: "directive-for.mk" line 211: XXX: Should not reach before--after +make: "directive-for.mk" line 211: 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 +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 For: new loop 2 For: end for 2 For: end for 1 @@ -35,7 +36,7 @@ For: loop body with outer = o: endfor For: end for 1 For: loop body with inner = i: -make: "directive-for.mk" line 303: newline-item=(a) +make: "directive-for.mk" line 318: newline-item=(a) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/directive-for.mk b/unit-tests/directive-for.mk index 22b9f78e5fe1..becc314226dc 100755 --- a/unit-tests/directive-for.mk +++ b/unit-tests/directive-for.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-for.mk,v 1.22 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-for.mk,v 1.24 2023/12/06 22:28:20 rillig Exp $ # # Tests for the .for directive. # @@ -25,7 +25,7 @@ NUMBERS+= ${num} # The .for loop also works for multiple iteration variables. -# This is something that the modifier :@ cannot do. +# This is something that the modifier :@ cannot do as easily. .for name value in VARNAME value NAME2 value2 ${name}= ${value} .endfor @@ -189,15 +189,30 @@ INDIRECT= direct .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: Unknown modifier "Z" -.for var in word1 ${:Uword2:Z} word3 -. info XXX: Not reached ${var} +.for var in word1 before-${:Uword2:Z}-after word3 +. info XXX: Should not reach ${var} .endfor -# expect-2: XXX: Not reached word1 -# expect-3: XXX: Not reached word3 +# expect-2: XXX: Should not reach word1 +# expect-3: XXX: Should not reach before--after +# expect-4: XXX: Should not reach word3 # An empty list of variables to the left of the 'in' is a parse error. diff --git a/unit-tests/directive-ifmake.mk b/unit-tests/directive-ifmake.mk index 2a0cedda463e..fa0a56c60030 100644 --- a/unit-tests/directive-ifmake.mk +++ b/unit-tests/directive-ifmake.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-ifmake.mk,v 1.11 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-ifmake.mk,v 1.12 2023/11/19 21:47:52 rillig Exp $ # # Tests for the .ifmake directive, which provides a shortcut for asking # whether a certain target is requested to be made from the command line. @@ -75,7 +75,7 @@ . error .endif -# A condition that consists of a variable expression only (without any +# A condition that consists of an expression only (without any # comparison operator) can be used with .if and the other .ifxxx directives. .ifmake ${:Ufirst} # expect+1: ok diff --git a/unit-tests/directive-ifndef.mk b/unit-tests/directive-ifndef.mk index 95b8df6bcbb8..44eec55b4a87 100644 --- a/unit-tests/directive-ifndef.mk +++ b/unit-tests/directive-ifndef.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-ifndef.mk,v 1.8 2023/06/19 20:44:06 rillig Exp $ +# $NetBSD: directive-ifndef.mk,v 1.9 2023/10/19 18:24:33 rillig Exp $ # # Tests for the .ifndef directive, which can be used for multiple-inclusion # guards. In contrast to C, where #ifndef and #define nicely line up the @@ -50,4 +50,38 @@ DEFINED= . error .endif + +# The negation from the 'if-not-defined' directive only applies to bare words, +# but not to numbers, quoted strings or expressions. Those are evaluated +# without extra negation, just like in a plain '.if' directive. +.ifndef 0 +. error +.endif +.ifndef 1 +.else +. error +.endif +.ifndef "" +. error +.endif +.ifndef "word" +.else +. error +.endif +.ifndef ${:UUNDEFINED} +.else +. error +.endif +.ifndef ${:UDEFINED} +. error +.endif +.ifndef ${:U0} +. error +.endif +.ifndef ${:U1} +.else +. error +.endif + + all: diff --git a/unit-tests/directive-include-guard.exp b/unit-tests/directive-include-guard.exp index ba1ea1a8b388..70d23a19fe7c 100644 --- a/unit-tests/directive-include-guard.exp +++ b/unit-tests/directive-include-guard.exp @@ -2,6 +2,10 @@ 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 @@ -10,10 +14,16 @@ 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 @@ -85,4 +95,10 @@ 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-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/unit-tests/directive-include-guard.mk b/unit-tests/directive-include-guard.mk index fcd80ca8a1b3..85c0242c2009 100644 --- a/unit-tests/directive-include-guard.mk +++ b/unit-tests/directive-include-guard.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-include-guard.mk,v 1.12 2023/08/11 04:56:31 rillig Exp $ +# $NetBSD: directive-include-guard.mk,v 1.16 2023/12/17 14:07:22 rillig Exp $ # # Tests for multiple-inclusion guards in makefiles. # @@ -15,8 +15,9 @@ # .endif # # When such a file is included for the second or later time, and the guard -# variable or the guard target is defined, including the file has no effect, -# as all its content is skipped. +# 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 @@ -27,7 +28,7 @@ # This is the canonical form of a variable-based multiple-inclusion guard. -INCS+= variable-ifndef +CASES+= variable-ifndef LINES.variable-ifndef= \ '.ifndef VARIABLE_IFNDEF' \ 'VARIABLE_IFNDEF=' \ @@ -38,7 +39,7 @@ LINES.variable-ifndef= \ # 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. -INCS+= variable-ifndef-reuse +CASES+= variable-ifndef-reuse LINES.variable-ifndef-reuse= \ '.ifndef VARIABLE_IFNDEF' \ 'syntax error' \ @@ -46,8 +47,27 @@ LINES.variable-ifndef-reuse= \ # 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. -INCS+= comments +CASES+= comments LINES.comments= \ '\# comment' \ '' \ @@ -62,7 +82,7 @@ LINES.comments= \ # 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. -INCS+= variable-if +CASES+= variable-if LINES.variable-if= \ '.if !defined(VARIABLE_IF)' \ 'VARIABLE_IF=' \ @@ -73,7 +93,7 @@ LINES.variable-if= \ # 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. -INCS+= variable-if-reuse +CASES+= variable-if-reuse LINES.variable-if-reuse= \ '.if !defined(VARIABLE_IF)' \ 'syntax error' \ @@ -83,7 +103,7 @@ LINES.variable-if-reuse= \ # Triple negation is so uncommon that it's not recognized, even though it has # the same effect as a single negation. -INCS+= variable-if-triple-negation +CASES+= variable-if-triple-negation LINES.variable-if-triple-negation= \ '.if !!!defined(VARIABLE_IF_TRIPLE_NEGATION)' \ 'VARIABLE_IF_TRIPLE_NEGATION=' \ @@ -91,9 +111,29 @@ LINES.variable-if-triple-negation= \ # 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. -INCS+= variable-ifdef-negated +CASES+= variable-ifdef-negated LINES.variable-ifdef-negated= \ '.ifdef !VARIABLE_IFDEF_NEGATED' \ 'VARIABLE_IFDEF_NEGATED=' \ @@ -102,7 +142,7 @@ LINES.variable-ifdef-negated= \ # expect: Parse_PushInput: file variable-ifdef-negated.tmp, line 1 # The variable names in the '.if' and the assignment must be the same. -INCS+= variable-name-mismatch +CASES+= variable-name-mismatch LINES.variable-name-mismatch= \ '.ifndef VARIABLE_NAME_MISMATCH' \ 'VARIABLE_NAME_DIFFERENT=' \ @@ -110,13 +150,23 @@ LINES.variable-name-mismatch= \ # 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. -INCS+= variable-name-exclamation +CASES+= variable-name-exclamation LINES.variable-name-exclamation= \ '.if !defined(!VARIABLE_NAME_EXCLAMATION)' \ '${:U!}VARIABLE_NAME_EXCLAMATION=' \ @@ -124,11 +174,11 @@ LINES.variable-name-exclamation= \ # expect: Parse_PushInput: file variable-name-exclamation.tmp, line 1 # expect: Parse_PushInput: file variable-name-exclamation.tmp, line 1 -# 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. -INCS+= variable-name-exclamation-middle +# 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=' \ @@ -141,7 +191,7 @@ LINES.variable-name-exclamation-middle= \ # 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. -INCS+= variable-name-parentheses +CASES+= variable-name-parentheses LINES.variable-name-parentheses= \ '.ifndef VARIABLE_NAME(&)PARENTHESES' \ 'VARIABLE_NAME(&)PARENTHESES=' \ @@ -150,7 +200,7 @@ LINES.variable-name-parentheses= \ # expect: Parse_PushInput: file variable-name-parentheses.tmp, line 1 # The guard condition must consist of only the guard variable, nothing else. -INCS+= variable-ifndef-plus +CASES+= variable-ifndef-plus LINES.variable-ifndef-plus= \ '.ifndef VARIABLE_IFNDEF_PLUS && VARIABLE_IFNDEF_SECOND' \ 'VARIABLE_IFNDEF_PLUS=' \ @@ -160,7 +210,7 @@ LINES.variable-ifndef-plus= \ # expect: Parse_PushInput: file variable-ifndef-plus.tmp, line 1 # The guard condition must consist of only the guard variable, nothing else. -INCS+= variable-if-plus +CASES+= variable-if-plus LINES.variable-if-plus= \ '.if !defined(VARIABLE_IF_PLUS) && !defined(VARIABLE_IF_SECOND)' \ 'VARIABLE_IF_PLUS=' \ @@ -171,7 +221,7 @@ LINES.variable-if-plus= \ # The variable name in an '.ifndef' guard must be given directly, it must not # contain any '$' expression. -INCS+= variable-ifndef-indirect +CASES+= variable-ifndef-indirect LINES.variable-ifndef-indirect= \ '.ifndef $${VARIABLE_IFNDEF_INDIRECT:L}' \ 'VARIABLE_IFNDEF_INDIRECT=' \ @@ -181,7 +231,7 @@ LINES.variable-ifndef-indirect= \ # The variable name in an '.if' guard must be given directly, it must not # contain any '$' expression. -INCS+= variable-if-indirect +CASES+= variable-if-indirect LINES.variable-if-indirect= \ '.if !defined($${VARIABLE_IF_INDIRECT:L})' \ 'VARIABLE_IF_INDIRECT=' \ @@ -193,7 +243,7 @@ LINES.variable-if-indirect= \ # 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. -INCS+= variable-assign-indirect +CASES+= variable-assign-indirect LINES.variable-assign-indirect= \ '.ifndef VARIABLE_ASSIGN_INDIRECT' \ '$${VARIABLE_ASSIGN_INDIRECT:L}=' \ @@ -203,7 +253,7 @@ LINES.variable-assign-indirect= \ # 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. -INCS+= variable-assign-late +CASES+= variable-assign-late LINES.variable-assign-late= \ '.ifndef VARIABLE_ASSIGN_LATE' \ 'VARIABLE_ASSIGN_LATE_OTHER=' \ @@ -214,7 +264,7 @@ LINES.variable-assign-late= \ # 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. -INCS+= variable-assign-nested +CASES+= variable-assign-nested LINES.variable-assign-nested= \ '.ifndef VARIABLE_ASSIGN_NESTED' \ '. if 1' \ @@ -231,7 +281,7 @@ LINES.variable-assign-nested= \ # 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. -INCS+= variable-already-defined +CASES+= variable-already-defined LINES.variable-already-defined= \ '.ifndef VARIABLE_ALREADY_DEFINED' \ 'VARIABLE_ALREADY_DEFINED=' \ @@ -244,7 +294,7 @@ VARIABLE_ALREADY_DEFINED= # 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. -INCS+= variable-defined-then-undefined +CASES+= variable-defined-then-undefined LINES.variable-defined-then-undefined= \ '.ifndef VARIABLE_DEFINED_THEN_UNDEFINED' \ '.endif' @@ -258,7 +308,7 @@ UNDEF_BETWEEN.variable-defined-then-undefined= \ # 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. -INCS+= variable-two-times +CASES+= variable-two-times LINES.variable-two-times= \ '.ifndef VARIABLE_TWO_TIMES_1' \ 'VARIABLE_TWO_TIMES_1=' \ @@ -275,7 +325,7 @@ LINES.variable-two-times= \ # 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 '_'. -INCS+= variable-clash +CASES+= variable-clash LINES.variable-clash= \ ${LINES.variable-if} # expect: Parse_PushInput: file variable-clash.tmp, line 1 @@ -283,7 +333,7 @@ LINES.variable-clash= \ # The conditional must come before the assignment, otherwise the conditional # is useless, as it always evaluates to false. -INCS+= variable-swapped +CASES+= variable-swapped LINES.variable-swapped= \ 'SWAPPED=' \ '.ifndef SWAPPED' \ @@ -294,7 +344,7 @@ LINES.variable-swapped= \ # If the guard variable is undefined between the first and the second time the # file is included, the guarded file is included again. -INCS+= variable-undef-between +CASES+= variable-undef-between LINES.variable-undef-between= \ '.ifndef VARIABLE_UNDEF_BETWEEN' \ 'VARIABLE_UNDEF_BETWEEN=' \ @@ -306,7 +356,7 @@ UNDEF_BETWEEN.variable-undef-between= \ # 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. -INCS+= variable-undef-inside +CASES+= variable-undef-inside LINES.variable-undef-inside= \ '.ifndef VARIABLE_UNDEF_INSIDE' \ 'VARIABLE_UNDEF_INSIDE=' \ @@ -317,7 +367,7 @@ LINES.variable-undef-inside= \ # If the file does not define the guard variable, the guard does not have an # effect, and the file is included again. -INCS+= variable-not-defined +CASES+= variable-not-defined LINES.variable-not-defined= \ '.ifndef VARIABLE_NOT_DEFINED' \ '.endif' @@ -325,7 +375,7 @@ LINES.variable-not-defined= \ # expect: Parse_PushInput: file variable-not-defined.tmp, line 1 # The outermost '.if' must not have an '.elif' branch. -INCS+= elif +CASES+= elif LINES.elif= \ '.ifndef ELIF' \ 'ELIF=' \ @@ -336,7 +386,7 @@ LINES.elif= \ # 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. -INCS+= elif-reuse +CASES+= elif-reuse LINES.elif-reuse= \ '.ifndef ELIF' \ 'syntax error' \ @@ -346,7 +396,7 @@ LINES.elif-reuse= \ # expect: Parse_PushInput: file elif-reuse.tmp, line 1 # The outermost '.if' must not have an '.else' branch. -INCS+= else +CASES+= else LINES.else= \ '.ifndef ELSE' \ 'ELSE=' \ @@ -357,7 +407,7 @@ LINES.else= \ # 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. -INCS+= else-reuse +CASES+= else-reuse LINES.else-reuse= \ '.ifndef ELSE' \ 'syntax error' \ @@ -368,7 +418,7 @@ LINES.else-reuse= \ # The inner '.if' directives may have an '.elif' or '.else', and it doesn't # matter which of their branches are taken. -INCS+= inner-if-elif-else +CASES+= inner-if-elif-else LINES.inner-if-elif-else= \ '.ifndef INNER_IF_ELIF_ELSE' \ 'INNER_IF_ELIF_ELSE=' \ @@ -394,7 +444,7 @@ LINES.inner-if-elif-else= \ # 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. -INCS+= target +CASES+= target LINES.target= \ '.if !target(__target.tmp__)' \ '__target.tmp__: .NOTMAIN' \ @@ -405,7 +455,7 @@ LINES.target= \ # When used for system files, the target name may include '<' and '>', for # symmetry with the '.include ' directive. The characters '<' and '>' # are ordinary characters. -INCS+= target-sys +CASES+= target-sys LINES.target-sys= \ '.if !target(____)' \ '____: .NOTMAIN' \ @@ -419,7 +469,7 @@ LINES.target-sys= \ # 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. -INCS+= target-indirect +CASES+= target-indirect LINES.target-indirect= \ '.if !target($${target-indirect.tmp:L})' \ 'target-indirect.tmp: .NOTMAIN' \ @@ -432,7 +482,7 @@ LINES.target-indirect= \ # 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. -INCS+= target-indirect-PARSEFILE +CASES+= target-indirect-PARSEFILE LINES.target-indirect-PARSEFILE= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ @@ -442,7 +492,7 @@ LINES.target-indirect-PARSEFILE= \ # Two files with different basenames can both use the same syntactic pattern # for the target guard name, as the expressions expand to different strings. -INCS+= target-indirect-PARSEFILE2 +CASES+= target-indirect-PARSEFILE2 LINES.target-indirect-PARSEFILE2= \ '.if !target(__$${.PARSEFILE}__)' \ '__$${.PARSEFILE}__: .NOTMAIN' \ @@ -453,8 +503,8 @@ LINES.target-indirect-PARSEFILE2= \ # 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 variable, the file is considered guarded. -INCS+= subdir/target-indirect-PARSEFILE +# without defining the guard target, the file is considered guarded. +CASES+= subdir/target-indirect-PARSEFILE LINES.subdir/target-indirect-PARSEFILE= \ '.if !target(__$${.PARSEFILE}__)' \ '.endif' @@ -463,7 +513,7 @@ LINES.subdir/target-indirect-PARSEFILE= \ # Another common form of guard target is __${.PARSEDIR}/${.PARSEFILE}__ # or __${.PARSEDIR:tA}/${.PARSEFILE}__ to be truly unique. -INCS+= target-indirect-PARSEDIR-PARSEFILE +CASES+= target-indirect-PARSEDIR-PARSEFILE LINES.target-indirect-PARSEDIR-PARSEFILE= \ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \ @@ -475,7 +525,7 @@ LINES.target-indirect-PARSEDIR-PARSEFILE= \ # Using the combination of '.PARSEDIR' and '.PARSEFILE', a file in a # subdirectory gets a different guard target name than the previous one. -INCS+= subdir/target-indirect-PARSEDIR-PARSEFILE +CASES+= subdir/target-indirect-PARSEDIR-PARSEFILE LINES.subdir/target-indirect-PARSEDIR-PARSEFILE= \ '.if !target(__$${.PARSEDIR}/$${.PARSEFILE}__)' \ '__$${.PARSEDIR}/$${.PARSEFILE}__: .NOTMAIN' \ @@ -487,7 +537,7 @@ LINES.subdir/target-indirect-PARSEDIR-PARSEFILE= \ # If the guard target is not defined when including the file the next time, # the file is processed again. -INCS+= target-unguarded +CASES+= target-unguarded LINES.target-unguarded= \ '.if !target(target-unguarded)' \ '.endif' @@ -495,7 +545,7 @@ LINES.target-unguarded= \ # expect: Parse_PushInput: file target-unguarded.tmp, line 1 # The guard condition must consist of only the guard target, nothing else. -INCS+= target-plus +CASES+= target-plus LINES.target-plus= \ '.if !target(target-plus) && 1' \ 'target-plus: .NOTMAIN' \ @@ -505,7 +555,7 @@ LINES.target-plus= \ # If the guard target is defined before the file is included the first time, # the file is read once and then considered guarded. -INCS+= target-already-defined +CASES+= target-already-defined LINES.target-already-defined= \ '.if !target(target-already-defined)' \ 'target-already-defined: .NOTMAIN' \ @@ -523,7 +573,7 @@ target-already-defined: .NOTMAIN # 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'. -INCS+= target-name-exclamation +CASES+= target-name-exclamation LINES.target-name-exclamation= \ '.if !target(!target-name-exclamation)' \ '\!target-name-exclamation: .NOTMAIN' \ @@ -531,13 +581,47 @@ LINES.target-name-exclamation= \ # 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, +# 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' \ + '.endif' +# expect: Parse_PushInput: file target-name-parenthesized.tmp, line 1 +# expect: Parse_PushInput: file target-name-parenthesized.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 ${INCS} +.for i in ${CASES} . for fname in $i.tmp _:= ${fname:H:N.:@dir@${:!mkdir -p ${dir}!}@} _!= printf '%s\n' ${LINES.$i} > ${fname} diff --git a/unit-tests/directive-warning.mk b/unit-tests/directive-warning.mk index b4c8f4730b78..bf0683f8911f 100644 --- a/unit-tests/directive-warning.mk +++ b/unit-tests/directive-warning.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive-warning.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: directive-warning.mk,v 1.9 2023/12/17 09:44:00 rillig Exp $ # # Tests for the .warning directive. # @@ -16,7 +16,7 @@ .warnin message # misspelled # expect+1: Missing argument for ".warning" .warning # "Missing argument" -.warning message # expect+0: message +.warning message # expect+0: warning: message # expect+1: Unknown directive "warnings" .warnings # misspelled # expect+1: Unknown directive "warnings" diff --git a/unit-tests/directive.mk b/unit-tests/directive.mk index d51f0c2c8747..61938360dfc7 100644 --- a/unit-tests/directive.mk +++ b/unit-tests/directive.mk @@ -1,4 +1,4 @@ -# $NetBSD: directive.mk,v 1.8 2023/08/19 11:09:02 rillig Exp $ +# $NetBSD: directive.mk,v 1.9 2023/11/19 22:32:44 rillig Exp $ # # Tests for the preprocessing directives, such as .if or .info. @@ -13,7 +13,7 @@ # expect+1: Unknown directive "indented" . indented tab -# Directives must be written directly, not indirectly via variable +# Directives must be written directly, not indirectly via # expressions. # expect+1: Unknown directive "" .${:Uinfo} directives cannot be indirect diff --git a/unit-tests/escape.exp b/unit-tests/escape.exp index 6238e27b0191..ff9c8b7cf811 100644 --- a/unit-tests/escape.exp +++ b/unit-tests/escape.exp @@ -1,5 +1,4 @@ var-1bs -printf "%s=:%s:\n" VAR1BS 111\\111; printf "%s=:%s:\n" VAR1BSa 111\\aaa; printf "%s=:%s:\n" VAR1BSA 111\\aaa; printf "%s=:%s:\n" VAR1BSda 111\\\$\{a\}; printf "%s=:%s:\n" VAR1BSdA 111\\\$\{A\}; printf "%s=:%s:\n" VAR1BSc 111\#\ backslash\ escapes\ comment\ char,\ so\ this\ is\ part\ of\ the\ value; printf "%s=:%s:\n" VAR1BSsc 111\\\ ; VAR1BS=:111\111: VAR1BSa=:111\aaa: VAR1BSA=:111\aaa: @@ -8,25 +7,22 @@ VAR1BSdA=:111\${A}: VAR1BSc=:111# backslash escapes comment char, so this is part of the value: VAR1BSsc=:111\ : var-2bs -printf "%s=:%s:\n" VAR2BS 222\\\\222; printf "%s=:%s:\n" VAR2BSa 222\\\\aaa; printf "%s=:%s:\n" VAR2BSA 222\\\\aaa; printf "%s=:%s:\n" VAR2BSda 222\\\\\$\{a\}; printf "%s=:%s:\n" VAR2BSdA 222\\\\\$\{A\}; printf "%s=:%s:\n" VAR2BSc 222\\\\; printf "%s=:%s:\n" VAR2BSsc 222\\\\; -VAR2BS=:222\\222: -VAR2BSa=:222\\aaa: -VAR2BSA=:222\\aaa: -VAR2BSda=:222\\${a}: -VAR2BSdA=:222\\${A}: -VAR2BSc=:222\\: -VAR2BSsc=:222\\: -var-1bsnl -printf "%s=:%s:\n" VAR1BSNL 111\ 111; printf "%s=:%s:\n" VAR1BSNLa 111\ aaa; printf "%s=:%s:\n" VAR1BSNLA 111\ aaa; printf "%s=:%s:\n" VAR1BSNLda 111\ \$\{a\}; printf "%s=:%s:\n" VAR1BSNLdA 111\ \$\{A\}; printf "%s=:%s:\n" VAR1BSNLc 111; printf "%s=:%s:\n" VAR1BSNLsc 111; -VAR1BSNL=:111 111: -VAR1BSNLa=:111 aaa: -VAR1BSNLA=:111 aaa: -VAR1BSNLda=:111 ${a}: -VAR1BSNLdA=:111 ${A}: -VAR1BSNLc=:111: -VAR1BSNLsc=:111: +VAR2.BS=:222\\222: +VAR2.BS.a=:222\\aaa: +VAR2.BS.A=:222\\aaa: +VAR2.BS.d.a=:222\\${a}: +VAR2.BS.d.A=:222\\${A}: +VAR2.BS.c=:222\\: +VAR2.BS.s.c=:222\\: +var-1bs-nl +VAR1.BS-NL=:111 111: +VAR1.BS-NL.a=:111 aaa: +VAR1.BS-NL.A=:111 aaa: +VAR1.BS-NL.d-a=:111 ${a}: +VAR1.BS-NL.d-A=:111 ${A}: +VAR1.BS-NL.c=:111: +VAR1.BS-NL.s-c=:111: var-2bsnl -printf "%s=:%s:\n" VAR2BSNL 222\\\\; printf "%s=:%s:\n" VAR2BSNLa 222\\\\; printf "%s=:%s:\n" VAR2BSNLA 222\\\\; printf "%s=:%s:\n" VAR2BSNLda 222\\\\; printf "%s=:%s:\n" VAR2BSNLdA 222\\\\; printf "%s=:%s:\n" VAR2BSNLc 222\\\\; printf "%s=:%s:\n" VAR2BSNLsc 222\\\\; VAR2BSNL=:222\\: VAR2BSNLa=:222\\: VAR2BSNLA=:222\\: @@ -35,7 +31,6 @@ VAR2BSNLdA=:222\\: VAR2BSNLc=:222\\: VAR2BSNLsc=:222\\: var-3bsnl -printf "%s=:%s:\n" VAR3BSNL 333\\\\\ 333=; printf "%s=:%s:\n" VAR3BSNLa 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLA 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLda 333\\\\\ \$\{a\}=; printf "%s=:%s:\n" VAR3BSNLdA 333\\\\\ \$\{A\}=; printf "%s=:%s:\n" VAR3BSNLc 333\\\\; printf "%s=:%s:\n" VAR3BSNLsc 333\\\\; VAR3BSNL=:333\\ 333=: VAR3BSNLa=:333\\ aaa=: VAR3BSNLA=:333\\ aaa=: @@ -44,7 +39,6 @@ VAR3BSNLdA=:333\\ ${A}=: VAR3BSNLc=:333\\: VAR3BSNLsc=:333\\: var-1bsnl-space -printf "%s=:%s:\n" VAR1BSNL00 first\ line; printf "%s=:%s:\n" VAR1BSNL0 first\ line\ no\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLs first\ line\ one\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLss first\ line\ two\ spaces\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLt first\ line\ one\ tab\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLtt first\ line\ two\ tabs\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLxx first\ line\ many\ spaces\ and\ tabs\ \[\ \ \ \ \]\ on\ second\ line; VAR1BSNL00=:first line: VAR1BSNL0=:first line no space on second line: VAR1BSNLs=:first line one space on second line: diff --git a/unit-tests/escape.mk b/unit-tests/escape.mk index 8bdd3ad2ab49..a363a19f1946 100644 --- a/unit-tests/escape.mk +++ b/unit-tests/escape.mk @@ -1,4 +1,4 @@ -# $NetBSD: escape.mk,v 1.14 2020/11/03 17:38:45 rillig Exp $ +# $NetBSD: escape.mk,v 1.15 2023/10/19 18:24:33 rillig Exp $ # # Test backslash escaping. @@ -53,7 +53,7 @@ should continue the comment. \ __printvars: .USE .MADE @echo ${.TARGET} - ${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @} + @${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @} # Embedded backslash in variable should be taken literally. # @@ -83,7 +83,8 @@ all: var-2bs var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ VAR2BSc VAR2BSsc -# Backslash-newline in a variable setting is replaced by a single space. +# In a variable assignment, when the sequence occurs at +# the end of a physical line, it is replaced with a single space. # VAR1BSNL= 111\ 111 diff --git a/unit-tests/hanoi-include.mk b/unit-tests/hanoi-include.mk index 5e7d5c476dfc..f243af83d1df 100644 --- a/unit-tests/hanoi-include.mk +++ b/unit-tests/hanoi-include.mk @@ -1,4 +1,4 @@ -# $NetBSD: hanoi-include.mk,v 1.4 2023/01/19 22:48:42 rillig Exp $ +# $NetBSD: hanoi-include.mk,v 1.5 2023/10/19 18:24:33 rillig Exp $ # # Implements the Towers of Hanoi puzzle, demonstrating a bunch of more or less # useful programming techniques: @@ -6,14 +6,15 @@ # * default assignment using the ?= assignment operator # * including the same file recursively (rather unusual) # * extracting the current value of a variable using the .for loop -# * using shell commands for calculations since make is a text processor # * using the :: dependency operator for adding commands to a target # * on-the-fly variable assignment expressions using the ::= modifier # # usage: # env N=3 make -r -f hanoi-include.mk # -# endless loop, since command line variables cannot be overridden: +# Specifying N in the command line instead of in the environment would produce +# an endless loop, since variables from the command line cannot be overridden +# by global variables: # make -r -f hanoi-include.mk N=3 N?= 5 # Move this number of disks ... diff --git a/unit-tests/jobs-error-indirect.exp b/unit-tests/jobs-error-indirect.exp index 5c5a3801f4f6..79843a235666 100644 --- a/unit-tests/jobs-error-indirect.exp +++ b/unit-tests/jobs-error-indirect.exp @@ -2,7 +2,7 @@ false *** [indirect] Error code 1 make: stopped in unit-tests -1 error +make: 1 error make: stopped in unit-tests exit status 2 diff --git a/unit-tests/jobs-error-nested-make.exp b/unit-tests/jobs-error-nested-make.exp index 88c32ab8d1f6..2baf893c6623 100644 --- a/unit-tests/jobs-error-nested-make.exp +++ b/unit-tests/jobs-error-nested-make.exp @@ -3,7 +3,7 @@ false *** [nested] Error code 1 make: stopped in unit-tests -1 error +make: 1 error make: stopped in unit-tests diff --git a/unit-tests/jobs-error-nested.exp b/unit-tests/jobs-error-nested.exp index f96b5d016777..873613d40d48 100644 --- a/unit-tests/jobs-error-nested.exp +++ b/unit-tests/jobs-error-nested.exp @@ -3,13 +3,13 @@ false *** [nested] Error code 1 make: stopped in unit-tests -1 error +make: 1 error make: stopped in unit-tests *** [all] Error code 2 make: stopped in unit-tests -1 error +make: 1 error make: stopped in unit-tests exit status 2 diff --git a/unit-tests/lint.mk b/unit-tests/lint.mk index 5db417639d0b..431143c644ee 100755 --- a/unit-tests/lint.mk +++ b/unit-tests/lint.mk @@ -1,4 +1,4 @@ -# $NetBSD: lint.mk,v 1.4 2021/01/30 13:50:18 rillig Exp $ +# $NetBSD: lint.mk,v 1.5 2023/11/19 21:47:52 rillig Exp $ # # Demonstrates stricter checks that are only enabled in lint mode, using the # option -dL. @@ -6,7 +6,7 @@ # Before main.c 1.421 from 2020-11-01, make exited successfully even though # the error message had been issued as PARSE_FATAL. This was because back # then, make checked for parse errors only after parsing each top-level -# makefile, in Parse_File. After that, when expanding variable expressions +# makefile, in Parse_File. After that, when expanding expressions # in shell commands, the parse errors were not checked again. # Ouch: as of 2020-08-03, the variable is malformed and parsing stops diff --git a/unit-tests/moderrs.exp b/unit-tests/moderrs.exp index 9d8bd308c36c..6b41241b6800 100644 --- a/unit-tests/moderrs.exp +++ b/unit-tests/moderrs.exp @@ -9,13 +9,13 @@ make: Unknown modifier "Z" VAR:Z=before-inner}-after unclosed-direct: -want: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" -make: Unclosed variable expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" +want: Unclosed expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" +make: Unclosed expression, expecting '}' for modifier "S,V,v," of variable "VAR" with value "Thevariable" VAR:S,V,v,=Thevariable unclosed-indirect: -want: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" -make: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" +want: Unclosed expression after indirect modifier, expecting '}' for variable "VAR" +make: Unclosed expression after indirect modifier, expecting '}' for variable "VAR" VAR:S,V,v,=Thevariable unfinished-indirect: @@ -33,7 +33,7 @@ make: Unfinished modifier for "UNDEF" ('@' missing) 1 2 3 loop-close: -make: Unclosed variable expression, expecting '}' for modifier "@var@${var}}...@" of variable "UNDEF" with value "1}... 2}... 3}..." +make: Unclosed expression, expecting '}' for modifier "@var@${var}}...@" of variable "UNDEF" with value "1}... 2}... 3}..." 1}... 2}... 3}... 1}... 2}... 3}... @@ -67,7 +67,7 @@ make: Unfinished modifier for "VAR" (',' missing) 4: make: Unfinished modifier for "VAR" (',' missing) 5: -make: Unclosed variable expression, expecting '}' for modifier "S,from,to," of variable "VAR" with value "TheVariable" +make: Unclosed expression, expecting '}' for modifier "S,from,to," of variable "VAR" with value "TheVariable" 6: TheVariable 7: TheVariable @@ -82,7 +82,7 @@ make: Unfinished modifier for "VAR" (',' missing) 4: make: Unfinished modifier for "VAR" (',' missing) 5: -make: Unclosed variable expression, expecting '}' for modifier "C,from,to," of variable "VAR" with value "TheVariable" +make: Unclosed expression, expecting '}' for modifier "C,from,to," of variable "VAR" with value "TheVariable" 6: TheVariable 7: TheVariable @@ -124,13 +124,13 @@ make: Unknown modifier "__" mod-sysv-parse: make: Unknown modifier "3" -make: Unclosed variable expression, expecting '}' for modifier "3" of variable "FIB" with value "" +make: Unclosed expression, expecting '}' for modifier "3" of variable "FIB" with value "" make: Unknown modifier "3=" -make: Unclosed variable expression, expecting '}' for modifier "3=" of variable "FIB" with value "" +make: Unclosed expression, expecting '}' for modifier "3=" of variable "FIB" with value "" make: Unknown modifier "3=x3" -make: Unclosed variable expression, expecting '}' for modifier "3=x3" of variable "FIB" with value "" +make: Unclosed expression, expecting '}' for modifier "3=x3" of variable "FIB" with value "" 1 1 2 x3 5 8 1x3 21 34 diff --git a/unit-tests/moderrs.mk b/unit-tests/moderrs.mk index ffd920314c5d..bde263af4079 100644 --- a/unit-tests/moderrs.mk +++ b/unit-tests/moderrs.mk @@ -1,4 +1,4 @@ -# $NetBSD: moderrs.mk,v 1.30 2021/06/21 08:28:37 rillig Exp $ +# $NetBSD: moderrs.mk,v 1.31 2023/11/19 22:32:44 rillig Exp $ # # various modifier error tests @@ -34,11 +34,11 @@ mod-unknown-indirect: print-header print-footer @echo 'VAR:${MOD_UNKN}=before-${VAR:${MOD_UNKN}:inner}-after' unclosed-direct: print-header print-footer - @echo 'want: Unclosed variable expression, expecting $'}$' for modifier "S,V,v," of variable "VAR" with value "Thevariable"' + @echo 'want: Unclosed expression, expecting $'}$' for modifier "S,V,v," of variable "VAR" with value "Thevariable"' @echo VAR:S,V,v,=${VAR:S,V,v, unclosed-indirect: print-header print-footer - @echo 'want: Unclosed variable expression after indirect modifier, expecting $'}$' for variable "VAR"' + @echo 'want: Unclosed expression after indirect modifier, expecting $'}$' for variable "VAR"' @echo VAR:${MOD_TERM},=${VAR:${MOD_S} unfinished-indirect: print-header print-footer diff --git a/unit-tests/opt-debug-file.mk b/unit-tests/opt-debug-file.mk index a8190b2f1b50..e6c23c4faa1a 100644 --- a/unit-tests/opt-debug-file.mk +++ b/unit-tests/opt-debug-file.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-file.mk,v 1.9 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: opt-debug-file.mk,v 1.10 2023/11/19 21:47:52 rillig Exp $ # # Tests for the -dF command line option, which redirects the debug log # to a file instead of writing it to stderr. @@ -18,7 +18,7 @@ VAR= value ${:Uexpanded} # Make sure that the debug logging file contains some logging. DEBUG_OUTPUT:= ${:!cat opt-debug-file.debuglog!} # Grmbl. Because of the := operator in the above line, the variable -# value contains ${:Uexpanded}. This variable expression is expanded +# value contains ${:Uexpanded}. This expression is expanded # when it is used in the condition below. Therefore, be careful when storing # untrusted input in variables. #.MAKEFLAGS: -dc -dFstderr diff --git a/unit-tests/opt-debug-jobs.mk b/unit-tests/opt-debug-jobs.mk index f3732df7e25d..ac63bb9c5e86 100644 --- a/unit-tests/opt-debug-jobs.mk +++ b/unit-tests/opt-debug-jobs.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-jobs.mk,v 1.5 2020/11/12 21:54:52 rillig Exp $ +# $NetBSD: opt-debug-jobs.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ # # Tests for the -dj command line option, which adds debug logging about # running jobs in multiple shells. @@ -11,7 +11,7 @@ all: # Only the actual command is logged. - # To see the evaluation of the variable expressions, use -dv. + # To see the evaluation of the expressions, use -dv. : ${:Uexpanded} expression # Undefined variables expand to empty strings. diff --git a/unit-tests/opt-debug-lint.mk b/unit-tests/opt-debug-lint.mk index 0fcaf8b102ad..042bfd51d35b 100644 --- a/unit-tests/opt-debug-lint.mk +++ b/unit-tests/opt-debug-lint.mk @@ -1,7 +1,7 @@ -# $NetBSD: opt-debug-lint.mk,v 1.15 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: opt-debug-lint.mk,v 1.16 2023/11/19 21:47:52 rillig Exp $ # # Tests for the -dL command line option, which runs additional checks -# to catch common mistakes, such as unclosed variable expressions. +# to catch common mistakes, such as unclosed expressions. .MAKEFLAGS: -dL @@ -91,7 +91,7 @@ ${UNDEF}: ${UNDEF} # # 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 variable expressions. The unexpanded '$' then resulted in a wrong +# 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 diff --git a/unit-tests/opt-debug-loud.mk b/unit-tests/opt-debug-loud.mk index 38a3c7d7a8e1..5ea1f90ff7be 100644 --- a/unit-tests/opt-debug-loud.mk +++ b/unit-tests/opt-debug-loud.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-loud.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# $NetBSD: opt-debug-loud.mk,v 1.5 2023/12/19 19:33:40 rillig Exp $ # # Tests for the -dl command line option, which prints the commands before # running them, ignoring the command line option for silent mode (-s) as @@ -8,8 +8,8 @@ .MAKEFLAGS: -dl -s .SILENT: -# The -dl command line option does not affect commands that are run during -# variable expansion, such as :!cmd! or :sh. +# The -dl command line option does not affect commands that are run when +# evaluating expressions and their modifiers, such as :!cmd! or :sh. .if ${:!echo word!} != "word" . error .endif diff --git a/unit-tests/opt-debug-var.mk b/unit-tests/opt-debug-var.mk index 5b0c5648ab55..9017f18e81d3 100644 --- a/unit-tests/opt-debug-var.mk +++ b/unit-tests/opt-debug-var.mk @@ -1,4 +1,4 @@ -# $NetBSD: opt-debug-var.mk,v 1.2 2022/01/23 16:09:38 rillig Exp $ +# $NetBSD: opt-debug-var.mk,v 1.3 2023/11/19 21:47:52 rillig Exp $ # # Tests for the -dv command line option, which adds debug logging about # variable assignment and evaluation. @@ -15,7 +15,7 @@ SUBST:= value .if defined(ASSIGNED) .endif -# The usual form of variable expressions is ${VAR}. The form $(VAR) is used +# 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. # diff --git a/unit-tests/parse-var.mk b/unit-tests/parse-var.mk index 6205664c558e..b35726e76efc 100644 --- a/unit-tests/parse-var.mk +++ b/unit-tests/parse-var.mk @@ -1,6 +1,6 @@ -# $NetBSD: parse-var.mk,v 1.8 2023/02/18 11:16:09 rillig Exp $ +# $NetBSD: parse-var.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ # -# Tests for parsing variable expressions. +# Tests for parsing expressions. # # TODO: Add systematic tests for all of the below combinations. # @@ -77,7 +77,7 @@ .MAKEFLAGS: -dL # In variable assignments, there may be spaces in the middle of the left-hand -# side of the assignment, but only if they occur inside variable expressions. +# side of the assignment, but only if they occur inside expressions. # Leading spaces (but not tabs) are possible but unusual. # Trailing spaces are common in some coding styles, others omit them. VAR.${:U param }= value diff --git a/unit-tests/recursive.exp b/unit-tests/recursive.exp index dc50fe034969..f63b7a54049e 100644 --- a/unit-tests/recursive.exp +++ b/unit-tests/recursive.exp @@ -1,5 +1,5 @@ -make: "recursive.mk" line 37: Unclosed variable "MISSING_PAREN" -make: "recursive.mk" line 39: Unclosed variable "MISSING_BRACE" +make: "recursive.mk" line 38: Unclosed variable "MISSING_PAREN" +make: "recursive.mk" line 40: Unclosed variable "MISSING_BRACE" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/recursive.mk b/unit-tests/recursive.mk index 307515d70a08..b97c4b37eabb 100644 --- a/unit-tests/recursive.mk +++ b/unit-tests/recursive.mk @@ -1,4 +1,4 @@ -# $NetBSD: recursive.mk,v 1.6 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: recursive.mk,v 1.7 2023/10/19 18:24:33 rillig Exp $ # # In -dL mode, a variable may get expanded before it makes sense. # This would stop make from doing anything since the "recursive" error @@ -8,22 +8,23 @@ # whether there are unclosed variables. The variable value is therefore # parsed with VARE_PARSE_ONLY for that purpose. # -# Seen in pkgsrc/x11/libXfixes, and probably many more package that use -# GNU Automake. .MAKEFLAGS: -dL + AM_V_lt= ${am__v_lt_${V}} am__v_lt_= ${am__v_lt_${AM_DEFAULT_VERBOSITY}} am__v_lt_0= --silent am__v_lt_1= -# On 2020-08-06, make reported: "Variable am__v_lt_ is recursive." +# Since parse.c 1.243 from 2020-07-31 and before parse.c 1.249 from +# 2020-08-06, when make ran in -dL mode, it reported: "Variable am__v_lt_ is +# recursive." +# +# Seen in pkgsrc/x11/libXfixes, and probably many more package that use +# GNU Automake. libXfixes_la_LINK= ... ${AM_V_lt} ... -# somewhere later ... -AM_DEFAULT_VERBOSITY= 1 - # The purpose of the -dL flag is to detect unclosed variables. This # can be achieved by just parsing the variable and not evaluating it. diff --git a/unit-tests/sh-dots.mk b/unit-tests/sh-dots.mk index f85af9025e55..5294a4175b63 100755 --- a/unit-tests/sh-dots.mk +++ b/unit-tests/sh-dots.mk @@ -1,4 +1,4 @@ -# $NetBSD: sh-dots.mk,v 1.3 2020/10/25 22:04:24 rillig Exp $ +# $NetBSD: sh-dots.mk,v 1.4 2023/11/19 21:47:52 rillig Exp $ # # Tests for the special shell command line "...", which does not run the # commands below it but appends them to the list of commands that are run @@ -29,8 +29,8 @@ commented: .IGNORE ... # Run the below commands later @echo commented delayed ${.TARGET} -# The dots don't have to be written literally, they can also come from a -# variable expression. +# The dots don't have to be written literally, they can also come from an +# expression. indirect: @echo indirect regular ${:U...} diff --git a/unit-tests/sh-leading-hyphen.exp b/unit-tests/sh-leading-hyphen.exp index d049757777cd..50bcbbf9bb71 100644 --- a/unit-tests/sh-leading-hyphen.exp +++ b/unit-tests/sh-leading-hyphen.exp @@ -6,6 +6,6 @@ unknown-command: not found *** Error code 127 (ignored) unknown-long-option 'needed for needshell in compat.c' unknown-long-option: not found -whitespace in leading part *** Error code 127 (ignored) +whitespace in leading part exit status 0 diff --git a/unit-tests/shell-sh.mk b/unit-tests/shell-sh.mk index b3d4f18bbac9..5f7b04716ee1 100644 --- a/unit-tests/shell-sh.mk +++ b/unit-tests/shell-sh.mk @@ -1,9 +1,9 @@ -# $NetBSD: shell-sh.mk,v 1.1 2020/10/03 14:39:36 rillig Exp $ +# $NetBSD: shell-sh.mk,v 1.2 2023/12/24 16:48:30 sjg Exp $ # # Tests for using a bourne shell for running the commands. # This is the default shell, so there's nothing surprising. -.SHELL: name="sh" path="sh" +.SHELL: name="sh" all: : normal diff --git a/unit-tests/unexport.mk b/unit-tests/unexport.mk index 4363aaac3eee..4bcc5b21ca02 100644 --- a/unit-tests/unexport.mk +++ b/unit-tests/unexport.mk @@ -1,4 +1,4 @@ -# $NetBSD: unexport.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ +# $NetBSD: unexport.mk,v 1.6 2023/10/19 18:24:33 rillig Exp $ # pick up a bunch of exported vars FILTER_CMD= grep ^UT_ @@ -10,7 +10,7 @@ UT_TEST= unexport # Until 2020-08-08, Var_UnExport had special handling for '\n', that code # was not reachable though. At that point, backslash-newline has already -# been replaced with a simple space, and variables are not yet expanded. +# been replaced with a simple space, and expressions are not yet expanded. UT_BEFORE_NL= before UT_AFTER_NL= after .export UT_BEFORE_NL UT_AFTER_NL diff --git a/unit-tests/var-eval-short.mk b/unit-tests/var-eval-short.mk index aa8bf79decec..2b25d82e96b8 100644 --- a/unit-tests/var-eval-short.mk +++ b/unit-tests/var-eval-short.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-eval-short.mk,v 1.10 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: var-eval-short.mk,v 1.11 2023/10/19 18:24:33 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, @@ -18,7 +18,7 @@ FAIL= ${:!echo unexpected 1>&2!} # 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 appear in ApplyModifier. +# TODO: Test the modifiers in the same order as they occur in ApplyModifier. .if 0 && ${FAIL} .endif diff --git a/unit-tests/var-op-append.mk b/unit-tests/var-op-append.mk index 5f06fd887332..e16b89139cc1 100644 --- a/unit-tests/var-op-append.mk +++ b/unit-tests/var-op-append.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-append.mk,v 1.10 2023/06/21 07:30:50 rillig Exp $ +# $NetBSD: var-op-append.mk,v 1.12 2023/11/02 05:46:26 rillig Exp $ # # Tests for the '+=' variable assignment operator, which appends to a # variable, creating it if necessary. @@ -39,7 +39,7 @@ VAR+= # empty # '+=' assignment operator. As far as possible, the '+' is interpreted as # part of the assignment operator. # -# See Parse_Var +# See Parse_Var, AdjustVarassignOp. C++= value .if ${C+} != "value" || defined(C++) . error @@ -56,4 +56,33 @@ VAR.${:U\$\$\$\$\$\$\$\$}+= dollars . error .endif + +# Appending to an environment variable in the global scope creates a global +# variable of the same name, taking its initial value from the environment +# variable. After the assignment, the environment variable is left as-is, +# the value of the global variable is not synced back to the environment +# variable. +export ENV_PLUS_GLOBAL=from-env-value +ENV_PLUS_GLOBAL+= appended-value +.if ${ENV_PLUS_GLOBAL} != "from-env-value appended-value" +. error +.endif +EXPORTED!= echo "$$ENV_PLUS_GLOBAL" +.if ${EXPORTED} != "from-env-value" +. error +.endif + +# Appending to an environment variable in the command line scope ignores the +# environment variable. +export ENV_PLUS_COMMAND=from-env-value +.MAKEFLAGS: ENV_PLUS_COMMAND+=appended-command +.if ${ENV_PLUS_COMMAND} != "appended-command" +. error ${ENV_PLUS_COMMAND} +.endif +EXPORTED!= echo "$$ENV_PLUS_GLOBAL" +.if ${EXPORTED} != "from-env-value" +. error +.endif + + all: diff --git a/unit-tests/var-op-assign.mk b/unit-tests/var-op-assign.mk index 4753975ec138..a900c28a918d 100644 --- a/unit-tests/var-op-assign.mk +++ b/unit-tests/var-op-assign.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-assign.mk,v 1.10 2023/08/19 10:52:14 rillig Exp $ +# $NetBSD: var-op-assign.mk,v 1.11 2023/11/19 21:47:52 rillig Exp $ # # Tests for the = variable assignment operator, which overwrites an existing # variable or creates it. @@ -66,7 +66,7 @@ VARIABLE NAME= variable value # neither contain parentheses nor braces. This is only a side-effect from # the implementation of the parser, which cheats when parsing a variable # name. It only counts parentheses and braces instead of properly parsing -# nested variable expressions such as VAR.${param}. +# nested expressions such as VAR.${param}. # VAR(spaces in parentheses)= () VAR{spaces in braces}= {} diff --git a/unit-tests/var-op-default.mk b/unit-tests/var-op-default.mk index ca4fbcc27c88..9d07ddf39e41 100644 --- a/unit-tests/var-op-default.mk +++ b/unit-tests/var-op-default.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-default.mk,v 1.3 2020/12/07 21:35:43 rillig Exp $ +# $NetBSD: var-op-default.mk,v 1.5 2023/11/19 22:32:44 rillig Exp $ # # Tests for the ?= variable assignment operator, which only assigns # if the variable is still undefined. @@ -45,7 +45,7 @@ i?= default # and 'VAR.${param}' expand to 'VAR.param', and the second '?=' assignment # has no effect. # -# Since 2000.05.11.07.43.42 it has been possible to use nested variable +# Since 2000.05.11.07.43.42 it has been possible to use nested # expressions in variable names, which made make much more versatile. # On 2008.03.31.00.12.21, this particular case of the '?=' operator has been # fixed. Before, the '?=' operator had not expanded the variable name @@ -61,8 +61,8 @@ VAR.${:Uparam}?= not used # Now demonstrate that the variable name is indeed expanded exactly once. # This is tricky to measure correctly since there are many inconsistencies -# in and around the code that expands variable expressions in the various -# places where variable expressions can occur. If in doubt, enable the +# in and around the code that expands expressions in the various +# places where expressions can occur. If in doubt, enable the # following debug flags to see what happens: #.MAKEFLAGS: -dcpv EXPAND_NAME= EXPAND.$$$$ # The full variable name is EXPAND.$$ diff --git a/unit-tests/var-op-expand.mk b/unit-tests/var-op-expand.mk index a0b9cc475404..76b90bf72b56 100644 --- a/unit-tests/var-op-expand.mk +++ b/unit-tests/var-op-expand.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-expand.mk,v 1.18 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: var-op-expand.mk,v 1.19 2023/11/19 21:47:52 rillig Exp $ # # Tests for the := variable assignment operator, which expands its # right-hand side. @@ -20,9 +20,9 @@ VAR:= value # When a ':=' assignment is performed, its right-hand side is evaluated and # expanded as far as possible. Contrary to other situations, '$$' and -# variable expressions based on undefined variables are preserved though. +# expressions based on undefined variables are preserved though. # -# Whether a variable expression is undefined or not is determined at the end +# 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 diff --git a/unit-tests/var-op-shell.mk b/unit-tests/var-op-shell.mk index de26bff94905..4441efaf4a90 100644 --- a/unit-tests/var-op-shell.mk +++ b/unit-tests/var-op-shell.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-op-shell.mk,v 1.7 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: var-op-shell.mk,v 1.8 2024/01/05 23:36:45 rillig Exp $ # # Tests for the != variable assignment operator, which runs its right-hand # side through the shell. @@ -91,4 +91,22 @@ OUTPUT!= echo '$$$$$$$$' 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} != ${.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 + + all: diff --git a/unit-tests/var-readonly.exp b/unit-tests/var-readonly.exp index 39a9383953dd..ae266753ee71 100644 --- a/unit-tests/var-readonly.exp +++ b/unit-tests/var-readonly.exp @@ -1 +1,4 @@ +Global: ignoring delete 'N' as it is read-only +Global: .MAKEFLAGS = -r -k -d v -d +Global: .MAKEFLAGS = -r -k -d v -d 0 exit status 0 diff --git a/unit-tests/var-readonly.mk b/unit-tests/var-readonly.mk index 050b44d1b4e8..e9ff6f38819f 100644 --- a/unit-tests/var-readonly.mk +++ b/unit-tests/var-readonly.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-readonly.mk,v 1.3 2023/06/19 15:37:48 sjg Exp $ +# $NetBSD: var-readonly.mk,v 1.4 2023/12/20 08:42:10 rillig Exp $ # the answer N = 42 @@ -10,10 +10,12 @@ N = 666 .endif # undef should fail +.MAKEFLAGS: -dv .undef N .ifndef N .error N should not be undef'd .endif +.MAKEFLAGS: -d0 .NOREADONLY: N # now we can change it diff --git a/unit-tests/var-recursive.exp b/unit-tests/var-recursive.exp index 2158286749f3..5415dc93a53d 100644 --- a/unit-tests/var-recursive.exp +++ b/unit-tests/var-recursive.exp @@ -1,19 +1,19 @@ make: "var-recursive.mk" line 21: still there -Variable DIRECT is recursive. +make: Variable DIRECT is recursive. in var-recursive.mk:22 make: stopped in unit-tests -Variable INDIRECT1 is recursive. +make: Variable INDIRECT1 is recursive. in var-recursive.mk:29 make: stopped in unit-tests make: "var-recursive.mk" line 37: ok -Variable V is recursive. +make: Variable V is recursive. in var-recursive.mk:45 make: stopped in unit-tests : OK -In a command near "var-recursive.mk" line 57: Variable VAR is recursive. +In a command near "var-recursive.mk" line 57: make[1]: Variable VAR is recursive. make: stopped in unit-tests exit status 0 diff --git a/unit-tests/var-recursive.mk b/unit-tests/var-recursive.mk index 998d0d8c312a..72231656673c 100644 --- a/unit-tests/var-recursive.mk +++ b/unit-tests/var-recursive.mk @@ -1,6 +1,6 @@ -# $NetBSD: var-recursive.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: var-recursive.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ # -# Tests for variable expressions that refer to themselves and thus +# Tests for expressions that refer to themselves and thus # cannot be evaluated. TESTS= direct indirect conditional short target diff --git a/unit-tests/var-scope-cmdline.mk b/unit-tests/var-scope-cmdline.mk index eaa2c58347ae..5c0f246a0a22 100644 --- a/unit-tests/var-scope-cmdline.mk +++ b/unit-tests/var-scope-cmdline.mk @@ -1,4 +1,4 @@ -# $NetBSD: var-scope-cmdline.mk,v 1.3 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: var-scope-cmdline.mk,v 1.4 2023/11/19 21:47:52 rillig Exp $ # # Tests for variables specified on the command line. # @@ -55,7 +55,7 @@ # temporary loop variable after finishing the loop. It was probably not # intended back then that a side effect of this seemingly simple change was # that both global and cmdline variables could now be undefined at will as a -# side effect of evaluating a variable expression. As of 2021-02-23, this is +# side effect of evaluating an expression. As of 2021-02-23, this is # still possible. # # Most cmdline variables are set at the very beginning, when parsing the diff --git a/unit-tests/var-scope-local-legacy.exp b/unit-tests/var-scope-local-legacy.exp index fb7e0863b2e6..33ce145fb8fd 100644 --- a/unit-tests/var-scope-local-legacy.exp +++ b/unit-tests/var-scope-local-legacy.exp @@ -2,5 +2,5 @@ : XY=undef_ : AF=undef_ : %D=undef_ %F=undef_ -: @D=._ @F=all_ +: @D=global-value_ @F=all_ exit status 0 diff --git a/unit-tests/var-scope-local-legacy.mk b/unit-tests/var-scope-local-legacy.mk index 9b70e3f8f143..70bc20fd9848 100644 --- a/unit-tests/var-scope-local-legacy.mk +++ b/unit-tests/var-scope-local-legacy.mk @@ -1,7 +1,24 @@ -# $NetBSD: var-scope-local-legacy.mk,v 1.2 2022/09/27 19:18:45 rillig Exp $ +# $NetBSD: var-scope-local-legacy.mk,v 1.3 2023/12/17 14:07:22 rillig Exp $ # # Tests for legacy target-local variables, such as ${ -Command: .SHELL = overwritten ignored (read-only) +Command: ignoring '.SHELL = overwritten' as it is read-only Global: .MAKEFLAGS = -r -k -d v -d Global: .MAKEFLAGS = -r -k -d v -d 0 make: Fatal errors encountered -- cannot continue diff --git a/unit-tests/vardebug.mk b/unit-tests/vardebug.mk index 34015aa71db9..b9b094772b54 100644 --- a/unit-tests/vardebug.mk +++ b/unit-tests/vardebug.mk @@ -1,32 +1,46 @@ -# $NetBSD: vardebug.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: vardebug.mk,v 1.9 2023/12/20 09:46:00 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 -.undef VAR # Var_Delete (found) -.undef VAR # Var_Delete (not found) +# 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 -.if ${VAR:N[2]} # ModifyWord_NoMatch (no debug output) +# 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 @@ -34,13 +48,16 @@ VAR+= 3 .endif # ApplyModifiers, "Got ..." +# expect: Result of ${:Mvalu[e]} is "value" (eval-defined, 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: Unknown modifier "unknown" .if ${:Uvariable:unknown} @@ -59,9 +76,7 @@ VAR+= 3 # 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 .MAKEFLAGS: -d0 - -all: - @: diff --git a/unit-tests/varmisc.exp b/unit-tests/varmisc.exp index f56f72d0ab9c..61e6a49963a0 100644 --- a/unit-tests/varmisc.exp +++ b/unit-tests/varmisc.exp @@ -17,12 +17,9 @@ false FALSE do not evaluate or expand :? if discarding is set -year=2016 month=04 day=01 -date=20160401 Version=123.456.789 == 123456789 Literal=3.4.5 == 3004005 We have target specific vars -MAN= make.1 save-dollars: 0 = $ save-dollars: 1 = $$ save-dollars: 2 = $$ @@ -54,7 +51,7 @@ make: Unclosed variable "UNCLOSED" make: Unclosed variable "UNCLOSED" make: Unclosed variable "PATTERN" -make: Unclosed variable expression, expecting '}' for modifier "M${PATTERN" of variable "UNCLOSED" with value "" +make: Unclosed expression, expecting '}' for modifier "M${PATTERN" of variable "UNCLOSED" with value "" make: Unclosed variable "param" make: Unclosed variable "UNCLOSED." diff --git a/unit-tests/varmisc.mk b/unit-tests/varmisc.mk index 81818f3fb8bb..f6a0e4da2d88 100644 --- a/unit-tests/varmisc.mk +++ b/unit-tests/varmisc.mk @@ -1,10 +1,10 @@ -# $Id: varmisc.mk,v 1.25 2021/12/07 00:03:11 sjg Exp $ -# $NetBSD: varmisc.mk,v 1.32 2021/12/05 10:02:51 rillig Exp $ +# $Id: varmisc.mk,v 1.26 2023/11/25 01:39:31 sjg Exp $ +# $NetBSD: varmisc.mk,v 1.33 2023/10/19 18:24:33 rillig Exp $ # # Miscellaneous variable tests. all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \ - strftime cmpv manok + cmpv all: save-dollars all: export-appended all: parse-dynamic @@ -47,13 +47,6 @@ NQ_none: @echo do not evaluate or expand :? if discarding @echo ${VSET:U${1:L:?${True}:${False}}} -April1= 1459494000 - -# slightly contorted syntax to use utc via variable -strftime: - @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000} - @echo date=${%Y%m%d:L:${gmtime=${April1}:L}} - # 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 @@ -66,17 +59,6 @@ cmpv: @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}} @echo We have ${${.TARGET:T}.only} -# catch mishandling of nested variables in .for loop -MAN= -MAN1= make.1 -.for s in 1 2 -. if defined(MAN$s) && !empty(MAN$s) -MAN+= ${MAN$s} -. endif -.endfor - -manok: - @echo MAN=${MAN} # Test parsing of boolean values. # begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean. @@ -131,10 +113,10 @@ VAR.${PARAM}+= 2 .if ${VAR.+} != "1 2" . error "${VAR.+}" .endif -.for param in + ! ? +.for param in : + ! ? VAR.${param}= ${param} .endfor -.if ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?" +.if ${VAR.${:U\:}} != ":" || ${VAR.+} != "+" || ${VAR.!} != "!" || ${VAR.?} != "?" . error "${VAR.+}" "${VAR.!}" "${VAR.?}" .endif diff --git a/unit-tests/varmod-assign.exp b/unit-tests/varmod-assign.exp index 1ad388418ab5..f258f92ea05b 100644 --- a/unit-tests/varmod-assign.exp +++ b/unit-tests/varmod-assign.exp @@ -12,6 +12,31 @@ Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined) Var_Parse: ${VARNAME}} != "assigned-value" (eval-defined) 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) +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) +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) +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: Bad modifier ":" for variable "" mod-assign-empty: value} make: Bad modifier ":" for variable "" @@ -25,4 +50,11 @@ make: Unfinished modifier for "ASSIGN" ('}' missing) ok=word make: " echo word; false " returned non-zero status 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 0 diff --git a/unit-tests/varmod-assign.mk b/unit-tests/varmod-assign.mk index a6236253068d..f7112c47c935 100644 --- a/unit-tests/varmod-assign.mk +++ b/unit-tests/varmod-assign.mk @@ -1,40 +1,46 @@ -# $NetBSD: varmod-assign.mk,v 1.15 2022/02/09 21:09:24 rillig Exp $ +# $NetBSD: varmod-assign.mk,v 1.19 2024/01/07 11:42:22 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-shell-error -# The modifier '::?=' applies the assignment operator '?=' 3 times. The +# 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 -# The modifier '::=' applies the assignment operator '=' 3 times. The +# 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 -# The modifier '::+=' applies the assignment operator '+=' 3 times. The +# 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 -# The modifier '::!=' applies the assignment operator '!=' 3 times. Just as +# 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 -# The assignments were performed as part of .if conditions and thus happened +# 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 @@ -116,7 +122,7 @@ APPEND.dollar= $${APPEND.indirect} .endif -# The assignment modifier can be used in a variable expression that is +# 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 @@ -149,3 +155,54 @@ ${VARNAME}= initial-value # Sets 'VAR.${param}' to 'expanded'. . 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/unit-tests/varmod-defined.exp b/unit-tests/varmod-defined.exp index b44d58c657aa..d82a292292a4 100644 --- a/unit-tests/varmod-defined.exp +++ b/unit-tests/varmod-defined.exp @@ -14,7 +14,7 @@ Modifier part: "${8_DOLLARS}" ModifyWords: split "$$$$$$$$" into 1 word Global: var = $$$$$$$$ Var_Parse: ${8_DOLLARS} (eval-keep-undefined) -ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$" +ModifyWord_Loop: expand "${8_DOLLARS}" to "$$$$" Global: delete var Result of ${VAR:@var@${8_DOLLARS}@} is "$$$$" (eval-keep-dollar-and-undefined, regular) Global: VAR = $$$$ diff --git a/unit-tests/varmod-defined.mk b/unit-tests/varmod-defined.mk index 8effec82620d..2ee9def9e164 100644 --- a/unit-tests/varmod-defined.mk +++ b/unit-tests/varmod-defined.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-defined.mk,v 1.13 2022/08/24 20:22:10 rillig Exp $ +# $NetBSD: varmod-defined.mk,v 1.16 2023/11/19 21:47:52 rillig Exp $ # # Tests for the :D variable modifier, which returns the given string # if the variable is defined. It is closely related to the :U modifier. @@ -46,10 +46,10 @@ DEF= defined . error .endif -# Like in several other places in variable expressions, when +# Like in several other places in expressions, when # ApplyModifier_Defined calls Var_Parse, double dollars lead to a parse # error that is silently ignored. This makes all dollar signs disappear, -# except for the last, which is a well-formed variable expression. +# except for the last, which is a well-formed expression. # .if ${DEF:D$$$$$${DEF}} != "defined" . error @@ -58,7 +58,7 @@ DEF= defined # Any other text is written without any further escaping. In contrast # to the :M modifier, parentheses and braces do not need to be nested. # Instead, the :D modifier is implemented sanely by parsing nested -# expressions as such, without trying any shortcuts. See ApplyModifier_Match +# expressions as such, without trying any shortcuts. See ParseModifier_Match # for an inferior variant. # .if ${DEF:D!&((((} != "!&((((" @@ -106,7 +106,7 @@ VAR:= ${VAR:@var@${8_DOLLARS}@} # Before var.c 1.1030 from 2022-08-24, the following expression caused an -# out-of-bounds read when parsing the indirect ':D' modifier. +# out-of-bounds read when parsing the indirect ':U' modifier. M_U_backslash:= ${:UU\\} .if ${:${M_U_backslash}} != "\\" . error diff --git a/unit-tests/varmod-edge.exp b/unit-tests/varmod-edge.exp index 92204d8e1840..c2477f16e950 100644 --- a/unit-tests/varmod-edge.exp +++ b/unit-tests/varmod-edge.exp @@ -1,7 +1,7 @@ make: "varmod-edge.mk" line 184: ok M-paren make: "varmod-edge.mk" line 184: ok M-mixed make: "varmod-edge.mk" line 184: ok M-unescape -make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)" +make: Unclosed expression, expecting '}' for modifier "U*)" of variable "" with value "*)" make: "varmod-edge.mk" line 184: ok M-nest-mix make: "varmod-edge.mk" line 184: ok M-nest-brk make: "varmod-edge.mk" line 184: ok M-pat-err diff --git a/unit-tests/varmod-edge.mk b/unit-tests/varmod-edge.mk index 2d63b372e85e..91220d99e47d 100644 --- a/unit-tests/varmod-edge.mk +++ b/unit-tests/varmod-edge.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-edge.mk,v 1.17 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-edge.mk,v 1.19 2023/11/19 22:06:15 rillig Exp $ # # Tests for edge cases in variable modifiers. # @@ -16,7 +16,7 @@ MOD.M-paren= ${INP.M-paren:M(*)} EXP.M-paren= (parentheses) () # The first closing brace matches the opening parenthesis. -# The second closing brace actually ends the variable expression. +# The second closing brace actually ends the expression. # # XXX: This is unexpected but rarely occurs in practice. TESTS+= M-mixed @@ -40,7 +40,7 @@ EXP.M-unescape= \(\{}\): # as open_parens + open_braces == closing_parens + closing_braces. This # means that ( and } form a matching pair. # -# Nested variable expressions are not parsed as such. Instead, only the +# 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. @@ -51,7 +51,7 @@ TESTS+= M-nest-mix INP.M-nest-mix= (parentheses) MOD.M-nest-mix= ${INP.M-nest-mix:M${:U*)}} EXP.M-nest-mix= (parentheses)} -# make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)" +# make: Unclosed expression, expecting '}' for modifier "U*)" of variable "" with value "*)" # In contrast to parentheses and braces, the brackets are not counted # when the :M modifier is parsed since Makefile variables only take the diff --git a/unit-tests/varmod-gmtime.mk b/unit-tests/varmod-gmtime.mk index 3dbc5e32e7e4..c30f5edbecc2 100644 --- a/unit-tests/varmod-gmtime.mk +++ b/unit-tests/varmod-gmtime.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-gmtime.mk,v 1.19 2023/08/19 11:53:10 rillig Exp $ +# $NetBSD: varmod-gmtime.mk,v 1.21 2023/11/19 21:47:52 rillig Exp $ # # Tests for the :gmtime variable modifier, which formats a timestamp # using strftime(3) in UTC. @@ -45,7 +45,7 @@ # Before var.c 1.1050 from 2023-05-09, it was not possible to pass the -# seconds via a variable expression. +# seconds via an expression. .if ${%Y:L:gmtime=${:U1593536400}} != "2020" . error .endif @@ -174,4 +174,15 @@ TIMESTAMPS+= $t . 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/unit-tests/varmod-ifelse.exp b/unit-tests/varmod-ifelse.exp index 413f0b9ba4de..653fe104ddaa 100644 --- a/unit-tests/varmod-ifelse.exp +++ b/unit-tests/varmod-ifelse.exp @@ -1,14 +1,14 @@ -make: Bad conditional expression 'variable expression == "literal"' in 'variable expression == "literal"?bad:bad' -make: "varmod-ifelse.mk" line 28: Malformed conditional (${${:Uvariable expression} == "literal":?bad:bad}) -make: Bad conditional expression ' == ""' in ' == ""?bad-assign:bad-assign' -make: Bad conditional expression ' == ""' in ' == ""?bad-cond:bad-cond' +make: Bad conditional expression 'bare words == "literal"' before '?bad:bad' +make: "varmod-ifelse.mk" line 28: Malformed conditional (${${:Ubare words} == "literal":?bad:bad}) +make: Bad conditional expression ' == ""' before '?bad-assign:bad-assign' +make: Bad conditional expression ' == ""' before '?bad-cond:bad-cond' make: "varmod-ifelse.mk" line 46: Malformed conditional (${${UNDEF} == "":?bad-cond:bad-cond}) -make: Bad conditional expression '1 == == 2' in '1 == == 2?yes:no' +make: Bad conditional expression '1 == == 2' before '?yes:no' make: "varmod-ifelse.mk" line 69: Malformed conditional (${1 == == 2:?yes:no} != "") CondParser_Eval: "${1 == == 2:?yes:no}" != "" CondParser_Eval: 1 == == 2 Comparing 1.000000 == 0.000000 -make: Bad conditional expression '1 == == 2' in '1 == == 2?yes:no' +make: Bad conditional expression '1 == == 2' before '?yes:no' Comparing "" != "" make: "varmod-ifelse.mk" line 96: warning: Oops, the parse error should have been propagated. CondParser_Eval: ${ ${:U\$}{VAR} == value:?ok:bad} != "ok" @@ -17,15 +17,15 @@ Comparing "value" == "value" Comparing "ok" != "ok" make: "varmod-ifelse.mk" line 158: no. make: "varmod-ifelse.mk" line 162: Comparison with '>=' requires both operands 'no' and '10' to be numeric -make: Bad conditional expression 'string == "literal" || no >= 10' in 'string == "literal" || no >= 10?yes:no' +make: Bad conditional expression 'string == "literal" || no >= 10' before '?yes:no' make: "varmod-ifelse.mk" line 162: . -make: Bad conditional expression 'string == "literal" && >= 10' in 'string == "literal" && >= 10?yes:no' +make: Bad conditional expression 'string == "literal" && >= 10' before '?yes:no' make: "varmod-ifelse.mk" line 169: . -make: Bad conditional expression 'string == "literal" || >= 10' in 'string == "literal" || >= 10?yes:no' +make: Bad conditional expression 'string == "literal" || >= 10' before '?yes:no' make: "varmod-ifelse.mk" line 172: . make: "varmod-ifelse.mk" line 180: make: "varmod-ifelse.mk" line 183: -make: Bad conditional expression ' ' in ' ?true:false' +make: Bad conditional expression ' ' before '?true:false' make: "varmod-ifelse.mk" line 186: <> CondParser_Eval: 0 && ${1:?${:Uthen0:S,}},,}:${:Uelse0:S,}},,}} != "not evaluated" CondParser_Eval: 1 && ${0:?${:Uthen1:S,}},,}:${:Uelse1:S,}},,}} != "else1" diff --git a/unit-tests/varmod-ifelse.mk b/unit-tests/varmod-ifelse.mk index 62d3333b71a4..5c8b55d92717 100644 --- a/unit-tests/varmod-ifelse.mk +++ b/unit-tests/varmod-ifelse.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-ifelse.mk,v 1.23 2023/07/01 09:06:34 rillig Exp $ +# $NetBSD: varmod-ifelse.mk,v 1.26 2023/12/10 20:12:28 rillig Exp $ # # Tests for the ${cond:?then:else} variable modifier, which evaluates either # the then-expression or the else-expression, depending on the condition. @@ -13,19 +13,19 @@ # The variable name of the expression is expanded and then taken as the # condition. In the below example it becomes: # -# variable expression == "literal" +# 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: # -# ${:Uvariable expression} == "literal" +# ${: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+1: Malformed conditional (${${:Uvariable expression} == "literal":?bad:bad}) -.if ${${:Uvariable expression} == "literal":?bad:bad} +# expect+1: Malformed conditional (${${:Ubare words} == "literal":?bad:bad}) +.if ${${:Ubare words} == "literal":?bad:bad} . error .else . error @@ -61,7 +61,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign} # 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 variable expression containing that +# 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. @@ -79,7 +79,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign} # XXX: The left-hand side is enclosed in quotes. This results in Var_Parse # being called without VARE_UNDEFERR. When ApplyModifier_IfElse # returns AMR_CLEANUP as result, Var_Parse returns varUndefined since the -# value of the variable expression is still undefined. CondParser_String is +# 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 @@ -98,7 +98,7 @@ COND:= ${${UNDEF} == "":?bad-assign:bad-assign} .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 variable +# 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 @@ -157,17 +157,17 @@ NUMBER= no # not really a number # expect+1: no. .info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. # expect+3: Comparison with '>=' requires both operands 'no' and '10' to be numeric -# expect: make: Bad conditional expression 'string == "literal" || no >= 10' in 'string == "literal" || no >= 10?yes:no' +# expect: make: Bad conditional expression 'string == "literal" || no >= 10' before '?yes:no' # 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: make: Bad conditional expression 'string == "literal" && >= 10' in 'string == "literal" && >= 10?yes:no' +# expect: make: Bad conditional expression 'string == "literal" && >= 10' before '?yes:no' # expect+1: . .info ${${STRING} == "literal" && ${NUMBER} >= 10:?yes:no}. -# expect: make: Bad conditional expression 'string == "literal" || >= 10' in 'string == "literal" || >= 10?yes:no' +# expect: make: Bad conditional expression 'string == "literal" || >= 10' before '?yes:no' # expect+1: . .info ${${STRING} == "literal" || ${NUMBER} >= 10:?yes:no}. @@ -291,3 +291,17 @@ INDIRECT_COND2= $${DELAYED} == "two" .MAKEFLAGS: -d0 + + +# In the modifier parts for the 'then' and 'else' branches, subexpressions are +# parsed in 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 diff --git a/unit-tests/varmod-indirect.mk b/unit-tests/varmod-indirect.mk index f79f041cd1ea..66714c75dca3 100644 --- a/unit-tests/varmod-indirect.mk +++ b/unit-tests/varmod-indirect.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-indirect.mk,v 1.12 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-indirect.mk,v 1.14 2023/11/19 22:32:44 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 @@ -11,9 +11,9 @@ # To apply a modifier indirectly via another variable, the whole -# modifier must be put into a single variable expression. +# modifier must be put into a single expression. # The following expression generates a parse error since its indirect -# modifier contains more than a sole variable expression. +# modifier contains more than a sole expression. # # expect+1: Unknown modifier "${" .if ${value:L:${:US}${:U,value,replacement,}} != "S,value,replacement,}" @@ -71,20 +71,20 @@ .endif -# The nested variable expression expands to "tu", and this is interpreted as +# 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 variable expression expands to "tl", and this is interpreted as +# 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 variable expression is ${1 != 1:?Z:tl}, consisting of the +# 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. @@ -133,7 +133,7 @@ M_NoPrimes= ${PRIMES:${M_ListToSkip}} .MAKEFLAGS: -d0 -# In contrast to the .if conditions, the .for loop allows undefined variable +# 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. @@ -172,10 +172,10 @@ M_NoPrimes= ${PRIMES:${M_ListToSkip}} # a variable assignment using ':='. .MAKEFLAGS: -dpv -# The undefined variable expression is kept as-is. +# The undefined expression is kept as-is. _:= before ${UNDEF} after -# The undefined variable expression is kept as-is. +# The undefined expression is kept as-is. _:= before ${UNDEF:${:US,a,a,}} after # XXX: The subexpression ${:U} is fully defined, therefore it is expanded. @@ -189,7 +189,7 @@ _:= before ${UNDEF:${:US,a,a,}} after _:= before ${UNDEF:${:U}} after # XXX: This expands to ${UNDEF:Z}, which will behave differently if the -# variable '_' is used in a context where the variable expression ${_} is +# variable '_' is used in a context where the expression ${_} is # parsed but not evaluated. # expect+1: Unknown modifier "Z" _:= before ${UNDEF:${:UZ}} after @@ -201,7 +201,7 @@ _:= before ${UNDEF:${:UZ}} after # 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 variable expression. +# not influence the outer expression. .if ${1 2 3:L:tW:[#]} != 1 # direct :tW applies to the :[#] . error .endif @@ -213,7 +213,7 @@ _:= before ${UNDEF:${:UZ}} after # 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 variable expression. +# 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 diff --git a/unit-tests/varmod-l-name-to-value.mk b/unit-tests/varmod-l-name-to-value.mk index 354622cf098b..e87e68967544 100644 --- a/unit-tests/varmod-l-name-to-value.mk +++ b/unit-tests/varmod-l-name-to-value.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-l-name-to-value.mk,v 1.7 2020/10/24 08:46:08 rillig Exp $ +# $NetBSD: varmod-l-name-to-value.mk,v 1.8 2023/11/19 21:47:52 rillig Exp $ # # Tests for the :L modifier, which returns the variable name as the new value. @@ -28,7 +28,7 @@ .endif # Between 2020-09-22 (var.c 1.527) and 2020-09-30 (var.c 1.553), there was -# a bug in the evaluation of variable expressions. Indirect modifiers like +# a bug in the evaluation of expressions. Indirect modifiers like # the below :L did not update the definedness of the enclosing expression. # This resulted in a wrong "Malformed conditional". .if ${value:${:UL}} == "" diff --git a/unit-tests/varmod-localtime.mk b/unit-tests/varmod-localtime.mk index 86f90064ded3..f6fcc61a7fa6 100644 --- a/unit-tests/varmod-localtime.mk +++ b/unit-tests/varmod-localtime.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-localtime.mk,v 1.13 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-localtime.mk,v 1.14 2023/11/19 21:47:52 rillig Exp $ # # Tests for the :localtime variable modifier, which formats a timestamp # using strftime(3) in local time. @@ -45,7 +45,7 @@ # Before var.c 1.1050 from 2023-05-09, it was not possible to pass the -# seconds via a variable expression. +# seconds via an expression. .if ${%Y:L:localtime=${:U1593536400}} != "2020" . error .endif diff --git a/unit-tests/varmod-loop-varname.mk b/unit-tests/varmod-loop-varname.mk index 43c72cee3ee5..6f7436f277da 100644 --- a/unit-tests/varmod-loop-varname.mk +++ b/unit-tests/varmod-loop-varname.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-loop-varname.mk,v 1.5 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-loop-varname.mk,v 1.6 2023/11/19 21:47:52 rillig Exp $ # # Tests for the first part of the variable modifier ':@var@...@', which # contains the variable name to use during the loop. @@ -112,7 +112,7 @@ RES3= 3 # # 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 variable +# 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 diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk index d09e49a6d842..64cc6ca85043 100644 --- a/unit-tests/varmod-loop.mk +++ b/unit-tests/varmod-loop.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-loop.mk,v 1.23 2023/02/18 11:55:20 rillig Exp $ +# $NetBSD: varmod-loop.mk,v 1.24 2023/11/19 21:47:52 rillig Exp $ # # Tests for the expression modifier ':@var@body@', which replaces each word of # the expression with the expanded body, which may contain references to the @@ -52,7 +52,7 @@ mod-loop-dollar: # # 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 variable +# 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 @@ -111,7 +111,7 @@ SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS} # The variable SUBST_CONTAINING_LOOP therefore gets assigned the raw value # "$$$$ $$$$$$$$ $$$$$$$$". # -# The variable expression in the condition then expands this raw stored value +# The expression in the condition then expands this raw stored value # once, resulting in "$$ $$$$ $$$$". The effects from VARE_KEEP_DOLLAR no # longer take place since they had only been active during the evaluation of # the variable assignment. diff --git a/unit-tests/varmod-match-escape.mk b/unit-tests/varmod-match-escape.mk index 098a24dd9f72..1da3918fe1a5 100755 --- a/unit-tests/varmod-match-escape.mk +++ b/unit-tests/varmod-match-escape.mk @@ -1,8 +1,8 @@ -# $NetBSD: varmod-match-escape.mk,v 1.10 2023/06/23 04:56:54 rillig Exp $ +# $NetBSD: varmod-match-escape.mk,v 1.12 2023/11/19 21:47:52 rillig Exp $ # # As of 2020-08-01, the :M and :N modifiers interpret backslashes differently, -# depending on whether there was a variable expression somewhere before the -# first backslash or not. See ApplyModifier_Match, "copy = true". +# 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 \: @@ -18,23 +18,23 @@ SPECIALS= \: : \\ * \* .endif # And now both cases combined: A single modifier with both an escaped ':' -# as well as a variable expression that expands to a ':'. +# as well as an expression that expands to a ':'. # -# XXX: As of 2020-11-01, when an escaped ':' occurs before the variable +# XXX: As of 2020-11-01, when an escaped ':' occurs before the # expression, the whole modifier text is subject to unescaping '\:' to ':', -# before the variable expression is expanded. This means that the '\:' in -# the variable expression is expanded as well, turning ${:U\:} into a simple +# 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 variable expression +# 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 ApplyModifier_Match had used the same parsing algorithm as Var_Subst, +# If ParseModifier_Match had used the same parsing algorithm as Var_Subst, # both patterns would end up as '::'. # VALUES= : :: :\: @@ -53,7 +53,7 @@ VALUES= : :: :\: .endif # XXX: As of 2020-11-01, unlike all other variable modifiers, '\$' is not -# parsed as an escaped '$'. Instead, ApplyModifier_Match first scans for +# 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 diff --git a/unit-tests/varmod-match.exp b/unit-tests/varmod-match.exp index 4ac89bda8e0b..e4e0783a7b15 100644 --- a/unit-tests/varmod-match.exp +++ b/unit-tests/varmod-match.exp @@ -1,25 +1,14 @@ -CondParser_Eval: ${NUMBERS:M[A-Z]*} != "One Two Three Four" -Comparing "One Two Three Four" != "One Two Three Four" -CondParser_Eval: ${NUMBERS:M[^A-Z]*} != "five six seven" -Comparing "five six seven" != "five six seven" -CondParser_Eval: ${NUMBERS:M[^s]*[ex]} != "One Three five" -Comparing "One Three five" != "One Three five" -CondParser_Eval: ${:U****************:M****************b} -CondParser_Eval: ${:U..................................................b:M*?*?*?*?*?a} -CondParser_Eval: ${:Ua \$ sign:M*$$*} != "\$" -Comparing "$" != "$" -CondParser_Eval: ${:Ua \$ sign any-asterisk:M*\$*} != "any-asterisk" -Comparing "any-asterisk" != "any-asterisk" -make: "varmod-match.mk" line 162: warning: Unfinished character list in pattern '[' of modifier ':M' -make: "varmod-match.mk" line 162: Unknown modifier "]" -make: "varmod-match.mk" line 162: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") -make: "varmod-match.mk" line 205: warning: Unfinished character list in pattern 'a[' of modifier ':M' -make: "varmod-match.mk" line 213: warning: Unfinished character list in pattern 'a[^' of modifier ':M' -make: "varmod-match.mk" line 221: warning: Unfinished character list in pattern '[-x1-3' of modifier ':M' -make: "varmod-match.mk" line 229: warning: Unfinished character list in pattern '*[-x1-3' of modifier ':M' -make: "varmod-match.mk" line 238: warning: Unfinished character list in pattern '[^-x1-3' of modifier ':M' -make: "varmod-match.mk" line 258: warning: Unfinished character range in pattern '[x-' of modifier ':M' -make: "varmod-match.mk" line 270: warning: Unfinished character range in pattern '[^x-' of modifier ':M' +make: "varmod-match.mk" line 289: warning: Unfinished character list in pattern 'a[' of modifier ':M' +make: "varmod-match.mk" line 297: warning: Unfinished character list in pattern 'a[^' of modifier ':M' +make: "varmod-match.mk" line 305: warning: Unfinished character list in pattern '[-x1-3' of modifier ':M' +make: "varmod-match.mk" line 313: warning: Unfinished character list in pattern '*[-x1-3' of modifier ':M' +make: "varmod-match.mk" line 322: warning: Unfinished character list in pattern '[^-x1-3' of modifier ':M' +make: "varmod-match.mk" line 336: warning: Unfinished character list in pattern '?[\' of modifier ':M' +make: "varmod-match.mk" line 344: warning: Unfinished character range in pattern '[x-' of modifier ':M' +make: "varmod-match.mk" line 356: warning: Unfinished character range in pattern '[^x-' of modifier ':M' +make: "varmod-match.mk" line 364: warning: Unfinished character list in pattern '[' of modifier ':M' +make: "varmod-match.mk" line 364: Unknown modifier "]" +make: "varmod-match.mk" line 364: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-match.mk b/unit-tests/varmod-match.mk index 2804c2f2da4a..d93d1839192d 100644 --- a/unit-tests/varmod-match.mk +++ b/unit-tests/varmod-match.mk @@ -1,97 +1,97 @@ -# $NetBSD: varmod-match.mk,v 1.15 2023/06/23 04:56:54 rillig Exp $ +# $NetBSD: varmod-match.mk,v 1.20 2023/12/17 23:19:02 rillig Exp $ # -# Tests for the :M variable modifier, which filters words that match the +# Tests for the ':M' modifier, which keeps only those words that match the # given pattern. # -# See ApplyModifier_Match and ModifyWord_Match for the implementation. +# 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. -.MAKEFLAGS: -dc -NUMBERS= One Two Three Four five six seven +# 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:L:Ms??*} != "six seven" +. error +.endif + +# Ensure that a pattern without placeholders only matches itself. +.if ${a aa aaa b ba baa bab:L:Ma} != "a" +. error +.endif + +# Ensure that a pattern that ends with '*' is properly anchored at the +# beginning. +.if ${a aa aaa b ba baa bab:L:Ma*} != "a aa aaa" +. error +.endif + +# Ensure that a pattern that starts with '*' is properly 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 ${NUMBERS:M[A-Z]*} != "One Two Three Four" +.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 ${NUMBERS:M[^A-Z]*} != "five six seven" +.if ${One Two Three Four five six seven:L:M[^A-Z]*} != "five six seven" . 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 class -# does not propagate to the second character class. -.if ${NUMBERS:M[^s]*[ex]} != "One Three five" -. error -.endif - -# 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 - -# To match a dollar sign in a word, double it. -# -# This is different from the :S and :C variable 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 variable 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(((\}\}\}}} - -.MAKEFLAGS: -d0 - -# Special characters: -# * matches 0 or more arbitrary characters -# ? matches a single arbitrary character -# \ starts an escape sequence, only outside ranges -# [ starts a set for matching a single character -# ] ends a set for matching a single character -# - in a set, forms a range of characters -# ^ as the first character in a set, negates the set -# ( during parsing of the pattern, starts a nesting level -# ) during parsing of the pattern, ends a nesting level -# { during parsing of the pattern, starts a nesting level -# } during parsing of the pattern, ends a nesting level -# : during parsing of the pattern, finishes the pattern -# $ during parsing of the pattern, starts a nested expression -# # in a line except a shell command, starts a comment -# -# Pattern parts: -# * matches 0 or more arbitrary characters -# ? matches exactly 1 arbitrary character -# \x matches exactly the character 'x' -# [...] matches exactly 1 character from the set -# [^...] matches exactly 1 character outside the set -# [a-z] matches exactly 1 character from the range 'a' to 'z' -# - # [] matches never .if ${ ab a[]b a[b a b :L:M[]} != "" . error @@ -134,43 +134,12 @@ ${:U*}= asterisk . error .endif -# [\] matches a single backslash -WORDS= a\b a[\]b ab -.if ${WORDS:Ma[\]b} != "a\\b" -. 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 - -# [:] matches never since the ':' starts the next modifier -# expect+3: warning: Unfinished character list in pattern '[' of modifier ':M' -# expect+2: Unknown modifier "]" -# expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") -.if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":" -. error -.else -. error -.endif - -# [\] matches exactly a backslash; no escaping takes place in +# [\] matches a single backslash; no escaping takes place in # character ranges -# Without the 'a' in the below words, the backslash would end a word and thus +# Without the 'b' in the below words, the backslash would end a word and thus # influence how the string is split into words. -WORDS= 1\a 2\\a -.if ${WORDS:M?[\]a} != "1\\a" +WORDS= a\b a[\]b ab a\\b +.if ${WORDS:Ma[\]b} != "a\\b" . error .endif @@ -199,6 +168,121 @@ WORDS= - -] . 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. All whitespace +# around and 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: warning: Unfinished character list in pattern 'a[' of modifier ':M' @@ -247,7 +331,9 @@ WORDS= - + x xx 0 1 2 3 4 [x1-3 # 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\\} -.if ${WORDS:M?[\]} != "\\\\ x\\" +PATTERN= ${:U?[\\} +# expect+1: warning: Unfinished character list in pattern '?[\' of modifier ':M' +.if ${WORDS:M${PATTERN}} != "\\\\ x\\" . error .endif @@ -271,35 +357,18 @@ WORDS= [x- x x- y yyyyy . error .endif - -# The modifier ':tW' prevents splitting at whitespace. Even leading and -# trailing whitespace is preserved. -.if ${ plain string :L:tW:M*} != " plain string " +# [:] matches never since the ':' starts the next modifier +# expect+3: warning: Unfinished character list in pattern '[' of modifier ':M' +# expect+2: Unknown modifier "]" +# expect+1: Malformed conditional (${ ${:U\:} ${:U\:\:} :L:M[:]} != ":") +.if ${ ${:U\:} ${:U\:\:} :L:M[:]} != ":" . error -.endif - -# Without the modifier ':tW', the string is split into words. All whitespace -# around and between the words is normalized to a single space. -.if ${ plain string :L:M*} != "plain string" +.else . error .endif -# The pattern can come from a variable 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 - +# 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. diff --git a/unit-tests/varmod-mtime.exp b/unit-tests/varmod-mtime.exp index a97c9805b5f1..039173feddc3 100644 --- a/unit-tests/varmod-mtime.exp +++ b/unit-tests/varmod-mtime.exp @@ -1,8 +1,14 @@ -make: "varmod-mtime.mk" line 60: Cannot determine mtime for 'no/such/file1': -make: "varmod-mtime.mk" line 60: Cannot determine mtime for 'no/such/file2': -make: "varmod-mtime.mk" line 60: Malformed conditional (${no/such/file1 no/such/file2:L:mtime=error}) -make: "varmod-mtime.mk" line 71: Invalid argument 'errorhandler-no' for modifier ':mtime' -make: "varmod-mtime.mk" line 71: Malformed conditional (${MAKEFILE:mtime=errorhandler-no} > 0) +make: "varmod-mtime.mk" line 47: 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: Cannot determine mtime for 'no/such/file1': +make: "varmod-mtime.mk" line 70: 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: 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: Invalid argument 'warn' for modifier ':mtime' +make: "varmod-mtime.mk" line 90: Malformed conditional (${MAKEFILE:mtime=warn} > 0) +make: "varmod-mtime.mk" line 115: Unknown modifier "mtim" +make: "varmod-mtime.mk" line 115: Malformed conditional (${anything:L:mtim}) make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-mtime.mk b/unit-tests/varmod-mtime.mk index 84cc7c996952..298756e152c1 100644 --- a/unit-tests/varmod-mtime.mk +++ b/unit-tests/varmod-mtime.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-mtime.mk,v 1.5 2023/08/19 08:19:25 rillig Exp $ +# $NetBSD: varmod-mtime.mk,v 1.9 2023/12/17 14:07:22 rillig Exp $ # # Tests for the ':mtime' variable modifier, which maps each word of the # expression to that file's modification time. @@ -41,6 +41,16 @@ not_found_mtime:= ${no/such/file:L:mtime} .endif +# The fallback timestamp must only be an integer, without trailing characters. +# expect+2: Invalid argument '123x' for modifier ':mtime' +# expect+1: Malformed conditional (${no/such/file:L:mtime=123x}) +.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 @@ -74,9 +84,42 @@ _!= rm -f ${COOKIE} .endif +# Only the word 'error' can be used as a fallback argument to the modifier. +# expect+2: Invalid argument 'warn' for modifier ':mtime' +# expect+1: Malformed conditional (${MAKEFILE:mtime=warn} > 0) +.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: Unknown modifier "mtim" +# expect+1: Malformed conditional (${anything:L: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/unit-tests/varmod-order.exp b/unit-tests/varmod-order.exp index 487243a1678d..12d0bff75157 100644 --- a/unit-tests/varmod-order.exp +++ b/unit-tests/varmod-order.exp @@ -2,9 +2,9 @@ make: Bad modifier ":OX" for variable "WORDS" make: "varmod-order.mk" line 16: Undefined variable "${WORDS:OX" make: Bad modifier ":OxXX" for variable "WORDS" make: "varmod-order.mk" line 21: Undefined variable "${WORDS:Ox" -make: Unclosed variable expression, expecting '}' for modifier "O" of variable "WORDS" with value "eight five four nine one seven six ten three two" -make: Unclosed variable expression, expecting '}' for modifier "On" of variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10" -make: Unclosed variable expression, expecting '}' for modifier "Onr" of variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1" +make: Unclosed expression, expecting '}' for modifier "O" of variable "WORDS" with value "eight five four nine one seven six ten three two" +make: Unclosed expression, expecting '}' for modifier "On" of variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10" +make: Unclosed expression, expecting '}' for modifier "Onr" of variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1" make: Bad modifier ":Oxn" for variable "NUMBERS" make: "varmod-order.mk" line 33: Malformed conditional (${NUMBERS:Oxn}) make: Bad modifier ":On_typo" for variable "NUMBERS" diff --git a/unit-tests/varmod-range.exp b/unit-tests/varmod-range.exp index b6226ec82c5b..9b0dad40a78e 100644 --- a/unit-tests/varmod-range.exp +++ b/unit-tests/varmod-range.exp @@ -1,13 +1,14 @@ -make: "varmod-range.mk" line 55: Invalid number "x}Rest" != "Rest"" for ':range' modifier -make: "varmod-range.mk" line 55: Malformed conditional ("${:U:range=x}Rest" != "Rest") -make: "varmod-range.mk" line 66: Unknown modifier "x0" -make: "varmod-range.mk" line 66: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") -make: "varmod-range.mk" line 84: Unknown modifier "rang" -make: "varmod-range.mk" line 84: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") -make: "varmod-range.mk" line 93: Unknown modifier "rango" -make: "varmod-range.mk" line 93: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") -make: "varmod-range.mk" line 102: Unknown modifier "ranger" -make: "varmod-range.mk" line 102: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest") +make: "varmod-range.mk" line 43: Malformed conditional (${:range=5} != "") +make: "varmod-range.mk" line 67: 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: Unknown modifier "x0" +make: "varmod-range.mk" line 78: Malformed conditional ("${:U:range=0x0}Rest" != "Rest") +make: "varmod-range.mk" line 96: Unknown modifier "rang" +make: "varmod-range.mk" line 96: Malformed conditional ("${a b c:L:rang}Rest" != "Rest") +make: "varmod-range.mk" line 105: Unknown modifier "rango" +make: "varmod-range.mk" line 105: Malformed conditional ("${a b c:L:rango}Rest" != "Rest") +make: "varmod-range.mk" line 114: Unknown modifier "ranger" +make: "varmod-range.mk" line 114: Malformed conditional ("${a b c:L:ranger}Rest" != "Rest") make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varmod-range.mk b/unit-tests/varmod-range.mk index 86ef11a59eaf..920001096054 100644 --- a/unit-tests/varmod-range.mk +++ b/unit-tests/varmod-range.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-range.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-range.mk,v 1.10 2023/12/17 14:07:22 rillig Exp $ # # Tests for the :range variable modifier, which generates sequences # of integers from the given range. @@ -7,7 +7,7 @@ # modword.mk # The :range modifier generates a sequence of integers, one number per -# word of the variable expression's value. +# word of the expression's value. .if ${a b c:L:range} != "1 2 3" . error .endif @@ -19,20 +19,32 @@ .endif # The :range modifier takes the number of words from the value of the -# variable expression. If that expression is undefined, the range is +# 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 variable expression. -# -# XXX: As of 2020-09-27, the :range=... modifier does not turn the undefined -# expression into a defined one. This looks like an oversight. +# 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} != "") +.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. diff --git a/unit-tests/varmod-subst-regex.mk b/unit-tests/varmod-subst-regex.mk index 197691d73aad..c1ffc4580d3a 100644 --- a/unit-tests/varmod-subst-regex.mk +++ b/unit-tests/varmod-subst-regex.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-subst-regex.mk,v 1.7 2021/06/21 08:17:39 rillig Exp $ +# $NetBSD: varmod-subst-regex.mk,v 1.11 2023/12/18 11:13:51 rillig Exp $ # # Tests for the :C,from,to, variable modifier. @@ -10,7 +10,7 @@ all: mod-regex-limits all: mod-regex-errors all: unmatched-subexpression -# The variable expression expands to 4 words. Of these words, none matches +# 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" @@ -18,7 +18,7 @@ all: unmatched-subexpression .endif # Using the '1' modifier does not change anything. The '1' modifier just -# means to apply at most 1 replacement in the whole variable expression. +# 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 @@ -84,9 +84,54 @@ all: unmatched-subexpression . 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 -# variable expression. The unparsed part of the expression is then copied +# 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: @@ -101,7 +146,7 @@ mod-regex-limits: @echo $@:22-missing:${:U1 23 456:C,(.).,\2\2,:Q} @echo $@:22-ok:${:U1 23 456:C,(.)(.),\2\2,:Q} # The :C modifier only handles single-digit capturing groups, - # which is more than enough for daily use. + # 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: @@ -109,7 +154,7 @@ mod-regex-errors: # If the replacement pattern produces a parse error because of an # unknown modifier, the parse error is ignored in ParseModifierPart - # and the faulty variable expression expands to "". + # 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 diff --git a/unit-tests/varmod-subst.mk b/unit-tests/varmod-subst.mk index 0ddf2cfb2019..2903d36445f8 100644 --- a/unit-tests/varmod-subst.mk +++ b/unit-tests/varmod-subst.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-subst.mk,v 1.12 2023/06/16 07:20:45 rillig Exp $ +# $NetBSD: varmod-subst.mk,v 1.14 2023/12/18 11:13:51 rillig Exp $ # # Tests for the :S,from,to, variable modifier. @@ -9,7 +9,8 @@ all: mod-subst-dollar WORDS= sequences of letters -# The empty pattern never matches anything. +# 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 @@ -141,6 +142,47 @@ WORDS= sequences of letters .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}: diff --git a/unit-tests/varmod-sysv.mk b/unit-tests/varmod-sysv.mk index 78651ea869dc..0f92e1df7032 100644 --- a/unit-tests/varmod-sysv.mk +++ b/unit-tests/varmod-sysv.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-sysv.mk,v 1.15 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-sysv.mk,v 1.16 2023/11/19 21:47:52 rillig Exp $ # # Tests for the variable modifier ':from=to', which replaces the suffix # "from" with "to". It can also use '%' as a wildcard. @@ -49,7 +49,7 @@ . error .endif -# In the modifier ':from=to', both parts can contain variable expressions. +# In the modifier ':from=to', both parts can contain expressions. .if ${one two:L:${:Uone}=${:U1}} != "1 two" . error .endif @@ -69,7 +69,7 @@ .endif # The replacement string can contain spaces, thereby changing the number -# of words in the variable expression. +# of words in the expression. .if ${In:L:%=% ${:Uthe Sun}} != "In the Sun" . error .endif @@ -206,7 +206,7 @@ . error .endif -# This is not a SysV modifier since the nested variable expression expands +# 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 2020-12-05, this expression generates an "Unfinished modifier" # error, while the correct error message would be "Unknown modifier" since @@ -221,7 +221,7 @@ # "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 variable expressions. The two parts +# differently, properly handling nested expressions. The two parts # are now "fromto}..." and "replaced". .if "${:Ufromto\}...:from${:D=}to}...=replaced}" != "replaced" . error diff --git a/unit-tests/varmod-to-separator.mk b/unit-tests/varmod-to-separator.mk index f17dece447a9..57a7bd84ec44 100644 --- a/unit-tests/varmod-to-separator.mk +++ b/unit-tests/varmod-to-separator.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-to-separator.mk,v 1.12 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod-to-separator.mk,v 1.13 2023/11/19 21:47:52 rillig Exp $ # # Tests for the :ts variable modifier, which joins the words of the variable # using an arbitrary character as word separator. @@ -39,7 +39,7 @@ WORDS= one two three four five six # 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 variable +# as a separator, but as the closing delimiter of the whole # expression. .if ${WORDS:tu:ts} != "ONETWOTHREEFOURFIVESIX" . warning Colon as separator does not work. diff --git a/unit-tests/varmod-undefined.mk b/unit-tests/varmod-undefined.mk index 9fd41c7fdb60..fd56ffd35e30 100644 --- a/unit-tests/varmod-undefined.mk +++ b/unit-tests/varmod-undefined.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod-undefined.mk,v 1.8 2022/08/06 21:26:05 rillig Exp $ +# $NetBSD: varmod-undefined.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ # # Tests for the :U variable modifier, which returns the given string # if the variable is undefined. @@ -19,14 +19,14 @@ .endif # .endfor -# The variable expressions in the text of the :U modifier may be arbitrarily +# The expressions in the text of the :U modifier may be arbitrarily # nested. .if ${:U${:Unested}${${${:Udeeply}}}} != nested . error .endif -# The nested variable expressions may contain braces, and these braces don't +# The nested expressions may contain braces, and these braces don't # need to match pairwise. In the following example, the :S modifier uses '{' # as delimiter, which confuses both editors and humans because the opening # and closing braces don't match anymore. It's syntactically valid though. diff --git a/unit-tests/varmod.mk b/unit-tests/varmod.mk index 2ed2f6ee7cea..8e4c43bdfec7 100644 --- a/unit-tests/varmod.mk +++ b/unit-tests/varmod.mk @@ -1,4 +1,4 @@ -# $NetBSD: varmod.mk,v 1.8 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varmod.mk,v 1.9 2023/11/19 21:47:52 rillig Exp $ # # Tests for variable modifiers, such as :Q, :S,from,to or :Ufallback. # @@ -59,7 +59,7 @@ DOLLAR1= $$ DOLLAR2= ${:U\$} -# To get a single '$' sign in the value of a variable expression, it has to +# 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. @@ -107,7 +107,7 @@ DOLLAR2= ${:U\$} # 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 variable expression is +# 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: Missing delimiter ':' after modifier "P" diff --git a/unit-tests/varname-dot-shell.exp b/unit-tests/varname-dot-shell.exp index 800e8a375761..22c2afe218a9 100755 --- a/unit-tests/varname-dot-shell.exp +++ b/unit-tests/varname-dot-shell.exp @@ -1,26 +1,26 @@ Parsing line 10: ORIG_SHELL:= ${.SHELL} Global: ORIG_SHELL = # (empty) Var_Parse: ${.SHELL} (eval-keep-dollar-and-undefined) -Global: delete .SHELL (not found) +Global: ignoring delete '.SHELL' as it is not found Command: .SHELL = (details omitted) Global: ORIG_SHELL = (details omitted) Parsing line 12: .SHELL= overwritten -Global: .SHELL = overwritten +Global: ignoring '.SHELL = overwritten' due to a command line variable of the same name CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) Var_Parse: ${ORIG_SHELL} (eval-defined) Comparing "(details omitted)" != "(details omitted)" Parsing line 19: .MAKEFLAGS: .SHELL+=appended ParseDependency(.MAKEFLAGS: .SHELL+=appended) -Ignoring append to .SHELL since it is read-only +Command: ignoring '.SHELL += appended' as it is read-only CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) Var_Parse: ${ORIG_SHELL} (eval-defined) Comparing "(details omitted)" != "(details omitted)" Parsing line 27: .undef .SHELL -Global: delete .SHELL +Global: ignoring delete '.SHELL' as it is not found Parsing line 28: .SHELL= newly overwritten -Global: .SHELL = newly overwritten +Global: ignoring '.SHELL = newly overwritten' due to a command line variable of the same name CondParser_Eval: ${.SHELL} != ${ORIG_SHELL} Var_Parse: ${.SHELL} != ${ORIG_SHELL} (eval-defined) Var_Parse: ${ORIG_SHELL} (eval-defined) diff --git a/unit-tests/varname-dot-suffixes.exp b/unit-tests/varname-dot-suffixes.exp index 186b5f06c227..230ba36d56ed 100644 --- a/unit-tests/varname-dot-suffixes.exp +++ b/unit-tests/varname-dot-suffixes.exp @@ -1,20 +1,20 @@ -Global: delete .SUFFIXES (not found) +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: .SUFFIXES = set ignored (read-only) -Global: .SUFFIXES = append ignored (read-only) +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: .SUFFIXES = assign ignored (read-only) +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: .SUFFIXES = preserve ignored (read-only) +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 @@ -26,13 +26,13 @@ Evaluating modifier ${1 2:@...} on value "1 2" (eval-defined, defined) Modifier part: ".SUFFIXES" Modifier part: "${.SUFFIXES}" ModifyWords: split "1 2" into 2 words -Command: .SUFFIXES = 1 ignored (read-only) +Command: ignoring '.SUFFIXES = 1' as it is read-only Var_Parse: ${.SUFFIXES} (eval-defined) -ModifyWord_Loop: in "1", replace ".SUFFIXES" with "${.SUFFIXES}" to ".c .o .1 .err .tar.gz" -Command: .SUFFIXES = 2 ignored (read-only) +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) -ModifyWord_Loop: in "2", replace ".SUFFIXES" with "${.SUFFIXES}" to ".c .o .1 .err .tar.gz" -Command: delete .SUFFIXES (not found) +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) 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 diff --git a/unit-tests/varname-dot-suffixes.mk b/unit-tests/varname-dot-suffixes.mk index f9f995fcd845..27521f621cb0 100644 --- a/unit-tests/varname-dot-suffixes.mk +++ b/unit-tests/varname-dot-suffixes.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname-dot-suffixes.mk,v 1.3 2022/04/15 09:33:20 rillig Exp $ +# $NetBSD: varname-dot-suffixes.mk,v 1.5 2023/12/20 09:03:09 rillig Exp $ # # Tests for the special "variable" .SUFFIXES, which lists the suffixes that # have been registered for use in suffix transformation rules. Suffixes are @@ -51,7 +51,7 @@ # Deleting .SUFFIXES has no effect since there is no actual variable of that # name. .MAKEFLAGS: -dv -# expect: Global: delete .SUFFIXES (not found) +# expect: Global: ignoring delete '.SUFFIXES' as it is not found .undef .SUFFIXES .MAKEFLAGS: -d0 .if ${.SUFFIXES} != ".c .o .1 .err .tar.gz" @@ -61,13 +61,13 @@ # The list of suffixes can only be modified using dependency declarations, any # attempt at setting the variable named '.SUFFIXES' is rejected. .MAKEFLAGS: -dv -# expect: Global: .SUFFIXES = set ignored (read-only) +# expect: Global: ignoring '.SUFFIXES = set' as it is read-only .SUFFIXES= set -# expect: Global: .SUFFIXES = append ignored (read-only) +# expect: Global: ignoring '.SUFFIXES = append' as it is read-only .SUFFIXES+= append -# expect: Global: .SUFFIXES = assign ignored (read-only) +# expect: Global: ignoring '.SUFFIXES = assign' as it is read-only _:= ${.SUFFIXES::=assign} -# expect: Global: .SUFFIXES = preserve ignored (read-only) +# expect: Global: ignoring '.SUFFIXES = preserve' as it is read-only _:= ${preserve:L:_=.SUFFIXES} .MAKEFLAGS: -d0 @@ -94,10 +94,9 @@ _:= ${preserve:L:_=.SUFFIXES} # the name would be '.SUFFIXES.', furthermore the name of the iteration # variable is typically in singular form. .MAKEFLAGS: -dv -# expect: Command: .SUFFIXES = 1 ignored (read-only) -# expect: Command: .SUFFIXES = 2 ignored (read-only) -# XXX: Missing space after ':' -# expect: Command: delete .SUFFIXES (not found) +# expect: Command: ignoring '.SUFFIXES = 1' as it is read-only +# expect: Command: ignoring '.SUFFIXES = 2' as it is read-only +# expect: Command: ignoring delete '.SUFFIXES' as it is not found .if ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" . error .endif diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp index a861ba378cef..2165784933e4 100644 --- a/unit-tests/varname-empty.exp +++ b/unit-tests/varname-empty.exp @@ -1,22 +1,22 @@ -Var_SetExpand: variable name "${:U}" expands to empty string, with value "cmdline-u" - ignored -Var_SetExpand: variable name "" expands to empty string, with value "cmdline-plain" - ignored +Command: ignoring ' = cmdline-u' as the variable name '${:U}' expands to empty +Command: ignoring ' = cmdline-plain' as the variable name '' expands to empty Global: .CURDIR = Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE} (eval) Global: .TARGETS = # (empty) Internal: MAKEFILE = varname-empty.mk Global: .MAKE.MAKEFILES = varname-empty.mk Global: .PARSEFILE = varname-empty.mk -Global: delete .INCLUDEDFROMDIR (not found) -Global: delete .INCLUDEDFROMFILE (not found) -Var_SetExpand: variable name "" expands to empty string, with value "default" - ignored -Var_SetExpand: variable name "" expands to empty string, with value "assigned" - ignored -SetVar: variable name is empty - ignored -Var_SetExpand: variable name "" expands to empty string, with value "" - ignored -Var_SetExpand: variable name "" expands to empty string, with value "subst" - ignored +Global: ignoring delete '.INCLUDEDFROMDIR' as it is not found +Global: ignoring delete '.INCLUDEDFROMFILE' as it is not found +Global: ignoring ' = default' as the variable name '' expands to empty +Global: ignoring ' = assigned' as the variable name '' expands to empty +Global: ignoring ' = appended' as the variable name is empty +Global: ignoring ' = ' as the variable name '' expands to empty +Global: ignoring ' = subst' as the variable name '' expands to empty Capturing the output of command "echo 'shell-output'" -Var_SetExpand: variable name "" expands to empty string, with value "shell-output" - ignored -Var_SetExpand: variable name "${:U}" expands to empty string, with value "assigned indirectly" - ignored -Var_AppendExpand: variable name "${:U}" expands to empty string, with value "appended indirectly" - ignored +Global: ignoring ' = shell-output' as the variable name '' expands to empty +Global: ignoring ' = assigned indirectly' as the variable name '${:U}' expands to empty +Global: ignoring ' += appended indirectly' as the variable name '${:U}' expands to empty Global: .MAKEFLAGS = -r -d v -d Global: .MAKEFLAGS = -r -d v -d 0 out: fallback diff --git a/unit-tests/varname-empty.mk b/unit-tests/varname-empty.mk index f077d2ec07b4..e018a5d44894 100755 --- a/unit-tests/varname-empty.mk +++ b/unit-tests/varname-empty.mk @@ -1,9 +1,9 @@ -# $NetBSD: varname-empty.mk,v 1.9 2021/04/04 10:13:09 rillig Exp $ +# $NetBSD: varname-empty.mk,v 1.10 2023/11/19 21:47:52 rillig Exp $ # # Tests for the special variable with the empty name. # # There is no variable named "" at all, and this fact is used a lot in -# variable expressions of the form ${:Ufallback}. These expressions are +# expressions of the form ${:Ufallback}. These expressions are # based on the variable named "" and use the :U modifier to assign a # fallback value to the expression (but not to the variable). # diff --git a/unit-tests/varname-make_print_var_on_error-jobs.mk b/unit-tests/varname-make_print_var_on_error-jobs.mk index 10a9647fbd1e..b422f25ff12e 100644 --- a/unit-tests/varname-make_print_var_on_error-jobs.mk +++ b/unit-tests/varname-make_print_var_on_error-jobs.mk @@ -1,9 +1,9 @@ -# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.3 2021/02/04 21:33:14 rillig Exp $ +# $NetBSD: varname-make_print_var_on_error-jobs.mk,v 1.4 2023/11/19 22:32:44 rillig Exp $ # # Tests for the special MAKE_PRINT_VAR_ON_ERROR variable, which prints the # values of selected variables on error. # -# The variable .ERROR_CMD contains all commands of the target, with variable +# The variable .ERROR_CMD contains all commands of the target, with # expressions expanded, just as they were printed to the shell command file. # # The commands in .ERROR_CMD are space-separated. Since each command usually diff --git a/unit-tests/varname.mk b/unit-tests/varname.mk index 72db79078741..cad0a10fe563 100644 --- a/unit-tests/varname.mk +++ b/unit-tests/varname.mk @@ -1,4 +1,4 @@ -# $NetBSD: varname.mk,v 1.13 2023/08/19 11:09:02 rillig Exp $ +# $NetBSD: varname.mk,v 1.14 2023/11/19 21:47:52 rillig Exp $ # # Tests for special variables, such as .MAKE or .PARSEDIR. # And for variable names in general. @@ -12,7 +12,7 @@ VAR{{{}}}= 3 braces . error .endif -# In variable expressions, the parser works differently. It doesn't treat +# 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((( diff --git a/unit-tests/varparse-dynamic.mk b/unit-tests/varparse-dynamic.mk index 88e4e6a4917e..40f43b049b13 100644 --- a/unit-tests/varparse-dynamic.mk +++ b/unit-tests/varparse-dynamic.mk @@ -1,4 +1,4 @@ -# $NetBSD: varparse-dynamic.mk,v 1.7 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varparse-dynamic.mk,v 1.8 2023/11/19 22:32:44 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. @@ -24,7 +24,7 @@ .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 variable +# 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 diff --git a/unit-tests/varparse-errors.exp b/unit-tests/varparse-errors.exp index 41db6d8c6f43..4193bea181c9 100644 --- a/unit-tests/varparse-errors.exp +++ b/unit-tests/varparse-errors.exp @@ -6,20 +6,20 @@ make: Bad modifier ":OX" for variable "" make: Bad modifier ":OX" for variable "" make: "varparse-errors.mk" line 71: Undefined variable "${:U:OX" make: Bad modifier ":OX" for variable "" -make: Unclosed variable expression, expecting '}' for modifier "Q" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "sh" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "tA" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "tsX" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "ts" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "ts\040" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "u" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "H" of variable "" with value "." -make: Unclosed variable expression, expecting '}' for modifier "[1]" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "hash" of variable "" with value "b2af338b" -make: Unclosed variable expression, expecting '}' for modifier "range" of variable "" with value "1" -make: Unclosed variable expression, expecting '}' for modifier "_" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "gmtime" of variable "" with value "" -make: Unclosed variable expression, expecting '}' for modifier "localtime" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "Q" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "sh" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "tA" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "tsX" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "ts" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "ts\040" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "u" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "H" of variable "" with value "." +make: Unclosed expression, expecting '}' for modifier "[1]" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "hash" of variable "" with value "b2af338b" +make: Unclosed expression, expecting '}' for modifier "range" of variable "" with value "1" +make: Unclosed expression, expecting '}' for modifier "_" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "gmtime" of variable "" with value "" +make: Unclosed expression, expecting '}' for modifier "localtime" of variable "" with value "" make: Fatal errors encountered -- cannot continue make: stopped in unit-tests exit status 1 diff --git a/unit-tests/varparse-errors.mk b/unit-tests/varparse-errors.mk index 3ae6d2db1de9..fc94d9dd3d18 100644 --- a/unit-tests/varparse-errors.mk +++ b/unit-tests/varparse-errors.mk @@ -1,6 +1,6 @@ -# $NetBSD: varparse-errors.mk,v 1.9 2023/06/01 20:56:35 rillig Exp $ +# $NetBSD: varparse-errors.mk,v 1.11 2023/11/19 22:32:44 rillig Exp $ -# Tests for parsing and evaluating all kinds of variable expressions. +# 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. @@ -17,13 +17,13 @@ INDIRECT= An ${:Uindirect} value. REF_UNDEF= A reference to an ${UNDEF}undefined variable. -ERR_UNCLOSED= An ${UNCLOSED variable expression. +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, a variable expression that is not enclosed in quotes is +# In a conditional, an expression that is not enclosed in quotes is # expanded using the mode VARE_UNDEFERR. # The variable itself must be defined. # It may refer to undefined variables though. diff --git a/unit-tests/varparse-mod.mk b/unit-tests/varparse-mod.mk index 0b4cbf6ca40a..c5fa6f5ece71 100644 --- a/unit-tests/varparse-mod.mk +++ b/unit-tests/varparse-mod.mk @@ -1,6 +1,6 @@ -# $NetBSD: varparse-mod.mk,v 1.1 2020/10/02 20:34:59 rillig Exp $ +# $NetBSD: varparse-mod.mk,v 1.2 2023/11/19 21:47:52 rillig Exp $ -# Tests for parsing variable expressions with modifiers. +# Tests for parsing expressions with modifiers. # As of 2020-10-02, the below condition does not result in a parse error. # The condition contains two separate mistakes. The first mistake is that @@ -8,7 +8,7 @@ # there is a stray '}' at the end of the whole condition. # # As of 2020-10-02, the actual parse result of this condition is a single -# variable expression with 2 modifiers. The first modifier is +# expression with 2 modifiers. The first modifier is # ":!echo "\$VAR"} !". Afterwards, the parser optionally skips a ':' (at the # bottom of ApplyModifiers) and continues with the next modifier, in this case # "= "value"", which is interpreted as a SysV substitution modifier with an diff --git a/unit-tests/varparse-undef-partial.mk b/unit-tests/varparse-undef-partial.mk index 27f44d79b31a..9a5704265086 100644 --- a/unit-tests/varparse-undef-partial.mk +++ b/unit-tests/varparse-undef-partial.mk @@ -1,7 +1,7 @@ -# $NetBSD: varparse-undef-partial.mk,v 1.3 2020/11/04 05:10:01 rillig Exp $ +# $NetBSD: varparse-undef-partial.mk,v 1.5 2024/01/07 11:39:04 rillig Exp $ # When an undefined variable is expanded in a ':=' assignment, only the -# initial '$' of the variable expression is skipped by the parser, while +# initial '$' of the expression is skipped by the parser, while # the remaining expression is evaluated. In edge cases this can lead to # a completely different interpretation of the partially expanded text. @@ -11,11 +11,10 @@ PARAM= :Q # The expression ${VAR.${PARAM}} refers to the variable named "VAR.:Q", # with the ":Q" being part of the name. This variable is not defined, -# therefore the initial '$' of that whole expression is skipped by the -# parser (see Var_Subst, the Buf_AddByte in the else branch) and the rest -# of the expression is expanded as usual. +# therefore the initial '$' of that whole expression is skipped by the parser +# (see VarSubstExpr) and the rest of the expression is expanded as usual. # -# The resulting variable expression is ${VAR.:Q}, which means that the +# The resulting expression is ${VAR.:Q}, which means that the # interpretation of the ":Q" has changed from being part of the variable # name to being a variable modifier. This is a classical code injection. EVAL:= ${LIST} @@ -37,7 +36,7 @@ ${:UVAR.\:Q}= var-dot with parameter :Q # In contrast to the previous line, evaluating the original LIST again now # produces a different result since the variable named "VAR.:Q" is now # defined. It is expanded as usual, interpreting the ":Q" as part of the -# variable name, as would be expected from reading the variable expression. +# variable name, as would be expected from reading the expression. EVAL:= ${LIST} .if ${EVAL} != "defined var-dot with parameter :Q end" . error ${EVAL} diff --git a/util.c b/util.c index eeda3d8f8a0c..f660c21a228a 100644 --- a/util.c +++ b/util.c @@ -3,7 +3,7 @@ /* * Missing stuff from OS's * - * $Id: util.c,v 1.50 2021/12/21 18:47:24 sjg Exp $ + * $Id: util.c,v 1.52 2024/01/04 00:27:30 sjg Exp $ */ #include @@ -431,18 +431,28 @@ snprintf(char *s, size_t n, const char *fmt, ...) } #endif -#if !defined(HAVE_STRFTIME) +#if !defined(HAVE_STRFTIME) || defined(FORCE_BMAKE_STRFTIME) +/* we only implement enough to pass our unit-tests */ size_t strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) { - static char months[][4] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + static const char *months[] = { + "January", "February", "March", + "April", "May", "June", + "July", "August", "September", + "October", "November", "December" }; - + static const char *days[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }; + int i; size_t s; char *b = buf; + char *cp; + if (fmt == NULL || *fmt == '\0') + fmt = "%c"; while (*fmt) { if (len == 0) return buf - b; @@ -451,6 +461,7 @@ strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) len--; continue; } + fmt++; switch (*fmt++) { case '%': *buf++ = '%'; @@ -461,26 +472,87 @@ strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) *buf = '%'; s = 1; break; + case 'A': + s = snprintf(buf, len, "%s", days[tm->tm_wday]); + break; + case 'a': + s = snprintf(buf, len, "%.3s", days[tm->tm_wday]); + break; + case 'B': + if (tm->tm_mon >= 12) + return buf - b; + s = snprintf(buf, len, "%s", months[tm->tm_mon]); + break; + case 'b': + if (tm->tm_mon >= 12) + return buf - b; + s = snprintf(buf, len, "%.3s", months[tm->tm_mon]); + break; + case 'c': + s = strftime(buf, len, "%a %b %e %H:%M:%S %Y", tm); + break; + case 'd': + s = snprintf(buf, len, "%02d", tm->tm_mday); + break; + case 'e': + s = snprintf(buf, len, "%2d", tm->tm_mday); + break; + case 'F': + s = strftime(buf, len, "%y-%m-%d", tm); + break; + case 'H': + s = snprintf(buf, len, "%02d", tm->tm_hour); + break; + case 'I': + if ((i = tm->tm_hour) == 0) + i = 24; + s = snprintf(buf, len, "%02d", (i > 12) ? (i - 12) : i); + break; + case 'j': + s = snprintf(buf, len, "%03d", tm->tm_yday + 1); + break; case 'k': s = snprintf(buf, len, "%d", tm->tm_hour); break; case 'M': s = snprintf(buf, len, "%02d", tm->tm_min); break; + case 'm': + s = snprintf(buf, len, "%02d", 1 + tm->tm_mon); + break; case 'S': s = snprintf(buf, len, "%02d", tm->tm_sec); break; - case 'b': - if (tm->tm_mon >= 12) - return buf - b; - s = snprintf(buf, len, "%s", months[tm->tm_mon]); + case 's': + s = snprintf(buf, len, "%ld", (long)time(NULL)); break; - case 'd': - s = snprintf(buf, len, "%02d", tm->tm_mday); + case 'T': + s = strftime(buf, len, "%H:%M:%S", tm); + break; + case 'w': + s = snprintf(buf, len, "%02d", tm->tm_wday); break; case 'Y': s = snprintf(buf, len, "%d", 1900 + tm->tm_year); break; + case 'y': + s = snprintf(buf, len, "%02d", tm->tm_year % 100); + break; + case 'Z': + if ((cp = getenv("TZ")) != NULL) { + char tz[20]; + + i = snprintf(tz, sizeof(tz), "%s", cp); + if (i > 5) { + cp = &tz[i - 3]; + tz[3] = '\0'; + } else + cp = tz; + s = snprintf(buf, len, "%s", + tm->tm_isdst ? cp : tz); + } else + s = 0; + break; default: s = snprintf(buf, len, "Unsupported format %c", fmt[-1]); diff --git a/var.c b/var.c index 54bea0179b72..38f0d9b6913d 100644 --- a/var.c +++ b/var.c @@ -1,4 +1,4 @@ -/* $NetBSD: var.c,v 1.1064 2023/08/19 19:59:17 rillig Exp $ */ +/* $NetBSD: var.c,v 1.1094 2024/01/07 11:39:04 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -98,9 +98,9 @@ * Var_Value Return the unexpanded value of a variable, or NULL if * the variable is undefined. * - * Var_Subst Substitute all variable expressions in a string. + * Var_Subst Substitute all expressions in a string. * - * Var_Parse Parse a variable expression such as ${VAR:Mpattern}. + * Var_Parse Parse an expression such as ${VAR:Mpattern}. * * Var_Delete * Delete a variable. @@ -147,7 +147,7 @@ #include "metachar.h" /* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: var.c,v 1.1064 2023/08/19 19:59:17 rillig Exp $"); +MAKE_RCSID("$NetBSD: var.c,v 1.1094 2024/01/07 11:39:04 rillig Exp $"); /* * Variables are defined using one of the VAR=value assignments. Their @@ -166,7 +166,7 @@ MAKE_RCSID("$NetBSD: var.c,v 1.1064 2023/08/19 19:59:17 rillig Exp $"); * 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 variable expressions such + * Undefined variables occur during evaluation of expressions such * as ${UNDEF:Ufallback} in Var_Parse and ApplyModifiers. */ typedef struct Var { @@ -221,8 +221,8 @@ typedef struct Var { /* * 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 exported again, in the hope - * that the referenced variable can then be resolved. + * process is started, it needs to be actually exported, resolving + * the referenced variable just in time. */ bool reexport:1; } Var; @@ -230,9 +230,6 @@ typedef struct Var { /* * Exporting variables is expensive and may leak memory, so skip it if we * can. - * - * To avoid this, it might be worth encapsulating the environment variables - * in a separate data structure called EnvVars. */ typedef enum VarExportedMode { VAR_EXPORTED_NONE, @@ -272,10 +269,7 @@ typedef struct SepBuf { } SepBuf; -/* - * This lets us tell if we have replaced the original environ - * (which we cannot free). - */ +/* Whether we have replaced the original environ (which we cannot free). */ char **savedEnv = NULL; /* @@ -319,9 +313,7 @@ static bool save_dollars = false; * override variables from SCOPE_GLOBAL. * * There is no scope for environment variables, these are generated on-the-fly - * whenever they are referenced. If there were such a scope, each change to - * environment variables would have to be reflected in that scope, which may - * be simpler or more complex than the current implementation. + * 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 @@ -386,11 +378,11 @@ CanonicalVarname(Substring name) 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(); - /* GNU make has an additional alias $^ == ${.ALLSRC}. */ - return name; } @@ -514,15 +506,14 @@ Var_Delete(GNode *scope, const char *varname) Var *v; if (he == NULL) { - DEBUG2(VAR, "%s: delete %s (not found)\n", + DEBUG2(VAR, "%s: ignoring delete '%s' as it is not found\n", scope->name, varname); return; } - DEBUG2(VAR, "%s: delete %s\n", scope->name, varname); v = he->value; if (v->readOnly) { - DEBUG2(VAR, "%s: delete %s (readOnly)\n", + DEBUG2(VAR, "%s: ignoring delete '%s' as it is read-only\n", scope->name, varname); return; } @@ -533,6 +524,7 @@ Var_Delete(GNode *scope, const char *varname) 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) @@ -623,13 +615,8 @@ ExportVarEnv(Var *v) return true; } - if (v->inUse) { - /* - * We recursed while exporting in a child. - * This isn't going to end well, just skip it. - */ - return false; - } + if (v->inUse) + return false; /* see EMPTY_SHELL in directive-export.mk */ /* XXX: name is injected without escaping it */ expr = str_concat3("${", name, "}"); @@ -677,7 +664,7 @@ ExportVarLiteral(Var *v) /* * Mark a single variable to be exported later for subprocesses. * - * Internal variables (those starting with '.') are not exported. + * Internal variables are not exported. */ static bool ExportVar(const char *name, VarExportMode mode) @@ -714,9 +701,9 @@ Var_ReexportVars(void) * We allow the makefiles to update MAKELEVEL and ensure * children see a correctly incremented value. */ - char tmp[21]; - snprintf(tmp, sizeof tmp, "%d", makelevel + 1); - setenv(MAKE_LEVEL_ENV, tmp, 1); + 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; @@ -802,10 +789,10 @@ Var_ExportVars(const char *varnames) static void ClearEnv(void) { - const char *cp; + const char *level; char **newenv; - cp = getenv(MAKE_LEVEL_ENV); /* we should preserve this */ + level = getenv(MAKE_LEVEL_ENV); /* we should preserve this */ if (environ == savedEnv) { /* we have been here before! */ newenv = bmake_realloc(environ, 2 * sizeof(char *)); @@ -821,8 +808,8 @@ ClearEnv(void) environ = savedEnv = newenv; newenv[0] = NULL; newenv[1] = NULL; - if (cp != NULL && *cp != '\0') - setenv(MAKE_LEVEL_ENV, cp, 1); + if (level != NULL && *level != '\0') + setenv(MAKE_LEVEL_ENV, level, 1); } static void @@ -878,12 +865,12 @@ UnexportVar(Substring varname, UnexportWhat what) 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 *cp = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES); + char *expr = str_concat3( + "${.MAKE.EXPORTED:N", v->name.str, "}"); + char *filtered = Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES); /* TODO: handle errors */ - Global_Set(".MAKE.EXPORTED", cp); - free(cp); + Global_Set(".MAKE.EXPORTED", filtered); + free(filtered); free(expr); } } @@ -906,11 +893,7 @@ UnexportVars(FStr *varnames, UnexportWhat what) Global_Delete(".MAKE.EXPORTED"); } -/* - * This is called when .unexport[-env] is seen. - * - * str must have the form "unexport[-env] varname...". - */ +/* Handle the .unexport and .unexport-env directives. */ void Var_UnExport(bool isEnv, const char *arg) { @@ -922,32 +905,6 @@ Var_UnExport(bool isEnv, const char *arg) FStr_Done(&varnames); } -/* - * When there is a variable of the same name in the command line scope, the - * global variable would not be visible anywhere. Therefore there is no - * point in setting it at all. - * - * See 'scope == SCOPE_CMDLINE' in Var_SetWithFlags. - */ -static bool -ExistsInCmdline(const char *name, const char *val) -{ - Var *v; - - v = VarFind(name, SCOPE_CMDLINE, false); - if (v == NULL) - return false; - - if (v->fromCmd) { - DEBUG3(VAR, "%s: %s = %s ignored!\n", - SCOPE_GLOBAL->name, name, val); - return true; - } - - VarFreeShortLived(v); - return false; -} - /* Set the variable to the value; the name is not expanded. */ void Var_SetWithFlags(GNode *scope, const char *name, const char *val, @@ -957,12 +914,24 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, assert(val != NULL); if (name[0] == '\0') { - DEBUG0(VAR, "SetVar: variable name is empty - ignored\n"); + DEBUG3(VAR, + "%s: ignoring '%s = %s' as the variable name is empty\n", + scope->name, name, val); return; } - if (scope == SCOPE_GLOBAL && ExistsInCmdline(name, val)) + 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 @@ -982,15 +951,17 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, Var_Delete(SCOPE_GLOBAL, name); } if (strcmp(name, ".SUFFIXES") == 0) { - /* special: treat as readOnly */ - DEBUG3(VAR, "%s: %s = %s ignored (read-only)\n", + /* 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->readOnly && !(flags & VAR_SET_READONLY)) { - DEBUG3(VAR, "%s: %s = %s ignored (read-only)\n", + DEBUG3(VAR, + "%s: ignoring '%s = %s' as it is read-only\n", scope->name, name, val); return; } @@ -1003,29 +974,32 @@ Var_SetWithFlags(GNode *scope, const char *name, const char *val, ExportVar(name, VEM_PLAIN); } - /* - * Any variables given on the command line are automatically exported - * to the environment (as per POSIX standard), except for internals. - */ - if (scope == SCOPE_CMDLINE && !(flags & VAR_SET_NO_EXPORT) && - name[0] != '.') { + if (scope == SCOPE_CMDLINE) { v->fromCmd = true; /* - * 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) - setenv(name, val, 1); - /* XXX: What about .MAKE.EXPORTED? */ - /* - * XXX: Why not just mark the variable for needing export, as - * in ExportVarPlain? + * 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) && name[0] != '.') { - Global_Append(".MAKEOVERRIDES", name); + /* + * 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) + setenv(name, val, 1); + /* XXX: What about .MAKE.EXPORTED? */ + /* + * XXX: Why not just mark the variable for + * needing export, as in ExportVarPlain? + */ + Global_Append(".MAKEOVERRIDES", name); + } } if (name[0] == '.' && strcmp(name, MAKE_SAVE_DOLLARS) == 0) @@ -1042,20 +1016,12 @@ Var_Set(GNode *scope, const char *name, const char *val) } /* - * Set the variable name to the value val in the given scope. - * - * If the variable doesn't yet exist, it is created. - * Otherwise the new value overwrites and replaces the old value. - * - * Input: - * scope scope in which to set it - * name name of the variable to set, is expanded once - * val value to give to the variable + * 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) { - const char *unexpanded_name = name; FStr varname = FStr_InitRefer(name); assert(val != NULL); @@ -1063,10 +1029,10 @@ Var_SetExpand(GNode *scope, const char *name, const char *val) Var_Expand(&varname, scope, VARE_WANTRES); if (varname.str[0] == '\0') { - DEBUG2(VAR, - "Var_SetExpand: variable name \"%s\" expands " - "to empty string, with value \"%s\" - ignored\n", - unexpanded_name, val); + 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); @@ -1107,8 +1073,8 @@ Var_Append(GNode *scope, const char *name, const char *val) if (v == NULL) { Var_SetWithFlags(scope, name, val, VAR_SET_NONE); } else if (v->readOnly) { - DEBUG1(VAR, "Ignoring append to %s since it is read-only\n", - name); + 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); @@ -1129,24 +1095,12 @@ Var_Append(GNode *scope, const char *name, const char *val) } /* - * The variable of the given name has the given value appended to it in the - * given scope. + * 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. * - * If the variable doesn't exist, it is created. Otherwise the strings are - * concatenated, with a space in between. - * - * Input: - * scope scope in which this should occur - * name name of the variable to modify, is expanded once - * val string to append to it - * - * Notes: - * Only if the variable is being sought in the global scope is the - * environment searched. - * XXX: Knows its calling circumstances in that if called with scope - * an actual target, it will only search that scope since only - * a local variable could be being appended to. This is actually - * a big win and must be tolerated. + * 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) @@ -1157,10 +1111,10 @@ Var_AppendExpand(GNode *scope, const char *name, const char *val) Var_Expand(&xname, scope, VARE_WANTRES); if (xname.str != name && xname.str[0] == '\0') - DEBUG2(VAR, - "Var_AppendExpand: variable name \"%s\" expands " - "to empty string, with value \"%s\" - ignored\n", - name, val); + 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); @@ -1206,11 +1160,11 @@ Var_ExistsExpand(GNode *scope, const char *name) /* * Return the unexpanded value of the given variable in the given scope, - * or the usual scopes. + * falling back to the command, global and environment scopes, in this order, + * but see the -e option. * * Input: - * scope scope in which to search for it - * name name to find, is not expanded any further + * name the name to find, is not expanded any further * * Results: * The value if the variable exists, NULL if it doesn't. @@ -1235,9 +1189,7 @@ Var_Value(GNode *scope, const char *name) return FStr_InitOwn(value); } -/* - * set readOnly attribute of specified var if it exists - */ +/* Set or clear the read-only attribute of the variable if it exists. */ void Var_ReadOnly(const char *name, bool bf) { @@ -1352,7 +1304,7 @@ SepBuf_DoneData(SepBuf *buf) /* - * This callback for ModifyWords gets a single word from a variable expression + * 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. * @@ -1366,10 +1318,6 @@ SepBuf_DoneData(SepBuf *buf) typedef void (*ModifyWordProc)(Substring word, SepBuf *buf, void *data); -/* - * Callback for ModifyWords to implement the :H modifier. - * Add the dirname of the given word to the buffer. - */ /*ARGSUSED*/ static void ModifyWord_Head(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) @@ -1377,10 +1325,6 @@ ModifyWord_Head(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) SepBuf_AddSubstring(buf, Substring_Dirname(word)); } -/* - * Callback for ModifyWords to implement the :T modifier. - * Add the basename of the given word to the buffer. - */ /*ARGSUSED*/ static void ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) @@ -1388,30 +1332,22 @@ ModifyWord_Tail(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) SepBuf_AddSubstring(buf, Substring_Basename(word)); } -/* - * Callback for ModifyWords to implement the :E modifier. - * Add the filename suffix of the given word to the buffer, if it exists. - */ /*ARGSUSED*/ static void ModifyWord_Suffix(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { - const char *lastDot = Substring_LastIndex(word, '.'); + const char *lastDot = Substring_FindLast(word, '.'); if (lastDot != NULL) SepBuf_AddRange(buf, lastDot + 1, word.end); } -/* - * Callback for ModifyWords to implement the :R modifier. - * Add the filename without extension of the given word to the buffer. - */ /*ARGSUSED*/ static void ModifyWord_Root(Substring word, SepBuf *buf, void *dummy MAKE_ATTR_UNUSED) { const char *lastDot, *end; - lastDot = Substring_LastIndex(word, '.'); + lastDot = Substring_FindLast(word, '.'); end = lastDot != NULL ? lastDot : word.end; SepBuf_AddRange(buf, word.start, end); } @@ -1425,7 +1361,6 @@ struct ModifyWord_SysVSubstArgs { const char *rhs; }; -/* Callback for ModifyWords to implement the :%.from=%.to modifier. */ static void ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) { @@ -1459,14 +1394,6 @@ ModifyWord_SysVSubst(Substring word, SepBuf *buf, void *data) } #endif - -struct ModifyWord_SubstArgs { - Substring lhs; - Substring rhs; - PatternFlags pflags; - bool matched; -}; - static const char * Substring_Find(Substring haystack, Substring needle) { @@ -1480,19 +1407,21 @@ Substring_Find(Substring haystack, Substring needle) return NULL; } -/* - * Callback for ModifyWords to implement the :S,from,to, modifier. - * Perform a string substitution on the given word. - */ +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 *wordEnd, *match; + const char *match; wordLen = Substring_Length(word); - wordEnd = word.end; if (args->pflags.subOnce && args->matched) goto nosub; @@ -1507,7 +1436,7 @@ ModifyWord_Subst(Substring word, SepBuf *buf, void *data) /* :S,^prefix,replacement, or :S,^whole$,replacement, */ SepBuf_AddSubstring(buf, args->rhs); - SepBuf_AddRange(buf, word.start + lhsLen, wordEnd); + SepBuf_AddRange(buf, word.start + lhsLen, word.end); args->matched = true; return; } @@ -1515,11 +1444,11 @@ ModifyWord_Subst(Substring word, SepBuf *buf, void *data) if (args->pflags.anchorEnd) { if (wordLen < lhsLen) goto nosub; - if (memcmp(wordEnd - lhsLen, args->lhs.start, lhsLen) != 0) + if (memcmp(word.end - lhsLen, args->lhs.start, lhsLen) != 0) goto nosub; /* :S,suffix$,replacement, */ - SepBuf_AddRange(buf, word.start, wordEnd - lhsLen); + SepBuf_AddRange(buf, word.start, word.end - lhsLen); SepBuf_AddSubstring(buf, args->rhs); args->matched = true; return; @@ -1606,10 +1535,6 @@ struct ModifyWord_SubstRegexArgs { bool matched; }; -/* - * Callback for ModifyWords to implement the :C/from/to/ modifier. - * Perform a regex substitution on the given word. - */ static void ModifyWord_SubstRegex(Substring word, SepBuf *buf, void *data) { @@ -1643,7 +1568,7 @@ ok: 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) { + if (m[0].rm_so == 0 && m[0].rm_eo == 0 && *wp != '\0') { SepBuf_AddBytes(buf, wp, 1); wp++; } @@ -1663,7 +1588,6 @@ struct ModifyWord_LoopArgs { VarEvalMode emode; }; -/* Callback for ModifyWords to implement the :@var@...@ modifier of ODE make. */ static void ModifyWord_Loop(Substring word, SepBuf *buf, void *data) { @@ -1680,10 +1604,8 @@ ModifyWord_Loop(Substring word, SepBuf *buf, void *data) s = Var_Subst(args->body, args->scope, args->emode); /* TODO: handle errors */ - assert(word.end[0] == '\0'); /* assume null-terminated word */ - DEBUG4(VAR, "ModifyWord_Loop: " - "in \"%s\", replace \"%s\" with \"%s\" to \"%s\"\n", - word.start, args->var, args->body, s); + DEBUG2(VAR, "ModifyWord_Loop: expand \"%s\" to \"%s\"\n", + args->body, s); if (s[0] == '\n' || Buf_EndsWith(&buf->buf, '\n')) buf->needSep = false; @@ -1717,18 +1639,13 @@ VarSelectWords(const char *str, int first, int last, words = Substring_Words(str, false); } - /* - * Now sanitize the given range. If first or last are negative, - * convert them to the positive equivalents (-1 gets converted to len, - * -2 gets converted to (len - 1), etc.). - */ + /* 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; - /* We avoid scanning more of the list than we need to. */ if (first > last) { start = (first > len ? len : first) - 1; end = last < 1 ? 0 : last - 1; @@ -1750,10 +1667,6 @@ VarSelectWords(const char *str, int first, int last, } -/* - * Callback for ModifyWords to implement the :tA modifier. - * Replace each word with the result of realpath() if successful. - */ /*ARGSUSED*/ static void ModifyWord_Realpath(Substring word, SepBuf *buf, void *data MAKE_ATTR_UNUSED) @@ -1929,59 +1842,52 @@ FormatTime(const char *fmt, time_t t, bool gmt) * 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 (as in the :S, :C or :@ - * modifiers), return AMR_CLEANUP. + * 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 ${VAR:from=to} as fallback. This should only be + * 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, either issue an error message using Error or Parse_Error - * and then return AMR_CLEANUP, or return AMR_BAD for the default error - * message. Both of these return values will stop processing the variable - * expression. (XXX: As of 2020-08-23, evaluation of the whole string - * continues nevertheless after skipping a few bytes, which essentially is - * undefined behavior. Not in the sense of C, but still the resulting string - * is garbage.) + * 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 variable expressions in the modifier text often already happen + * 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 variable - * expression from ch->expr->value, or the variable name from ch->var->name - * and stores the result back in expr->value via Expr_SetValueOwn or + * 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 (as of 2020-08-23), an error message is printed using - * Error. This function has no side-effects, it really just prints the error - * message. Processing the expression continues as if everything were ok. - * XXX: This should be fixed by adding proper error handling to Var_Subst, - * Var_Parse, ApplyModifiers and ModifyWords. - * - * Housekeeping + * If evaluating fails, the fallback error message "Bad modifier" is printed + * using Error. This function has no side effects, it really just prints the + * error message, continuing as if nothing had happened. TODO: This should be + * fixed by adding proper error handling to Var_Subst, Var_Parse, + * ApplyModifiers and ModifyWords. * * Some modifiers such as :D and :U turn undefined expressions into defined - * expressions (see Expr_Define). - * - * Some modifiers need to free some memory. + * expressions using Expr_Define. */ typedef enum ExprDefined { - /* The variable expression is based on a regular, defined variable. */ + /* The expression is based on a regular, defined variable. */ DEF_REGULAR, - /* The variable expression is based on an undefined variable. */ + /* The expression is based on an undefined variable. */ DEF_UNDEF, /* - * The variable expression started as an undefined expression, but one + * 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. */ @@ -2030,9 +1936,6 @@ typedef struct Expr { * * After such a chain ends, its properties no longer have any effect. * - * It may or may not have been intended that 'defined' has scope Expr while - * 'sep' and 'oneBigWord' have smaller scope. - * * See varmod-indirect.mk. */ typedef struct ModChain { @@ -2041,10 +1944,10 @@ typedef struct ModChain { char const_member startc; /* '\0' or '}' or ')' */ char const_member endc; - /* Word separator in expansions (see the :ts modifier). */ + /* Separator when joining words (see the :ts modifier). */ char sep; /* - * True if some modifiers that otherwise split the variable value + * 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. */ @@ -2105,7 +2008,7 @@ ModChain_ShouldEval(const ModChain *ch) typedef enum ApplyModifierResult { /* Continue parsing */ AMR_OK, - /* Not a match, try other modifiers as well. */ + /* Not a match, try the ':from=to' modifier as well. */ AMR_UNKNOWN, /* Error out with "Bad modifier" message. */ AMR_BAD, @@ -2151,14 +2054,13 @@ ParseModifierPartExpr(const char **pp, LazyBuf *part, const ModChain *ch, * 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, it is not parsed as a - * short-name variable expression. + * 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; - const char *start = *pp; if (p[1] == '(' || p[1] == '{') { char startc = p[1]; @@ -2173,10 +2075,10 @@ ParseModifierPartBalanced(const char **pp, LazyBuf *part) depth--; } } - LazyBuf_AddSubstring(part, Substring_Init(start, p)); + LazyBuf_AddSubstring(part, Substring_Init(*pp, p)); *pp = p; } else { - LazyBuf_Add(part, *start); + LazyBuf_Add(part, *p); *pp = p + 1; } } @@ -2192,13 +2094,13 @@ ParseModifierPartSubst( ModChain *ch, LazyBuf *part, /* - * For the first part of the modifier ':S', set anchorEnd if the last + * 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. + * For the second part of the ':S' modifier, allow ampersands to be + * escaped and replace unescaped ampersands with subst->lhs. */ struct ModifyWord_SubstArgs *subst ) @@ -2268,7 +2170,7 @@ ParseModifierPart( const char **pp, /* Parsing stops at this delimiter */ char delim, - /* Mode for evaluating nested variables. */ + /* Mode for evaluating nested expressions. */ VarEvalMode emode, ModChain *ch, LazyBuf *part @@ -2465,12 +2367,11 @@ ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval, /* * XXX: This code is similar to the one in Var_Parse. See if - * the code can be merged. See also ApplyModifier_Match and + * the code can be merged. See also ParseModifier_Match and * ParseModifierPart. */ - /* Escaped delimiter or other special character */ - /* See Buf_AddEscaped in for.c. */ + /* See Buf_AddEscaped in for.c for the counterpart. */ if (*p == '\\') { char c = p[1]; if ((IsDelimiter(c, ch) && c != '\0') || @@ -2482,7 +2383,6 @@ ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval, } } - /* Nested variable expression */ if (*p == '$') { FStr val = Var_Parse(&p, ch->expr->scope, shouldEval ? ch->expr->emode : VARE_PARSE_ONLY); @@ -2493,7 +2393,6 @@ ParseModifier_Defined(const char **pp, ModChain *ch, bool shouldEval, continue; } - /* Ordinary text */ if (shouldEval) LazyBuf_Add(buf, *p); p++; @@ -2631,11 +2530,11 @@ ApplyModifier_Path(const char **pp, ModChain *ch) Expr_Define(expr); gn = Targ_FindNode(expr->name); - if (gn == NULL || gn->type & OP_NOPATH) { + if (gn == NULL || gn->type & OP_NOPATH) path = NULL; - } else if (gn->path != NULL) { + else if (gn->path != NULL) path = bmake_strdup(gn->path); - } else { + else { SearchPath *searchPath = Suff_FindPath(gn); path = Dir_FindFile(expr->name, searchPath); } @@ -2752,9 +2651,9 @@ ParseModifier_Match(const char **pp, const ModChain *ch) * See if the code can be merged. * See also ApplyModifier_Defined. */ - int nest = 0; + int depth = 0; const char *p; - for (p = mod + 1; *p != '\0' && !(*p == ':' && nest == 0); 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) @@ -2765,10 +2664,10 @@ ParseModifier_Match(const char **pp, const ModChain *ch) if (*p == '$') needSubst = true; if (*p == '(' || *p == '{') - nest++; + depth++; if (*p == ')' || *p == '}') { - nest--; - if (nest < 0) + depth--; + if (depth < 0) break; } } @@ -2906,22 +2805,24 @@ ApplyModifier_Mtime(const char **pp, ModChain *ch) if (args.use_fallback) { p++; if (TryParseTime(&p, &args.fallback)) { - } else if (strncmp(p, "error", 5) == 0 - && IsDelimiter(p[5], ch)) { + } else if (strncmp(p, "error", 5) == 0) { p += 5; args.error = true; - } else { - Parse_Error(PARSE_FATAL, - "Invalid argument '%.*s' for modifier ':mtime'", - (int)strcspn(p, ":{}()"), p); - return AMR_CLEANUP; - } + } else + goto invalid_argument; + if (!IsDelimiter(*p, ch)) + goto invalid_argument; *pp = p; } - if (!ModChain_ShouldEval(ch)) - return AMR_OK; - ModifyWords(ch, ModifyWord_Mtime, &args, ch->oneBigWord); + if (ModChain_ShouldEval(ch)) + 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 @@ -3093,8 +2994,8 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch) const char *sep = *pp + 2; /* - * Even in parse-only mode, proceed as normal since there is - * neither any observable side effect nor a performance penalty. + * Even in parse-only mode, apply the side effects, since the side + * effects are neither observable nor is there a performance penalty. * Checking for wantRes for every single piece of code in here * would make the code in this function too hard to read. */ @@ -3113,7 +3014,7 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch) goto ok; } - /* ":ts". */ + /* ":ts". */ if (sep[0] != '\\') { (*pp)++; /* just for backwards compatibility */ return AMR_BAD; @@ -3143,7 +3044,7 @@ ApplyModifier_ToSep(const char **pp, ModChain *ch) p++; } else if (!ch_isdigit(sep[1])) { (*pp)++; /* just for backwards compatibility */ - return AMR_BAD; /* ":ts". */ + return AMR_BAD; /* ":ts". */ } if (!TryParseChar(&p, base, &ch->sep)) { @@ -3239,7 +3140,7 @@ ApplyModifier_To(const char **pp, ModChain *ch) return AMR_OK; } - /* Found ":t:" or ":t". */ + /* Found ":t:" or ":t". */ *pp = mod + 1; /* XXX: unnecessary but observable */ return AMR_BAD; } @@ -3249,17 +3150,16 @@ static ApplyModifierResult ApplyModifier_Words(const char **pp, ModChain *ch) { Expr *expr = ch->expr; - const char *estr; int first, last; const char *p; - LazyBuf estrBuf; - FStr festr; + LazyBuf argBuf; + FStr arg; (*pp)++; /* skip the '[' */ - if (!ParseModifierPart(pp, ']', expr->emode, ch, &estrBuf)) + if (!ParseModifierPart(pp, ']', expr->emode, ch, &argBuf)) return AMR_CLEANUP; - festr = LazyBuf_DoneGet(&estrBuf); - estr = festr.str; + arg = LazyBuf_DoneGet(&argBuf); + p = arg.str; if (!IsDelimiter(**pp, ch)) goto bad_modifier; /* Found junk after ']' */ @@ -3267,80 +3167,67 @@ ApplyModifier_Words(const char **pp, ModChain *ch) if (!ModChain_ShouldEval(ch)) goto ok; - if (estr[0] == '\0') - goto bad_modifier; /* Found ":[]". */ + if (p[0] == '\0') + goto bad_modifier; /* Found ":[]". */ - if (estr[0] == '#' && estr[1] == '\0') { /* Found ":[#]" */ - if (ch->oneBigWord) { + if (strcmp(p, "#") == 0) { /* Found ":[#]" */ + if (ch->oneBigWord) Expr_SetValueRefer(expr, "1"); - } else { + else { Buffer buf; SubstringWords words = Expr_Words(expr); size_t ac = words.len; SubstringWords_Free(words); - /* 3 digits + '\0' is usually enough */ - Buf_InitSize(&buf, 4); + Buf_Init(&buf); Buf_AddInt(&buf, (int)ac); Expr_SetValueOwn(expr, Buf_DoneData(&buf)); } goto ok; } - if (estr[0] == '*' && estr[1] == '\0') { /* Found ":[*]" */ + if (strcmp(p, "*") == 0) { /* ":[*]" */ ch->oneBigWord = true; goto ok; } - if (estr[0] == '@' && estr[1] == '\0') { /* Found ":[@]" */ + if (strcmp(p, "@") == 0) { /* ":[@]" */ ch->oneBigWord = false; goto ok; } - /* - * We expect estr to contain a single integer for :[N], or two - * integers separated by ".." for :[start..end]. - */ - p = estr; + /* Expect ":[N]" or ":[start..end]" */ if (!TryParseIntBase0(&p, &first)) - goto bad_modifier; /* Found junk instead of a number */ + goto bad_modifier; - if (p[0] == '\0') { /* Found only one integer in :[N] */ + if (p[0] == '\0') /* ":[N]" */ last = first; - } else if (p[0] == '.' && p[1] == '.' && p[2] != '\0') { - /* Expecting another integer after ".." */ + else if (strncmp(p, "..", 2) == 0) { p += 2; if (!TryParseIntBase0(&p, &last) || *p != '\0') - goto bad_modifier; /* Found junk after ".." */ + goto bad_modifier; } else - goto bad_modifier; /* Found junk instead of ".." */ + goto bad_modifier; - /* - * Now first and last are properly filled in, but we still have to - * check for 0 as a special case. - */ - if (first == 0 && last == 0) { - /* ":[0]" or perhaps ":[0..0]" */ + if (first == 0 && last == 0) { /* ":[0]" or ":[0..0]" */ ch->oneBigWord = true; goto ok; } - /* ":[0..N]" or ":[N..0]" */ - if (first == 0 || last == 0) + if (first == 0 || last == 0) /* ":[0..N]" or ":[N..0]" */ goto bad_modifier; - /* Normal case: select the words described by first and last. */ Expr_SetValueOwn(expr, VarSelectWords(Expr_Str(expr), first, last, ch->sep, ch->oneBigWord)); ok: - FStr_Done(&festr); + FStr_Done(&arg); return AMR_OK; bad_modifier: - FStr_Done(&festr); + FStr_Done(&arg); return AMR_BAD; } @@ -3385,7 +3272,7 @@ SubNumAsc(const void *sa, const void *sb) a = num_val(*((const Substring *)sa)); b = num_val(*((const Substring *)sb)); - return (a > b) ? 1 : (b > a) ? -1 : 0; + return a > b ? 1 : b > a ? -1 : 0; } static int @@ -3394,11 +3281,20 @@ 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 strcmp( - ((const Substring *)sa)->start, ((const Substring *)sb)->start); + return Substring_Cmp(*(const Substring *)sa, *(const Substring *)sb); } static int @@ -3510,8 +3406,8 @@ ApplyModifier_IfElse(const char **pp, ModChain *ch) if (cond_rc == CR_ERROR) { Substring thenExpr = LazyBuf_Get(&thenBuf); Substring elseExpr = LazyBuf_Get(&elseBuf); - Error("Bad conditional expression '%s' in '%s?%.*s:%.*s'", - expr->name, expr->name, + Error("Bad conditional expression '%s' before '?%.*s:%.*s'", + expr->name, (int)Substring_Length(thenExpr), thenExpr.start, (int)Substring_Length(elseExpr), elseExpr.start); LazyBuf_Done(&thenBuf); @@ -3570,7 +3466,7 @@ ApplyModifier_Assign(const char **pp, ModChain *ch) goto found_op; if ((op[0] == '+' || op[0] == '?' || op[0] == '!') && op[1] == '=') goto found_op; - return AMR_UNKNOWN; /* "::" */ + return AMR_UNKNOWN; /* "::" */ found_op: if (expr->name[0] == '\0') { @@ -3578,7 +3474,7 @@ found_op: return AMR_BAD; } - *pp = mod + (op[0] == '+' || op[0] == '?' || op[0] == '!' ? 3 : 2); + *pp = mod + (op[0] != '=' ? 3 : 2); if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &buf)) return AMR_CLEANUP; @@ -3590,13 +3486,9 @@ found_op: goto done; scope = expr->scope; /* scope where v belongs */ - if (expr->defined == DEF_REGULAR && expr->scope != SCOPE_GLOBAL) { - Var *v = VarFind(expr->name, expr->scope, false); - if (v == NULL) - scope = SCOPE_GLOBAL; - else - VarFreeShortLived(v); - } + 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); @@ -3640,7 +3532,7 @@ ApplyModifier_Remember(const char **pp, ModChain *ch) /* * XXX: This ad-hoc call to strcspn deviates from the usual * behavior defined in ParseModifierPart. This creates an - * unnecessary, undocumented inconsistency in make. + * unnecessary and undocumented inconsistency in make. */ const char *arg = mod + 2; size_t argLen = strcspn(arg, ":)}"); @@ -3690,7 +3582,7 @@ ApplyModifier_Unique(const char **pp, ModChain *ch) words = Expr_Words(ch->expr); if (words.len > 1) { - size_t si, di; + size_t di, si; di = 0; for (si = 1; si < words.len; si++) { @@ -3709,6 +3601,26 @@ ApplyModifier_Unique(const char **pp, ModChain *ch) } #ifdef SYSVVARSUB +/* 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) @@ -3721,33 +3633,15 @@ ApplyModifier_SysV(const char **pp, ModChain *ch) const char *lhsSuffix; const char *mod = *pp; - bool eqFound = false; - /* - * First we make a pass through the string trying to verify it is a - * SysV-make-style translation. It must be: = - */ - int depth = 1; - const char *p = mod; - while (*p != '\0' && depth > 0) { - if (*p == '=') { /* XXX: should also test depth == 1 */ - eqFound = true; - /* continue looking for ch->endc */ - } else if (*p == ch->endc) - depth--; - else if (*p == ch->startc) - depth++; - if (depth > 0) - p++; - } - if (*p != ch->endc || !eqFound) + if (!IsSysVModifier(mod, ch->startc, ch->endc)) return AMR_UNKNOWN; if (!ParseModifierPart(pp, '=', expr->emode, ch, &lhsBuf)) return AMR_CLEANUP; /* - * The SysV modifier lasts until the end of the variable expression. + * The SysV modifier lasts until the end of the expression. */ if (!ParseModifierPart(pp, ch->endc, expr->emode, ch, &rhsBuf)) { LazyBuf_Done(&lhsBuf); @@ -3948,16 +3842,16 @@ typedef enum ApplyModifiersIndirectResult { } ApplyModifiersIndirectResult; /* - * While expanding a variable expression, expand and apply indirect modifiers, + * 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 variable + * 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 variable expression is not followed by ch->endc or ':', fall + * 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 @@ -3990,8 +3884,8 @@ ApplyModifiersIndirect(ModChain *ch, const char **pp) if (*p == ':') p++; else if (*p == '\0' && ch->endc != '\0') { - Error("Unclosed variable expression after indirect " - "modifier, expecting '%c' for variable \"%s\"", + Error("Unclosed expression after indirect modifier, " + "expecting '%c' for variable \"%s\"", ch->endc, expr->name); *pp = p; return AMIR_OUT; @@ -4043,7 +3937,7 @@ ApplySingleModifier(const char **pp, ModChain *ch) if (*p == '\0' && ch->endc != '\0') { Error( - "Unclosed variable expression, expecting '%c' for " + "Unclosed expression, expecting '%c' for " "modifier \"%.*s\" of variable \"%s\" with value \"%s\"", ch->endc, (int)(p - mod), mod, @@ -4064,11 +3958,11 @@ ApplySingleModifier(const char **pp, ModChain *ch) } #if __STDC_VERSION__ >= 199901L -#define ModChain_Literal(expr, startc, endc, sep, oneBigWord) \ +#define ModChain_Init(expr, startc, endc, sep, oneBigWord) \ (ModChain) { expr, startc, endc, sep, oneBigWord } #else MAKE_INLINE ModChain -ModChain_Literal(Expr *expr, char startc, char endc, char sep, bool oneBigWord) +ModChain_Init(Expr *expr, char startc, char endc, char sep, bool oneBigWord) { ModChain ch; ch.expr = expr; @@ -4089,7 +3983,7 @@ ApplyModifiers( char endc /* ')' or '}'; or '\0' for indirect modifiers */ ) { - ModChain ch = ModChain_Literal(expr, startc, endc, ' ', false); + ModChain ch = ModChain_Init(expr, startc, endc, ' ', false); const char *p; const char *mod; @@ -4101,7 +3995,7 @@ ApplyModifiers( if (*p == '\0' && endc != '\0') { Error( - "Unclosed variable expression (expecting '%c') for \"%s\"", + "Unclosed expression, expecting '%c' for \"%s\"", ch.endc, expr->name); goto cleanup; } @@ -4110,17 +4004,17 @@ ApplyModifiers( 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; - /* - * It's neither '${VAR}:' nor '${VAR}}'. Try to parse - * it as a SysV modifier, as that is the only modifier - * that can start with '$'. - */ } mod = p; @@ -4137,7 +4031,7 @@ ApplyModifiers( return; bad_modifier: - /* XXX: The modifier end is only guessed. */ + /* Take a guess at where the modifier ends. */ Error("Bad modifier \":%.*s\" for variable \"%s\"", (int)strcspn(mod, ":)}"), mod, expr->name); @@ -4226,7 +4120,7 @@ ParseVarname(const char **pp, char startc, char endc, LazyBuf *buf) { const char *p = *pp; - int depth = 0; /* Track depth so we can spot parse errors. */ + int depth = 0; LazyBuf_Init(buf, p); @@ -4238,7 +4132,6 @@ ParseVarname(const char **pp, char startc, char endc, if (*p == endc) depth--; - /* A variable inside a variable, expand. */ if (*p == '$') { FStr nested_val = Var_Parse(&p, scope, emode); /* TODO: handle errors */ @@ -4336,7 +4229,8 @@ FindLocalLegacyVar(Substring varname, GNode *scope, if (strchr("@%?*!<>", varname.start[0]) == NULL) return NULL; - v = VarFindSubstring(Substring_Sub(varname, 0, 1), scope, false); + v = VarFindSubstring(Substring_Init(varname.start, varname.start + 1), + scope, false); if (v == NULL) return NULL; @@ -4400,11 +4294,11 @@ ParseVarnameLong( ParseVarname(&p, startc, endc, scope, emode, &varname); name = LazyBuf_Get(&varname); - if (*p == ':') { + if (*p == ':') haveModifier = true; - } else if (*p == endc) { + else if (*p == endc) haveModifier = false; - } else { + else { Parse_Error(PARSE_FATAL, "Unclosed variable \"%.*s\"", (int)Substring_Length(name), name.start); LazyBuf_Done(&varname); @@ -4446,14 +4340,14 @@ ParseVarnameLong( } /* - * The variable expression is based on an undefined variable. + * 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 (VES_UNDEF), only a few modifiers like :D, :U, :L, + * state (DEF_UNDEF), only a few modifiers like :D, :U, :L, * :P turn this undefined expression into a defined - * expression (VES_DEF). + * expression (DEF_DEFINED). * * In the end, after applying all modifiers, if the expression * is still undefined, Var_Parse will return an empty string @@ -4474,12 +4368,12 @@ ParseVarnameLong( } #if __STDC_VERSION__ >= 199901L -#define Expr_Literal(name, value, emode, scope, defined) \ - { name, value, emode, scope, defined } +#define Expr_Init(name, value, emode, scope, defined) \ + (Expr) { name, value, emode, scope, defined } #else MAKE_INLINE Expr -Expr_Literal(const char *name, FStr value, - VarEvalMode emode, GNode *scope, ExprDefined defined) +Expr_Init(const char *name, FStr value, + VarEvalMode emode, GNode *scope, ExprDefined defined) { Expr expr; @@ -4494,8 +4388,7 @@ Expr_Literal(const char *name, FStr value, /* * Expressions of the form ${:U...} with a trivial value are often generated - * by .for loops and are boring, therefore parse and evaluate them in a fast - * lane without debug logging. + * by .for loops and are boring, so evaluate them without debug logging. */ static bool Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value) @@ -4513,27 +4406,24 @@ Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value) if (*p != '}') return false; - if (emode == VARE_PARSE_ONLY) - *out_value = FStr_InitRefer(""); - else - *out_value = FStr_InitOwn(bmake_strsedup(*pp + 4, p)); + *out_value = emode == VARE_PARSE_ONLY + ? FStr_InitRefer("") + : FStr_InitOwn(bmake_strsedup(*pp + 4, p)); *pp = p + 1; return true; } /* - * Given the start of a variable expression (such as $v, $(VAR), - * ${VAR:Mpattern}), extract the variable name and value, and the modifiers, - * if any. While doing that, apply the modifiers to the value of the - * expression, forming its final value. A few of the modifiers such as :!cmd! - * or ::= have side effects. + * 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 + * scope The scope for finding variables. + * emode Controls the exact details of parsing and evaluation. * * Output: * *pp The position where to continue parsing. @@ -4542,7 +4432,7 @@ Var_Parse_FastLane(const char **pp, VarEvalMode emode, FStr *out_value) * 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 variable expression, never NULL. + * 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_UNDEFERR, and none of @@ -4574,15 +4464,13 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode) bool dynamic; const char *extramodifiers; Var *v; - Expr expr = Expr_Literal(NULL, FStr_InitRefer(NULL), emode, + Expr expr = Expr_Init(NULL, FStr_InitRefer(NULL), emode, scope, DEF_REGULAR); FStr val; if (Var_Parse_FastLane(pp, emode, &val)) return val; - /* TODO: Reduce computations in parse-only mode. */ - DEBUG2(VAR, "Var_Parse: %s (%s)\n", start, VarEvalMode_Name[emode]); val = FStr_InitRefer(NULL); @@ -4615,11 +4503,18 @@ Var_Parse(const char **pp, GNode *scope, VarEvalMode emode) } /* - * XXX: This assignment creates an alias to the current value of the + * 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. - * Using the '::=' modifier, it could be possible to trigger exactly - * this situation. + * 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@loop@}: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 @@ -4711,10 +4606,10 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, } else if (val.str == var_Error) { /* - * XXX: This condition is wrong. If val == var_Error, - * this doesn't necessarily mean there was an undefined - * variable. It could equally well be a parse error; - * see unit-tests/varmod-order.exp. + * 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. */ /* @@ -4725,10 +4620,10 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, if (!*inout_errorReported) { Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"", - (int)(size_t)(nested_p - p), p); + (int)(nested_p - p), p); + *inout_errorReported = true; } p = nested_p; - *inout_errorReported = true; } else { /* * Copy the initial '$' of the undefined expression, @@ -4750,8 +4645,8 @@ VarSubstExpr(const char **pp, Buffer *buf, GNode *scope, } /* - * Skip as many characters as possible -- either to the end of the string - * or to the next dollar sign (variable expression). + * 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) @@ -4766,11 +4661,11 @@ VarSubstPlain(const char **pp, Buffer *res) } /* - * Expand all variable expressions like $V, ${VAR}, $(VAR:Modifiers) in the + * Expand all expressions like $V, ${VAR}, $(VAR:Modifiers) in the * given string. * * Input: - * str The string in which the variable expressions are + * 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. @@ -4786,7 +4681,6 @@ Var_Subst(const char *str, GNode *scope, VarEvalMode emode) * Set true if an error has already been reported, to prevent a * plethora of messages when recursing */ - /* See varparse-errors.mk for why the 'static' is necessary here. */ static bool errorReported; Buf_Init(&res);