From 21b3ce3e7e335a8cbdfd23263c864b02c54dbd8c Mon Sep 17 00:00:00 2001 From: Marcelo Alencar Date: Tue, 30 Mar 2021 18:26:46 -0300 Subject: [PATCH] tools/make: Synchronize on NetBSD-current (30/03/2021 16:09 UTC) --- distrib/sets/lists/tests/mi | 930 +- tools/gmp/Makefile | 4 +- tools/make/buildmake.sh.in | 37 +- usr.bin/make/Makefile | 250 +- usr.bin/make/Makefile.boot | 54 +- usr.bin/make/PSD.doc/tutorial.ms | 23 +- usr.bin/make/arch.c | 1817 ++-- usr.bin/make/buf.c | 345 +- usr.bin/make/buf.h | 70 +- usr.bin/make/compat.c | 1081 +-- usr.bin/make/cond.c | 2342 +++-- usr.bin/make/config.h | 45 +- usr.bin/make/dir.c | 2753 +++--- usr.bin/make/dir.h | 53 +- usr.bin/make/enum.c | 80 + usr.bin/make/enum.h | 179 + usr.bin/make/{strlist.h => filemon/filemon.h} | 49 +- usr.bin/make/filemon/filemon_dev.c | 151 + usr.bin/make/filemon/filemon_ktrace.c | 894 ++ usr.bin/make/for.c | 801 +- usr.bin/make/hash.c | 562 +- usr.bin/make/hash.h | 148 +- usr.bin/make/job.c | 4575 +++++---- usr.bin/make/job.h | 208 +- usr.bin/make/lst.c | 292 + usr.bin/make/lst.h | 217 +- usr.bin/make/lst.lib/Makefile | 10 - usr.bin/make/lst.lib/lstAppend.c | 122 - usr.bin/make/lst.lib/lstAtEnd.c | 79 - usr.bin/make/lst.lib/lstAtFront.c | 76 - usr.bin/make/lst.lib/lstClose.c | 86 - usr.bin/make/lst.lib/lstConcat.c | 185 - usr.bin/make/lst.lib/lstDatum.c | 77 - usr.bin/make/lst.lib/lstDeQueue.c | 87 - usr.bin/make/lst.lib/lstDestroy.c | 101 - usr.bin/make/lst.lib/lstDupl.c | 107 - usr.bin/make/lst.lib/lstEnQueue.c | 78 - usr.bin/make/lst.lib/lstFind.c | 74 - usr.bin/make/lst.lib/lstFindFrom.c | 90 - usr.bin/make/lst.lib/lstFirst.c | 77 - usr.bin/make/lst.lib/lstForEach.c | 76 - usr.bin/make/lst.lib/lstForEachFrom.c | 125 - usr.bin/make/lst.lib/lstInit.c | 85 - usr.bin/make/lst.lib/lstInsert.c | 122 - usr.bin/make/lst.lib/lstInt.h | 105 - usr.bin/make/lst.lib/lstIsAtEnd.c | 87 - usr.bin/make/lst.lib/lstIsEmpty.c | 75 - usr.bin/make/lst.lib/lstLast.c | 77 - usr.bin/make/lst.lib/lstMember.c | 77 - usr.bin/make/lst.lib/lstNext.c | 120 - usr.bin/make/lst.lib/lstOpen.c | 87 - usr.bin/make/lst.lib/lstPrev.c | 79 - usr.bin/make/lst.lib/lstRemove.c | 136 - usr.bin/make/lst.lib/lstReplace.c | 78 - usr.bin/make/lst.lib/lstSucc.c | 79 - usr.bin/make/main.c | 2789 +++--- usr.bin/make/make.1 | 396 +- usr.bin/make/make.c | 2496 +++-- usr.bin/make/make.h | 999 +- usr.bin/make/make_malloc.c | 74 +- usr.bin/make/make_malloc.h | 29 +- usr.bin/make/meta.c | 1211 ++- usr.bin/make/meta.h | 37 +- usr.bin/make/metachar.c | 46 +- usr.bin/make/metachar.h | 33 +- usr.bin/make/nonints.h | 376 +- usr.bin/make/parse.c | 5311 +++++------ usr.bin/make/pathnames.h | 14 +- usr.bin/make/sprite.h | 116 - usr.bin/make/str.c | 537 +- usr.bin/make/strlist.c | 93 - usr.bin/make/suff.c | 3834 ++++---- usr.bin/make/targ.c | 1065 +-- usr.bin/make/test-variants.sh | 196 + usr.bin/make/trace.c | 39 +- usr.bin/make/trace.h | 12 +- usr.bin/make/unit-tests/.cvsignore | 3 + usr.bin/make/unit-tests/Makefile | 766 +- usr.bin/make/unit-tests/archive-suffix.exp | 2 + usr.bin/make/unit-tests/archive-suffix.mk | 23 + usr.bin/make/unit-tests/archive.exp | 28 + usr.bin/make/unit-tests/archive.mk | 60 + usr.bin/make/unit-tests/cmd-errors-jobs.exp | 9 + usr.bin/make/unit-tests/cmd-errors-jobs.mk | 32 + usr.bin/make/unit-tests/cmd-errors-lint.exp | 9 + usr.bin/make/unit-tests/cmd-errors-lint.mk | 32 + usr.bin/make/unit-tests/cmd-errors.exp | 9 + usr.bin/make/unit-tests/cmd-errors.mk | 30 + usr.bin/make/unit-tests/cmd-interrupt.exp | 9 + usr.bin/make/unit-tests/cmd-interrupt.mk | 50 + .../{misc.exp => cmdline-redirect-stdin.exp} | 0 .../make/unit-tests/cmdline-redirect-stdin.mk | 34 + usr.bin/make/unit-tests/cmdline-undefined.exp | 17 + usr.bin/make/unit-tests/cmdline-undefined.mk | 40 + usr.bin/make/unit-tests/cmdline.exp | 5 + usr.bin/make/unit-tests/cmdline.mk | 36 + usr.bin/make/unit-tests/comment.exp | 9 +- usr.bin/make/unit-tests/comment.mk | 91 +- usr.bin/make/unit-tests/compat-error.exp | 15 + usr.bin/make/unit-tests/compat-error.mk | 37 + .../make/unit-tests/cond-cmp-numeric-eq.exp | 5 + .../make/unit-tests/cond-cmp-numeric-eq.mk | 81 + .../{qequals.exp => cond-cmp-numeric-ge.exp} | 1 - .../make/unit-tests/cond-cmp-numeric-ge.mk | 75 + .../make/unit-tests/cond-cmp-numeric-gt.exp | 1 + .../make/unit-tests/cond-cmp-numeric-gt.mk | 73 + .../make/unit-tests/cond-cmp-numeric-le.exp | 1 + .../make/unit-tests/cond-cmp-numeric-le.mk | 75 + .../make/unit-tests/cond-cmp-numeric-lt.exp | 1 + .../make/unit-tests/cond-cmp-numeric-lt.mk | 73 + .../make/unit-tests/cond-cmp-numeric-ne.exp | 1 + .../make/unit-tests/cond-cmp-numeric-ne.mk | 49 + usr.bin/make/unit-tests/cond-cmp-numeric.exp | 11 + usr.bin/make/unit-tests/cond-cmp-numeric.mk | 41 + usr.bin/make/unit-tests/cond-cmp-string.exp | 11 + usr.bin/make/unit-tests/cond-cmp-string.mk | 138 + usr.bin/make/unit-tests/cond-cmp-unary.exp | 2 + usr.bin/make/unit-tests/cond-cmp-unary.mk | 58 + usr.bin/make/unit-tests/cond-eof.exp | 9 + usr.bin/make/unit-tests/cond-eof.mk | 20 + .../make/unit-tests/cond-func-commands.exp | 1 + usr.bin/make/unit-tests/cond-func-commands.mk | 37 + usr.bin/make/unit-tests/cond-func-defined.exp | 8 + usr.bin/make/unit-tests/cond-func-defined.mk | 52 + usr.bin/make/unit-tests/cond-func-empty.exp | 5 + usr.bin/make/unit-tests/cond-func-empty.mk | 184 + usr.bin/make/unit-tests/cond-func-exists.exp | 1 + usr.bin/make/unit-tests/cond-func-exists.mk | 51 + .../make/unit-tests/cond-func-make-main.exp | 3 + .../make/unit-tests/cond-func-make-main.mk | 62 + usr.bin/make/unit-tests/cond-func-make.exp | 3 + usr.bin/make/unit-tests/cond-func-make.mk | 24 + usr.bin/make/unit-tests/cond-func-target.exp | 1 + usr.bin/make/unit-tests/cond-func-target.mk | 38 + usr.bin/make/unit-tests/cond-func.exp | 12 + usr.bin/make/unit-tests/cond-func.mk | 137 + usr.bin/make/unit-tests/cond-late.exp | 4 + usr.bin/make/unit-tests/cond-late.mk | 31 + usr.bin/make/unit-tests/cond-op-and-lint.exp | 4 + usr.bin/make/unit-tests/cond-op-and-lint.mk | 13 + usr.bin/make/unit-tests/cond-op-and.exp | 4 + usr.bin/make/unit-tests/cond-op-and.mk | 48 + usr.bin/make/unit-tests/cond-op-not.exp | 9 + usr.bin/make/unit-tests/cond-op-not.mk | 66 + usr.bin/make/unit-tests/cond-op-or-lint.exp | 4 + usr.bin/make/unit-tests/cond-op-or-lint.mk | 13 + usr.bin/make/unit-tests/cond-op-or.exp | 4 + usr.bin/make/unit-tests/cond-op-or.mk | 48 + .../make/unit-tests/cond-op-parentheses.exp | 6 + .../make/unit-tests/cond-op-parentheses.mk | 36 + usr.bin/make/unit-tests/cond-op.exp | 20 + usr.bin/make/unit-tests/cond-op.mk | 136 + usr.bin/make/unit-tests/cond-short.exp | 16 + usr.bin/make/unit-tests/cond-short.mk | 214 + usr.bin/make/unit-tests/cond-token-number.exp | 8 + usr.bin/make/unit-tests/cond-token-number.mk | 82 + usr.bin/make/unit-tests/cond-token-plain.exp | 54 + usr.bin/make/unit-tests/cond-token-plain.mk | 192 + usr.bin/make/unit-tests/cond-token-string.exp | 18 + usr.bin/make/unit-tests/cond-token-string.mk | 82 + usr.bin/make/unit-tests/cond-token-var.exp | 7 + usr.bin/make/unit-tests/cond-token-var.mk | 48 + usr.bin/make/unit-tests/cond-undef-lint.exp | 7 + usr.bin/make/unit-tests/cond-undef-lint.mk | 73 + usr.bin/make/unit-tests/cond1.exp | 6 +- usr.bin/make/unit-tests/cond1.mk | 7 +- usr.bin/make/unit-tests/cond2.exp | 7 - usr.bin/make/unit-tests/cond2.mk | 25 - usr.bin/make/unit-tests/counter-append.exp | 2 + usr.bin/make/unit-tests/counter-append.mk | 28 + usr.bin/make/unit-tests/counter.exp | 2 + usr.bin/make/unit-tests/counter.mk | 28 + .../unit-tests/dep-colon-bug-cross-file.exp | 4 + .../unit-tests/dep-colon-bug-cross-file.mk | 41 + usr.bin/make/unit-tests/dep-colon.exp | 3 + usr.bin/make/unit-tests/dep-colon.mk | 20 + .../unit-tests/dep-double-colon-indep.exp | 3 + .../make/unit-tests/dep-double-colon-indep.mk | 32 + usr.bin/make/unit-tests/dep-double-colon.exp | 5 + usr.bin/make/unit-tests/dep-double-colon.mk | 23 + usr.bin/make/unit-tests/dep-exclam.exp | 1 + usr.bin/make/unit-tests/dep-exclam.mk | 13 + usr.bin/make/unit-tests/dep-none.exp | 4 + usr.bin/make/unit-tests/dep-none.mk | 3 + usr.bin/make/unit-tests/dep-percent.exp | 6 + usr.bin/make/unit-tests/dep-percent.mk | 14 + usr.bin/make/unit-tests/dep-var.exp | 6 + usr.bin/make/unit-tests/dep-var.mk | 88 + usr.bin/make/unit-tests/dep-wildcards.exp | 10 + usr.bin/make/unit-tests/dep-wildcards.mk | 9 + usr.bin/make/unit-tests/dep.exp | 1 + usr.bin/make/unit-tests/dep.mk | 8 + usr.bin/make/unit-tests/depsrc-end.exp | 4 + usr.bin/make/unit-tests/depsrc-end.mk | 14 + usr.bin/make/unit-tests/depsrc-exec.exp | 2 + usr.bin/make/unit-tests/depsrc-exec.mk | 16 + usr.bin/make/unit-tests/depsrc-ignore.exp | 11 + usr.bin/make/unit-tests/depsrc-ignore.mk | 66 + usr.bin/make/unit-tests/depsrc-made.exp | 3 + usr.bin/make/unit-tests/depsrc-made.mk | 15 + usr.bin/make/unit-tests/depsrc-make.exp | 3 + usr.bin/make/unit-tests/depsrc-make.mk | 18 + usr.bin/make/unit-tests/depsrc-meta.exp | 5 + usr.bin/make/unit-tests/depsrc-meta.mk | 31 + usr.bin/make/unit-tests/depsrc-nometa.exp | 1 + usr.bin/make/unit-tests/depsrc-nometa.mk | 8 + usr.bin/make/unit-tests/depsrc-nometa_cmp.exp | 1 + usr.bin/make/unit-tests/depsrc-nometa_cmp.mk | 8 + usr.bin/make/unit-tests/depsrc-nopath.exp | 1 + usr.bin/make/unit-tests/depsrc-nopath.mk | 8 + usr.bin/make/unit-tests/depsrc-notmain.exp | 2 + usr.bin/make/unit-tests/depsrc-notmain.mk | 11 + usr.bin/make/unit-tests/depsrc-optional.exp | 20 + usr.bin/make/unit-tests/depsrc-optional.mk | 21 + usr.bin/make/unit-tests/depsrc-phony.exp | 2 + usr.bin/make/unit-tests/depsrc-phony.mk | 9 + usr.bin/make/unit-tests/depsrc-precious.exp | 1 + usr.bin/make/unit-tests/depsrc-precious.mk | 16 + usr.bin/make/unit-tests/depsrc-recursive.exp | 3 + usr.bin/make/unit-tests/depsrc-recursive.mk | 16 + usr.bin/make/unit-tests/depsrc-silent.exp | 4 + usr.bin/make/unit-tests/depsrc-silent.mk | 12 + usr.bin/make/unit-tests/depsrc-use.exp | 6 + usr.bin/make/unit-tests/depsrc-use.mk | 24 + .../depsrc-usebefore-double-colon.exp | 2 + .../depsrc-usebefore-double-colon.mk | 30 + usr.bin/make/unit-tests/depsrc-usebefore.exp | 6 + usr.bin/make/unit-tests/depsrc-usebefore.mk | 28 + usr.bin/make/unit-tests/depsrc-wait.exp | 13 + usr.bin/make/unit-tests/depsrc-wait.mk | 21 + usr.bin/make/unit-tests/depsrc.exp | 4 + usr.bin/make/unit-tests/depsrc.mk | 26 + .../unit-tests/deptgt-begin-fail-indirect.exp | 6 + .../unit-tests/deptgt-begin-fail-indirect.mk | 16 + usr.bin/make/unit-tests/deptgt-begin-fail.exp | 6 + usr.bin/make/unit-tests/deptgt-begin-fail.mk | 10 + usr.bin/make/unit-tests/deptgt-begin.exp | 7 + usr.bin/make/unit-tests/deptgt-begin.mk | 47 + usr.bin/make/unit-tests/deptgt-default.exp | 1 + usr.bin/make/unit-tests/deptgt-default.mk | 8 + .../unit-tests/deptgt-delete_on_error.exp | 51 + .../make/unit-tests/deptgt-delete_on_error.mk | 45 + .../make/unit-tests/deptgt-end-fail-all.exp | 7 + .../make/unit-tests/deptgt-end-fail-all.mk | 19 + .../unit-tests/deptgt-end-fail-indirect.exp | 7 + .../unit-tests/deptgt-end-fail-indirect.mk | 16 + usr.bin/make/unit-tests/deptgt-end-fail.exp | 163 + usr.bin/make/unit-tests/deptgt-end-fail.mk | 69 + usr.bin/make/unit-tests/deptgt-end-jobs.exp | 8 + usr.bin/make/unit-tests/deptgt-end-jobs.mk | 46 + usr.bin/make/unit-tests/deptgt-end.exp | 9 + usr.bin/make/unit-tests/deptgt-end.mk | 45 + usr.bin/make/unit-tests/deptgt-error.exp | 1 + usr.bin/make/unit-tests/deptgt-error.mk | 9 + usr.bin/make/unit-tests/deptgt-ignore.exp | 1 + usr.bin/make/unit-tests/deptgt-ignore.mk | 9 + usr.bin/make/unit-tests/deptgt-interrupt.exp | 1 + usr.bin/make/unit-tests/deptgt-interrupt.mk | 10 + usr.bin/make/unit-tests/deptgt-main.exp | 1 + usr.bin/make/unit-tests/deptgt-main.mk | 10 + usr.bin/make/unit-tests/deptgt-makeflags.exp | 10 + usr.bin/make/unit-tests/deptgt-makeflags.mk | 93 + .../make/unit-tests/deptgt-no_parallel.exp | 1 + usr.bin/make/unit-tests/deptgt-no_parallel.mk | 8 + usr.bin/make/unit-tests/deptgt-nopath.exp | 1 + usr.bin/make/unit-tests/deptgt-nopath.mk | 8 + .../make/unit-tests/deptgt-notparallel.exp | 1 + usr.bin/make/unit-tests/deptgt-notparallel.mk | 8 + usr.bin/make/unit-tests/deptgt-objdir.exp | 1 + usr.bin/make/unit-tests/deptgt-objdir.mk | 8 + usr.bin/make/unit-tests/deptgt-order.exp | 1 + usr.bin/make/unit-tests/deptgt-order.mk | 8 + .../make/unit-tests/deptgt-path-suffix.exp | 1 + usr.bin/make/unit-tests/deptgt-path-suffix.mk | 8 + usr.bin/make/unit-tests/deptgt-path.exp | 1 + usr.bin/make/unit-tests/deptgt-path.mk | 8 + usr.bin/make/unit-tests/deptgt-phony.exp | 1 + usr.bin/make/unit-tests/deptgt-phony.mk | 8 + usr.bin/make/unit-tests/deptgt-precious.exp | 1 + usr.bin/make/unit-tests/deptgt-precious.mk | 8 + usr.bin/make/unit-tests/deptgt-shell.exp | 1 + usr.bin/make/unit-tests/deptgt-shell.mk | 8 + usr.bin/make/unit-tests/deptgt-silent.exp | 5 + usr.bin/make/unit-tests/deptgt-silent.mk | 15 + usr.bin/make/unit-tests/deptgt-stale.exp | 1 + usr.bin/make/unit-tests/deptgt-stale.mk | 8 + usr.bin/make/unit-tests/deptgt-suffixes.exp | 33 + usr.bin/make/unit-tests/deptgt-suffixes.mk | 35 + usr.bin/make/unit-tests/deptgt.exp | 14 + usr.bin/make/unit-tests/deptgt.mk | 49 + usr.bin/make/unit-tests/dir-expand-path.exp | 4 + usr.bin/make/unit-tests/dir-expand-path.mk | 19 + usr.bin/make/unit-tests/dir.exp | 50 + usr.bin/make/unit-tests/dir.mk | 94 + .../make/unit-tests/directive-dinclude.exp | 1 + usr.bin/make/unit-tests/directive-dinclude.mk | 9 + usr.bin/make/unit-tests/directive-elif.exp | 21 + usr.bin/make/unit-tests/directive-elif.mk | 142 + usr.bin/make/unit-tests/directive-elifdef.exp | 1 + usr.bin/make/unit-tests/directive-elifdef.mk | 8 + .../make/unit-tests/directive-elifmake.exp | 1 + usr.bin/make/unit-tests/directive-elifmake.mk | 8 + .../make/unit-tests/directive-elifndef.exp | 1 + usr.bin/make/unit-tests/directive-elifndef.mk | 8 + .../make/unit-tests/directive-elifnmake.exp | 1 + .../make/unit-tests/directive-elifnmake.mk | 8 + usr.bin/make/unit-tests/directive-else.exp | 11 + usr.bin/make/unit-tests/directive-else.mk | 49 + usr.bin/make/unit-tests/directive-endfor.exp | 4 + usr.bin/make/unit-tests/directive-endfor.mk | 9 + usr.bin/make/unit-tests/directive-endif.exp | 8 + usr.bin/make/unit-tests/directive-endif.mk | 49 + usr.bin/make/unit-tests/directive-error.exp | 4 + usr.bin/make/unit-tests/directive-error.mk | 13 + .../make/unit-tests/directive-export-env.exp | 1 + .../make/unit-tests/directive-export-env.mk | 10 + .../unit-tests/directive-export-gmake.exp | 1 + .../make/unit-tests/directive-export-gmake.mk | 64 + .../make/unit-tests/directive-export-impl.exp | 56 + .../make/unit-tests/directive-export-impl.mk | 62 + .../unit-tests/directive-export-literal.exp | 2 + .../unit-tests/directive-export-literal.mk | 13 + usr.bin/make/unit-tests/directive-export.exp | 1 + usr.bin/make/unit-tests/directive-export.mk | 44 + .../make/unit-tests/directive-for-errors.exp | 22 + .../make/unit-tests/directive-for-errors.mk | 75 + .../make/unit-tests/directive-for-escape.exp | 75 + .../make/unit-tests/directive-for-escape.mk | 113 + .../directive-for-generating-endif.exp | 7 + .../directive-for-generating-endif.mk | 25 + .../make/unit-tests/directive-for-lines.exp | 10 + .../make/unit-tests/directive-for-lines.mk | 32 + .../make/unit-tests/directive-for-null.exp | 10 + usr.bin/make/unit-tests/directive-for-null.mk | 19 + usr.bin/make/unit-tests/directive-for.exp | 24 + usr.bin/make/unit-tests/directive-for.mk | 159 + .../unit-tests/directive-hyphen-include.exp | 1 + .../unit-tests/directive-hyphen-include.mk | 9 + .../make/unit-tests/directive-if-nested.exp | 2 + .../make/unit-tests/directive-if-nested.mk | 25 + usr.bin/make/unit-tests/directive-if.exp | 17 + usr.bin/make/unit-tests/directive-if.mk | 89 + usr.bin/make/unit-tests/directive-ifdef.exp | 4 + usr.bin/make/unit-tests/directive-ifdef.mk | 33 + usr.bin/make/unit-tests/directive-ifmake.exp | 11 + usr.bin/make/unit-tests/directive-ifmake.mk | 82 + usr.bin/make/unit-tests/directive-ifndef.exp | 2 + usr.bin/make/unit-tests/directive-ifndef.mk | 24 + usr.bin/make/unit-tests/directive-ifnmake.exp | 3 + usr.bin/make/unit-tests/directive-ifnmake.mk | 22 + .../unit-tests/directive-include-fatal.exp | 4 + .../unit-tests/directive-include-fatal.mk | 27 + usr.bin/make/unit-tests/directive-include.exp | 8 + usr.bin/make/unit-tests/directive-include.mk | 34 + usr.bin/make/unit-tests/directive-info.exp | 15 + usr.bin/make/unit-tests/directive-info.mk | 39 + .../unit-tests/directive-misspellings.exp | 45 + .../make/unit-tests/directive-misspellings.mk | 79 + .../make/unit-tests/directive-sinclude.exp | 1 + usr.bin/make/unit-tests/directive-sinclude.mk | 13 + usr.bin/make/unit-tests/directive-undef.exp | 6 + usr.bin/make/unit-tests/directive-undef.mk | 107 + .../unit-tests/directive-unexport-env.exp | 18 + .../make/unit-tests/directive-unexport-env.mk | 25 + .../make/unit-tests/directive-unexport.exp | 5 + usr.bin/make/unit-tests/directive-unexport.mk | 33 + usr.bin/make/unit-tests/directive-warning.exp | 11 + usr.bin/make/unit-tests/directive-warning.mk | 21 + usr.bin/make/unit-tests/directive.exp | 12 + usr.bin/make/unit-tests/directive.mk | 35 + usr.bin/make/unit-tests/dollar.exp | 51 + usr.bin/make/unit-tests/dollar.mk | 81 + usr.bin/make/unit-tests/doterror.exp | 2 +- usr.bin/make/unit-tests/doterror.mk | 2 +- usr.bin/make/unit-tests/dotwait.exp | 4 +- usr.bin/make/unit-tests/dotwait.mk | 10 +- usr.bin/make/unit-tests/envfirst.exp | 1 + usr.bin/make/unit-tests/envfirst.mk | 44 + usr.bin/make/unit-tests/error.exp | 8 +- usr.bin/make/unit-tests/error.mk | 10 +- usr.bin/make/unit-tests/escape.mk | 98 +- usr.bin/make/unit-tests/export-all.mk | 19 +- usr.bin/make/unit-tests/export-env.exp | 2 + usr.bin/make/unit-tests/export-env.mk | 17 +- usr.bin/make/unit-tests/export-variants.exp | 1 + usr.bin/make/unit-tests/export-variants.mk | 40 + usr.bin/make/unit-tests/export.exp | 2 + usr.bin/make/unit-tests/export.mk | 50 +- usr.bin/make/unit-tests/forloop.exp | 29 +- usr.bin/make/unit-tests/forloop.mk | 56 +- usr.bin/make/unit-tests/forsubst.mk | 14 +- usr.bin/make/unit-tests/gnode-submake.exp | 11 + usr.bin/make/unit-tests/gnode-submake.mk | 42 + usr.bin/make/unit-tests/hanoi-include.exp | 32 + usr.bin/make/unit-tests/hanoi-include.mk | 41 + usr.bin/make/unit-tests/hash.exp | 9 - usr.bin/make/unit-tests/hash.mk | 18 - usr.bin/make/unit-tests/impsrc.exp | 16 +- usr.bin/make/unit-tests/impsrc.mk | 29 +- usr.bin/make/unit-tests/include-main.exp | 17 + usr.bin/make/unit-tests/include-main.mk | 43 + usr.bin/make/unit-tests/include-sub.mk | 49 + usr.bin/make/unit-tests/include-subsub.mk | 9 + usr.bin/make/unit-tests/job-flags.exp | 12 + usr.bin/make/unit-tests/job-flags.mk | 32 + .../make/unit-tests/job-output-long-lines.exp | 1 + .../make/unit-tests/job-output-long-lines.mk | 32 + .../make/unit-tests/jobs-empty-commands.exp | 2 + .../make/unit-tests/jobs-empty-commands.mk | 18 + .../make/unit-tests/jobs-error-indirect.exp | 8 + .../make/unit-tests/jobs-error-indirect.mk | 21 + .../unit-tests/jobs-error-nested-make.exp | 11 + .../make/unit-tests/jobs-error-nested-make.mk | 20 + usr.bin/make/unit-tests/jobs-error-nested.exp | 15 + usr.bin/make/unit-tests/jobs-error-nested.mk | 20 + usr.bin/make/unit-tests/lint.exp | 4 + usr.bin/make/unit-tests/lint.mk | 22 + usr.bin/make/unit-tests/make-exported.exp | 2 + usr.bin/make/unit-tests/make-exported.mk | 25 + usr.bin/make/unit-tests/meta-cmd-cmp.exp | 37 + usr.bin/make/unit-tests/meta-cmd-cmp.mk | 52 + usr.bin/make/unit-tests/misc.mk | 16 - usr.bin/make/unit-tests/moderrs.exp | 154 +- usr.bin/make/unit-tests/moderrs.mk | 179 +- usr.bin/make/unit-tests/modmatch.mk | 21 +- usr.bin/make/unit-tests/modmisc.exp | 10 + usr.bin/make/unit-tests/modmisc.mk | 49 +- usr.bin/make/unit-tests/modorder.exp | 11 - usr.bin/make/unit-tests/modorder.mk | 22 - usr.bin/make/unit-tests/modts.exp | 37 +- usr.bin/make/unit-tests/modts.mk | 56 +- usr.bin/make/unit-tests/modword.exp | 26 +- usr.bin/make/unit-tests/modword.mk | 38 +- usr.bin/make/unit-tests/objdir-writable.exp | 5 + usr.bin/make/unit-tests/objdir-writable.mk | 31 + usr.bin/make/unit-tests/opt-backwards.exp | 1 + usr.bin/make/unit-tests/opt-backwards.mk | 8 + usr.bin/make/unit-tests/opt-chdir.exp | 6 + usr.bin/make/unit-tests/opt-chdir.mk | 27 + usr.bin/make/unit-tests/opt-debug-all.exp | 1 + usr.bin/make/unit-tests/opt-debug-all.mk | 10 + usr.bin/make/unit-tests/opt-debug-archive.exp | 1 + usr.bin/make/unit-tests/opt-debug-archive.mk | 9 + usr.bin/make/unit-tests/opt-debug-cond.exp | 1 + usr.bin/make/unit-tests/opt-debug-cond.mk | 10 + usr.bin/make/unit-tests/opt-debug-curdir.exp | 1 + usr.bin/make/unit-tests/opt-debug-curdir.mk | 8 + usr.bin/make/unit-tests/opt-debug-dir.exp | 1 + usr.bin/make/unit-tests/opt-debug-dir.mk | 9 + usr.bin/make/unit-tests/opt-debug-errors.exp | 37 + usr.bin/make/unit-tests/opt-debug-errors.mk | 42 + usr.bin/make/unit-tests/opt-debug-file.exp | 1 + usr.bin/make/unit-tests/opt-debug-file.mk | 37 + usr.bin/make/unit-tests/opt-debug-for.exp | 22 + usr.bin/make/unit-tests/opt-debug-for.mk | 26 + usr.bin/make/unit-tests/opt-debug-graph1.exp | 55 + usr.bin/make/unit-tests/opt-debug-graph1.mk | 23 + usr.bin/make/unit-tests/opt-debug-graph2.exp | 93 + usr.bin/make/unit-tests/opt-debug-graph2.mk | 23 + usr.bin/make/unit-tests/opt-debug-graph3.exp | 93 + usr.bin/make/unit-tests/opt-debug-graph3.mk | 23 + usr.bin/make/unit-tests/opt-debug-hash.exp | 1 + usr.bin/make/unit-tests/opt-debug-hash.mk | 10 + usr.bin/make/unit-tests/opt-debug-jobs.exp | 27 + usr.bin/make/unit-tests/opt-debug-jobs.mk | 33 + usr.bin/make/unit-tests/opt-debug-lint.exp | 8 + usr.bin/make/unit-tests/opt-debug-lint.mk | 95 + usr.bin/make/unit-tests/opt-debug-loud.exp | 3 + usr.bin/make/unit-tests/opt-debug-loud.mk | 22 + usr.bin/make/unit-tests/opt-debug-making.exp | 1 + usr.bin/make/unit-tests/opt-debug-making.mk | 9 + usr.bin/make/unit-tests/opt-debug-meta.exp | 1 + usr.bin/make/unit-tests/opt-debug-meta.mk | 9 + usr.bin/make/unit-tests/opt-debug-no-rm.exp | 1 + usr.bin/make/unit-tests/opt-debug-no-rm.mk | 13 + usr.bin/make/unit-tests/opt-debug-parse.exp | 1 + usr.bin/make/unit-tests/opt-debug-parse.mk | 9 + usr.bin/make/unit-tests/opt-debug-suff.exp | 1 + usr.bin/make/unit-tests/opt-debug-suff.mk | 11 + usr.bin/make/unit-tests/opt-debug-targets.exp | 1 + usr.bin/make/unit-tests/opt-debug-targets.mk | 11 + usr.bin/make/unit-tests/opt-debug-var.exp | 1 + usr.bin/make/unit-tests/opt-debug-var.mk | 9 + usr.bin/make/unit-tests/opt-debug-varraw.exp | 1 + usr.bin/make/unit-tests/opt-debug-varraw.mk | 12 + usr.bin/make/unit-tests/opt-debug-x-trace.exp | 1 + usr.bin/make/unit-tests/opt-debug-x-trace.mk | 10 + usr.bin/make/unit-tests/opt-debug.exp | 4 + usr.bin/make/unit-tests/opt-debug.mk | 14 + usr.bin/make/unit-tests/opt-define.exp | 1 + usr.bin/make/unit-tests/opt-define.mk | 8 + usr.bin/make/unit-tests/opt-env.exp | 1 + usr.bin/make/unit-tests/opt-env.mk | 8 + usr.bin/make/unit-tests/opt-file.exp | 12 + usr.bin/make/unit-tests/opt-file.mk | 105 + usr.bin/make/unit-tests/opt-ignore.exp | 12 + usr.bin/make/unit-tests/opt-ignore.mk | 33 + usr.bin/make/unit-tests/opt-include-dir.exp | 1 + usr.bin/make/unit-tests/opt-include-dir.mk | 8 + usr.bin/make/unit-tests/opt-jobs-internal.exp | 1 + usr.bin/make/unit-tests/opt-jobs-internal.mk | 8 + .../make/unit-tests/opt-jobs-no-action.exp | 61 + usr.bin/make/unit-tests/opt-jobs-no-action.mk | 102 + usr.bin/make/unit-tests/opt-jobs.exp | 1 + usr.bin/make/unit-tests/opt-jobs.mk | 8 + .../unit-tests/opt-keep-going-multiple.exp | 9 + .../unit-tests/opt-keep-going-multiple.mk | 21 + usr.bin/make/unit-tests/opt-keep-going.exp | 9 + usr.bin/make/unit-tests/opt-keep-going.mk | 31 + usr.bin/make/unit-tests/opt-m-include-dir.exp | 2 + usr.bin/make/unit-tests/opt-m-include-dir.mk | 61 + .../make/unit-tests/opt-no-action-at-all.exp | 1 + .../make/unit-tests/opt-no-action-at-all.mk | 8 + .../unit-tests/opt-no-action-runflags.exp | 34 + .../make/unit-tests/opt-no-action-runflags.mk | 32 + .../make/unit-tests/opt-no-action-touch.exp | 11 + .../make/unit-tests/opt-no-action-touch.mk | 48 + usr.bin/make/unit-tests/opt-no-action.exp | 13 + usr.bin/make/unit-tests/opt-no-action.mk | 35 + usr.bin/make/unit-tests/opt-query.exp | 2 + usr.bin/make/unit-tests/opt-query.mk | 26 + usr.bin/make/unit-tests/opt-raw.exp | 1 + usr.bin/make/unit-tests/opt-raw.mk | 8 + usr.bin/make/unit-tests/opt-silent.exp | 1 + usr.bin/make/unit-tests/opt-silent.mk | 8 + usr.bin/make/unit-tests/opt-touch-jobs.exp | 4 + usr.bin/make/unit-tests/opt-touch-jobs.mk | 36 + usr.bin/make/unit-tests/opt-touch.exp | 4 + usr.bin/make/unit-tests/opt-touch.mk | 21 + usr.bin/make/unit-tests/opt-tracefile.exp | 1 + usr.bin/make/unit-tests/opt-tracefile.mk | 8 + usr.bin/make/unit-tests/opt-var-expanded.exp | 3 + usr.bin/make/unit-tests/opt-var-expanded.mk | 8 + usr.bin/make/unit-tests/opt-var-literal.exp | 3 + usr.bin/make/unit-tests/opt-var-literal.mk | 8 + .../unit-tests/opt-warnings-as-errors.exp | 7 + .../make/unit-tests/opt-warnings-as-errors.mk | 18 + usr.bin/make/unit-tests/opt-where-am-i.exp | 1 + usr.bin/make/unit-tests/opt-where-am-i.mk | 8 + .../make/unit-tests/opt-x-reduce-exported.exp | 1 + .../make/unit-tests/opt-x-reduce-exported.mk | 8 + usr.bin/make/unit-tests/opt.exp | 26 + usr.bin/make/unit-tests/opt.mk | 28 + usr.bin/make/unit-tests/order.mk | 4 +- usr.bin/make/unit-tests/parse-var.exp | 1 + usr.bin/make/unit-tests/parse-var.mk | 13 + usr.bin/make/unit-tests/phony-end.exp | 2 +- usr.bin/make/unit-tests/phony-end.mk | 2 +- usr.bin/make/unit-tests/posix.exp | 5 +- usr.bin/make/unit-tests/posix.mk | 2 +- usr.bin/make/unit-tests/posix1.mk | 20 +- usr.bin/make/unit-tests/qequals.mk | 8 - usr.bin/make/unit-tests/recursive.exp | 5 + usr.bin/make/unit-tests/recursive.mk | 38 + usr.bin/make/unit-tests/sh-dots.exp | 22 + usr.bin/make/unit-tests/sh-dots.mk | 54 + usr.bin/make/unit-tests/sh-errctl.exp | 27 + usr.bin/make/unit-tests/sh-errctl.mk | 26 + usr.bin/make/unit-tests/sh-flags.exp | 4325 +++++++++ usr.bin/make/unit-tests/sh-flags.mk | 138 + usr.bin/make/unit-tests/sh-jobs-error.exp | 1 + usr.bin/make/unit-tests/sh-jobs-error.mk | 9 + usr.bin/make/unit-tests/sh-jobs.exp | 6 + usr.bin/make/unit-tests/sh-jobs.mk | 35 + usr.bin/make/unit-tests/sh-leading-at.exp | 6 + usr.bin/make/unit-tests/sh-leading-at.mk | 18 + usr.bin/make/unit-tests/sh-leading-hyphen.exp | 1 + usr.bin/make/unit-tests/sh-leading-hyphen.mk | 14 + usr.bin/make/unit-tests/sh-leading-plus.exp | 4 + usr.bin/make/unit-tests/sh-leading-plus.mk | 10 + usr.bin/make/unit-tests/sh-meta-chars.exp | 1 + usr.bin/make/unit-tests/sh-meta-chars.mk | 24 + usr.bin/make/unit-tests/sh-multi-line.exp | 1 + usr.bin/make/unit-tests/sh-multi-line.mk | 9 + usr.bin/make/unit-tests/sh-single-line.exp | 1 + usr.bin/make/unit-tests/sh-single-line.mk | 12 + usr.bin/make/unit-tests/sh.exp | 1 + usr.bin/make/unit-tests/sh.mk | 9 + usr.bin/make/unit-tests/shell-csh.exp | 9 + usr.bin/make/unit-tests/shell-csh.mk | 40 + usr.bin/make/unit-tests/shell-custom.exp | 8 + usr.bin/make/unit-tests/shell-custom.mk | 14 + usr.bin/make/unit-tests/shell-ksh.exp | 4 + usr.bin/make/unit-tests/shell-ksh.mk | 11 + usr.bin/make/unit-tests/shell-sh.exp | 4 + usr.bin/make/unit-tests/shell-sh.mk | 12 + usr.bin/make/unit-tests/suff-add-later.exp | 21 + usr.bin/make/unit-tests/suff-add-later.mk | 34 + .../make/unit-tests/suff-clear-regular.exp | 8 + usr.bin/make/unit-tests/suff-clear-regular.mk | 30 + usr.bin/make/unit-tests/suff-clear-single.exp | 6 + usr.bin/make/unit-tests/suff-clear-single.mk | 19 + usr.bin/make/unit-tests/suff-incomplete.exp | 42 + usr.bin/make/unit-tests/suff-incomplete.mk | 31 + usr.bin/make/unit-tests/suff-lookup.exp | 71 + usr.bin/make/unit-tests/suff-lookup.mk | 40 + usr.bin/make/unit-tests/suff-main-several.exp | 143 + usr.bin/make/unit-tests/suff-main-several.mk | 42 + usr.bin/make/unit-tests/suff-main.exp | 2 + usr.bin/make/unit-tests/suff-main.mk | 22 + usr.bin/make/unit-tests/suff-phony.exp | 13 + usr.bin/make/unit-tests/suff-phony.mk | 21 + usr.bin/make/unit-tests/suff-rebuild.exp | 73 + usr.bin/make/unit-tests/suff-rebuild.mk | 44 + usr.bin/make/unit-tests/suff-self.exp | 6 + usr.bin/make/unit-tests/suff-self.mk | 11 + .../make/unit-tests/suff-transform-debug.exp | 64 + .../make/unit-tests/suff-transform-debug.mk | 12 + .../unit-tests/suff-transform-endless.exp | 46 + .../make/unit-tests/suff-transform-endless.mk | 39 + .../make/unit-tests/suff-transform-expand.exp | 8 + .../make/unit-tests/suff-transform-expand.mk | 25 + .../make/unit-tests/suff-transform-select.exp | 47 + .../make/unit-tests/suff-transform-select.mk | 31 + usr.bin/make/unit-tests/suffixes.exp | 35 - usr.bin/make/unit-tests/suffixes.mk | 89 - usr.bin/make/unit-tests/sunshcmd.mk | 8 +- usr.bin/make/unit-tests/sysv.exp | 7 - usr.bin/make/unit-tests/sysv.mk | 26 - usr.bin/make/unit-tests/ternary.mk | 1 + usr.bin/make/unit-tests/unexport-env.mk | 7 +- usr.bin/make/unit-tests/unexport.mk | 15 +- usr.bin/make/unit-tests/use-inference.exp | 7 + usr.bin/make/unit-tests/use-inference.mk | 38 + usr.bin/make/unit-tests/var-class-cmdline.exp | 4 + usr.bin/make/unit-tests/var-class-cmdline.mk | 80 + usr.bin/make/unit-tests/var-class-env.exp | 1 + usr.bin/make/unit-tests/var-class-env.mk | 8 + usr.bin/make/unit-tests/var-class-global.exp | 1 + usr.bin/make/unit-tests/var-class-global.mk | 8 + .../unit-tests/var-class-local-legacy.exp | 1 + .../make/unit-tests/var-class-local-legacy.mk | 8 + usr.bin/make/unit-tests/var-class-local.exp | 5 + usr.bin/make/unit-tests/var-class-local.mk | 45 + usr.bin/make/unit-tests/var-class.exp | 1 + usr.bin/make/unit-tests/var-class.mk | 9 + usr.bin/make/unit-tests/var-eval-short.exp | 27 + usr.bin/make/unit-tests/var-eval-short.mk | 161 + usr.bin/make/unit-tests/var-op-append.exp | 7 + usr.bin/make/unit-tests/var-op-append.mk | 46 + usr.bin/make/unit-tests/var-op-assign.exp | 6 + usr.bin/make/unit-tests/var-op-assign.mk | 96 + usr.bin/make/unit-tests/var-op-default.exp | 1 + usr.bin/make/unit-tests/var-op-default.mk | 77 + usr.bin/make/unit-tests/var-op-expand.exp | 1 + usr.bin/make/unit-tests/var-op-expand.mk | 178 + usr.bin/make/unit-tests/var-op-shell.exp | 7 + usr.bin/make/unit-tests/var-op-shell.mk | 81 + usr.bin/make/unit-tests/var-op-sunsh.exp | 1 + usr.bin/make/unit-tests/var-op-sunsh.mk | 124 + usr.bin/make/unit-tests/var-op.exp | 1 + usr.bin/make/unit-tests/var-op.mk | 8 + usr.bin/make/unit-tests/var-recursive.exp | 12 + usr.bin/make/unit-tests/var-recursive.mk | 49 + usr.bin/make/unit-tests/varcmd.exp | 2 + usr.bin/make/unit-tests/varcmd.mk | 45 +- usr.bin/make/unit-tests/vardebug.exp | 86 + usr.bin/make/unit-tests/vardebug.mk | 64 + usr.bin/make/unit-tests/varfind.exp | 15 + usr.bin/make/unit-tests/varfind.mk | 31 + usr.bin/make/unit-tests/varmisc.exp | 72 + usr.bin/make/unit-tests/varmisc.mk | 213 +- usr.bin/make/unit-tests/varmod-assign.exp | 40 + usr.bin/make/unit-tests/varmod-assign.mk | 141 + usr.bin/make/unit-tests/varmod-defined.exp | 23 + usr.bin/make/unit-tests/varmod-defined.mk | 105 + usr.bin/make/unit-tests/varmod-edge.exp | 27 + usr.bin/make/unit-tests/varmod-edge.mk | 195 + .../make/unit-tests/varmod-exclam-shell.exp | 1 + .../make/unit-tests/varmod-exclam-shell.mk | 37 + usr.bin/make/unit-tests/varmod-extension.exp | 11 + usr.bin/make/unit-tests/varmod-extension.mk | 9 + usr.bin/make/unit-tests/varmod-gmtime.exp | 13 + usr.bin/make/unit-tests/varmod-gmtime.mk | 137 + usr.bin/make/unit-tests/varmod-hash.exp | 9 + usr.bin/make/unit-tests/varmod-hash.mk | 64 + usr.bin/make/unit-tests/varmod-head.exp | 11 + usr.bin/make/unit-tests/varmod-head.mk | 9 + usr.bin/make/unit-tests/varmod-ifelse.exp | 20 + usr.bin/make/unit-tests/varmod-ifelse.mk | 115 + usr.bin/make/unit-tests/varmod-indirect.exp | 61 + usr.bin/make/unit-tests/varmod-indirect.mk | 247 + .../unit-tests/varmod-l-name-to-value.exp | 1 + .../make/unit-tests/varmod-l-name-to-value.mk | 44 + usr.bin/make/unit-tests/varmod-localtime.exp | 13 + usr.bin/make/unit-tests/varmod-localtime.mk | 137 + usr.bin/make/unit-tests/varmod-loop.exp | 20 + usr.bin/make/unit-tests/varmod-loop.mk | 258 + .../make/unit-tests/varmod-match-escape.exp | 61 + .../make/unit-tests/varmod-match-escape.mk | 90 + usr.bin/make/unit-tests/varmod-match.exp | 12 + usr.bin/make/unit-tests/varmod-match.mk | 60 + usr.bin/make/unit-tests/varmod-no-match.exp | 1 + usr.bin/make/unit-tests/varmod-no-match.mk | 9 + .../make/unit-tests/varmod-order-reverse.exp | 1 + .../make/unit-tests/varmod-order-reverse.mk | 13 + .../make/unit-tests/varmod-order-shuffle.exp | 1 + .../make/unit-tests/varmod-order-shuffle.mk | 43 + usr.bin/make/unit-tests/varmod-order.exp | 7 + usr.bin/make/unit-tests/varmod-order.mk | 19 + usr.bin/make/unit-tests/varmod-path.exp | 4 + usr.bin/make/unit-tests/varmod-path.mk | 35 + .../make/unit-tests/varmod-quote-dollar.exp | 1 + .../make/unit-tests/varmod-quote-dollar.mk | 10 + usr.bin/make/unit-tests/varmod-quote.exp | 1 + usr.bin/make/unit-tests/varmod-quote.mk | 21 + usr.bin/make/unit-tests/varmod-range.exp | 13 + usr.bin/make/unit-tests/varmod-range.mk | 98 + usr.bin/make/unit-tests/varmod-remember.exp | 1 + usr.bin/make/unit-tests/varmod-remember.mk | 35 + usr.bin/make/unit-tests/varmod-root.exp | 11 + usr.bin/make/unit-tests/varmod-root.mk | 9 + .../make/unit-tests/varmod-select-words.exp | 1 + .../make/unit-tests/varmod-select-words.mk | 9 + usr.bin/make/unit-tests/varmod-shell.exp | 3 + usr.bin/make/unit-tests/varmod-shell.mk | 32 + .../make/unit-tests/varmod-subst-regex.exp | 25 + usr.bin/make/unit-tests/varmod-subst-regex.mk | 109 + usr.bin/make/unit-tests/varmod-subst.exp | 62 + usr.bin/make/unit-tests/varmod-subst.mk | 181 + usr.bin/make/unit-tests/varmod-sun-shell.exp | 2 + usr.bin/make/unit-tests/varmod-sun-shell.mk | 21 + usr.bin/make/unit-tests/varmod-sysv.exp | 5 + usr.bin/make/unit-tests/varmod-sysv.mk | 241 + usr.bin/make/unit-tests/varmod-tail.exp | 11 + usr.bin/make/unit-tests/varmod-tail.mk | 9 + usr.bin/make/unit-tests/varmod-to-abs.exp | 5 + usr.bin/make/unit-tests/varmod-to-abs.mk | 28 + usr.bin/make/unit-tests/varmod-to-lower.exp | 1 + usr.bin/make/unit-tests/varmod-to-lower.mk | 21 + .../make/unit-tests/varmod-to-many-words.exp | 1 + .../make/unit-tests/varmod-to-many-words.mk | 21 + .../make/unit-tests/varmod-to-one-word.exp | 1 + usr.bin/make/unit-tests/varmod-to-one-word.mk | 21 + .../make/unit-tests/varmod-to-separator.exp | 19 + .../make/unit-tests/varmod-to-separator.mk | 175 + usr.bin/make/unit-tests/varmod-to-upper.exp | 2 + usr.bin/make/unit-tests/varmod-to-upper.mk | 21 + usr.bin/make/unit-tests/varmod-undefined.exp | 1 + usr.bin/make/unit-tests/varmod-undefined.mk | 68 + usr.bin/make/unit-tests/varmod-unique.exp | 1 + usr.bin/make/unit-tests/varmod-unique.mk | 39 + usr.bin/make/unit-tests/varmod.exp | 8 + usr.bin/make/unit-tests/varmod.mk | 60 + usr.bin/make/unit-tests/varname-dollar.exp | 5 + usr.bin/make/unit-tests/varname-dollar.mk | 29 + .../unit-tests/varname-dot-alltargets.exp | 4 + .../make/unit-tests/varname-dot-alltargets.mk | 25 + .../make/unit-tests/varname-dot-curdir.exp | 1 + usr.bin/make/unit-tests/varname-dot-curdir.mk | 45 + .../varname-dot-includedfromdir.exp | 1 + .../unit-tests/varname-dot-includedfromdir.mk | 8 + .../varname-dot-includedfromfile.exp | 1 + .../varname-dot-includedfromfile.mk | 8 + .../make/unit-tests/varname-dot-includes.exp | 2 + .../make/unit-tests/varname-dot-includes.mk | 20 + usr.bin/make/unit-tests/varname-dot-libs.exp | 2 + usr.bin/make/unit-tests/varname-dot-libs.mk | 20 + .../varname-dot-make-dependfile.exp | 1 + .../unit-tests/varname-dot-make-dependfile.mk | 8 + .../varname-dot-make-expand_variables.exp | 1 + .../varname-dot-make-expand_variables.mk | 8 + .../unit-tests/varname-dot-make-exported.exp | 1 + .../unit-tests/varname-dot-make-exported.mk | 8 + .../varname-dot-make-jobs-prefix.exp | 1 + .../varname-dot-make-jobs-prefix.mk | 8 + .../make/unit-tests/varname-dot-make-jobs.exp | 1 + .../make/unit-tests/varname-dot-make-jobs.mk | 8 + .../unit-tests/varname-dot-make-level.exp | 1 + .../make/unit-tests/varname-dot-make-level.mk | 8 + .../varname-dot-make-makefile_preference.exp | 1 + .../varname-dot-make-makefile_preference.mk | 8 + .../unit-tests/varname-dot-make-makefiles.exp | 1 + .../unit-tests/varname-dot-make-makefiles.mk | 8 + .../varname-dot-make-meta-bailiwick.exp | 1 + .../varname-dot-make-meta-bailiwick.mk | 8 + .../varname-dot-make-meta-created.exp | 1 + .../varname-dot-make-meta-created.mk | 8 + .../varname-dot-make-meta-files.exp | 1 + .../unit-tests/varname-dot-make-meta-files.mk | 8 + .../varname-dot-make-meta-ignore_filter.exp | 1 + .../varname-dot-make-meta-ignore_filter.mk | 8 + .../varname-dot-make-meta-ignore_paths.exp | 1 + .../varname-dot-make-meta-ignore_paths.mk | 8 + .../varname-dot-make-meta-ignore_patterns.exp | 1 + .../varname-dot-make-meta-ignore_patterns.mk | 8 + .../varname-dot-make-meta-prefix.exp | 1 + .../varname-dot-make-meta-prefix.mk | 8 + .../make/unit-tests/varname-dot-make-mode.exp | 1 + .../make/unit-tests/varname-dot-make-mode.mk | 8 + .../varname-dot-make-path_filemon.exp | 1 + .../varname-dot-make-path_filemon.mk | 8 + .../make/unit-tests/varname-dot-make-pid.exp | 1 + .../make/unit-tests/varname-dot-make-pid.mk | 8 + .../make/unit-tests/varname-dot-make-ppid.exp | 1 + .../make/unit-tests/varname-dot-make-ppid.mk | 8 + .../varname-dot-make-save_dollars.exp | 1 + .../varname-dot-make-save_dollars.mk | 8 + .../make/unit-tests/varname-dot-makeflags.exp | 3 + .../make/unit-tests/varname-dot-makeflags.mk | 15 + .../unit-tests/varname-dot-makeoverrides.exp | 1 + .../unit-tests/varname-dot-makeoverrides.mk | 8 + .../make/unit-tests/varname-dot-newline.exp | 4 + .../make/unit-tests/varname-dot-newline.mk | 23 + .../make/unit-tests/varname-dot-objdir.exp | 1 + usr.bin/make/unit-tests/varname-dot-objdir.mk | 8 + .../make/unit-tests/varname-dot-parsedir.exp | 5 + .../make/unit-tests/varname-dot-parsedir.mk | 41 + .../make/unit-tests/varname-dot-parsefile.exp | 5 + .../make/unit-tests/varname-dot-parsefile.mk | 36 + usr.bin/make/unit-tests/varname-dot-path.exp | 1 + usr.bin/make/unit-tests/varname-dot-path.mk | 56 + usr.bin/make/unit-tests/varname-dot-shell.exp | 32 + usr.bin/make/unit-tests/varname-dot-shell.mk | 36 + .../make/unit-tests/varname-dot-targets.exp | 1 + .../make/unit-tests/varname-dot-targets.mk | 8 + usr.bin/make/unit-tests/varname-empty.exp | 47 + usr.bin/make/unit-tests/varname-empty.mk | 69 + usr.bin/make/unit-tests/varname-make.exp | 1 + usr.bin/make/unit-tests/varname-make.mk | 8 + .../varname-make_print_var_on_error-jobs.exp | 8 + .../varname-make_print_var_on_error-jobs.mk | 28 + .../varname-make_print_var_on_error.exp | 9 + .../varname-make_print_var_on_error.mk | 23 + usr.bin/make/unit-tests/varname-makefile.exp | 2 + usr.bin/make/unit-tests/varname-makefile.mk | 49 + usr.bin/make/unit-tests/varname-makeflags.exp | 1 + usr.bin/make/unit-tests/varname-makeflags.mk | 26 + usr.bin/make/unit-tests/varname-pwd.exp | 1 + usr.bin/make/unit-tests/varname-pwd.mk | 8 + usr.bin/make/unit-tests/varname-vpath.exp | 12 + usr.bin/make/unit-tests/varname-vpath.mk | 42 + usr.bin/make/unit-tests/varname.exp | 24 + usr.bin/make/unit-tests/varname.mk | 44 + usr.bin/make/unit-tests/varparse-dynamic.exp | 5 + usr.bin/make/unit-tests/varparse-dynamic.mk | 35 + usr.bin/make/unit-tests/varparse-errors.exp | 5 + usr.bin/make/unit-tests/varparse-errors.mk | 51 + usr.bin/make/unit-tests/varparse-mod.exp | 1 + usr.bin/make/unit-tests/varparse-mod.mk | 61 + .../unit-tests/varparse-undef-partial.exp | 1 + .../make/unit-tests/varparse-undef-partial.mk | 64 + usr.bin/make/unit-tests/varquote.exp | 3 + usr.bin/make/unit-tests/varquote.mk | 14 + usr.bin/make/unit-tests/varshell.exp | 12 - usr.bin/make/unit-tests/varshell.mk | 18 - usr.bin/make/util.c | 220 +- usr.bin/make/var.c | 8224 +++++++++-------- 848 files changed, 47753 insertions(+), 25225 deletions(-) create mode 100644 usr.bin/make/enum.c create mode 100644 usr.bin/make/enum.h rename usr.bin/make/{strlist.h => filemon/filemon.h} (56%) create mode 100644 usr.bin/make/filemon/filemon_dev.c create mode 100644 usr.bin/make/filemon/filemon_ktrace.c create mode 100644 usr.bin/make/lst.c delete mode 100644 usr.bin/make/lst.lib/Makefile delete mode 100644 usr.bin/make/lst.lib/lstAppend.c delete mode 100644 usr.bin/make/lst.lib/lstAtEnd.c delete mode 100644 usr.bin/make/lst.lib/lstAtFront.c delete mode 100644 usr.bin/make/lst.lib/lstClose.c delete mode 100644 usr.bin/make/lst.lib/lstConcat.c delete mode 100644 usr.bin/make/lst.lib/lstDatum.c delete mode 100644 usr.bin/make/lst.lib/lstDeQueue.c delete mode 100644 usr.bin/make/lst.lib/lstDestroy.c delete mode 100644 usr.bin/make/lst.lib/lstDupl.c delete mode 100644 usr.bin/make/lst.lib/lstEnQueue.c delete mode 100644 usr.bin/make/lst.lib/lstFind.c delete mode 100644 usr.bin/make/lst.lib/lstFindFrom.c delete mode 100644 usr.bin/make/lst.lib/lstFirst.c delete mode 100644 usr.bin/make/lst.lib/lstForEach.c delete mode 100644 usr.bin/make/lst.lib/lstForEachFrom.c delete mode 100644 usr.bin/make/lst.lib/lstInit.c delete mode 100644 usr.bin/make/lst.lib/lstInsert.c delete mode 100644 usr.bin/make/lst.lib/lstInt.h delete mode 100644 usr.bin/make/lst.lib/lstIsAtEnd.c delete mode 100644 usr.bin/make/lst.lib/lstIsEmpty.c delete mode 100644 usr.bin/make/lst.lib/lstLast.c delete mode 100644 usr.bin/make/lst.lib/lstMember.c delete mode 100644 usr.bin/make/lst.lib/lstNext.c delete mode 100644 usr.bin/make/lst.lib/lstOpen.c delete mode 100644 usr.bin/make/lst.lib/lstPrev.c delete mode 100644 usr.bin/make/lst.lib/lstRemove.c delete mode 100644 usr.bin/make/lst.lib/lstReplace.c delete mode 100644 usr.bin/make/lst.lib/lstSucc.c delete mode 100644 usr.bin/make/sprite.h delete mode 100644 usr.bin/make/strlist.c create mode 100644 usr.bin/make/test-variants.sh create mode 100644 usr.bin/make/unit-tests/.cvsignore create mode 100644 usr.bin/make/unit-tests/archive-suffix.exp create mode 100644 usr.bin/make/unit-tests/archive-suffix.mk create mode 100644 usr.bin/make/unit-tests/archive.exp create mode 100644 usr.bin/make/unit-tests/archive.mk create mode 100644 usr.bin/make/unit-tests/cmd-errors-jobs.exp create mode 100644 usr.bin/make/unit-tests/cmd-errors-jobs.mk create mode 100644 usr.bin/make/unit-tests/cmd-errors-lint.exp create mode 100644 usr.bin/make/unit-tests/cmd-errors-lint.mk create mode 100644 usr.bin/make/unit-tests/cmd-errors.exp create mode 100644 usr.bin/make/unit-tests/cmd-errors.mk create mode 100644 usr.bin/make/unit-tests/cmd-interrupt.exp create mode 100644 usr.bin/make/unit-tests/cmd-interrupt.mk rename usr.bin/make/unit-tests/{misc.exp => cmdline-redirect-stdin.exp} (100%) create mode 100644 usr.bin/make/unit-tests/cmdline-redirect-stdin.mk create mode 100644 usr.bin/make/unit-tests/cmdline-undefined.exp create mode 100644 usr.bin/make/unit-tests/cmdline-undefined.mk create mode 100644 usr.bin/make/unit-tests/cmdline.exp create mode 100644 usr.bin/make/unit-tests/cmdline.mk create mode 100644 usr.bin/make/unit-tests/compat-error.exp create mode 100644 usr.bin/make/unit-tests/compat-error.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-eq.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-eq.mk rename usr.bin/make/unit-tests/{qequals.exp => cond-cmp-numeric-ge.exp} (51%) create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-ge.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-gt.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-gt.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-le.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-le.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-lt.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-lt.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-ne.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric-ne.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-numeric.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-string.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-string.mk create mode 100644 usr.bin/make/unit-tests/cond-cmp-unary.exp create mode 100644 usr.bin/make/unit-tests/cond-cmp-unary.mk create mode 100644 usr.bin/make/unit-tests/cond-eof.exp create mode 100644 usr.bin/make/unit-tests/cond-eof.mk create mode 100644 usr.bin/make/unit-tests/cond-func-commands.exp create mode 100644 usr.bin/make/unit-tests/cond-func-commands.mk create mode 100644 usr.bin/make/unit-tests/cond-func-defined.exp create mode 100644 usr.bin/make/unit-tests/cond-func-defined.mk create mode 100644 usr.bin/make/unit-tests/cond-func-empty.exp create mode 100644 usr.bin/make/unit-tests/cond-func-empty.mk create mode 100644 usr.bin/make/unit-tests/cond-func-exists.exp create mode 100644 usr.bin/make/unit-tests/cond-func-exists.mk create mode 100644 usr.bin/make/unit-tests/cond-func-make-main.exp create mode 100644 usr.bin/make/unit-tests/cond-func-make-main.mk create mode 100644 usr.bin/make/unit-tests/cond-func-make.exp create mode 100644 usr.bin/make/unit-tests/cond-func-make.mk create mode 100644 usr.bin/make/unit-tests/cond-func-target.exp create mode 100644 usr.bin/make/unit-tests/cond-func-target.mk create mode 100644 usr.bin/make/unit-tests/cond-func.exp create mode 100644 usr.bin/make/unit-tests/cond-func.mk create mode 100644 usr.bin/make/unit-tests/cond-late.exp create mode 100644 usr.bin/make/unit-tests/cond-late.mk create mode 100644 usr.bin/make/unit-tests/cond-op-and-lint.exp create mode 100644 usr.bin/make/unit-tests/cond-op-and-lint.mk create mode 100644 usr.bin/make/unit-tests/cond-op-and.exp create mode 100644 usr.bin/make/unit-tests/cond-op-and.mk create mode 100644 usr.bin/make/unit-tests/cond-op-not.exp create mode 100644 usr.bin/make/unit-tests/cond-op-not.mk create mode 100644 usr.bin/make/unit-tests/cond-op-or-lint.exp create mode 100644 usr.bin/make/unit-tests/cond-op-or-lint.mk create mode 100644 usr.bin/make/unit-tests/cond-op-or.exp create mode 100644 usr.bin/make/unit-tests/cond-op-or.mk create mode 100644 usr.bin/make/unit-tests/cond-op-parentheses.exp create mode 100644 usr.bin/make/unit-tests/cond-op-parentheses.mk create mode 100644 usr.bin/make/unit-tests/cond-op.exp create mode 100644 usr.bin/make/unit-tests/cond-op.mk create mode 100644 usr.bin/make/unit-tests/cond-short.exp create mode 100644 usr.bin/make/unit-tests/cond-short.mk create mode 100644 usr.bin/make/unit-tests/cond-token-number.exp create mode 100644 usr.bin/make/unit-tests/cond-token-number.mk create mode 100644 usr.bin/make/unit-tests/cond-token-plain.exp create mode 100644 usr.bin/make/unit-tests/cond-token-plain.mk create mode 100644 usr.bin/make/unit-tests/cond-token-string.exp create mode 100644 usr.bin/make/unit-tests/cond-token-string.mk create mode 100644 usr.bin/make/unit-tests/cond-token-var.exp create mode 100644 usr.bin/make/unit-tests/cond-token-var.mk create mode 100644 usr.bin/make/unit-tests/cond-undef-lint.exp create mode 100644 usr.bin/make/unit-tests/cond-undef-lint.mk delete mode 100644 usr.bin/make/unit-tests/cond2.exp delete mode 100644 usr.bin/make/unit-tests/cond2.mk create mode 100644 usr.bin/make/unit-tests/counter-append.exp create mode 100644 usr.bin/make/unit-tests/counter-append.mk create mode 100644 usr.bin/make/unit-tests/counter.exp create mode 100644 usr.bin/make/unit-tests/counter.mk create mode 100644 usr.bin/make/unit-tests/dep-colon-bug-cross-file.exp create mode 100644 usr.bin/make/unit-tests/dep-colon-bug-cross-file.mk create mode 100644 usr.bin/make/unit-tests/dep-colon.exp create mode 100644 usr.bin/make/unit-tests/dep-colon.mk create mode 100644 usr.bin/make/unit-tests/dep-double-colon-indep.exp create mode 100644 usr.bin/make/unit-tests/dep-double-colon-indep.mk create mode 100644 usr.bin/make/unit-tests/dep-double-colon.exp create mode 100644 usr.bin/make/unit-tests/dep-double-colon.mk create mode 100644 usr.bin/make/unit-tests/dep-exclam.exp create mode 100644 usr.bin/make/unit-tests/dep-exclam.mk create mode 100644 usr.bin/make/unit-tests/dep-none.exp create mode 100644 usr.bin/make/unit-tests/dep-none.mk create mode 100644 usr.bin/make/unit-tests/dep-percent.exp create mode 100644 usr.bin/make/unit-tests/dep-percent.mk create mode 100644 usr.bin/make/unit-tests/dep-var.exp create mode 100644 usr.bin/make/unit-tests/dep-var.mk create mode 100644 usr.bin/make/unit-tests/dep-wildcards.exp create mode 100644 usr.bin/make/unit-tests/dep-wildcards.mk create mode 100644 usr.bin/make/unit-tests/dep.exp create mode 100644 usr.bin/make/unit-tests/dep.mk create mode 100644 usr.bin/make/unit-tests/depsrc-end.exp create mode 100644 usr.bin/make/unit-tests/depsrc-end.mk create mode 100644 usr.bin/make/unit-tests/depsrc-exec.exp create mode 100644 usr.bin/make/unit-tests/depsrc-exec.mk create mode 100644 usr.bin/make/unit-tests/depsrc-ignore.exp create mode 100644 usr.bin/make/unit-tests/depsrc-ignore.mk create mode 100644 usr.bin/make/unit-tests/depsrc-made.exp create mode 100644 usr.bin/make/unit-tests/depsrc-made.mk create mode 100644 usr.bin/make/unit-tests/depsrc-make.exp create mode 100644 usr.bin/make/unit-tests/depsrc-make.mk create mode 100644 usr.bin/make/unit-tests/depsrc-meta.exp create mode 100644 usr.bin/make/unit-tests/depsrc-meta.mk create mode 100644 usr.bin/make/unit-tests/depsrc-nometa.exp create mode 100644 usr.bin/make/unit-tests/depsrc-nometa.mk create mode 100644 usr.bin/make/unit-tests/depsrc-nometa_cmp.exp create mode 100644 usr.bin/make/unit-tests/depsrc-nometa_cmp.mk create mode 100644 usr.bin/make/unit-tests/depsrc-nopath.exp create mode 100644 usr.bin/make/unit-tests/depsrc-nopath.mk create mode 100644 usr.bin/make/unit-tests/depsrc-notmain.exp create mode 100644 usr.bin/make/unit-tests/depsrc-notmain.mk create mode 100644 usr.bin/make/unit-tests/depsrc-optional.exp create mode 100644 usr.bin/make/unit-tests/depsrc-optional.mk create mode 100644 usr.bin/make/unit-tests/depsrc-phony.exp create mode 100644 usr.bin/make/unit-tests/depsrc-phony.mk create mode 100644 usr.bin/make/unit-tests/depsrc-precious.exp create mode 100644 usr.bin/make/unit-tests/depsrc-precious.mk create mode 100644 usr.bin/make/unit-tests/depsrc-recursive.exp create mode 100644 usr.bin/make/unit-tests/depsrc-recursive.mk create mode 100644 usr.bin/make/unit-tests/depsrc-silent.exp create mode 100644 usr.bin/make/unit-tests/depsrc-silent.mk create mode 100644 usr.bin/make/unit-tests/depsrc-use.exp create mode 100644 usr.bin/make/unit-tests/depsrc-use.mk create mode 100644 usr.bin/make/unit-tests/depsrc-usebefore-double-colon.exp create mode 100644 usr.bin/make/unit-tests/depsrc-usebefore-double-colon.mk create mode 100644 usr.bin/make/unit-tests/depsrc-usebefore.exp create mode 100644 usr.bin/make/unit-tests/depsrc-usebefore.mk create mode 100644 usr.bin/make/unit-tests/depsrc-wait.exp create mode 100644 usr.bin/make/unit-tests/depsrc-wait.mk create mode 100644 usr.bin/make/unit-tests/depsrc.exp create mode 100644 usr.bin/make/unit-tests/depsrc.mk create mode 100644 usr.bin/make/unit-tests/deptgt-begin-fail-indirect.exp create mode 100644 usr.bin/make/unit-tests/deptgt-begin-fail-indirect.mk create mode 100644 usr.bin/make/unit-tests/deptgt-begin-fail.exp create mode 100644 usr.bin/make/unit-tests/deptgt-begin-fail.mk create mode 100644 usr.bin/make/unit-tests/deptgt-begin.exp create mode 100644 usr.bin/make/unit-tests/deptgt-begin.mk create mode 100644 usr.bin/make/unit-tests/deptgt-default.exp create mode 100644 usr.bin/make/unit-tests/deptgt-default.mk create mode 100644 usr.bin/make/unit-tests/deptgt-delete_on_error.exp create mode 100644 usr.bin/make/unit-tests/deptgt-delete_on_error.mk create mode 100644 usr.bin/make/unit-tests/deptgt-end-fail-all.exp create mode 100644 usr.bin/make/unit-tests/deptgt-end-fail-all.mk create mode 100644 usr.bin/make/unit-tests/deptgt-end-fail-indirect.exp create mode 100644 usr.bin/make/unit-tests/deptgt-end-fail-indirect.mk create mode 100644 usr.bin/make/unit-tests/deptgt-end-fail.exp create mode 100644 usr.bin/make/unit-tests/deptgt-end-fail.mk create mode 100644 usr.bin/make/unit-tests/deptgt-end-jobs.exp create mode 100644 usr.bin/make/unit-tests/deptgt-end-jobs.mk create mode 100644 usr.bin/make/unit-tests/deptgt-end.exp create mode 100644 usr.bin/make/unit-tests/deptgt-end.mk create mode 100644 usr.bin/make/unit-tests/deptgt-error.exp create mode 100644 usr.bin/make/unit-tests/deptgt-error.mk create mode 100644 usr.bin/make/unit-tests/deptgt-ignore.exp create mode 100644 usr.bin/make/unit-tests/deptgt-ignore.mk create mode 100644 usr.bin/make/unit-tests/deptgt-interrupt.exp create mode 100644 usr.bin/make/unit-tests/deptgt-interrupt.mk create mode 100644 usr.bin/make/unit-tests/deptgt-main.exp create mode 100644 usr.bin/make/unit-tests/deptgt-main.mk create mode 100644 usr.bin/make/unit-tests/deptgt-makeflags.exp create mode 100644 usr.bin/make/unit-tests/deptgt-makeflags.mk create mode 100644 usr.bin/make/unit-tests/deptgt-no_parallel.exp create mode 100644 usr.bin/make/unit-tests/deptgt-no_parallel.mk create mode 100644 usr.bin/make/unit-tests/deptgt-nopath.exp create mode 100644 usr.bin/make/unit-tests/deptgt-nopath.mk create mode 100644 usr.bin/make/unit-tests/deptgt-notparallel.exp create mode 100644 usr.bin/make/unit-tests/deptgt-notparallel.mk create mode 100644 usr.bin/make/unit-tests/deptgt-objdir.exp create mode 100644 usr.bin/make/unit-tests/deptgt-objdir.mk create mode 100644 usr.bin/make/unit-tests/deptgt-order.exp create mode 100644 usr.bin/make/unit-tests/deptgt-order.mk create mode 100644 usr.bin/make/unit-tests/deptgt-path-suffix.exp create mode 100644 usr.bin/make/unit-tests/deptgt-path-suffix.mk create mode 100644 usr.bin/make/unit-tests/deptgt-path.exp create mode 100644 usr.bin/make/unit-tests/deptgt-path.mk create mode 100644 usr.bin/make/unit-tests/deptgt-phony.exp create mode 100644 usr.bin/make/unit-tests/deptgt-phony.mk create mode 100644 usr.bin/make/unit-tests/deptgt-precious.exp create mode 100644 usr.bin/make/unit-tests/deptgt-precious.mk create mode 100644 usr.bin/make/unit-tests/deptgt-shell.exp create mode 100644 usr.bin/make/unit-tests/deptgt-shell.mk create mode 100644 usr.bin/make/unit-tests/deptgt-silent.exp create mode 100644 usr.bin/make/unit-tests/deptgt-silent.mk create mode 100644 usr.bin/make/unit-tests/deptgt-stale.exp create mode 100644 usr.bin/make/unit-tests/deptgt-stale.mk create mode 100644 usr.bin/make/unit-tests/deptgt-suffixes.exp create mode 100644 usr.bin/make/unit-tests/deptgt-suffixes.mk create mode 100644 usr.bin/make/unit-tests/deptgt.exp create mode 100644 usr.bin/make/unit-tests/deptgt.mk create mode 100644 usr.bin/make/unit-tests/dir-expand-path.exp create mode 100644 usr.bin/make/unit-tests/dir-expand-path.mk create mode 100644 usr.bin/make/unit-tests/dir.exp create mode 100644 usr.bin/make/unit-tests/dir.mk create mode 100644 usr.bin/make/unit-tests/directive-dinclude.exp create mode 100644 usr.bin/make/unit-tests/directive-dinclude.mk create mode 100644 usr.bin/make/unit-tests/directive-elif.exp create mode 100644 usr.bin/make/unit-tests/directive-elif.mk create mode 100644 usr.bin/make/unit-tests/directive-elifdef.exp create mode 100644 usr.bin/make/unit-tests/directive-elifdef.mk create mode 100644 usr.bin/make/unit-tests/directive-elifmake.exp create mode 100644 usr.bin/make/unit-tests/directive-elifmake.mk create mode 100644 usr.bin/make/unit-tests/directive-elifndef.exp create mode 100644 usr.bin/make/unit-tests/directive-elifndef.mk create mode 100644 usr.bin/make/unit-tests/directive-elifnmake.exp create mode 100644 usr.bin/make/unit-tests/directive-elifnmake.mk create mode 100644 usr.bin/make/unit-tests/directive-else.exp create mode 100644 usr.bin/make/unit-tests/directive-else.mk create mode 100644 usr.bin/make/unit-tests/directive-endfor.exp create mode 100644 usr.bin/make/unit-tests/directive-endfor.mk create mode 100644 usr.bin/make/unit-tests/directive-endif.exp create mode 100644 usr.bin/make/unit-tests/directive-endif.mk create mode 100644 usr.bin/make/unit-tests/directive-error.exp create mode 100644 usr.bin/make/unit-tests/directive-error.mk create mode 100644 usr.bin/make/unit-tests/directive-export-env.exp create mode 100644 usr.bin/make/unit-tests/directive-export-env.mk create mode 100644 usr.bin/make/unit-tests/directive-export-gmake.exp create mode 100644 usr.bin/make/unit-tests/directive-export-gmake.mk create mode 100644 usr.bin/make/unit-tests/directive-export-impl.exp create mode 100644 usr.bin/make/unit-tests/directive-export-impl.mk create mode 100644 usr.bin/make/unit-tests/directive-export-literal.exp create mode 100644 usr.bin/make/unit-tests/directive-export-literal.mk create mode 100644 usr.bin/make/unit-tests/directive-export.exp create mode 100644 usr.bin/make/unit-tests/directive-export.mk create mode 100644 usr.bin/make/unit-tests/directive-for-errors.exp create mode 100644 usr.bin/make/unit-tests/directive-for-errors.mk create mode 100644 usr.bin/make/unit-tests/directive-for-escape.exp create mode 100644 usr.bin/make/unit-tests/directive-for-escape.mk create mode 100644 usr.bin/make/unit-tests/directive-for-generating-endif.exp create mode 100644 usr.bin/make/unit-tests/directive-for-generating-endif.mk create mode 100644 usr.bin/make/unit-tests/directive-for-lines.exp create mode 100644 usr.bin/make/unit-tests/directive-for-lines.mk create mode 100644 usr.bin/make/unit-tests/directive-for-null.exp create mode 100644 usr.bin/make/unit-tests/directive-for-null.mk create mode 100644 usr.bin/make/unit-tests/directive-for.exp create mode 100644 usr.bin/make/unit-tests/directive-for.mk create mode 100644 usr.bin/make/unit-tests/directive-hyphen-include.exp create mode 100644 usr.bin/make/unit-tests/directive-hyphen-include.mk create mode 100644 usr.bin/make/unit-tests/directive-if-nested.exp create mode 100644 usr.bin/make/unit-tests/directive-if-nested.mk create mode 100644 usr.bin/make/unit-tests/directive-if.exp create mode 100644 usr.bin/make/unit-tests/directive-if.mk create mode 100644 usr.bin/make/unit-tests/directive-ifdef.exp create mode 100644 usr.bin/make/unit-tests/directive-ifdef.mk create mode 100644 usr.bin/make/unit-tests/directive-ifmake.exp create mode 100644 usr.bin/make/unit-tests/directive-ifmake.mk create mode 100644 usr.bin/make/unit-tests/directive-ifndef.exp create mode 100644 usr.bin/make/unit-tests/directive-ifndef.mk create mode 100644 usr.bin/make/unit-tests/directive-ifnmake.exp create mode 100644 usr.bin/make/unit-tests/directive-ifnmake.mk create mode 100644 usr.bin/make/unit-tests/directive-include-fatal.exp create mode 100644 usr.bin/make/unit-tests/directive-include-fatal.mk create mode 100644 usr.bin/make/unit-tests/directive-include.exp create mode 100644 usr.bin/make/unit-tests/directive-include.mk create mode 100644 usr.bin/make/unit-tests/directive-info.exp create mode 100644 usr.bin/make/unit-tests/directive-info.mk create mode 100644 usr.bin/make/unit-tests/directive-misspellings.exp create mode 100644 usr.bin/make/unit-tests/directive-misspellings.mk create mode 100644 usr.bin/make/unit-tests/directive-sinclude.exp create mode 100644 usr.bin/make/unit-tests/directive-sinclude.mk create mode 100644 usr.bin/make/unit-tests/directive-undef.exp create mode 100644 usr.bin/make/unit-tests/directive-undef.mk create mode 100644 usr.bin/make/unit-tests/directive-unexport-env.exp create mode 100644 usr.bin/make/unit-tests/directive-unexport-env.mk create mode 100644 usr.bin/make/unit-tests/directive-unexport.exp create mode 100644 usr.bin/make/unit-tests/directive-unexport.mk create mode 100644 usr.bin/make/unit-tests/directive-warning.exp create mode 100644 usr.bin/make/unit-tests/directive-warning.mk create mode 100644 usr.bin/make/unit-tests/directive.exp create mode 100644 usr.bin/make/unit-tests/directive.mk create mode 100644 usr.bin/make/unit-tests/dollar.exp create mode 100644 usr.bin/make/unit-tests/dollar.mk create mode 100644 usr.bin/make/unit-tests/envfirst.exp create mode 100644 usr.bin/make/unit-tests/envfirst.mk create mode 100644 usr.bin/make/unit-tests/export-variants.exp create mode 100644 usr.bin/make/unit-tests/export-variants.mk create mode 100644 usr.bin/make/unit-tests/gnode-submake.exp create mode 100644 usr.bin/make/unit-tests/gnode-submake.mk create mode 100644 usr.bin/make/unit-tests/hanoi-include.exp create mode 100644 usr.bin/make/unit-tests/hanoi-include.mk delete mode 100644 usr.bin/make/unit-tests/hash.exp delete mode 100644 usr.bin/make/unit-tests/hash.mk create mode 100644 usr.bin/make/unit-tests/include-main.exp create mode 100644 usr.bin/make/unit-tests/include-main.mk create mode 100644 usr.bin/make/unit-tests/include-sub.mk create mode 100644 usr.bin/make/unit-tests/include-subsub.mk create mode 100644 usr.bin/make/unit-tests/job-flags.exp create mode 100644 usr.bin/make/unit-tests/job-flags.mk create mode 100644 usr.bin/make/unit-tests/job-output-long-lines.exp create mode 100644 usr.bin/make/unit-tests/job-output-long-lines.mk create mode 100644 usr.bin/make/unit-tests/jobs-empty-commands.exp create mode 100644 usr.bin/make/unit-tests/jobs-empty-commands.mk create mode 100644 usr.bin/make/unit-tests/jobs-error-indirect.exp create mode 100644 usr.bin/make/unit-tests/jobs-error-indirect.mk create mode 100644 usr.bin/make/unit-tests/jobs-error-nested-make.exp create mode 100644 usr.bin/make/unit-tests/jobs-error-nested-make.mk create mode 100644 usr.bin/make/unit-tests/jobs-error-nested.exp create mode 100644 usr.bin/make/unit-tests/jobs-error-nested.mk create mode 100644 usr.bin/make/unit-tests/lint.exp create mode 100644 usr.bin/make/unit-tests/lint.mk create mode 100644 usr.bin/make/unit-tests/make-exported.exp create mode 100644 usr.bin/make/unit-tests/make-exported.mk create mode 100644 usr.bin/make/unit-tests/meta-cmd-cmp.exp create mode 100644 usr.bin/make/unit-tests/meta-cmd-cmp.mk delete mode 100644 usr.bin/make/unit-tests/misc.mk delete mode 100644 usr.bin/make/unit-tests/modorder.exp delete mode 100644 usr.bin/make/unit-tests/modorder.mk create mode 100644 usr.bin/make/unit-tests/objdir-writable.exp create mode 100644 usr.bin/make/unit-tests/objdir-writable.mk create mode 100644 usr.bin/make/unit-tests/opt-backwards.exp create mode 100644 usr.bin/make/unit-tests/opt-backwards.mk create mode 100644 usr.bin/make/unit-tests/opt-chdir.exp create mode 100644 usr.bin/make/unit-tests/opt-chdir.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-all.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-all.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-archive.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-archive.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-cond.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-cond.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-curdir.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-curdir.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-dir.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-dir.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-errors.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-errors.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-file.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-file.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-for.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-for.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-graph1.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-graph1.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-graph2.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-graph2.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-graph3.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-graph3.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-hash.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-hash.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-jobs.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-jobs.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-lint.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-lint.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-loud.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-loud.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-making.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-making.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-meta.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-meta.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-no-rm.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-no-rm.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-parse.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-parse.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-suff.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-suff.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-targets.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-targets.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-var.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-var.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-varraw.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-varraw.mk create mode 100644 usr.bin/make/unit-tests/opt-debug-x-trace.exp create mode 100644 usr.bin/make/unit-tests/opt-debug-x-trace.mk create mode 100644 usr.bin/make/unit-tests/opt-debug.exp create mode 100644 usr.bin/make/unit-tests/opt-debug.mk create mode 100644 usr.bin/make/unit-tests/opt-define.exp create mode 100644 usr.bin/make/unit-tests/opt-define.mk create mode 100644 usr.bin/make/unit-tests/opt-env.exp create mode 100644 usr.bin/make/unit-tests/opt-env.mk create mode 100644 usr.bin/make/unit-tests/opt-file.exp create mode 100644 usr.bin/make/unit-tests/opt-file.mk create mode 100644 usr.bin/make/unit-tests/opt-ignore.exp create mode 100644 usr.bin/make/unit-tests/opt-ignore.mk create mode 100644 usr.bin/make/unit-tests/opt-include-dir.exp create mode 100644 usr.bin/make/unit-tests/opt-include-dir.mk create mode 100644 usr.bin/make/unit-tests/opt-jobs-internal.exp create mode 100644 usr.bin/make/unit-tests/opt-jobs-internal.mk create mode 100644 usr.bin/make/unit-tests/opt-jobs-no-action.exp create mode 100644 usr.bin/make/unit-tests/opt-jobs-no-action.mk create mode 100644 usr.bin/make/unit-tests/opt-jobs.exp create mode 100644 usr.bin/make/unit-tests/opt-jobs.mk create mode 100644 usr.bin/make/unit-tests/opt-keep-going-multiple.exp create mode 100644 usr.bin/make/unit-tests/opt-keep-going-multiple.mk create mode 100644 usr.bin/make/unit-tests/opt-keep-going.exp create mode 100644 usr.bin/make/unit-tests/opt-keep-going.mk create mode 100644 usr.bin/make/unit-tests/opt-m-include-dir.exp create mode 100644 usr.bin/make/unit-tests/opt-m-include-dir.mk create mode 100644 usr.bin/make/unit-tests/opt-no-action-at-all.exp create mode 100644 usr.bin/make/unit-tests/opt-no-action-at-all.mk create mode 100644 usr.bin/make/unit-tests/opt-no-action-runflags.exp create mode 100644 usr.bin/make/unit-tests/opt-no-action-runflags.mk create mode 100644 usr.bin/make/unit-tests/opt-no-action-touch.exp create mode 100644 usr.bin/make/unit-tests/opt-no-action-touch.mk create mode 100644 usr.bin/make/unit-tests/opt-no-action.exp create mode 100644 usr.bin/make/unit-tests/opt-no-action.mk create mode 100644 usr.bin/make/unit-tests/opt-query.exp create mode 100644 usr.bin/make/unit-tests/opt-query.mk create mode 100644 usr.bin/make/unit-tests/opt-raw.exp create mode 100644 usr.bin/make/unit-tests/opt-raw.mk create mode 100644 usr.bin/make/unit-tests/opt-silent.exp create mode 100644 usr.bin/make/unit-tests/opt-silent.mk create mode 100644 usr.bin/make/unit-tests/opt-touch-jobs.exp create mode 100644 usr.bin/make/unit-tests/opt-touch-jobs.mk create mode 100644 usr.bin/make/unit-tests/opt-touch.exp create mode 100644 usr.bin/make/unit-tests/opt-touch.mk create mode 100644 usr.bin/make/unit-tests/opt-tracefile.exp create mode 100644 usr.bin/make/unit-tests/opt-tracefile.mk create mode 100644 usr.bin/make/unit-tests/opt-var-expanded.exp create mode 100644 usr.bin/make/unit-tests/opt-var-expanded.mk create mode 100644 usr.bin/make/unit-tests/opt-var-literal.exp create mode 100644 usr.bin/make/unit-tests/opt-var-literal.mk create mode 100644 usr.bin/make/unit-tests/opt-warnings-as-errors.exp create mode 100644 usr.bin/make/unit-tests/opt-warnings-as-errors.mk create mode 100644 usr.bin/make/unit-tests/opt-where-am-i.exp create mode 100644 usr.bin/make/unit-tests/opt-where-am-i.mk create mode 100644 usr.bin/make/unit-tests/opt-x-reduce-exported.exp create mode 100644 usr.bin/make/unit-tests/opt-x-reduce-exported.mk create mode 100644 usr.bin/make/unit-tests/opt.exp create mode 100644 usr.bin/make/unit-tests/opt.mk create mode 100644 usr.bin/make/unit-tests/parse-var.exp create mode 100644 usr.bin/make/unit-tests/parse-var.mk delete mode 100644 usr.bin/make/unit-tests/qequals.mk create mode 100644 usr.bin/make/unit-tests/recursive.exp create mode 100644 usr.bin/make/unit-tests/recursive.mk create mode 100644 usr.bin/make/unit-tests/sh-dots.exp create mode 100644 usr.bin/make/unit-tests/sh-dots.mk create mode 100644 usr.bin/make/unit-tests/sh-errctl.exp create mode 100644 usr.bin/make/unit-tests/sh-errctl.mk create mode 100644 usr.bin/make/unit-tests/sh-flags.exp create mode 100644 usr.bin/make/unit-tests/sh-flags.mk create mode 100644 usr.bin/make/unit-tests/sh-jobs-error.exp create mode 100644 usr.bin/make/unit-tests/sh-jobs-error.mk create mode 100644 usr.bin/make/unit-tests/sh-jobs.exp create mode 100644 usr.bin/make/unit-tests/sh-jobs.mk create mode 100644 usr.bin/make/unit-tests/sh-leading-at.exp create mode 100644 usr.bin/make/unit-tests/sh-leading-at.mk create mode 100644 usr.bin/make/unit-tests/sh-leading-hyphen.exp create mode 100644 usr.bin/make/unit-tests/sh-leading-hyphen.mk create mode 100644 usr.bin/make/unit-tests/sh-leading-plus.exp create mode 100644 usr.bin/make/unit-tests/sh-leading-plus.mk create mode 100644 usr.bin/make/unit-tests/sh-meta-chars.exp create mode 100644 usr.bin/make/unit-tests/sh-meta-chars.mk create mode 100644 usr.bin/make/unit-tests/sh-multi-line.exp create mode 100644 usr.bin/make/unit-tests/sh-multi-line.mk create mode 100644 usr.bin/make/unit-tests/sh-single-line.exp create mode 100644 usr.bin/make/unit-tests/sh-single-line.mk create mode 100644 usr.bin/make/unit-tests/sh.exp create mode 100644 usr.bin/make/unit-tests/sh.mk create mode 100644 usr.bin/make/unit-tests/shell-csh.exp create mode 100644 usr.bin/make/unit-tests/shell-csh.mk create mode 100644 usr.bin/make/unit-tests/shell-custom.exp create mode 100644 usr.bin/make/unit-tests/shell-custom.mk create mode 100644 usr.bin/make/unit-tests/shell-ksh.exp create mode 100644 usr.bin/make/unit-tests/shell-ksh.mk create mode 100644 usr.bin/make/unit-tests/shell-sh.exp create mode 100644 usr.bin/make/unit-tests/shell-sh.mk create mode 100644 usr.bin/make/unit-tests/suff-add-later.exp create mode 100644 usr.bin/make/unit-tests/suff-add-later.mk create mode 100644 usr.bin/make/unit-tests/suff-clear-regular.exp create mode 100644 usr.bin/make/unit-tests/suff-clear-regular.mk create mode 100644 usr.bin/make/unit-tests/suff-clear-single.exp create mode 100644 usr.bin/make/unit-tests/suff-clear-single.mk create mode 100644 usr.bin/make/unit-tests/suff-incomplete.exp create mode 100644 usr.bin/make/unit-tests/suff-incomplete.mk create mode 100644 usr.bin/make/unit-tests/suff-lookup.exp create mode 100644 usr.bin/make/unit-tests/suff-lookup.mk create mode 100644 usr.bin/make/unit-tests/suff-main-several.exp create mode 100644 usr.bin/make/unit-tests/suff-main-several.mk create mode 100644 usr.bin/make/unit-tests/suff-main.exp create mode 100644 usr.bin/make/unit-tests/suff-main.mk create mode 100644 usr.bin/make/unit-tests/suff-phony.exp create mode 100644 usr.bin/make/unit-tests/suff-phony.mk create mode 100644 usr.bin/make/unit-tests/suff-rebuild.exp create mode 100644 usr.bin/make/unit-tests/suff-rebuild.mk create mode 100644 usr.bin/make/unit-tests/suff-self.exp create mode 100644 usr.bin/make/unit-tests/suff-self.mk create mode 100644 usr.bin/make/unit-tests/suff-transform-debug.exp create mode 100644 usr.bin/make/unit-tests/suff-transform-debug.mk create mode 100644 usr.bin/make/unit-tests/suff-transform-endless.exp create mode 100644 usr.bin/make/unit-tests/suff-transform-endless.mk create mode 100644 usr.bin/make/unit-tests/suff-transform-expand.exp create mode 100644 usr.bin/make/unit-tests/suff-transform-expand.mk create mode 100644 usr.bin/make/unit-tests/suff-transform-select.exp create mode 100644 usr.bin/make/unit-tests/suff-transform-select.mk delete mode 100644 usr.bin/make/unit-tests/suffixes.exp delete mode 100644 usr.bin/make/unit-tests/suffixes.mk delete mode 100644 usr.bin/make/unit-tests/sysv.exp delete mode 100644 usr.bin/make/unit-tests/sysv.mk create mode 100644 usr.bin/make/unit-tests/use-inference.exp create mode 100644 usr.bin/make/unit-tests/use-inference.mk create mode 100644 usr.bin/make/unit-tests/var-class-cmdline.exp create mode 100644 usr.bin/make/unit-tests/var-class-cmdline.mk create mode 100644 usr.bin/make/unit-tests/var-class-env.exp create mode 100644 usr.bin/make/unit-tests/var-class-env.mk create mode 100644 usr.bin/make/unit-tests/var-class-global.exp create mode 100644 usr.bin/make/unit-tests/var-class-global.mk create mode 100644 usr.bin/make/unit-tests/var-class-local-legacy.exp create mode 100644 usr.bin/make/unit-tests/var-class-local-legacy.mk create mode 100644 usr.bin/make/unit-tests/var-class-local.exp create mode 100644 usr.bin/make/unit-tests/var-class-local.mk create mode 100644 usr.bin/make/unit-tests/var-class.exp create mode 100644 usr.bin/make/unit-tests/var-class.mk create mode 100644 usr.bin/make/unit-tests/var-eval-short.exp create mode 100644 usr.bin/make/unit-tests/var-eval-short.mk create mode 100644 usr.bin/make/unit-tests/var-op-append.exp create mode 100644 usr.bin/make/unit-tests/var-op-append.mk create mode 100644 usr.bin/make/unit-tests/var-op-assign.exp create mode 100644 usr.bin/make/unit-tests/var-op-assign.mk create mode 100644 usr.bin/make/unit-tests/var-op-default.exp create mode 100644 usr.bin/make/unit-tests/var-op-default.mk create mode 100644 usr.bin/make/unit-tests/var-op-expand.exp create mode 100644 usr.bin/make/unit-tests/var-op-expand.mk create mode 100644 usr.bin/make/unit-tests/var-op-shell.exp create mode 100644 usr.bin/make/unit-tests/var-op-shell.mk create mode 100644 usr.bin/make/unit-tests/var-op-sunsh.exp create mode 100644 usr.bin/make/unit-tests/var-op-sunsh.mk create mode 100644 usr.bin/make/unit-tests/var-op.exp create mode 100644 usr.bin/make/unit-tests/var-op.mk create mode 100644 usr.bin/make/unit-tests/var-recursive.exp create mode 100644 usr.bin/make/unit-tests/var-recursive.mk create mode 100644 usr.bin/make/unit-tests/vardebug.exp create mode 100644 usr.bin/make/unit-tests/vardebug.mk create mode 100644 usr.bin/make/unit-tests/varfind.exp create mode 100644 usr.bin/make/unit-tests/varfind.mk create mode 100644 usr.bin/make/unit-tests/varmod-assign.exp create mode 100644 usr.bin/make/unit-tests/varmod-assign.mk create mode 100644 usr.bin/make/unit-tests/varmod-defined.exp create mode 100644 usr.bin/make/unit-tests/varmod-defined.mk create mode 100644 usr.bin/make/unit-tests/varmod-edge.exp create mode 100644 usr.bin/make/unit-tests/varmod-edge.mk create mode 100644 usr.bin/make/unit-tests/varmod-exclam-shell.exp create mode 100644 usr.bin/make/unit-tests/varmod-exclam-shell.mk create mode 100644 usr.bin/make/unit-tests/varmod-extension.exp create mode 100644 usr.bin/make/unit-tests/varmod-extension.mk create mode 100644 usr.bin/make/unit-tests/varmod-gmtime.exp create mode 100644 usr.bin/make/unit-tests/varmod-gmtime.mk create mode 100644 usr.bin/make/unit-tests/varmod-hash.exp create mode 100644 usr.bin/make/unit-tests/varmod-hash.mk create mode 100644 usr.bin/make/unit-tests/varmod-head.exp create mode 100644 usr.bin/make/unit-tests/varmod-head.mk create mode 100644 usr.bin/make/unit-tests/varmod-ifelse.exp create mode 100644 usr.bin/make/unit-tests/varmod-ifelse.mk create mode 100644 usr.bin/make/unit-tests/varmod-indirect.exp create mode 100644 usr.bin/make/unit-tests/varmod-indirect.mk create mode 100644 usr.bin/make/unit-tests/varmod-l-name-to-value.exp create mode 100644 usr.bin/make/unit-tests/varmod-l-name-to-value.mk create mode 100644 usr.bin/make/unit-tests/varmod-localtime.exp create mode 100644 usr.bin/make/unit-tests/varmod-localtime.mk create mode 100644 usr.bin/make/unit-tests/varmod-loop.exp create mode 100644 usr.bin/make/unit-tests/varmod-loop.mk create mode 100644 usr.bin/make/unit-tests/varmod-match-escape.exp create mode 100644 usr.bin/make/unit-tests/varmod-match-escape.mk create mode 100644 usr.bin/make/unit-tests/varmod-match.exp create mode 100644 usr.bin/make/unit-tests/varmod-match.mk create mode 100644 usr.bin/make/unit-tests/varmod-no-match.exp create mode 100644 usr.bin/make/unit-tests/varmod-no-match.mk create mode 100644 usr.bin/make/unit-tests/varmod-order-reverse.exp create mode 100644 usr.bin/make/unit-tests/varmod-order-reverse.mk create mode 100644 usr.bin/make/unit-tests/varmod-order-shuffle.exp create mode 100644 usr.bin/make/unit-tests/varmod-order-shuffle.mk create mode 100644 usr.bin/make/unit-tests/varmod-order.exp create mode 100644 usr.bin/make/unit-tests/varmod-order.mk create mode 100644 usr.bin/make/unit-tests/varmod-path.exp create mode 100644 usr.bin/make/unit-tests/varmod-path.mk create mode 100644 usr.bin/make/unit-tests/varmod-quote-dollar.exp create mode 100644 usr.bin/make/unit-tests/varmod-quote-dollar.mk create mode 100644 usr.bin/make/unit-tests/varmod-quote.exp create mode 100644 usr.bin/make/unit-tests/varmod-quote.mk create mode 100644 usr.bin/make/unit-tests/varmod-range.exp create mode 100644 usr.bin/make/unit-tests/varmod-range.mk create mode 100644 usr.bin/make/unit-tests/varmod-remember.exp create mode 100644 usr.bin/make/unit-tests/varmod-remember.mk create mode 100644 usr.bin/make/unit-tests/varmod-root.exp create mode 100644 usr.bin/make/unit-tests/varmod-root.mk create mode 100644 usr.bin/make/unit-tests/varmod-select-words.exp create mode 100644 usr.bin/make/unit-tests/varmod-select-words.mk create mode 100644 usr.bin/make/unit-tests/varmod-shell.exp create mode 100644 usr.bin/make/unit-tests/varmod-shell.mk create mode 100644 usr.bin/make/unit-tests/varmod-subst-regex.exp create mode 100644 usr.bin/make/unit-tests/varmod-subst-regex.mk create mode 100644 usr.bin/make/unit-tests/varmod-subst.exp create mode 100644 usr.bin/make/unit-tests/varmod-subst.mk create mode 100644 usr.bin/make/unit-tests/varmod-sun-shell.exp create mode 100644 usr.bin/make/unit-tests/varmod-sun-shell.mk create mode 100644 usr.bin/make/unit-tests/varmod-sysv.exp create mode 100644 usr.bin/make/unit-tests/varmod-sysv.mk create mode 100644 usr.bin/make/unit-tests/varmod-tail.exp create mode 100644 usr.bin/make/unit-tests/varmod-tail.mk create mode 100644 usr.bin/make/unit-tests/varmod-to-abs.exp create mode 100644 usr.bin/make/unit-tests/varmod-to-abs.mk create mode 100644 usr.bin/make/unit-tests/varmod-to-lower.exp create mode 100644 usr.bin/make/unit-tests/varmod-to-lower.mk create mode 100644 usr.bin/make/unit-tests/varmod-to-many-words.exp create mode 100644 usr.bin/make/unit-tests/varmod-to-many-words.mk create mode 100644 usr.bin/make/unit-tests/varmod-to-one-word.exp create mode 100644 usr.bin/make/unit-tests/varmod-to-one-word.mk create mode 100644 usr.bin/make/unit-tests/varmod-to-separator.exp create mode 100644 usr.bin/make/unit-tests/varmod-to-separator.mk create mode 100644 usr.bin/make/unit-tests/varmod-to-upper.exp create mode 100644 usr.bin/make/unit-tests/varmod-to-upper.mk create mode 100644 usr.bin/make/unit-tests/varmod-undefined.exp create mode 100644 usr.bin/make/unit-tests/varmod-undefined.mk create mode 100644 usr.bin/make/unit-tests/varmod-unique.exp create mode 100644 usr.bin/make/unit-tests/varmod-unique.mk create mode 100644 usr.bin/make/unit-tests/varmod.exp create mode 100644 usr.bin/make/unit-tests/varmod.mk create mode 100644 usr.bin/make/unit-tests/varname-dollar.exp create mode 100644 usr.bin/make/unit-tests/varname-dollar.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-alltargets.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-alltargets.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-curdir.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-curdir.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-includedfromdir.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-includedfromdir.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-includedfromfile.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-includedfromfile.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-includes.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-includes.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-libs.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-libs.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-dependfile.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-dependfile.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-expand_variables.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-expand_variables.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-exported.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-exported.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-jobs-prefix.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-jobs-prefix.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-jobs.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-jobs.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-level.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-level.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-makefile_preference.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-makefile_preference.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-makefiles.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-makefiles.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-bailiwick.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-bailiwick.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-created.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-created.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-files.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-files.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-ignore_filter.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-ignore_filter.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-ignore_paths.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-ignore_paths.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-ignore_patterns.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-ignore_patterns.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-prefix.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-meta-prefix.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-mode.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-mode.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-path_filemon.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-path_filemon.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-pid.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-pid.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-ppid.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-ppid.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-make-save_dollars.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-make-save_dollars.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-makeflags.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-makeflags.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-makeoverrides.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-makeoverrides.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-newline.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-newline.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-objdir.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-objdir.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-parsedir.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-parsedir.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-parsefile.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-parsefile.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-path.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-path.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-shell.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-shell.mk create mode 100644 usr.bin/make/unit-tests/varname-dot-targets.exp create mode 100644 usr.bin/make/unit-tests/varname-dot-targets.mk create mode 100644 usr.bin/make/unit-tests/varname-empty.exp create mode 100644 usr.bin/make/unit-tests/varname-empty.mk create mode 100644 usr.bin/make/unit-tests/varname-make.exp create mode 100644 usr.bin/make/unit-tests/varname-make.mk create mode 100644 usr.bin/make/unit-tests/varname-make_print_var_on_error-jobs.exp create mode 100644 usr.bin/make/unit-tests/varname-make_print_var_on_error-jobs.mk create mode 100644 usr.bin/make/unit-tests/varname-make_print_var_on_error.exp create mode 100644 usr.bin/make/unit-tests/varname-make_print_var_on_error.mk create mode 100644 usr.bin/make/unit-tests/varname-makefile.exp create mode 100644 usr.bin/make/unit-tests/varname-makefile.mk create mode 100644 usr.bin/make/unit-tests/varname-makeflags.exp create mode 100644 usr.bin/make/unit-tests/varname-makeflags.mk create mode 100644 usr.bin/make/unit-tests/varname-pwd.exp create mode 100644 usr.bin/make/unit-tests/varname-pwd.mk create mode 100644 usr.bin/make/unit-tests/varname-vpath.exp create mode 100644 usr.bin/make/unit-tests/varname-vpath.mk create mode 100644 usr.bin/make/unit-tests/varname.exp create mode 100644 usr.bin/make/unit-tests/varname.mk create mode 100644 usr.bin/make/unit-tests/varparse-dynamic.exp create mode 100644 usr.bin/make/unit-tests/varparse-dynamic.mk create mode 100644 usr.bin/make/unit-tests/varparse-errors.exp create mode 100644 usr.bin/make/unit-tests/varparse-errors.mk create mode 100644 usr.bin/make/unit-tests/varparse-mod.exp create mode 100644 usr.bin/make/unit-tests/varparse-mod.mk create mode 100644 usr.bin/make/unit-tests/varparse-undef-partial.exp create mode 100644 usr.bin/make/unit-tests/varparse-undef-partial.mk create mode 100644 usr.bin/make/unit-tests/varquote.exp create mode 100644 usr.bin/make/unit-tests/varquote.mk delete mode 100644 usr.bin/make/unit-tests/varshell.exp delete mode 100644 usr.bin/make/unit-tests/varshell.mk diff --git a/distrib/sets/lists/tests/mi b/distrib/sets/lists/tests/mi index 42c654bd2..fa7c8c8d4 100644 --- a/distrib/sets/lists/tests/mi +++ b/distrib/sets/lists/tests/mi @@ -3544,114 +3544,828 @@ ./usr/tests/usr.bin/m4/d_m4wrap.m4 tests-usr.bin-tests compattestfile,atf ./usr/tests/usr.bin/m4/d_m4wrap.out tests-usr.bin-tests compattestfile,atf ./usr/tests/usr.bin/m4/t_m4 tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/Atffile tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/Kyuafile tests-usr.bin-tests compattestfile,atf,kyua -./usr/tests/usr.bin/make/d_comment.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_comment.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_cond1.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_cond1.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_dotwait.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_dotwait.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_export.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_export.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_export_all.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_export_all.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_moderrs.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_moderrs.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modmatch.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modmatch.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modmisc.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modmisc.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modorder.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modorder.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modts.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modts.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modword.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_modword.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_posix.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_posix.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_qequals.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_qequals.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_ternary.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_ternary.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_unmatchedvarparen.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_unmatchedvarparen.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_varcmd.mk tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/d_varcmd.out tests-obsolete obsolete,atf -./usr/tests/usr.bin/make/t_make tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/Makefile tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/comment.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/comment.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/cond1.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/cond1.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/cond2.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/cond2.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/doterror.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/doterror.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/dotwait.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/dotwait.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/error.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/error.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/escape.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/escape.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/export-all.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/export-all.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/export-env.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/export-env.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/export.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/export.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/forloop.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/forloop.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/forsubst.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/forsubst.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/hash.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/hash.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/impsrc.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/impsrc.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/misc.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/misc.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/moderrs.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/moderrs.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modmatch.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modmatch.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modmisc.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modmisc.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modorder.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modorder.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modts.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modts.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modword.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/modword.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/order.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/order.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/phony-end.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/phony-end.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/posix.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/posix.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/posix1.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/posix1.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/qequals.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/qequals.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/suffixes.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/suffixes.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/sunshcmd.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/sunshcmd.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/sysv.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/sysv.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/ternary.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/ternary.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/unexport-env.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/unexport-env.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/unexport.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/unexport.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/varcmd.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/varcmd.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/varmisc.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/varmisc.mk tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/varshell.exp tests-usr.bin-tests compattestfile,atf -./usr/tests/usr.bin/make/unit-tests/varshell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/Atffile tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/Kyuafile tests-usr.bin-tests compattestfile,atf,kyua +./usr/tests/usr.bin/make/d_comment.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_comment.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_cond1.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_cond1.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_dotwait.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_dotwait.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_export.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_export.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_export_all.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_export_all.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_moderrs.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_moderrs.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modmatch.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modmatch.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modmisc.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modmisc.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modorder.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modorder.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modts.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modts.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modword.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_modword.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_posix.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_posix.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_qequals.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_qequals.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_ternary.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_ternary.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_unmatchedvarparen.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_unmatchedvarparen.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_varcmd.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/d_varcmd.out tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/t_make tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/Makefile tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/archive-suffix.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/archive-suffix.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/archive.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/archive.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-errors-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-errors-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-errors-lint.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-errors-lint.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-errors.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-errors.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-interrupt.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmd-interrupt.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmdline-redirect-stdin.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmdline-redirect-stdin.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmdline-undefined.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmdline-undefined.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmdline.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cmdline.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/comment.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/comment.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/compat-error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/compat-error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-eq.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-eq.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-ge.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-ge.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-gt.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-gt.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-le.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-le.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-lt.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-lt.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-ne.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric-ne.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-numeric.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-string.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-string.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-unary.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-cmp-unary.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-eof.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-eof.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-commands.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-commands.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-defined.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-defined.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-empty.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-empty.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-exists.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-exists.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-make-main.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-make-main.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-make.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-make.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-target.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func-target.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-func.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-late.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-late.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-and-lint.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-and-lint.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-and.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-and.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-not.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-not.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-or-lint.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-or-lint.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-or.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-or.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-parentheses.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op-parentheses.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-op.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-short.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-short.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-number.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-number.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-plain.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-plain.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-string.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-string.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-var.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-token-var.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-undef-lint.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond-undef-lint.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond1.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond1.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/cond2.exp obsolete-tests obsolete +./usr/tests/usr.bin/make/unit-tests/cond2.mk obsolete-tests obsolete +./usr/tests/usr.bin/make/unit-tests/counter-append.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/counter-append.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/counter.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/counter.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-colon-bug-cross-file.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-colon-bug-cross-file.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-colon.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-colon.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-double-colon-indep.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-double-colon-indep.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-double-colon.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-double-colon.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-exclam.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-exclam.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-none.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-none.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-percent.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-percent.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-var.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-var.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-wildcards.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep-wildcards.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dep.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-end.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-end.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-exec.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-exec.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-ignore.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-ignore.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-made.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-made.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-make.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-make.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-meta.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-meta.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-nometa.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-nometa.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-nometa_cmp.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-nometa_cmp.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-nopath.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-nopath.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-notmain.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-notmain.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-optional.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-optional.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-phony.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-phony.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-precious.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-precious.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-recursive.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-recursive.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-silent.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-silent.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-use.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-use.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-usebefore.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-usebefore.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-wait.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc-wait.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/depsrc.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-begin-fail.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-begin-fail.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-begin.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-begin.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-default.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-default.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-delete_on_error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-delete_on_error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-fail-all.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-fail-all.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-fail-indirect.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-fail-indirect.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-fail.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-fail.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-end.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-ignore.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-ignore.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-interrupt.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-interrupt.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-main.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-main.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-makeflags.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-makeflags.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-no_parallel.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-no_parallel.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-nopath.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-nopath.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-notparallel.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-notparallel.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-objdir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-objdir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-order.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-order.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-path-suffix.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-path-suffix.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-path.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-path.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-phony.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-phony.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-precious.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-precious.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-shell.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-shell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-silent.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-silent.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-stale.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-stale.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-suffixes.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt-suffixes.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/deptgt.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dir-expand-path.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dir-expand-path.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-dinclude.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-dinclude.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elif.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elif.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifdef.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifdef.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifmake.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifmake.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifndef.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifndef.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifnmake.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-elifnmake.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-else.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-else.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-endfor.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-endfor.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-endif.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-endif.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-env.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-env.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-gmake.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-gmake.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-impl.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-impl.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-literal.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export-literal.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-export.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-errors.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-errors.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-escape.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-escape.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-generating-endif.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-generating-endif.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-lines.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-lines.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-null.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for-null.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-for.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-hyphen-include.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-hyphen-include.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-if-nested.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-if-nested.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-if.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-if.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifdef.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifdef.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifmake.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifmake.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifndef.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifndef.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifnmake.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-ifnmake.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-include-fatal.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-include-fatal.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-include.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-include.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-info.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-info.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-misspellings.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-misspellings.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-sinclude.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-sinclude.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-undef.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-undef.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-unexport-env.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-unexport-env.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-unexport.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-unexport.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-warning.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive-warning.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directive.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/directives.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/directives.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/dollar.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dollar.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/doterror.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/doterror.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dotwait.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/dotwait.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/envfirst.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/envfirst.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/escape.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/escape.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export-all.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export-all.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export-env.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export-env.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export-variants.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export-variants.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/export.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/forloop.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/forloop.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/forsubst.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/forsubst.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/gnode-submake.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/gnode-submake.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/hanoi-include.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/hanoi-include.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/hash.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/hash.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/impsrc.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/impsrc.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/include-main.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/include-main.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/include-sub.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/include-subsub.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/job-flags.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/job-flags.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/job-output-long-lines.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/job-output-long-lines.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-empty-commands.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-empty-commands.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-error-indirect.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-error-indirect.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-error-nested-make.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-error-nested-make.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-error-nested.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/jobs-error-nested.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/lint.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/lint.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/make-exported.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/make-exported.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/meta-cmd-cmp.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/meta-cmd-cmp.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/misc.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/misc.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/moderrs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/moderrs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modmatch.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modmatch.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modmisc.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modmisc.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modorder.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/modorder.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/modts.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modts.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modword.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/modword.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/objdir-writable.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/objdir-writable.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-backwards.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-backwards.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-chdir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-chdir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-all.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-all.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-archive.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-archive.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-cond.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-cond.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-curdir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-curdir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-dir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-dir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-errors.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-errors.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-file.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-file.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-for.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-for.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-g1.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/opt-debug-g1.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/opt-debug-graph1.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-graph1.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-graph2.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-graph2.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-graph3.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-graph3.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-hash.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-hash.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-lint.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-lint.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-loud.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-loud.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-making.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-making.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-meta.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-meta.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-no-rm.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-no-rm.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-parse.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-parse.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-suff.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-suff.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-targets.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-targets.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-var.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-var.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-varraw.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-varraw.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-x-trace.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug-x-trace.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-debug.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-define.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-define.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-env.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-env.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-file.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-file.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-ignore.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-ignore.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-include-dir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-include-dir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-jobs-internal.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-jobs-internal.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-jobs-no-action.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-jobs-no-action.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-keep-going-multiple.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-keep-going-multiple.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-keep-going.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-keep-going.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-m-include-dir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-m-include-dir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action-at-all.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action-at-all.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action-runflags.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action-runflags.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action-touch.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action-touch.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-no-action.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-query.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-query.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-raw.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-raw.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-silent.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-silent.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-touch-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-touch-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-touch.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-touch.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-tracefile.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-tracefile.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-var-expanded.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-var-expanded.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-var-literal.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-var-literal.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-warnings-as-errors.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-warnings-as-errors.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-where-am-i.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-where-am-i.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-x-reduce-exported.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt-x-reduce-exported.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/opt.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/order.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/order.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/parse-var.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/parse-var.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/phony-end.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/phony-end.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/posix.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/posix.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/posix1.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/posix1.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/qequals.exp tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/unit-tests/qequals.mk tests-obsolete obsolete,atf +./usr/tests/usr.bin/make/unit-tests/recursive.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/recursive.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-dots.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-dots.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-errctl.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-errctl.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-flags.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-flags.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-jobs-error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-jobs-error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-leading-at.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-leading-at.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-leading-hyphen.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-leading-hyphen.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-leading-plus.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-leading-plus.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-meta-chars.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-meta-chars.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-multi-line.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-multi-line.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-single-line.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh-single-line.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sh.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-csh.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-csh.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-custom.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-custom.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-ksh.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-ksh.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-sh.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/shell-sh.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-add-later.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-add-later.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-clear-regular.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-clear-regular.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-clear-single.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-clear-single.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-incomplete.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-incomplete.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-lookup.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-lookup.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-main-several.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-main-several.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-main.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-main.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-phony.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-phony.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-rebuild.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-rebuild.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-self.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-self.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-debug.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-debug.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-endless.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-endless.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-expand.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-expand.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-select.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suff-transform-select.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/suffixes.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/suffixes.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/sunshcmd.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sunshcmd.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/sysv.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/sysv.mk tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/ternary.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/ternary.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/unexport-env.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/unexport-env.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/unexport.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/unexport.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/use-inference.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/use-inference.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-cmdline.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-cmdline.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-env.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-env.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-global.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-global.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-local-legacy.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-local-legacy.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-local.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class-local.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-class.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-eval-short.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-eval-short.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-append.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-append.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-assign.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-assign.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-default.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-default.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-expand.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-expand.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-shell.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-shell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-sunsh.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op-sunsh.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-op.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-recursive.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/var-recursive.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varcmd.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varcmd.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/vardebug.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/vardebug.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varfind.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varfind.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmisc.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmisc.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-assign.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-assign.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-defined.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-defined.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-edge.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-edge.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-exclam-shell.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-exclam-shell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-extension.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-extension.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-gmtime.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-gmtime.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-hash.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-hash.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-head.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-head.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-ifelse.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-ifelse.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-indirect.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-indirect.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-l-name-to-value.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-l-name-to-value.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-localtime.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-localtime.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-loop.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-loop.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-match-escape.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-match-escape.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-match.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-match.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-no-match.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-no-match.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-order-reverse.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-order-reverse.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-order-shuffle.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-order-shuffle.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-order.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-order.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-path.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-path.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-quote-dollar.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-quote-dollar.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-quote.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-quote.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-range.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-range.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-remember.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-remember.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-root.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-root.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-select-words.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-select-words.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-shell.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-shell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-subst-regex.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-subst-regex.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-subst.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-subst.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-sun-shell.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-sun-shell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-sysv.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-sysv.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-tail.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-tail.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-abs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-abs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-lower.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-lower.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-many-words.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-many-words.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-one-word.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-one-word.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-separator.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-separator.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-upper.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-to-upper.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-undefined.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-undefined.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-unique.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod-unique.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varmod.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dollar.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dollar.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-alltargets.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-alltargets.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-curdir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-curdir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-includedfromdir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-includedfromdir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-includedfromfile.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-includedfromfile.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-includes.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-includes.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-libs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-libs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-dependfile.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-dependfile.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-expand_variables.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-expand_variables.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-exported.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-exported.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-jobs-prefix.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-jobs-prefix.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-level.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-level.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-makefile_preference.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-makefile_preference.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-makefiles.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-makefiles.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-bailiwick.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-bailiwick.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-created.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-created.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-files.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-files.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-ignore_filter.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-ignore_filter.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-ignore_paths.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-ignore_paths.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-ignore_patterns.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-ignore_patterns.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-prefix.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-meta-prefix.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-mode.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-mode.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-path_filemon.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-path_filemon.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-pid.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-pid.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-ppid.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-ppid.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-save_dollars.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-make-save_dollars.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-makeflags.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-makeflags.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-makeoverrides.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-makeoverrides.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-newline.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-newline.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-objdir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-objdir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-parsedir.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-parsedir.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-parsefile.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-parsefile.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-path.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-path.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-shell.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-shell.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-targets.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-dot-targets.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-empty.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-empty.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-make.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-make.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-make_print_var_on_error-jobs.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-make_print_var_on_error-jobs.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-make_print_var_on_error.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-make_print_var_on_error.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-makefile.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-makefile.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-makeflags.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-makeflags.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-pwd.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-pwd.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-vpath.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname-vpath.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varname.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-dynamic.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-dynamic.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-errors.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-errors.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-mod.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-mod.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-undef-partial.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varparse-undef-partial.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varquote.exp tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varquote.mk tests-usr.bin-tests compattestfile,atf +./usr/tests/usr.bin/make/unit-tests/varshell.exp tests-obsolete obsolete +./usr/tests/usr.bin/make/unit-tests/varshell.mk tests-obsolete obsolete ./usr/tests/usr.bin/mkdep tests-usr.bin-tests compattestfile,atf ./usr/tests/usr.bin/mkdep/Atffile tests-usr.bin-tests compattestfile,atf ./usr/tests/usr.bin/mkdep/Kyuafile tests-usr.bin-tests compattestfile,atf,kyua diff --git a/tools/gmp/Makefile b/tools/gmp/Makefile index b8c9b5d86..ee00c7e48 100644 --- a/tools/gmp/Makefile +++ b/tools/gmp/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.2 2012/04/12 01:05:31 mrg Exp $ +# $NetBSD: Makefile,v 1.3 2016/01/09 01:13:42 christos Exp $ # GNUHOSTDIST= ${.CURDIR}/../../external/lgpl3/gmp/dist @@ -11,7 +11,7 @@ CONFIGURE_ARGS+=--disable-shared # PATH needs special treating since "make" tries to expand $PATH # when variable is used. CONFIGURE_ENV:= ${CONFIGURE_ENV:NPATH=*:NCFLAGS=*:NCXXFLAGS=*} \ - PATH="${TOOLDIR:Q}/bin:$$$$PATH" + PATH="${TOOLDIR:Q}/bin:$$PATH" .if ${HOST_OSTYPE:C/\-.*//} != "Minix" # Force avoiding possibly non-executable install-sh. diff --git a/tools/make/buildmake.sh.in b/tools/make/buildmake.sh.in index 59e3bfbbf..d9ba0ec50 100755 --- a/tools/make/buildmake.sh.in +++ b/tools/make/buildmake.sh.in @@ -1,23 +1,36 @@ #! /bin/sh -# $NetBSD: buildmake.sh.in,v 1.8 2006/08/26 22:17:48 christos Exp $ +# $NetBSD: buildmake.sh.in,v 1.16 2020/07/26 09:17:24 rillig Exp $ # # buildmake.sh.in - Autoconf-processed shell script for building make(1). # -: ${HOST_CC="@CC@"} -: ${HOST_CFLAGS="@CPPFLAGS@ @CFLAGS@"} -: ${HOST_LDFLAGS="@LDFLAGS@ @LIBS@"} -: ${runcmd=""} +: ${NETBSDSRCDIR:=@srcdir@/../..} +MKSRCDIR=${NETBSDSRCDIR}/usr.bin/make + +_CC="@CC@" + +_CFLAGS="@CPPFLAGS@" +_CFLAGS="${_CFLAGS} -D_PATH_DEFSYSPATH=\"${NETBSDSRCDIR}/share/mk\"" +_CFLAGS="${_CFLAGS} @DEFS@" +_CFLAGS="${_CFLAGS} @CFLAGS@" + +_LDFLAGS="@LDFLAGS@ @LIBS@" docmd () { - echo "$1" - $1 || exit 1 + case "${MAKEVERBOSE:-2}" in + 0) + ;; + 1) + echo " $1 ${2##*/}" ;; + *) + echo "$3" ;; + esac + $3 || exit 1 } -MKSRCDIR=@srcdir@/../../usr.bin/make - -for f in $MKSRCDIR/*.c $MKSRCDIR/lst.lib/*.c; do - docmd "${HOST_CC} ${HOST_CFLAGS} @DEFS@ -c $f" +for f in $MKSRCDIR/*.c; do + docmd "compile " "$f" "${_CC} ${_CFLAGS} -c $f" done -docmd "${HOST_CC} ${HOST_CFLAGS} -o ${_TOOL_PREFIX:-nb}make *.o ${HOST_LDFLAGS}" +docmd " link " "${_TOOL_PREFIX:-nb}make" \ + "${_CC} -o ${_TOOL_PREFIX:-nb}make *.o ${_LDFLAGS}" diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile index b08eb1ddb..2c72ca29e 100644 --- a/usr.bin/make/Makefile +++ b/usr.bin/make/Makefile @@ -1,58 +1,222 @@ -# $NetBSD: Makefile,v 1.58 2015/06/17 17:43:23 christos Exp $ +# $NetBSD: Makefile,v 1.113 2021/02/22 20:45:46 rillig Exp $ # @(#)Makefile 5.2 (Berkeley) 12/28/90 PROG= make -SRCS= arch.c buf.c compat.c cond.c dir.c for.c hash.c job.c main.c \ - make.c metachar.c parse.c str.c suff.c targ.c trace.c var.c util.c -SRCS+= strlist.c +SRCS= arch.c +SRCS+= buf.c +SRCS+= compat.c +SRCS+= cond.c +SRCS+= dir.c +SRCS+= enum.c +SRCS+= for.c +SRCS+= hash.c +SRCS+= job.c +SRCS+= lst.c +SRCS+= main.c +SRCS+= make.c SRCS+= make_malloc.c -SRCS+= lstAppend.c lstAtEnd.c lstAtFront.c lstClose.c lstConcat.c \ - lstDatum.c lstDeQueue.c lstDestroy.c lstDupl.c lstEnQueue.c \ - lstFind.c lstFindFrom.c lstFirst.c lstForEach.c lstForEachFrom.c \ - lstInit.c lstInsert.c lstIsAtEnd.c lstIsEmpty.c lstLast.c \ - lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c -SRCS += lstPrev.c +SRCS+= metachar.c +SRCS+= parse.c +SRCS+= str.c +SRCS+= suff.c +SRCS+= targ.c +SRCS+= trace.c +SRCS+= var.c +SRCS+= util.c +HDRS= buf.h +HDRS+= config.h +HDRS+= dir.h +HDRS+= enum.h +HDRS+= hash.h +HDRS+= job.h +HDRS+= lst.h +HDRS+= make.h +HDRS+= make_malloc.h +HDRS+= meta.h +HDRS+= metachar.h +HDRS+= nonints.h +HDRS+= pathnames.h +HDRS+= trace.h -# let people experiment for a bit -USE_META ?= no +# Whether to generate a coverage report after running the tests. +USE_COVERAGE?= no # works only with gcc; clang9 fails to link +.if ${USE_COVERAGE} == "yes" +GCOV?= gcov +COPTS+= --coverage -O0 -ggdb +GCOV_PERL= if (/^File '(?:.*\/)?(\S+)'/) { +GCOV_PERL+= $$file = $$1; $$func = ""; +GCOV_PERL+= } elsif (/^Function '(\S+)'/) { +GCOV_PERL+= $$func = $$1; +GCOV_PERL+= } elsif (/^Lines executed:(\d+\.\d+)% of (\d+)/ && defined($$file)) { +GCOV_PERL+= my ($$percent, $$lines) = ($$1, $$2); +GCOV_PERL+= my $$uncovered = `grep -c '\#\#\#\#\#:' < \$$(basename $$file.gcov)`; +GCOV_PERL+= printf("%7.2f %4d/%4d %s%s\n", +GCOV_PERL+= $$percent, $$uncovered, $$lines, $$file, $$func); +GCOV_PERL+= $$file = undef; +GCOV_PERL+= } +LDADD+= --coverage +.endif +CLEANFILES+= *.gcda *.gcno *.gcov + +# Whether to compile using the Undefined Behavior Sanitizer (GCC, Clang). +USE_UBSAN?= no +.if ${USE_UBSAN} == "yes" +COPTS+= -fsanitize=undefined +LDADD+= -fsanitize=undefined +.endif + +# Whether to compile with GCC 10 from pkgsrc, during development. +USE_GCC10?= no +.if ${USE_GCC10} == "yes" +# CC is set further down in this file +COPTS+= -Wno-attributes # for abs and labs +COPTS.arch.c+= -Wno-error=format-truncation +COPTS.dir.c+= -Wno-error=format-truncation +COPTS.main.c+= -Wno-error=format-truncation +COPTS.meta.c+= -Wno-error=format-truncation +.endif + +# Whether to compile with GCC 9 from pkgsrc, during development. +USE_GCC9?= no +.if ${USE_GCC9} == "yes" +# CC is set further down in this file +COPTS+= -Wno-attributes # for abs and labs +COPTS.arch.c+= -Wno-error=format-truncation +COPTS.dir.c+= -Wno-error=format-truncation +COPTS.main.c+= -Wno-error=format-truncation +COPTS.meta.c+= -Wno-error=format-truncation +.endif + +# Whether to compile with GCC 8 from pkgsrc, during development. +USE_GCC8?= no +.if ${USE_GCC8} == "yes" +# CC is set further down in this file +COPTS+= -Wno-attributes # for abs and labs +COPTS.arch.c+= -Wno-error=format-truncation +COPTS.dir.c+= -Wno-error=format-truncation +COPTS.main.c+= -Wno-error=format-truncation +COPTS.meta.c+= -Wno-error=format-truncation +.endif + +USE_META?= yes .if ${USE_META:tl} != "no" -SRCS+= meta.c -CPPFLAGS+= -DUSE_META -FILEMON_H ?= ${.CURDIR:H:H}/sys/dev/filemon/filemon.h -.if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" -COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} -.endif -.endif -.if defined(__MINIX) -CPPFLAGS+= -DMAKE_MACHINE=\"${MACHINE}\" -DMAKE_MACHINE_ARCH=\"${MACHINE_ARCH}\" -.endif #defined(__MINIX) +SRCS+= meta.c +CPPFLAGS+= -DUSE_META -.PATH: ${.CURDIR}/lst.lib .if !defined(__MINIX) -SUBDIR= PSD.doc -.endif #!defined(__MINIX) -.if make(obj) || make(clean) -SUBDIR+= unit-tests +USE_FILEMON?= ktrace +. if ${USE_FILEMON:tl} != "no" + +.PATH: ${.CURDIR}/filemon +SRCS+= filemon_${USE_FILEMON}.c +CPPFLAGS+= -DUSE_FILEMON +CPPFLAGS+= -DUSE_FILEMON_${USE_FILEMON:tu} + +. if ${USE_FILEMON} == "dev" +FILEMON_H?= /usr/include/dev/filemon/filemon.h +. if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" +COPTS.filemon_dev.c+= \ + -DHAVE_FILEMON_H -I${FILEMON_H:H} +. endif +. endif +. endif .endif +.endif # !defined(__MINIX) + +SUBDIR.roff+= PSD.doc +.if make(obj) || make(clean) +SUBDIR+= unit-tests +.endif + +LINTFLAGS+= -T # strict bool mode, available since 2021-01-11 + +${SRCS:M*.c:.c=.o}: ${HDRS} +CLEANFILES+= *.o + +COPTS.arch.c+= ${GCC_NO_FORMAT_TRUNCATION} +COPTS.dir.c+= ${GCC_NO_FORMAT_TRUNCATION} +COPTS.job.c+= -Wno-format-nonliteral +COPTS.main.c+= ${GCC_NO_FORMAT_TRUNCATION} ${GCC_NO_STRINGOP_TRUNCATION} +COPTS.meta.c+= ${GCC_NO_FORMAT_TRUNCATION} +COPTS.parse.c+= -Wno-format-nonliteral +COPTS.var.c+= -Wno-format-nonliteral + +CPPFLAGS+= -DMAKE_NATIVE + +.if ${USE_GCC10} == "yes" +GCC10BASE?= /usr/pkg/gcc10 +LATE_CC= ${GCC10BASE}/bin/gcc +GCOV= ${GCC10BASE}/bin/gcov +.endif + +.if ${USE_GCC9} == "yes" +GCC9BASE?= /usr/pkg/gcc9 +LATE_CC= ${GCC9BASE}/bin/gcc +GCOV= ${GCC9BASE}/bin/gcov +.endif + +.if ${USE_GCC8} == "yes" +GCC8BASE?= /usr/pkg/gcc8 +LATE_CC= ${GCC8BASE}/bin/gcc +GCOV= ${GCC8BASE}/bin/gcov +.endif + +.if defined(TOOLDIR) +# This is a native NetBSD build, use libutil rather than the local emalloc etc. +CPPFLAGS+= -DUSE_EMALLOC +LDADD+= -lutil +DPADD+= ${LIBUTIL} +.endif + +COPTS+= -Wdeclaration-after-statement +CWARNFLAGS.gcc+= -Wno-error=clobbered + +# A simple unit-test driver to help catch regressions +TEST_MAKE ?= ${.OBJDIR}/${PROG:T} +test: .MAKE + cd ${.CURDIR}/unit-tests \ + && MAKEFLAGS= ${TEST_MAKE} -r -m / TEST_MAKE=${TEST_MAKE} ${TESTS:DTESTS=${TESTS:Q}} ${.TARGET} +.if ${USE_COVERAGE} == yes + ${MAKE} report-coverage +.endif + +accept sync-mi: .MAKE + cd ${.CURDIR}/unit-tests && ${.MAKE} ${.TARGET} + +retest: + ${.MAKE} -C ${.CURDIR}/unit-tests cleandir +.if ${USE_COVERAGE} == yes + rm -f *.gcov *.gcda +.endif + ${.MAKE} test + +# Just out of curiosity, during development. +.SUFFIXES: .cpre .casm +.c.cpre: + ${COMPILE.c:S,^-c$,-E,} ${COPTS.${.IMPSRC}} ${.IMPSRC} -o ${.TARGET} +.c.casm: + ${COMPILE.c:S,^-c$,-S,} ${COPTS.${.IMPSRC}} ${.IMPSRC} -o ${.TARGET} + +test-coverage: .PHONY + @make -s clean cleandir + @env USE_COVERAGE=yes COPTS="-O0 -ggdb" USER_CPPFLAGS="-DCLEANUP" \ + sh -c 'make -s all -j8 && make -s test' + +report-coverage: .PHONY + @echo 'covered uncovered file' + @${GCOV} ${GCOV_OPTS} *.gcda 2>&1 \ + | perl -ne ${GCOV_PERL:Q} \ + | sort -nr + @sed -i 's,^\([^:]*\): *[0-9]*:,\1: ,' *.gcov .include .include -CPPFLAGS+= -DMAKE_NATIVE -COPTS.var.c += -Wno-cast-qual -COPTS.job.c += -Wno-format-nonliteral -COPTS.parse.c += -Wno-format-nonliteral -COPTS.var.c += -Wno-format-nonliteral - -.ifdef TOOLDIR -# this is a native netbsd build, -# use libutil rather than the local emalloc etc. -CPPFLAGS+= -DUSE_EMALLOC -LDADD+=-lutil -DPADD+=${LIBUTIL} +# For -DCLEANUP and similar feature toggles. +CPPFLAGS+= ${USER_CPPFLAGS} +# For overriding -std=gnu99 or similar options. +CFLAGS+= ${USER_CFLAGS} +.if defined(LATE_CC) +CC= ${LATE_CC} .endif - -# A simple unit-test driver to help catch regressions -accept test: - cd ${.CURDIR}/unit-tests && MAKEFLAGS= ${.MAKE} -r -m / TEST_MAKE=${TEST_MAKE:U${.OBJDIR}/${PROG:T}} ${.TARGET} diff --git a/usr.bin/make/Makefile.boot b/usr.bin/make/Makefile.boot index f5be09c6a..e62e45e85 100644 --- a/usr.bin/make/Makefile.boot +++ b/usr.bin/make/Makefile.boot @@ -1,45 +1,39 @@ -# $NetBSD: Makefile.boot,v 1.21 2014/02/24 07:23:44 skrll Exp $ +# $NetBSD: Makefile.boot,v 1.23 2020/10/25 13:25:19 rillig Exp $ # -# a very simple makefile... +# A very simple makefile... # # You only want to use this if you aren't running NetBSD. # -# modify MACHINE and MACHINE_ARCH as appropriate for your target architecture +# Modify MACHINE and MACHINE_ARCH as appropriate for your target architecture. +# See config.h and the various #ifdef directives for further configuration. # -CC=gcc -O -g + +PROG= bmake +MACHINE= i386 +MACHINE_ARCH= i386 +CC= gcc +CFLAGS= -O -g +EXTRA_CFLAGS= +EXTRA_LIBS= + +OBJS= arch.o buf.o compat.o cond.o dir.o enum.o for.o hash.o \ + job.o lst.o main.o make.o make_malloc.o metachar.o parse.o \ + str.o suff.o targ.o trace.o var.o util.o .c.o: - ${CC} ${CFLAGS} -c $< -o $@ + ${CC} ${CPPFLAGS} ${CFLAGS} ${EXTRA_CFLAGS} -c $< -o $@ -MACHINE=i386 -MACHINE_ARCH=i386 -# tested on HP-UX 10.20 -#MAKE_MACHINE=hppa -#MAKE_MACHINE_ARCH=hppa -CFLAGS= -DTARGET_MACHINE=\"${MACHINE}\" \ +CPPFLAGS= \ + -DTARGET_MACHINE=\"${MACHINE}\" \ -DTARGET_MACHINE_ARCH=\"${MACHINE_ARCH}\" \ -DMAKE_MACHINE=\"${MACHINE}\" -LIBS= -OBJ=arch.o buf.o compat.o cond.o dir.o for.o hash.o job.o main.o make.o \ - make_malloc.o parse.o str.o strlist.o suff.o targ.o trace.o var.o util.o - -LIBOBJ= lst.lib/lstAppend.o lst.lib/lstAtEnd.o lst.lib/lstAtFront.o \ - lst.lib/lstClose.o lst.lib/lstConcat.o lst.lib/lstDatum.o \ - lst.lib/lstDeQueue.o lst.lib/lstDestroy.o lst.lib/lstDupl.o \ - lst.lib/lstEnQueue.o lst.lib/lstFind.o lst.lib/lstFindFrom.o \ - lst.lib/lstFirst.o lst.lib/lstForEach.o lst.lib/lstForEachFrom.o \ - lst.lib/lstInit.o lst.lib/lstInsert.o lst.lib/lstIsAtEnd.o \ - lst.lib/lstIsEmpty.o lst.lib/lstLast.o lst.lib/lstMember.o \ - lst.lib/lstNext.o lst.lib/lstOpen.o lst.lib/lstRemove.o \ - lst.lib/lstReplace.o lst.lib/lstSucc.o lst.lib/lstPrev.o - -bmake: ${OBJ} ${LIBOBJ} -# @echo 'make of make and make.0 started.' - ${CC} ${CFLAGS} ${OBJ} ${LIBOBJ} -o bmake ${LIBS} +${PROG}: ${OBJS} +# @echo 'make of ${PROG} and make.0 started.' + ${CC} ${CFLAGS} ${OBJS} -o $@ ${EXTRA_LIBS} @ls -l $@ # nroff -h -man make.1 > make.0 -# @echo 'make of make and make.0 completed.' +# @echo 'make of ${PROG} and make.0 completed.' clean: - rm -f ${OBJ} ${LIBOBJ} ${PORTOBJ} bmake + rm -f ${OBJS} ${PROG} diff --git a/usr.bin/make/PSD.doc/tutorial.ms b/usr.bin/make/PSD.doc/tutorial.ms index d5c8a7d54..da17f950f 100644 --- a/usr.bin/make/PSD.doc/tutorial.ms +++ b/usr.bin/make/PSD.doc/tutorial.ms @@ -1,4 +1,4 @@ -.\" $NetBSD: tutorial.ms,v 1.12 2014/09/30 21:33:14 christos Exp $ +.\" $NetBSD: tutorial.ms,v 1.14 2020/12/18 15:47:34 rillig Exp $ .\" Copyright (c) 1988, 1989, 1993 .\" The Regents of the University of California. All rights reserved. .\" @@ -117,6 +117,15 @@ .de No .br .ne 0.5i +.ie n \{\ +.nr g3 \w'NOTE ' +.po -\\n(g3u +.br +NOTE +.br +.po +\\n(g3u +.\} +.el \{\ .po -0.5i .br .mk @@ -148,12 +157,14 @@ .rt .ft \\n(g3 .ps \\n(g4 +.\} .. .de Bp .ie !\\n(.$ .IP \(bu 2 .el .IP "\&" 2 .. -.po +.3i +.ie n .po +\w'NOTE 'u +.el .po +.3i .TL PMake \*- A Tutorial .AU @@ -1907,15 +1918,15 @@ Suff_FindDeps (jive.c) applying .l -> .c to "jive.l" Suff_FindDeps (jive.l) Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date -Examining jive.c...non-existent...out-of-date +Examining jive.c...nonexistent...out-of-date --- jive.c --- lex jive.l \&.\|.\|. meaningless lex output deleted .\|.\|. mv lex.yy.c jive.c -Examining jive.o...non-existent...out-of-date +Examining jive.o...nonexistent...out-of-date --- jive.o --- cc -c jive.c -Examining jive.out...non-existent...out-of-date +Examining jive.out...nonexistent...out-of-date --- jive.out --- cc -o jive.out jive.o .DE @@ -2860,7 +2871,7 @@ current directory. While people have suggested that PMake should read the directories each time, my experience suggests that the caching seldom causes problems. In addition, not caching the directories slows things down enormously because of PMake's attempts to apply transformation -rules through non-existent files \*- the number of extra file-system +rules through nonexistent files \*- the number of extra file-system searches is truly staggering, especially if many files without suffixes are used and the null suffix isn't changed from .CW .out . diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c index 78f3601ce..2a628e9f1 100644 --- a/usr.bin/make/arch.c +++ b/usr.bin/make/arch.c @@ -1,4 +1,4 @@ -/* $NetBSD: arch.c,v 1.63 2012/06/12 19:21:50 joerg Exp $ */ +/* $NetBSD: arch.c,v 1.198 2021/03/15 12:15:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,1288 +68,1021 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: arch.c,v 1.63 2012/06/12 19:21:50 joerg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)arch.c 8.2 (Berkeley) 1/2/94"; -#else -__RCSID("$NetBSD: arch.c,v 1.63 2012/06/12 19:21:50 joerg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * arch.c -- - * Functions to manipulate libraries, archives and their members. +/* + * Manipulate libraries, archives and their members. * - * Once again, cacheing/hashing comes into play in the manipulation - * of archives. The first time an archive is referenced, all of its members' - * headers are read and hashed and the archive closed again. All hashed - * archives are kept on a list which is searched each time an archive member - * is referenced. + * The first time an archive is referenced, all of its members' headers are + * read and cached and the archive closed again. All cached archives are kept + * on a list which is searched each time an archive member is referenced. * * The interface to this module is: - * Arch_ParseArchive Given an archive specification, return a list - * of GNode's, one for each member in the spec. - * FAILURE is returned if the specification is - * invalid for some reason. * - * Arch_Touch Alter the modification time of the archive - * member described by the given node to be - * the current time. + * Arch_Init Initialize this module. * - * Arch_TouchLib Update the modification time of the library - * described by the given node. This is special - * because it also updates the modification time - * of the library's table of contents. + * Arch_End Clean up this module. * - * Arch_MTime Find the modification time of a member of - * an archive *in the archive*. The time is also - * placed in the member's GNode. Returns the - * modification time. + * Arch_ParseArchive + * Parse an archive specification such as + * "archive.a(member1 member2)". * - * Arch_MemTime Find the modification time of a member of - * an archive. Called when the member doesn't - * already exist. Looks in the archive for the - * modification time. Returns the modification - * time. + * Arch_Touch Alter the modification time of the archive + * member described by the given node to be + * the time when make was started. * - * Arch_FindLib Search for a library along a path. The - * library name in the GNode should be in - * -l format. + * Arch_TouchLib Update the modification time of the library + * described by the given node. This is special + * because it also updates the modification time + * of the library's table of contents. * - * Arch_LibOODate Special function to decide if a library node - * is out-of-date. + * Arch_UpdateMTime + * Find the modification time of a member of + * an archive *in the archive* and place it in the + * member's GNode. * - * Arch_Init Initialize this module. + * Arch_UpdateMemberMTime + * Find the modification time of a member of + * an archive. Called when the member doesn't + * already exist. Looks in the archive for the + * modification time. Returns the modification + * time. * - * Arch_End Cleanup this module. + * Arch_FindLib Search for a library along a path. The + * library name in the GNode should be in + * -l format. + * + * Arch_LibOODate Decide if a library node is out-of-date. */ -#include -#include -#include -#include +#include +#include +#include +#include -#include -#include -#include -#include -#include -#include +#include +#include -#include "make.h" -#include "hash.h" -#include "dir.h" -#include "config.h" +#include "make.h" +#include "dir.h" +#include "config.h" -#ifdef TARGET_MACHINE -#undef MAKE_MACHINE -#define MAKE_MACHINE TARGET_MACHINE -#endif -#ifdef TARGET_MACHINE_ARCH -#undef MAKE_MACHINE_ARCH -#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH -#endif +/* "@(#)arch.c 8.2 (Berkeley) 1/2/94" */ +MAKE_RCSID("$NetBSD: arch.c,v 1.198 2021/03/15 12:15:03 rillig Exp $"); -static Lst archives; /* Lst of archives we've already examined */ +typedef struct List ArchList; +typedef struct ListNode ArchListNode; + +static ArchList archives; /* The archives we've already examined */ typedef struct Arch { - char *name; /* Name of archive */ - Hash_Table members; /* All the members of the archive described - * by key/value pairs */ - char *fnametab; /* Extended name table strings */ - size_t fnamesize; /* Size of the string table */ + char *name; /* Name of archive */ + HashTable members; /* All the members of the archive described + * by key/value pairs */ + char *fnametab; /* Extended name table strings */ + size_t fnamesize; /* Size of the string table */ } Arch; -static int ArchFindArchive(const void *, const void *); -#ifdef CLEANUP -static void ArchFree(void *); -#endif -static struct ar_hdr *ArchStatMember(char *, char *, Boolean); -static FILE *ArchFindMember(char *, char *, struct ar_hdr *, const char *); +static FILE *ArchFindMember(const char *, const char *, + struct ar_hdr *, const char *); #if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) #define SVR4ARCHIVES static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); #endif #ifdef CLEANUP -/*- - *----------------------------------------------------------------------- - * ArchFree -- - * Free memory used by an archive - * - * Results: - * None. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ static void ArchFree(void *ap) { - Arch *a = (Arch *)ap; - Hash_Search search; - Hash_Entry *entry; + Arch *a = ap; + HashIter hi; - /* Free memory from hash entries */ - for (entry = Hash_EnumFirst(&a->members, &search); - entry != NULL; - entry = Hash_EnumNext(&search)) - free(Hash_GetValue(entry)); + /* Free memory from hash entries */ + HashIter_Init(&hi, &a->members); + while (HashIter_Next(&hi) != NULL) + free(hi.entry->value); - free(a->name); - if (a->fnametab) + free(a->name); free(a->fnametab); - Hash_DeleteTable(&a->members); - free(a); + HashTable_Done(&a->members); + free(a); } #endif - -/*- - *----------------------------------------------------------------------- - * Arch_ParseArchive -- - * Parse the archive specification in the given line and find/create - * the nodes for the specified archive members, placing their nodes - * on the given list. +/* + * Parse an archive specification such as "archive.a(member1 member2.${EXT})", + * adding nodes for the expanded members to gns. Nodes are created as + * necessary. * * Input: - * linePtr Pointer to start of specification - * nodeLst Lst on which to place the nodes - * ctxt Context in which to expand variables + * pp The start of the specification. + * gns The list on which to place the nodes. + * scope The scope in which to expand variables. * - * Results: - * SUCCESS if it was a valid specification. The linePtr is updated - * to point to the first non-space after the archive spec. The - * nodes for the members are placed on the given list. - * - * Side Effects: - * Some nodes may be created. The given list is extended. - * - *----------------------------------------------------------------------- + * Output: + * return TRUE if it was a valid specification. + * *pp Points to the first non-space after the archive spec. */ -ReturnStatus -Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) +Boolean +Arch_ParseArchive(char **pp, GNodeList *gns, GNode *scope) { - char *cp; /* Pointer into line */ - GNode *gn; /* New node */ - char *libName; /* Library-part of specification */ - char *memName; /* Member-part of specification */ - char *nameBuf; /* temporary place for node name */ - char saveChar; /* Ending delimiter of member-name */ - Boolean subLibName; /* TRUE if libName should have/had - * variable substitution performed on it */ + char *cp; /* Pointer into line */ + GNode *gn; /* New node */ + MFStr libName; /* Library-part of specification */ + char *memName; /* Member-part of specification */ + char saveChar; /* Ending delimiter of member-name */ + Boolean expandLibName; /* Whether the parsed libName contains + * variable expressions that need to be + * expanded */ - libName = *linePtr; + libName = MFStr_InitRefer(*pp); + expandLibName = FALSE; - subLibName = FALSE; + for (cp = libName.str; *cp != '(' && *cp != '\0';) { + if (*cp == '$') { + /* Expand nested variable expressions. */ + /* XXX: This code can probably be shortened. */ + const char *nested_p = cp; + FStr result; + Boolean isError; - for (cp = libName; *cp != '(' && *cp != '\0'; cp++) { - if (*cp == '$') { - /* - * Variable spec, so call the Var module to parse the puppy - * so we can safely advance beyond it... - */ - int length; - void *freeIt; - char *result; + /* XXX: is expanded twice: once here and once below */ + (void)Var_Parse(&nested_p, scope, + VARE_UNDEFERR, &result); + /* TODO: handle errors */ + isError = result.str == var_Error; + FStr_Done(&result); + if (isError) + return FALSE; - result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt); - if (freeIt) - free(freeIt); - if (result == var_Error) { - return(FAILURE); - } else { - subLibName = TRUE; - } - - cp += length-1; + expandLibName = TRUE; + cp += nested_p - cp; + } else + cp++; } - } - *cp++ = '\0'; - if (subLibName) { - libName = Var_Subst(NULL, libName, ctxt, TRUE); - } - - - for (;;) { - /* - * First skip to the start of the member's name, mark that - * place and skip to the end of it (either white-space or - * a close paren). - */ - Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ - - while (*cp != '\0' && *cp != ')' && isspace ((unsigned char)*cp)) { - cp++; + *cp++ = '\0'; + if (expandLibName) { + char *expanded; + (void)Var_Subst(libName.str, scope, VARE_UNDEFERR, &expanded); + /* TODO: handle errors */ + libName = MFStr_InitOwn(expanded); } - memName = cp; - while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char)*cp)) { - if (*cp == '$') { + + + for (;;) { /* - * Variable spec, so call the Var module to parse the puppy - * so we can safely advance beyond it... + * First skip to the start of the member's name, mark that + * place and skip to the end of it (either white-space or + * a close paren). */ - int length; - void *freeIt; - char *result; + Boolean doSubst = FALSE; - result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt); - if (freeIt) - free(freeIt); - if (result == var_Error) { - return(FAILURE); - } else { - doSubst = TRUE; + pp_skip_whitespace(&cp); + + memName = cp; + while (*cp != '\0' && *cp != ')' && !ch_isspace(*cp)) { + if (*cp == '$') { + /* Expand nested variable expressions. */ + /* XXX: This code can probably be shortened. */ + FStr result; + Boolean isError; + const char *nested_p = cp; + + (void)Var_Parse(&nested_p, scope, + VARE_UNDEFERR, &result); + /* TODO: handle errors */ + isError = result.str == var_Error; + FStr_Done(&result); + + if (isError) + return FALSE; + + doSubst = TRUE; + cp += nested_p - cp; + } else { + cp++; + } } - cp += length; - } else { - cp++; - } - } - - /* - * If the specification ends without a closing parenthesis, - * chances are there's something wrong (like a missing backslash), - * so it's better to return failure than allow such things to happen - */ - if (*cp == '\0') { - printf("No closing parenthesis in archive specification\n"); - return (FAILURE); - } - - /* - * If we didn't move anywhere, we must be done - */ - if (cp == memName) { - break; - } - - saveChar = *cp; - *cp = '\0'; - - /* - * XXX: This should be taken care of intelligently by - * SuffExpandChildren, both for the archive and the member portions. - */ - /* - * If member contains variables, try and substitute for them. - * This will slow down archive specs with dynamic sources, of course, - * since we'll be (non-)substituting them three times, but them's - * the breaks -- we need to do this since SuffExpandChildren calls - * us, otherwise we could assume the thing would be taken care of - * later. - */ - if (doSubst) { - char *buf; - char *sacrifice; - char *oldMemName = memName; - size_t sz; - - memName = Var_Subst(NULL, memName, ctxt, TRUE); - - /* - * Now form an archive spec and recurse to deal with nested - * variables and multi-word variable values.... The results - * are just placed at the end of the nodeLst we're returning. - */ - sz = strlen(memName)+strlen(libName)+3; - buf = sacrifice = bmake_malloc(sz); - - snprintf(buf, sz, "%s(%s)", libName, memName); - - if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { /* - * Must contain dynamic sources, so we can't deal with it now. - * Just create an ARCHV node for the thing and let - * SuffExpandChildren handle it... + * If the specification ends without a closing parenthesis, + * chances are there's something wrong (like a missing + * backslash), so it's better to return failure than allow + * such things to happen */ - gn = Targ_FindNode(buf, TARG_CREATE); - - if (gn == NULL) { - free(buf); - return(FAILURE); - } else { - gn->type |= OP_ARCHV; - (void)Lst_AtEnd(nodeLst, gn); + if (*cp == '\0') { + Parse_Error(PARSE_FATAL, + "No closing parenthesis " + "in archive specification"); + return FALSE; } - } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) { + /* - * Error in nested call -- free buffer and return FAILURE - * ourselves. + * If we didn't move anywhere, we must be done */ - free(buf); - return(FAILURE); - } - /* - * Free buffer and continue with our work. - */ - free(buf); - } else if (Dir_HasWildcards(memName)) { - Lst members = Lst_Init(FALSE); - char *member; - size_t sz = MAXPATHLEN, nsz; - nameBuf = bmake_malloc(sz); + if (cp == memName) + break; - Dir_Expand(memName, dirSearchPath, members); - while (!Lst_IsEmpty(members)) { - member = (char *)Lst_DeQueue(members); - nsz = strlen(libName) + strlen(member) + 3; - if (sz > nsz) - nameBuf = bmake_realloc(nameBuf, sz = nsz * 2); + saveChar = *cp; + *cp = '\0'; + + /* + * XXX: This should be taken care of intelligently by + * SuffExpandChildren, both for the archive and the member + * portions. + */ + /* + * If member contains variables, try and substitute for them. + * This will slow down archive specs with dynamic sources, of + * course, since we'll be (non-)substituting them three + * times, but them's the breaks -- we need to do this since + * SuffExpandChildren calls us, otherwise we could assume the + * thing would be taken care of later. + */ + if (doSubst) { + char *fullName; + char *p; + char *unexpandedMemName = memName; + + (void)Var_Subst(memName, scope, VARE_UNDEFERR, + &memName); + /* TODO: handle errors */ + + /* + * Now form an archive spec and recurse to deal with + * nested variables and multi-word variable values. + */ + fullName = str_concat4(libName.str, "(", memName, ")"); + p = fullName; + + if (strchr(memName, '$') != NULL && + strcmp(memName, unexpandedMemName) == 0) { + /* + * Must contain dynamic sources, so we can't + * deal with it now. Just create an ARCHV node + * for the thing and let SuffExpandChildren + * handle it. + */ + gn = Targ_GetNode(fullName); + gn->type |= OP_ARCHV; + Lst_Append(gns, gn); + + } else if (!Arch_ParseArchive(&p, gns, scope)) { + /* Error in nested call. */ + free(fullName); + /* XXX: does unexpandedMemName leak? */ + return FALSE; + } + free(fullName); + /* XXX: does unexpandedMemName leak? */ + + } else if (Dir_HasWildcards(memName)) { + StringList members = LST_INIT; + SearchPath_Expand(&dirSearchPath, memName, &members); + + while (!Lst_IsEmpty(&members)) { + char *member = Lst_Dequeue(&members); + char *fullname = str_concat4(libName.str, "(", + member, ")"); + free(member); + + gn = Targ_GetNode(fullname); + free(fullname); + + gn->type |= OP_ARCHV; + Lst_Append(gns, gn); + } + Lst_Done(&members); - snprintf(nameBuf, sz, "%s(%s)", libName, member); - free(member); - gn = Targ_FindNode(nameBuf, TARG_CREATE); - if (gn == NULL) { - free(nameBuf); - return (FAILURE); } else { - /* - * We've found the node, but have to make sure the rest of - * the world knows it's an archive member, without having - * to constantly check for parentheses, so we type the - * thing with the OP_ARCHV bit before we place it on the - * end of the provided list. - */ - gn->type |= OP_ARCHV; - (void)Lst_AtEnd(nodeLst, gn); + char *fullname = str_concat4(libName.str, "(", memName, + ")"); + gn = Targ_GetNode(fullname); + free(fullname); + + /* + * We've found the node, but have to make sure the + * rest of the world knows it's an archive member, + * without having to constantly check for parentheses, + * so we type the thing with the OP_ARCHV bit before + * we place it on the end of the provided list. + */ + gn->type |= OP_ARCHV; + Lst_Append(gns, gn); } - } - Lst_Destroy(members, NULL); - free(nameBuf); - } else { - size_t sz = strlen(libName) + strlen(memName) + 3; - nameBuf = bmake_malloc(sz); - snprintf(nameBuf, sz, "%s(%s)", libName, memName); - gn = Targ_FindNode(nameBuf, TARG_CREATE); - free(nameBuf); - if (gn == NULL) { - return (FAILURE); - } else { - /* - * We've found the node, but have to make sure the rest of the - * world knows it's an archive member, without having to - * constantly check for parentheses, so we type the thing with - * the OP_ARCHV bit before we place it on the end of the - * provided list. - */ - gn->type |= OP_ARCHV; - (void)Lst_AtEnd(nodeLst, gn); - } - } - if (doSubst) { - free(memName); + if (doSubst) + free(memName); + + *cp = saveChar; } - *cp = saveChar; - } + MFStr_Done(&libName); - /* - * If substituted libName, free it now, since we need it no longer. - */ - if (subLibName) { - free(libName); - } - - /* - * We promised the pointer would be set up at the next non-space, so - * we must advance cp there before setting *linePtr... (note that on - * entrance to the loop, cp is guaranteed to point at a ')') - */ - do { - cp++; - } while (*cp != '\0' && isspace ((unsigned char)*cp)); - - *linePtr = cp; - return (SUCCESS); + cp++; /* skip the ')' */ + /* We promised that pp would be set up at the next non-space. */ + pp_skip_whitespace(&cp); + *pp = cp; + return TRUE; } -/*- - *----------------------------------------------------------------------- - * ArchFindArchive -- - * See if the given archive is the one we are looking for. Called - * From ArchStatMember and ArchFindMember via Lst_Find. - * - * Input: - * ar Current list element - * archName Name we want - * - * Results: - * 0 if it is, non-zero if it isn't. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -static int -ArchFindArchive(const void *ar, const void *archName) -{ - return (strcmp(archName, ((const Arch *)ar)->name)); -} - -/*- - *----------------------------------------------------------------------- - * ArchStatMember -- - * Locate a member of an archive, given the path of the archive and - * the path of the desired member. +/* + * Locate a member of an archive, given the path of the archive and the path + * of the desired member. * * Input: * archive Path to the archive - * member Name of member. If it is a path, only the last - * component is used. - * hash TRUE if archive should be hashed if not already so. + * member Name of member; only its basename is used. + * addToCache TRUE if archive should be cached if not already so. * * Results: - * A pointer to the current struct ar_hdr structure for the member. Note - * That no position is returned, so this is not useful for touching - * archive members. This is mostly because we have no assurances that - * The archive will remain constant after we read all the headers, so - * there's not much point in remembering the position... + * The ar_hdr for the member, or NULL. * - * Side Effects: - * - *----------------------------------------------------------------------- + * See ArchFindMember for an almost identical copy of this code. */ static struct ar_hdr * -ArchStatMember(char *archive, char *member, Boolean hash) +ArchStatMember(const char *archive, const char *member, Boolean addToCache) { -#define AR_MAX_NAME_LEN (sizeof(arh.ar_name)-1) - FILE * arch; /* Stream to archive */ - int size; /* Size of archive member */ - char *cp; /* Useful character pointer */ - char magic[SARMAG]; - LstNode ln; /* Lst member containing archive descriptor */ - Arch *ar; /* Archive descriptor */ - Hash_Entry *he; /* Entry containing member's description */ - struct ar_hdr arh; /* archive-member header for reading archive */ - char memName[MAXPATHLEN+1]; - /* Current member name while hashing. */ +#define AR_MAX_NAME_LEN (sizeof arh.ar_name - 1) + FILE *arch; + size_t size; /* Size of archive member */ + char magic[SARMAG]; + ArchListNode *ln; + Arch *ar; /* Archive descriptor */ + struct ar_hdr arh; /* archive-member header for reading archive */ + char memName[MAXPATHLEN + 1]; + /* Current member name while hashing. */ - /* - * Because of space constraints and similar things, files are archived - * using their final path components, not the entire thing, so we need - * to point 'member' to the final component, if there is one, to make - * the comparisons easier... - */ - cp = strrchr(member, '/'); - if (cp != NULL) { - member = cp + 1; - } - - ln = Lst_Find(archives, archive, ArchFindArchive); - if (ln != NULL) { - ar = (Arch *)Lst_Datum(ln); - - he = Hash_FindEntry(&ar->members, member); - - if (he != NULL) { - return ((struct ar_hdr *)Hash_GetValue(he)); - } else { - /* Try truncated name */ - char copy[AR_MAX_NAME_LEN+1]; - size_t len = strlen(member); - - if (len > AR_MAX_NAME_LEN) { - len = AR_MAX_NAME_LEN; - strncpy(copy, member, AR_MAX_NAME_LEN); - copy[AR_MAX_NAME_LEN] = '\0'; - } - if ((he = Hash_FindEntry(&ar->members, copy)) != NULL) - return ((struct ar_hdr *)Hash_GetValue(he)); - return NULL; - } - } - - if (!hash) { /* - * Caller doesn't want the thing hashed, just use ArchFindMember - * to read the header for the member out and close down the stream - * again. Since the archive is not to be hashed, we assume there's - * no need to allocate extra room for the header we're returning, - * so just declare it static. + * Because of space constraints and similar things, files are archived + * using their basename, not the entire path. */ - static struct ar_hdr sarh; + member = str_basename(member); - arch = ArchFindMember(archive, member, &sarh, "r"); - - if (arch == NULL) { - return NULL; - } else { - fclose(arch); - return (&sarh); + for (ln = archives.first; ln != NULL; ln = ln->next) { + const Arch *a = ln->datum; + if (strcmp(a->name, archive) == 0) + break; } - } - /* - * We don't have this archive on the list yet, so we want to find out - * everything that's in it and cache it so we can get at it quickly. - */ - arch = fopen(archive, "r"); - if (arch == NULL) { - return NULL; - } + if (ln != NULL) { + struct ar_hdr *hdr; - /* - * We use the ARMAG string to make sure this is an archive we - * can handle... - */ - if ((fread(magic, SARMAG, 1, arch) != 1) || - (strncmp(magic, ARMAG, SARMAG) != 0)) { - fclose(arch); - return NULL; - } + ar = ln->datum; + hdr = HashTable_FindValue(&ar->members, member); + if (hdr != NULL) + return hdr; - ar = bmake_malloc(sizeof(Arch)); - ar->name = bmake_strdup(archive); - ar->fnametab = NULL; - ar->fnamesize = 0; - Hash_InitTable(&ar->members, -1); - memName[AR_MAX_NAME_LEN] = '\0'; + { + /* Try truncated name */ + char copy[AR_MAX_NAME_LEN + 1]; + size_t len = strlen(member); - while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) { - if (strncmp( arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) { - /* - * The header is bogus, so the archive is bad - * and there's no way we can recover... - */ - goto badarch; - } else { - /* - * We need to advance the stream's pointer to the start of the - * next header. Files are padded with newlines to an even-byte - * boundary, so we need to extract the size of the file from the - * 'size' field of the header and round it up during the seek. - */ - arh.ar_size[sizeof(arh.ar_size)-1] = '\0'; - size = (int)strtol(arh.ar_size, NULL, 10); + if (len > AR_MAX_NAME_LEN) { + snprintf(copy, sizeof copy, "%s", member); + hdr = HashTable_FindValue(&ar->members, copy); + } + return hdr; + } + } - (void)strncpy(memName, arh.ar_name, sizeof(arh.ar_name)); - for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) { - continue; - } - cp[1] = '\0'; + if (!addToCache) { + /* + * Caller doesn't want the thing cached, just use + * ArchFindMember to read the header for the member out and + * close down the stream again. Since the archive is not to be + * cached, we assume there's no need to allocate extra room + * for the header we're returning, so just declare it static. + */ + static struct ar_hdr sarh; + + arch = ArchFindMember(archive, member, &sarh, "r"); + if (arch == NULL) + return NULL; + + fclose(arch); + return &sarh; + } + + /* + * We don't have this archive on the list yet, so we want to find out + * everything that's in it and cache it so we can get at it quickly. + */ + arch = fopen(archive, "r"); + if (arch == NULL) + return NULL; + + /* + * We use the ARMAG string to make sure this is an archive we + * can handle... + */ + if (fread(magic, SARMAG, 1, arch) != 1 || + strncmp(magic, ARMAG, SARMAG) != 0) { + (void)fclose(arch); + return NULL; + } + + ar = bmake_malloc(sizeof *ar); + ar->name = bmake_strdup(archive); + ar->fnametab = NULL; + ar->fnamesize = 0; + HashTable_Init(&ar->members); + memName[AR_MAX_NAME_LEN] = '\0'; + + while (fread(&arh, sizeof arh, 1, arch) == 1) { + char *nameend; + + /* If the header is bogus, there's no way we can recover. */ + if (strncmp(arh.ar_fmag, ARFMAG, sizeof arh.ar_fmag) != 0) + goto badarch; + + /* + * We need to advance the stream's pointer to the start of the + * next header. Files are padded with newlines to an even-byte + * boundary, so we need to extract the size of the file from + * the 'size' field of the header and round it up during the + * seek. + */ + arh.ar_size[sizeof arh.ar_size - 1] = '\0'; + size = (size_t)strtol(arh.ar_size, NULL, 10); + + memcpy(memName, arh.ar_name, sizeof arh.ar_name); + nameend = memName + AR_MAX_NAME_LEN; + while (nameend > memName && *nameend == ' ') + nameend--; + nameend[1] = '\0'; #ifdef SVR4ARCHIVES - /* - * svr4 names are slash terminated. Also svr4 extended AR format. - */ - if (memName[0] == '/') { /* - * svr4 magic mode; handle it + * svr4 names are slash-terminated. + * Also svr4 extended the AR format. */ - switch (ArchSVR4Entry(ar, memName, size, arch)) { - case -1: /* Invalid data */ - goto badarch; - case 0: /* List of files entry */ - continue; - default: /* Got the entry */ - break; + if (memName[0] == '/') { + /* svr4 magic mode; handle it */ + switch (ArchSVR4Entry(ar, memName, size, arch)) { + case -1: /* Invalid data */ + goto badarch; + case 0: /* List of files entry */ + continue; + default: /* Got the entry */ + break; + } + } else { + if (nameend[0] == '/') + nameend[0] = '\0'; } - } - else { - if (cp[0] == '/') - cp[0] = '\0'; - } #endif #ifdef AR_EFMT1 - /* - * BSD 4.4 extended AR format: #1/, with name as the - * first bytes of the file - */ - if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && - isdigit((unsigned char)memName[sizeof(AR_EFMT1) - 1])) { + /* + * BSD 4.4 extended AR format: #1/, with name as the + * first bytes of the file + */ + if (strncmp(memName, AR_EFMT1, sizeof AR_EFMT1 - 1) == 0 && + ch_isdigit(memName[sizeof AR_EFMT1 - 1])) { - unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]); + int elen = atoi(memName + sizeof AR_EFMT1 - 1); - if (elen > MAXPATHLEN) - goto badarch; - if (fread(memName, elen, 1, arch) != 1) - goto badarch; - memName[elen] = '\0'; - fseek(arch, -elen, SEEK_CUR); - if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName); + if ((unsigned int)elen > MAXPATHLEN) + goto badarch; + if (fread(memName, (size_t)elen, 1, arch) != 1) + goto badarch; + memName[elen] = '\0'; + if (fseek(arch, -elen, SEEK_CUR) != 0) + goto badarch; + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf( + "ArchStatMember: " + "Extended format entry for %s\n", + memName); } - } #endif - he = Hash_CreateEntry(&ar->members, memName, NULL); - Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr))); - memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr)); + { + struct ar_hdr *cached_hdr = bmake_malloc( + sizeof *cached_hdr); + memcpy(cached_hdr, &arh, sizeof arh); + HashTable_Set(&ar->members, memName, cached_hdr); + } + + if (fseek(arch, ((long)size + 1) & ~1, SEEK_CUR) != 0) + goto badarch; } - fseek(arch, (size + 1) & ~1, SEEK_CUR); - } - fclose(arch); + fclose(arch); - (void)Lst_AtEnd(archives, ar); + Lst_Append(&archives, ar); - /* - * Now that the archive has been read and cached, we can look into - * the hash table to find the desired member's header. - */ - he = Hash_FindEntry(&ar->members, member); - - if (he != NULL) { - return ((struct ar_hdr *)Hash_GetValue(he)); - } else { - return NULL; - } + /* + * Now that the archive has been read and cached, we can look into + * the addToCache table to find the desired member's header. + */ + return HashTable_FindValue(&ar->members, member); badarch: - fclose(arch); - Hash_DeleteTable(&ar->members); - if (ar->fnametab) + fclose(arch); + HashTable_Done(&ar->members); free(ar->fnametab); - free(ar); - return NULL; + free(ar); + return NULL; } #ifdef SVR4ARCHIVES -/*- - *----------------------------------------------------------------------- - * ArchSVR4Entry -- - * Parse an SVR4 style entry that begins with a slash. - * If it is "//", then load the table of filenames - * If it is "/", then try to substitute the long file name - * from offset of a table previously read. +/* + * Parse an SVR4 style entry that begins with a slash. + * If it is "//", then load the table of filenames. + * If it is "/", then try to substitute the long file name + * from offset of a table previously read. + * If a table is read, the file pointer is moved to the next archive member. * * Results: * -1: Bad data in archive * 0: A table was loaded from the file * 1: Name was successfully substituted from table * 2: Name was not successfully substituted from table - * - * Side Effects: - * If a table is read, the file pointer is moved to the next archive - * member - * - *----------------------------------------------------------------------- */ static int -ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) +ArchSVR4Entry(Arch *ar, char *inout_name, size_t size, FILE *arch) { #define ARLONGNAMES1 "//" #define ARLONGNAMES2 "/ARFILENAMES" - size_t entry; - char *ptr, *eptr; + size_t entry; + char *ptr, *eptr; - if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 || - strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { + if (strncmp(inout_name, ARLONGNAMES1, sizeof ARLONGNAMES1 - 1) == 0 || + strncmp(inout_name, ARLONGNAMES2, sizeof ARLONGNAMES2 - 1) == 0) { - if (ar->fnametab != NULL) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "Attempted to redefine an SVR4 name table\n"); - } - return -1; + if (ar->fnametab != NULL) { + DEBUG0(ARCH, + "Attempted to redefine an SVR4 name table\n"); + return -1; + } + + /* + * This is a table of archive names, so we build one for + * ourselves + */ + ar->fnametab = bmake_malloc(size); + ar->fnamesize = size; + + if (fread(ar->fnametab, size, 1, arch) != 1) { + DEBUG0(ARCH, "Reading an SVR4 name table failed\n"); + return -1; + } + eptr = ar->fnametab + size; + for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) + if (*ptr == '/') { + entry++; + *ptr = '\0'; + } + DEBUG1(ARCH, "Found svr4 archive name table with %lu entries\n", + (unsigned long)entry); + return 0; } - /* - * This is a table of archive names, so we build one for - * ourselves - */ - ar->fnametab = bmake_malloc(size); - ar->fnamesize = size; + if (inout_name[1] == ' ' || inout_name[1] == '\0') + return 2; - if (fread(ar->fnametab, size, 1, arch) != 1) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "Reading an SVR4 name table failed\n"); - } - return -1; + entry = (size_t)strtol(&inout_name[1], &eptr, 0); + if ((*eptr != ' ' && *eptr != '\0') || eptr == &inout_name[1]) { + DEBUG1(ARCH, "Could not parse SVR4 name %s\n", inout_name); + return 2; } - eptr = ar->fnametab + size; - for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) - switch (*ptr) { - case '/': - entry++; - *ptr = '\0'; - break; - - case '\n': - break; - - default: - break; - } - if (DEBUG(ARCH)) { - fprintf(debug_file, "Found svr4 archive name table with %lu entries\n", - (u_long)entry); + if (entry >= ar->fnamesize) { + DEBUG2(ARCH, "SVR4 entry offset %s is greater than %lu\n", + inout_name, (unsigned long)ar->fnamesize); + return 2; } - return 0; - } - if (name[1] == ' ' || name[1] == '\0') - return 2; + DEBUG2(ARCH, "Replaced %s with %s\n", inout_name, &ar->fnametab[entry]); - entry = (size_t)strtol(&name[1], &eptr, 0); - if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "Could not parse SVR4 name %s\n", name); - } - return 2; - } - if (entry >= ar->fnamesize) { - if (DEBUG(ARCH)) { - fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n", - name, (u_long)ar->fnamesize); - } - return 2; - } - - if (DEBUG(ARCH)) { - fprintf(debug_file, "Replaced %s with %s\n", name, &ar->fnametab[entry]); - } - - (void)strncpy(name, &ar->fnametab[entry], MAXPATHLEN); - name[MAXPATHLEN] = '\0'; - return 1; + snprintf(inout_name, MAXPATHLEN + 1, "%s", &ar->fnametab[entry]); + return 1; } #endif -/*- - *----------------------------------------------------------------------- - * ArchFindMember -- - * Locate a member of an archive, given the path of the archive and - * the path of the desired member. If the archive is to be modified, - * the mode should be "r+", if not, it should be "r". +static Boolean +ArchiveMember_HasName(const struct ar_hdr *hdr, + const char *name, size_t namelen) +{ + const size_t ar_name_len = sizeof hdr->ar_name; + const char *ar_name = hdr->ar_name; + + if (strncmp(ar_name, name, namelen) != 0) + return FALSE; + + if (namelen >= ar_name_len) + return namelen == ar_name_len; + + /* hdr->ar_name is space-padded to the right. */ + if (ar_name[namelen] == ' ') + return TRUE; + + /* In archives created by GNU binutils 2.27, the member names end with + * a slash. */ + if (ar_name[namelen] == '/' && + (namelen == ar_name_len || ar_name[namelen + 1] == ' ')) + return TRUE; + + return FALSE; +} + +/* + * Locate a member of an archive, given the path of the archive and the path + * of the desired member. * * Input: * archive Path to the archive * member Name of member. If it is a path, only the last * component is used. - * arhPtr Pointer to header structure to be filled in - * mode The mode for opening the stream + * out_arh Archive header to be filled in + * mode "r" for read-only access, "r+" for read-write access * - * Results: - * An FILE *, opened for reading and writing, positioned at the - * start of the member's struct ar_hdr, or NULL if the member was - * nonexistent. The current struct ar_hdr for member. + * Output: + * return The archive file, positioned at the start of the + * member's struct ar_hdr, or NULL if the member doesn't + * exist. + * *out_arh The current struct ar_hdr for member. * - * Side Effects: - * The passed struct ar_hdr structure is filled in. - * - *----------------------------------------------------------------------- + * See ArchStatMember for an almost identical copy of this code. */ static FILE * -ArchFindMember(char *archive, char *member, struct ar_hdr *arhPtr, - const char *mode) +ArchFindMember(const char *archive, const char *member, struct ar_hdr *out_arh, + const char *mode) { - FILE * arch; /* Stream to archive */ - int size; /* Size of archive member */ - char *cp; /* Useful character pointer */ - char magic[SARMAG]; - size_t len, tlen; + FILE *arch; /* Stream to archive */ + int size; /* Size of archive member */ + char magic[SARMAG]; + size_t len; - arch = fopen(archive, mode); - if (arch == NULL) { - return NULL; - } + arch = fopen(archive, mode); + if (arch == NULL) + return NULL; - /* - * We use the ARMAG string to make sure this is an archive we - * can handle... - */ - if ((fread(magic, SARMAG, 1, arch) != 1) || - (strncmp(magic, ARMAG, SARMAG) != 0)) { - fclose(arch); - return NULL; - } + /* + * We use the ARMAG string to make sure this is an archive we + * can handle... + */ + if (fread(magic, SARMAG, 1, arch) != 1 || + strncmp(magic, ARMAG, SARMAG) != 0) { + fclose(arch); + return NULL; + } - /* - * Because of space constraints and similar things, files are archived - * using their final path components, not the entire thing, so we need - * to point 'member' to the final component, if there is one, to make - * the comparisons easier... - */ - cp = strrchr(member, '/'); - if (cp != NULL) { - member = cp + 1; - } - len = tlen = strlen(member); - if (len > sizeof(arhPtr->ar_name)) { - tlen = sizeof(arhPtr->ar_name); - } + /* + * Because of space constraints and similar things, files are archived + * using their basename, not the entire path. + */ + member = str_basename(member); + + len = strlen(member); + + while (fread(out_arh, sizeof *out_arh, 1, arch) == 1) { + + if (strncmp(out_arh->ar_fmag, ARFMAG, + sizeof out_arh->ar_fmag) != 0) { + /* + * The header is bogus, so the archive is bad + * and there's no way we can recover... + */ + fclose(arch); + return NULL; + } + + DEBUG5(ARCH, "Reading archive %s member %.*s mtime %.*s\n", + archive, + (int)sizeof out_arh->ar_name, out_arh->ar_name, + (int)sizeof out_arh->ar_date, out_arh->ar_date); + + if (ArchiveMember_HasName(out_arh, member, len)) { + /* + * To make life easier for callers that want to update + * the archive, we reposition the file at the start of + * the header we just read before we return the + * stream. In a more general situation, it might be + * better to leave the file at the actual member, + * rather than its header, but not here. + */ + if (fseek(arch, -(long)sizeof *out_arh, SEEK_CUR) != + 0) { + fclose(arch); + return NULL; + } + return arch; + } - while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { - if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag) ) != 0) { - /* - * The header is bogus, so the archive is bad - * and there's no way we can recover... - */ - fclose(arch); - return NULL; - } else if (strncmp(member, arhPtr->ar_name, tlen) == 0) { - /* - * If the member's name doesn't take up the entire 'name' field, - * we have to be careful of matching prefixes. Names are space- - * padded to the right, so if the character in 'name' at the end - * of the matched string is anything but a space, this isn't the - * member we sought. - */ - if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){ - goto skip; - } else { - /* - * To make life easier, we reposition the file at the start - * of the header we just read before we return the stream. - * In a more general situation, it might be better to leave - * the file at the actual member, rather than its header, but - * not here... - */ - fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR); - return (arch); - } - } else #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ - if (strncmp(arhPtr->ar_name, AR_EFMT1, - sizeof(AR_EFMT1) - 1) == 0 && - isdigit((unsigned char)arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) { + if (strncmp(out_arh->ar_name, AR_EFMT1, sizeof AR_EFMT1 - 1) == + 0 && + (ch_isdigit(out_arh->ar_name[sizeof AR_EFMT1 - 1]))) { + int elen = atoi(&out_arh->ar_name[sizeof AR_EFMT1 - 1]); + char ename[MAXPATHLEN + 1]; - unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]); - char ename[MAXPATHLEN + 1]; - - if (elen > MAXPATHLEN) { - fclose(arch); - return NULL; + if ((unsigned int)elen > MAXPATHLEN) { + fclose(arch); + return NULL; + } + if (fread(ename, (size_t)elen, 1, arch) != 1) { + fclose(arch); + return NULL; + } + ename[elen] = '\0'; + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf( + "ArchFindMember: " + "Extended format entry for %s\n", + ename); + if (strncmp(ename, member, len) == 0) { + /* Found as extended name */ + if (fseek(arch, + -(long)sizeof(struct ar_hdr) - elen, + SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } + return arch; + } + if (fseek(arch, -elen, SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } } - if (fread(ename, elen, 1, arch) != 1) { - fclose(arch); - return NULL; - } - ename[elen] = '\0'; - if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "ArchFind: Extended format entry for %s\n", ename); - } - if (strncmp(ename, member, len) == 0) { - /* Found as extended name */ - fseek(arch, -sizeof(struct ar_hdr) - elen, SEEK_CUR); - return (arch); - } - fseek(arch, -elen, SEEK_CUR); - goto skip; - } else #endif - { -skip: - /* - * This isn't the member we're after, so we need to advance the - * stream's pointer to the start of the next header. Files are - * padded with newlines to an even-byte boundary, so we need to - * extract the size of the file from the 'size' field of the - * header and round it up during the seek. - */ - arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0'; - size = (int)strtol(arhPtr->ar_size, NULL, 10); - fseek(arch, (size + 1) & ~1, SEEK_CUR); - } - } - /* - * We've looked everywhere, but the member is not to be found. Close the - * archive and return NULL -- an error. - */ - fclose(arch); - return NULL; + /* + * This isn't the member we're after, so we need to advance the + * stream's pointer to the start of the next header. Files are + * padded with newlines to an even-byte boundary, so we need to + * extract the size of the file from the 'size' field of the + * header and round it up during the seek. + */ + out_arh->ar_size[sizeof out_arh->ar_size - 1] = '\0'; + size = (int)strtol(out_arh->ar_size, NULL, 10); + if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } + } + + fclose(arch); + return NULL; } -/*- - *----------------------------------------------------------------------- - * Arch_Touch -- - * Touch a member of an archive. +/* + * Touch a member of an archive, on disk. + * The GNode's modification time is left as-is. + * + * The st_mtime of the entire archive is also changed. + * For a library, it may be required to run ranlib after this. * * Input: * gn Node of member to touch * * Results: * The 'time' field of the member's header is updated. - * - * Side Effects: - * The modification time of the entire archive is also changed. - * For a library, this could necessitate the re-ranlib'ing of the - * whole thing. - * - *----------------------------------------------------------------------- */ void Arch_Touch(GNode *gn) { - FILE * arch; /* Stream open to archive, positioned properly */ - struct ar_hdr arh; /* Current header describing member */ - char *p1, *p2; + FILE *f; + struct ar_hdr arh; - arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1), - Var_Value(MEMBER, gn, &p2), - &arh, "r+"); - if (p1) - free(p1); - if (p2) - free(p2); - snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now); + f = ArchFindMember(GNode_VarArchive(gn), GNode_VarMember(gn), &arh, + "r+"); + if (f == NULL) + return; - if (arch != NULL) { - (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); - fclose(arch); - } + snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); + (void)fwrite(&arh, sizeof arh, 1, f); + fclose(f); /* TODO: handle errors */ } -/*- - *----------------------------------------------------------------------- - * Arch_TouchLib -- - * Given a node which represents a library, touch the thing, making - * sure that the table of contents also is touched. +/* + * Given a node which represents a library, touch the thing, making sure that + * the table of contents is also touched. * - * Input: - * gn The node of the library to touch - * - * Results: - * None. - * - * Side Effects: - * Both the modification time of the library and of the RANLIBMAG - * member are set to 'now'. - * - *----------------------------------------------------------------------- + * Both the modification time of the library and of the RANLIBMAG member are + * set to 'now'. */ +/*ARGSUSED*/ void -#if !defined(RANLIBMAG) Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED) -#else -Arch_TouchLib(GNode *gn) -#endif { #ifdef RANLIBMAG - FILE * arch; /* Stream open to archive */ - struct ar_hdr arh; /* Header describing table of contents */ - struct utimbuf times; /* Times for utime() call */ + FILE *f; + struct ar_hdr arh; /* Header describing table of contents */ + struct utimbuf times; - arch = ArchFindMember(gn->path, UNCONST(RANLIBMAG), &arh, "r+"); - snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now); + f = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); + if (f == NULL) + return; - if (arch != NULL) { - (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); - fclose(arch); + snprintf(arh.ar_date, sizeof arh.ar_date, "%-ld", (unsigned long)now); + (void)fwrite(&arh, sizeof arh, 1, f); + fclose(f); /* TODO: handle errors */ times.actime = times.modtime = now; - utime(gn->path, ×); - } + utime(gn->path, ×); /* TODO: handle errors */ #endif } -/*- - *----------------------------------------------------------------------- - * Arch_MTime -- - * Return the modification time of a member of an archive. - * - * Input: - * gn Node describing archive member - * - * Results: - * The modification time(seconds). - * - * Side Effects: - * The mtime field of the given node is filled in with the value - * returned by the function. - * - *----------------------------------------------------------------------- +/* + * Update the mtime of the GNode with the mtime from the archive member on + * disk (or in the cache). */ -time_t -Arch_MTime(GNode *gn) +void +Arch_UpdateMTime(GNode *gn) { - struct ar_hdr *arhPtr; /* Header of desired member */ - time_t modTime; /* Modification time as an integer */ - char *p1, *p2; + struct ar_hdr *arh; - arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1), - Var_Value(MEMBER, gn, &p2), - TRUE); - if (p1) - free(p1); - if (p2) - free(p2); - - if (arhPtr != NULL) { - modTime = (time_t)strtol(arhPtr->ar_date, NULL, 10); - } else { - modTime = 0; - } - - gn->mtime = modTime; - return (modTime); + arh = ArchStatMember(GNode_VarArchive(gn), GNode_VarMember(gn), TRUE); + if (arh != NULL) + gn->mtime = (time_t)strtol(arh->ar_date, NULL, 10); + else + gn->mtime = 0; } -/*- - *----------------------------------------------------------------------- - * Arch_MemMTime -- - * Given a non-existent archive member's node, get its modification - * time from its archived form, if it exists. - * - * Results: - * The modification time. - * - * Side Effects: - * The mtime field is filled in. - * - *----------------------------------------------------------------------- +/* + * Given a nonexistent archive member's node, update gn->mtime from its + * archived form, if it exists. */ -time_t -Arch_MemMTime(GNode *gn) +void +Arch_UpdateMemberMTime(GNode *gn) { - LstNode ln; - GNode *pgn; - char *nameStart, - *nameEnd; + GNodeListNode *ln; - if (Lst_Open(gn->parents) != SUCCESS) { - gn->mtime = 0; - return (0); - } - while ((ln = Lst_Next(gn->parents)) != NULL) { - pgn = (GNode *)Lst_Datum(ln); + for (ln = gn->parents.first; ln != NULL; ln = ln->next) { + GNode *pgn = ln->datum; - if (pgn->type & OP_ARCHV) { - /* - * If the parent is an archive specification and is being made - * and its member's name matches the name of the node we were - * given, record the modification time of the parent in the - * child. We keep searching its parents in case some other - * parent requires this child to exist... - */ - nameStart = strchr(pgn->name, '(') + 1; - nameEnd = strchr(nameStart, ')'); + if (pgn->type & OP_ARCHV) { + /* + * If the parent is an archive specification and is + * being made and its member's name matches the name + * of the node we were given, record the modification + * time of the parent in the child. We keep searching + * its parents in case some other parent requires this + * child to exist. + */ + const char *nameStart = strchr(pgn->name, '(') + 1; + const char *nameEnd = strchr(nameStart, ')'); + size_t nameLen = (size_t)(nameEnd - nameStart); - if ((pgn->flags & REMAKE) && - strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) { - gn->mtime = Arch_MTime(pgn); - } - } else if (pgn->flags & REMAKE) { - /* - * Something which isn't a library depends on the existence of - * this target, so it needs to exist. - */ - gn->mtime = 0; - break; + if ((pgn->flags & REMAKE) && + strncmp(nameStart, gn->name, nameLen) == 0) { + Arch_UpdateMTime(pgn); + gn->mtime = pgn->mtime; + } + } else if (pgn->flags & REMAKE) { + /* + * Something which isn't a library depends on the + * existence of this target, so it needs to exist. + */ + gn->mtime = 0; + break; + } } - } - - Lst_Close(gn->parents); - - return (gn->mtime); } -/*- - *----------------------------------------------------------------------- - * Arch_FindLib -- - * Search for a library along the given search path. +/* + * Search for a library along the given search path. + * + * The node's 'path' field is set to the found path (including the + * actual file name, not -l...). If the system can handle the -L + * flag when linking (or we cannot find the library), we assume that + * the user has placed the .LIBS variable in the final linking + * command (or the linker will know where to find it) and set the + * TARGET variable for this node to be the node's name. Otherwise, + * we set the TARGET variable to be the full path of the library, + * as returned by Dir_FindFile. * * Input: * gn Node of library to find - * path Search path - * - * Results: - * None. - * - * Side Effects: - * The node's 'path' field is set to the found path (including the - * actual file name, not -l...). If the system can handle the -L - * flag when linking (or we cannot find the library), we assume that - * the user has placed the .LIBRARIES variable in the final linking - * command (or the linker will know where to find it) and set the - * TARGET variable for this node to be the node's name. Otherwise, - * we set the TARGET variable to be the full path of the library, - * as returned by Dir_FindFile. - * - *----------------------------------------------------------------------- */ void -Arch_FindLib(GNode *gn, Lst path) +Arch_FindLib(GNode *gn, SearchPath *path) { - char *libName; /* file name for archive */ - size_t sz = strlen(gn->name) + 6 - 2; - - libName = bmake_malloc(sz); - snprintf(libName, sz, "lib%s.a", &gn->name[2]); - - gn->path = Dir_FindFile(libName, path); - - free(libName); + char *libName = str_concat3("lib", gn->name + 2, ".a"); + gn->path = Dir_FindFile(libName, path); + free(libName); #ifdef LIBRARIES - Var_Set(TARGET, gn->name, gn, 0); + Var_Set(gn, TARGET, gn->name); #else - Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn, 0); -#endif /* LIBRARIES */ + Var_Set(gn, TARGET, GNode_Path(gn)); +#endif } -/*- - *----------------------------------------------------------------------- - * Arch_LibOODate -- - * Decide if a node with the OP_LIB attribute is out-of-date. Called - * from Make_OODate to make its life easier. +/* + * Decide if a node with the OP_LIB attribute is out-of-date. Called from + * GNode_IsOODate to make its life easier. + * The library is cached if it hasn't been already. * - * There are several ways for a library to be out-of-date that are - * not available to ordinary files. In addition, there are ways - * that are open to regular files that are not available to - * libraries. A library that is only used as a source is never - * considered out-of-date by itself. This does not preclude the - * library's modification time from making its parent be out-of-date. - * A library will be considered out-of-date for any of these reasons, - * given that it is a target on a dependency line somewhere: - * Its modification time is less than that of one of its - * sources (gn->mtime < gn->cmgn->mtime). - * Its modification time is greater than the time at which the - * make began (i.e. it's been modified in the course - * of the make, probably by archiving). - * The modification time of one of its sources is greater than - * the one of its RANLIBMAG member (i.e. its table of contents - * is out-of-date). We don't compare of the archive time - * vs. TOC time because they can be too close. In my - * opinion we should not bother with the TOC at all since - * this is used by 'ar' rules that affect the data contents - * of the archive, not by ranlib rules, which affect the - * TOC. + * There are several ways for a library to be out-of-date that are + * not available to ordinary files. In addition, there are ways + * that are open to regular files that are not available to + * libraries. * - * Input: - * gn The library's graph node + * A library that is only used as a source is never + * considered out-of-date by itself. This does not preclude the + * library's modification time from making its parent be out-of-date. + * A library will be considered out-of-date for any of these reasons, + * given that it is a target on a dependency line somewhere: * - * Results: - * TRUE if the library is out-of-date. FALSE otherwise. + * Its modification time is less than that of one of its sources + * (gn->mtime < gn->youngestChild->mtime). * - * Side Effects: - * The library will be hashed if it hasn't been already. + * Its modification time is greater than the time at which the make + * began (i.e. it's been modified in the course of the make, probably + * by archiving). * - *----------------------------------------------------------------------- + * The modification time of one of its sources is greater than the one + * of its RANLIBMAG member (i.e. its table of contents is out-of-date). + * We don't compare the archive time vs. TOC time because they can be + * too close. In my opinion we should not bother with the TOC at all + * since this is used by 'ar' rules that affect the data contents of the + * archive, not by ranlib rules, which affect the TOC. */ Boolean Arch_LibOODate(GNode *gn) { - Boolean oodate; + Boolean oodate; - if (gn->type & OP_PHONY) { - oodate = TRUE; - } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) { - oodate = FALSE; - } else if ((!Lst_IsEmpty(gn->children) && gn->cmgn == NULL) || - (gn->mtime > now) || - (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime)) { - oodate = TRUE; - } else { -#ifdef RANLIBMAG - struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ - int modTimeTOC; /* The table-of-contents's mod time */ - - arhPtr = ArchStatMember(gn->path, UNCONST(RANLIBMAG), FALSE); - - if (arhPtr != NULL) { - modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10); - - if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); - } - oodate = (gn->cmgn == NULL || gn->cmgn->mtime > modTimeTOC); + if (gn->type & OP_PHONY) { + oodate = TRUE; + } else if (!GNode_IsTarget(gn) && Lst_IsEmpty(&gn->children)) { + oodate = FALSE; + } else if ((!Lst_IsEmpty(&gn->children) && gn->youngestChild == NULL) || + (gn->mtime > now) || + (gn->youngestChild != NULL && + gn->mtime < gn->youngestChild->mtime)) { + oodate = TRUE; } else { - /* - * A library w/o a table of contents is out-of-date - */ - if (DEBUG(ARCH) || DEBUG(MAKE)) { - fprintf(debug_file, "No t.o.c...."); - } - oodate = TRUE; - } +#ifdef RANLIBMAG + struct ar_hdr *arh; /* Header for __.SYMDEF */ + int modTimeTOC; /* The table-of-contents' mod time */ + + arh = ArchStatMember(gn->path, RANLIBMAG, FALSE); + + if (arh != NULL) { + modTimeTOC = (int)strtol(arh->ar_date, NULL, 10); + + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("%s modified %s...", + RANLIBMAG, + Targ_FmtTime(modTimeTOC)); + oodate = gn->youngestChild == NULL || + gn->youngestChild->mtime > modTimeTOC; + } else { + /* + * A library without a table of contents is out-of-date. + */ + if (DEBUG(ARCH) || DEBUG(MAKE)) + debug_printf("no toc..."); + oodate = TRUE; + } #else - oodate = FALSE; + oodate = FALSE; #endif - } - return (oodate); + } + return oodate; } -/*- - *----------------------------------------------------------------------- - * Arch_Init -- - * Initialize things for this module. - * - * Results: - * None. - * - * Side Effects: - * The 'archives' list is initialized. - * - *----------------------------------------------------------------------- - */ +/* Initialize the archives module. */ void Arch_Init(void) { - archives = Lst_Init(FALSE); + Lst_Init(&archives); } - - -/*- - *----------------------------------------------------------------------- - * Arch_End -- - * Cleanup things for this module. - * - * Results: - * None. - * - * Side Effects: - * The 'archives' list is freed - * - *----------------------------------------------------------------------- - */ +/* Clean up the archives module. */ void Arch_End(void) { #ifdef CLEANUP - Lst_Destroy(archives, ArchFree); + Lst_DoneCall(&archives, ArchFree); #endif } -/*- - *----------------------------------------------------------------------- - * Arch_IsLib -- - * Check if the node is a library - * - * Results: - * True or False. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -int +Boolean Arch_IsLib(GNode *gn) { - static const char armag[] = "!\n"; - char buf[sizeof(armag)-1]; - int fd; + static const char armag[] = "!\n"; + char buf[sizeof armag - 1]; + int fd; - if ((fd = open(gn->path, O_RDONLY)) == -1) - return FALSE; + if ((fd = open(gn->path, O_RDONLY)) == -1) + return FALSE; + + if (read(fd, buf, sizeof buf) != sizeof buf) { + (void)close(fd); + return FALSE; + } - if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { (void)close(fd); - return FALSE; - } - (void)close(fd); - - return memcmp(buf, armag, sizeof(buf)) == 0; + return memcmp(buf, armag, sizeof buf) == 0; } diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c index ac95c16c1..cef082247 100644 --- a/usr.bin/make/buf.c +++ b/usr.bin/make/buf.c @@ -1,4 +1,4 @@ -/* $NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $ */ +/* $NetBSD: buf.c,v 1.51 2021/01/30 21:18:14 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,223 +69,152 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)buf.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $"); +/* Automatically-expanding null-terminated character buffers. */ + +#include +#include "make.h" + +/* "@(#)buf.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: buf.c,v 1.51 2021/01/30 21:18:14 rillig Exp $"); + +/* Make space in the buffer for adding at least 16 more bytes. */ +void +Buf_Expand(Buffer *buf) +{ + buf->cap += buf->cap > 16 ? buf->cap : 16; + buf->data = bmake_realloc(buf->data, buf->cap); +} + +/* Add the bytes to the buffer. */ +void +Buf_AddBytes(Buffer *buf, const char *bytes, size_t bytes_len) +{ + size_t old_len = buf->len; + char *end; + + if (old_len + bytes_len >= buf->cap) { + size_t minIncr = bytes_len + 16; + buf->cap += buf->cap > minIncr ? buf->cap : minIncr; + buf->data = bmake_realloc(buf->data, buf->cap); + } + + end = buf->data + old_len; + buf->len = old_len + bytes_len; + memcpy(end, bytes, bytes_len); + end[bytes_len] = '\0'; +} + +/* Add the bytes between start and end to the buffer. */ +void +Buf_AddBytesBetween(Buffer *buf, const char *start, const char *end) +{ + Buf_AddBytes(buf, start, (size_t)(end - start)); +} + +/* Add the string to the buffer. */ +void +Buf_AddStr(Buffer *buf, const char *str) +{ + Buf_AddBytes(buf, str, strlen(str)); +} + +/* Add the number to the buffer. */ +void +Buf_AddInt(Buffer *buf, int n) +{ + enum { + bits = sizeof(int) * CHAR_BIT, + max_octal_digits = (bits + 2) / 3, + max_decimal_digits = /* at most */ max_octal_digits, + max_sign_chars = 1, + str_size = max_sign_chars + max_decimal_digits + 1 + }; + char str[str_size]; + + size_t len = (size_t)snprintf(str, sizeof str, "%d", n); + Buf_AddBytes(buf, str, len); +} + +/* Mark the buffer as empty, so it can be filled with data again. */ +void +Buf_Empty(Buffer *buf) +{ + buf->len = 0; + buf->data[0] = '\0'; +} + +/* Initialize a buffer. */ +void +Buf_InitSize(Buffer *buf, size_t cap) +{ + buf->cap = cap; + buf->len = 0; + buf->data = bmake_malloc(cap); + buf->data[0] = '\0'; +} + +void +Buf_Init(Buffer *buf) +{ + Buf_InitSize(buf, 256); +} + +/* + * Free the data from the buffer. + * Leave the buffer itself in an indeterminate state. + */ +void +Buf_Done(Buffer *buf) +{ + free(buf->data); + +#ifdef CLEANUP + buf->cap = 0; + buf->len = 0; + buf->data = NULL; #endif -#endif /* not lint */ +} + +/* + * Return the data from the buffer. + * Leave the buffer itself in an indeterminate state. + */ +char * +Buf_DoneData(Buffer *buf) +{ + char *data = buf->data; + +#ifdef CLEANUP + buf->cap = 0; + buf->len = 0; + buf->data = NULL; #endif -/*- - * buf.c -- - * Functions for automatically-expanded buffers. - */ - -#include "make.h" -#include "buf.h" - -#ifndef max -#define max(a,b) ((a) > (b) ? (a) : (b)) -#endif - -#define BUF_DEF_SIZE 256 /* Default buffer size */ - -/*- - *----------------------------------------------------------------------- - * Buf_Expand_1 -- - * Extend buffer for single byte add. - * - *----------------------------------------------------------------------- - */ -void -Buf_Expand_1(Buffer *bp) -{ - bp->size += max(bp->size, 16); - bp->buffer = bmake_realloc(bp->buffer, bp->size); + return data; } -/*- - *----------------------------------------------------------------------- - * Buf_AddBytes -- - * Add a number of bytes to the buffer. - * - * Results: - * None. - * - * Side Effects: - * Guess what? - * - *----------------------------------------------------------------------- - */ -void -Buf_AddBytes(Buffer *bp, int numBytes, const Byte *bytesPtr) -{ - int count = bp->count; - Byte *ptr; - - if (__predict_false(count + numBytes >= bp->size)) { - bp->size += max(bp->size, numBytes + 16); - bp->buffer = bmake_realloc(bp->buffer, bp->size); - } - - ptr = bp->buffer + count; - bp->count = count + numBytes; - ptr[numBytes] = 0; - memcpy(ptr, bytesPtr, numBytes); -} - -/*- - *----------------------------------------------------------------------- - * Buf_GetAll -- - * Get all the available data at once. - * - * Results: - * A pointer to the data and the number of bytes available. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -Byte * -Buf_GetAll(Buffer *bp, int *numBytesPtr) -{ - - if (numBytesPtr != NULL) - *numBytesPtr = bp->count; - - return (bp->buffer); -} - -/*- - *----------------------------------------------------------------------- - * Buf_Empty -- - * Throw away bytes in a buffer. - * - * Results: - * None. - * - * Side Effects: - * The bytes are discarded. - * - *----------------------------------------------------------------------- - */ -void -Buf_Empty(Buffer *bp) -{ - - bp->count = 0; - *bp->buffer = 0; -} - -/*- - *----------------------------------------------------------------------- - * Buf_Init -- - * Initialize a buffer. If no initial size is given, a reasonable - * default is used. - * - * Input: - * size Initial size for the buffer - * - * Results: - * A buffer to be given to other functions in this library. - * - * Side Effects: - * The buffer is created, the space allocated and pointers - * initialized. - * - *----------------------------------------------------------------------- - */ -void -Buf_Init(Buffer *bp, int size) -{ - if (size <= 0) { - size = BUF_DEF_SIZE; - } - bp->size = size; - bp->count = 0; - bp->buffer = bmake_malloc(size); - *bp->buffer = 0; -} - -/*- - *----------------------------------------------------------------------- - * Buf_Destroy -- - * Nuke a buffer and all its resources. - * - * Input: - * buf Buffer to destroy - * freeData TRUE if the data should be destroyed - * - * Results: - * Data buffer, NULL if freed - * - * Side Effects: - * The buffer is freed. - * - *----------------------------------------------------------------------- - */ -Byte * -Buf_Destroy(Buffer *buf, Boolean freeData) -{ - Byte *data; - - data = buf->buffer; - if (freeData) { - free(data); - data = NULL; - } - - buf->size = 0; - buf->count = 0; - buf->buffer = NULL; - - return data; -} - - -/*- - *----------------------------------------------------------------------- - * Buf_DestroyCompact -- - * Nuke a buffer and return its data. - * - * Input: - * buf Buffer to destroy - * - * Results: - * Data buffer - * - * Side Effects: - * If the buffer size is much greater than its content, - * a new buffer will be allocated and the old one freed. - * - *----------------------------------------------------------------------- - */ #ifndef BUF_COMPACT_LIMIT -# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */ +# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */ #endif -Byte * -Buf_DestroyCompact(Buffer *buf) +/* + * Return the data from the buffer. + * Leave the buffer itself in an indeterminate state. + * + * If the buffer size is much greater than its content, + * a new buffer will be allocated and the old one freed. + */ +char * +Buf_DoneDataCompact(Buffer *buf) { #if BUF_COMPACT_LIMIT > 0 - Byte *data; - - if (buf->size - buf->count >= BUF_COMPACT_LIMIT) { - /* We trust realloc to be smart */ - data = bmake_realloc(buf->buffer, buf->count + 1); - if (data) { - data[buf->count] = 0; - Buf_Destroy(buf, FALSE); - return data; + if (buf->cap - buf->len >= BUF_COMPACT_LIMIT) { + /* We trust realloc to be smart */ + char *data = bmake_realloc(buf->data, buf->len + 1); + data[buf->len] = '\0'; /* XXX: unnecessary */ + Buf_DoneData(buf); + return data; } - } #endif - return Buf_Destroy(buf, FALSE); + return Buf_DoneData(buf); } diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h index 25be67d79..594e9651d 100644 --- a/usr.bin/make/buf.h +++ b/usr.bin/make/buf.h @@ -1,4 +1,4 @@ -/* $NetBSD: buf.h,v 1.17 2012/04/24 20:26:58 sjg Exp $ */ +/* $NetBSD: buf.h,v 1.42 2021/01/30 21:25:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -72,48 +72,50 @@ * from: @(#)buf.h 8.1 (Berkeley) 6/6/93 */ -/*- - * buf.h -- - * Header for users of the buf library. - */ +/* Automatically growing null-terminated buffers of characters. */ -#ifndef _BUF_H -#define _BUF_H +#ifndef MAKE_BUF_H +#define MAKE_BUF_H -typedef char Byte; +#include +/* An automatically growing null-terminated buffer of characters. */ typedef struct Buffer { - int size; /* Current size of the buffer */ - int count; /* Number of bytes in buffer */ - Byte *buffer; /* The buffer itself (zero terminated) */ + size_t cap; /* Allocated size of the buffer, including the '\0' */ + size_t len; /* Number of bytes in buffer, excluding the '\0' */ + char *data; /* The buffer itself (always null-terminated) */ } Buffer; -/* If we aren't on netbsd, __predict_false() might not be defined. */ -#ifndef __predict_false -#define __predict_false(x) (x) -#endif +void Buf_Expand(Buffer *); /* Buf_AddByte adds a single byte to a buffer. */ -#define Buf_AddByte(bp, byte) do { \ - int _count = ++(bp)->count; \ - char *_ptr; \ - if (__predict_false(_count >= (bp)->size)) \ - Buf_Expand_1(bp); \ - _ptr = (bp)->buffer + _count; \ - _ptr[-1] = (byte); \ - _ptr[0] = 0; \ - } while (0) +MAKE_INLINE void +Buf_AddByte(Buffer *buf, char byte) +{ + size_t old_len = buf->len++; + char *end; + if (old_len + 1 >= buf->cap) + Buf_Expand(buf); + end = buf->data + old_len; + end[0] = byte; + end[1] = '\0'; +} -#define BUF_ERROR 256 +MAKE_INLINE Boolean +Buf_EndsWith(const Buffer *buf, char ch) +{ + return buf->len > 0 && buf->data[buf->len - 1] == ch; +} -#define Buf_Size(bp) ((bp)->count) - -void Buf_Expand_1(Buffer *); -void Buf_AddBytes(Buffer *, int, const Byte *); -Byte *Buf_GetAll(Buffer *, int *); +void Buf_AddBytes(Buffer *, const char *, size_t); +void Buf_AddBytesBetween(Buffer *, const char *, const char *); +void Buf_AddStr(Buffer *, const char *); +void Buf_AddInt(Buffer *, int); void Buf_Empty(Buffer *); -void Buf_Init(Buffer *, int); -Byte *Buf_Destroy(Buffer *, Boolean); -Byte *Buf_DestroyCompact(Buffer *); +void Buf_Init(Buffer *); +void Buf_InitSize(Buffer *, size_t); +void Buf_Done(Buffer *); +char *Buf_DoneData(Buffer *); +char *Buf_DoneDataCompact(Buffer *); -#endif /* _BUF_H */ +#endif /* MAKE_BUF_H */ diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c index 7eecd5fb2..686acbf22 100644 --- a/usr.bin/make/compat.c +++ b/usr.bin/make/compat.c @@ -1,4 +1,4 @@ -/* $NetBSD: compat.c,v 1.100 2015/06/19 14:32:04 christos Exp $ */ +/* $NetBSD: compat.c,v 1.224 2021/02/05 05:15:12 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,20 +69,7 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: compat.c,v 1.100 2015/06/19 14:32:04 christos Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: compat.c,v 1.100 2015/06/19 14:32:04 christos Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- +/* * compat.c -- * The routines in this file implement the full-compatibility * mode of PMake. Most of the special functionality of PMake @@ -91,654 +78,682 @@ __RCSID("$NetBSD: compat.c,v 1.100 2015/06/19 14:32:04 christos Exp $"); * - friendly variable substitution. * * Interface: - * Compat_Run Initialize things for this module and recreate - * thems as need creatin' + * Compat_Run Initialize things for this module and recreate + * thems as need creatin' */ -#include -#include -#include +#include +#include +#include -#include -#include -#include -#include +#include +#include -#include "make.h" -#include "hash.h" -#include "dir.h" -#include "job.h" -#include "metachar.h" -#include "pathnames.h" +#include "make.h" +#include "dir.h" +#include "job.h" +#include "metachar.h" +#include "pathnames.h" +/* "@(#)compat.c 8.2 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: compat.c,v 1.224 2021/02/05 05:15:12 rillig Exp $"); -static GNode *curTarg = NULL; -static GNode *ENDNode; -static void CompatInterrupt(int); +static GNode *curTarg = NULL; +static pid_t compatChild; +static int compatSigno; -/*- - *----------------------------------------------------------------------- - * CompatInterrupt -- - * Interrupt the creation of the current target and remove it if - * it ain't precious. +/* + * CompatDeleteTarget -- delete the file of a failed, interrupted, or + * otherwise duffed target if not inhibited by .PRECIOUS. + */ +static void +CompatDeleteTarget(GNode *gn) +{ + if (gn != NULL && !Targ_Precious(gn)) { + const char *file = GNode_VarTarget(gn); + + if (!opts.noExecute && eunlink(file) != -1) { + Error("*** %s removed", file); + } + } +} + +/* + * Interrupt the creation of the current target and remove it if it ain't + * precious. Then exit. * - * Results: - * None. + * If .INTERRUPT exists, its commands are run first WITH INTERRUPTS IGNORED. * - * Side Effects: - * The target is removed and the process exits. If .INTERRUPT exists, - * its commands are run first WITH INTERRUPTS IGNORED.. - * - *----------------------------------------------------------------------- + * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've + * left the logic alone for now. - dholland 20160826 */ static void CompatInterrupt(int signo) { - GNode *gn; + CompatDeleteTarget(curTarg); - if ((curTarg != NULL) && !Targ_Precious (curTarg)) { - char *p1; - char *file = Var_Value(TARGET, curTarg, &p1); - - if (!noExecute && eunlink(file) != -1) { - Error("*** %s removed", file); + if (curTarg != NULL && !Targ_Precious(curTarg)) { + /* + * Run .INTERRUPT only if hit with interrupt signal + */ + if (signo == SIGINT) { + GNode *gn = Targ_FindNode(".INTERRUPT"); + if (gn != NULL) { + Compat_Make(gn, gn); + } + } } - if (p1) - free(p1); + + if (signo == SIGQUIT) + _exit(signo); /* - * Run .INTERRUPT only if hit with interrupt signal + * If there is a child running, pass the signal on. + * We will exist after it has exited. */ - if (signo == SIGINT) { - gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); - if (gn != NULL) { - Compat_Make(gn, gn); - } + compatSigno = signo; + if (compatChild > 0) { + KILLPG(compatChild, signo); + } else { + bmake_signal(signo, SIG_DFL); + kill(myPid, signo); } - - } - if (signo == SIGQUIT) - _exit(signo); - bmake_signal(signo, SIG_DFL); - kill(myPid, signo); } - -/*- - *----------------------------------------------------------------------- - * CompatRunCommand -- - * Execute the next command for a target. If the command returns an - * error, the node's made field is set to ERROR and creation stops. + +static void +DebugFailedTarget(const char *cmd, GNode *gn) +{ + const char *p = cmd; + debug_printf("\n*** Failed target: %s\n*** Failed command: ", + gn->name); + + /* Replace runs of whitespace with a single space, to reduce + * the amount of whitespace for multi-line command lines. */ + while (*p != '\0') { + if (ch_isspace(*p)) { + debug_printf(" "); + cpp_skip_whitespace(&p); + } else { + debug_printf("%c", *p); + p++; + } + } + debug_printf("\n"); +} + +static Boolean +UseShell(const char *cmd MAKE_ATTR_UNUSED) +{ +#if !defined(MAKE_NATIVE) + /* + * In a non-native build, the host environment might be weird enough + * that it's necessary to go through a shell to get the correct + * behaviour. Or perhaps the shell has been replaced with something + * that does extra logging, and that should not be bypassed. + */ + return TRUE; +#else + /* + * Search for meta characters in the command. If there are no meta + * characters, there's no need to execute a shell to execute the + * command. + * + * Additionally variable assignments and empty commands + * go to the shell. Therefore treat '=' and ':' like shell + * meta characters as documented in make(1). + */ + + return needshell(cmd); +#endif +} + +/* + * Execute the next command for a target. If the command returns an error, + * the node's made field is set to ERROR and creation stops. * * Input: * cmdp Command to execute - * gnp Node from which the command came + * gn Node from which the command came + * ln List node that contains the command * * Results: * 0 if the command succeeded, 1 if an error occurred. - * - * Side Effects: - * The node's 'made' field may be set to ERROR. - * - *----------------------------------------------------------------------- */ int -CompatRunCommand(void *cmdp, void *gnp) +Compat_RunCommand(const char *cmdp, GNode *gn, StringListNode *ln) { - char *cmdStart; /* Start of expanded command */ - char *cp, *bp; - Boolean silent, /* Don't print command */ - doIt; /* Execute even if -n */ - volatile Boolean errCheck; /* Check errors */ - int reason; /* Reason for child's death */ - int status; /* Description of child's death */ - pid_t cpid; /* Child actually found */ - pid_t retstat; /* Result of wait */ - LstNode cmdNode; /* Node where current command is located */ - const char ** volatile av; /* Argument vector for thing to exec */ - char ** volatile mav;/* Copy of the argument vector for freeing */ - int argc; /* Number of arguments in av or 0 if not - * dynamically allocated */ - Boolean local; /* TRUE if command should be executed - * locally */ - Boolean useShell; /* TRUE if command should be executed + char *cmdStart; /* Start of expanded command */ + char *bp; + Boolean silent; /* Don't print command */ + Boolean doIt; /* Execute even if -n */ + volatile Boolean errCheck; /* Check errors */ + int reason; /* Reason for child's death */ + int status; /* Description of child's death */ + pid_t cpid; /* Child actually found */ + pid_t retstat; /* Result of wait */ + const char **volatile av; /* Argument vector for thing to exec */ + char **volatile mav; /* Copy of the argument vector for freeing */ + Boolean useShell; /* TRUE if command should be executed * using a shell */ - char * volatile cmd = (char *)cmdp; - GNode *gn = (GNode *)gnp; + const char *volatile cmd = cmdp; - silent = gn->type & OP_SILENT; - errCheck = !(gn->type & OP_IGNORE); - doIt = FALSE; - - cmdNode = Lst_Member(gn->commands, cmd); - cmdStart = Var_Subst(NULL, cmd, gn, FALSE); + silent = (gn->type & OP_SILENT) != 0; + errCheck = !(gn->type & OP_IGNORE); + doIt = FALSE; - /* - * brk_string will return an argv with a NULL in av[0], thus causing - * execvp to choke and die horribly. Besides, how can we execute a null - * command? In any case, we warn the user that the command expanded to - * nothing (is this the right thing to do?). - */ + (void)Var_Subst(cmd, gn, VARE_WANTRES, &cmdStart); + /* TODO: handle errors */ - if (*cmdStart == '\0') { - free(cmdStart); - return(0); - } - cmd = cmdStart; - Lst_Replace(cmdNode, cmdStart); - - if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) { - (void)Lst_AtEnd(ENDNode->commands, cmdStart); - return(0); - } - if (strcmp(cmdStart, "...") == 0) { - gn->type |= OP_SAVE_CMDS; - return(0); - } - - while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) { - switch (*cmd) { - case '@': - silent = DEBUG(LOUD) ? FALSE : TRUE; - break; - case '-': - errCheck = FALSE; - break; - case '+': - doIt = TRUE; - if (!shellName) /* we came here from jobs */ - Shell_Init(); - break; + if (cmdStart[0] == '\0') { + free(cmdStart); + return 0; } - cmd++; - } + cmd = cmdStart; + LstNode_Set(ln, cmdStart); - while (isspace((unsigned char)*cmd)) - cmd++; - - /* - * If we did not end up with a command, just skip it. - */ - if (!*cmd) - return (0); - -#if !defined(MAKE_NATIVE) - /* - * In a non-native build, the host environment might be weird enough - * that it's necessary to go through a shell to get the correct - * behaviour. Or perhaps the shell has been replaced with something - * that does extra logging, and that should not be bypassed. - */ - useShell = TRUE; -#else - /* - * Search for meta characters in the command. If there are no meta - * characters, there's no need to execute a shell to execute the - * command. - * - * Additionally variable assignments and empty commands - * go to the shell. Therefore treat '=' and ':' like shell - * meta characters as documented in make(1). - */ - - useShell = needshell(cmd, FALSE); -#endif - - /* - * 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 || NoExecute(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 && NoExecute(gn)) { - return (0); - } - if (DEBUG(JOB)) - fprintf(debug_file, "Execute: '%s'\n", cmd); - -again: - 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]; - int shargc; - - shargc = 0; - shargv[shargc++] = shellPath; - /* - * The following work for any of the builtin shell specs. - */ - if (errCheck && shellErrFlag) { - shargv[shargc++] = shellErrFlag; + if (gn->type & OP_SAVE_CMDS) { + GNode *endNode = Targ_GetEndNode(); + if (gn != endNode) { + /* + * Append the expanded command, to prevent the + * local variables from being interpreted in the + * scope of the .END node. + * + * A probably unintended side effect of this is that + * the expanded command will be expanded again in the + * .END node. Therefore, a literal '$' in these + * commands must be written as '$$$$' instead of the + * usual '$$'. + */ + Lst_Append(&endNode->commands, cmdStart); + return 0; + } } - if (DEBUG(SHELL)) - shargv[shargc++] = "-xc"; - else - shargv[shargc++] = "-c"; - shargv[shargc++] = cmd; - shargv[shargc++] = NULL; - av = shargv; - argc = 0; - 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. - */ - mav = brk_string(cmd, &argc, TRUE, &bp); - if (mav == NULL) { - useShell = 1; - goto again; + if (strcmp(cmdStart, "...") == 0) { + gn->type |= OP_SAVE_CMDS; + return 0; } - av = (void *)mav; - } - local = TRUE; + for (;;) { + if (*cmd == '@') + silent = !DEBUG(LOUD); + else if (*cmd == '-') + errCheck = FALSE; + else if (*cmd == '+') { + doIt = TRUE; + if (shellName == NULL) /* we came here from jobs */ + Shell_Init(); + } else + break; + cmd++; + } + + while (ch_isspace(*cmd)) + cmd++; + + /* + * If we did not end up with a command, just skip it. + */ + if (cmd[0] == '\0') + return 0; + + 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 0; + + DEBUG1(JOB, "Execute: '%s'\n", cmd); + + 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. */ + int shargc = 0; + shargv[shargc++] = shellPath; + if (errCheck && shellErrFlag != NULL) + shargv[shargc++] = shellErrFlag; + shargv[shargc++] = DEBUG(SHELL) ? "-xc" : "-c"; + shargv[shargc++] = cmd; + shargv[shargc] = NULL; + av = shargv; + 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; + av = (void *)mav; + } -#ifdef USE_META - if (useMeta) { - meta_compat_start(); - } -#endif - - /* - * Fork and execute the single command. If the fork fails, we abort. - */ - cpid = vFork(); - if (cpid < 0) { - Fatal("Could not fork"); - } - if (cpid == 0) { - Var_ExportVars(); #ifdef USE_META if (useMeta) { - meta_compat_child(); + meta_compat_start(); } #endif - if (local) - (void)execvp(av[0], (char *const *)UNCONST(av)); - else - (void)execv(av[0], (char *const *)UNCONST(av)); - execError("exec", av[0]); - _exit(1); - } - if (mav) + + Var_ReexportVars(); + + /* + * Fork and execute the single command. If the fork fails, we abort. + */ + compatChild = cpid = vfork(); + if (cpid < 0) { + Fatal("Could not fork"); + } + if (cpid == 0) { +#ifdef USE_META + if (useMeta) { + meta_compat_child(); + } +#endif + (void)execvp(av[0], (char *const *)UNCONST(av)); + execDie("exec", av[0]); + } + free(mav); - if (bp) free(bp); - Lst_Replace(cmdNode, NULL); + + /* XXX: Memory management looks suspicious here. */ + /* XXX: Setting a list item to NULL is unexpected. */ + LstNode_SetNull(ln); #ifdef USE_META - if (useMeta) { - meta_compat_parent(); - } + if (useMeta) { + meta_compat_parent(cpid); + } #endif - /* - * The child is off and running. Now all we can do is wait... - */ - while (1) { - + /* + * 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) { - break; - } + if (retstat > 0) + JobReapChild(retstat, reason, FALSE); /* not ours? */ + if (retstat == -1 && errno != EINTR) { + break; + } } - if (retstat > -1) { - if (WIFSTOPPED(reason)) { - status = WSTOPSIG(reason); /* stopped */ - } else if (WIFEXITED(reason)) { - status = WEXITSTATUS(reason); /* exited */ + if (retstat < 0) + Fatal("error in wait: %d: %s", retstat, strerror(errno)); + + if (WIFSTOPPED(reason)) { + status = WSTOPSIG(reason); /* stopped */ + } else if (WIFEXITED(reason)) { + status = WEXITSTATUS(reason); /* exited */ #if defined(USE_META) && defined(USE_FILEMON_ONCE) if (useMeta) { meta_cmd_finish(NULL); } #endif if (status != 0) { - if (DEBUG(ERROR)) { - fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ", - gn->name); - for (cp = cmd; *cp; ) { - if (isspace((unsigned char)*cp)) { - fprintf(debug_file, " "); - while (isspace((unsigned char)*cp)) - cp++; - } else { - fprintf(debug_file, "%c", *cp); - cp++; - } - } - fprintf(debug_file, "\n"); - } - printf("*** Error code %d", status); + if (DEBUG(ERROR)) + DebugFailedTarget(cmd, gn); + printf("*** Error code %d", status); } - } else { - status = WTERMSIG(reason); /* signaled */ + } else { + status = WTERMSIG(reason); /* signaled */ printf("*** Signal %d", status); - } + } - if (!WIFEXITED(reason) || (status != 0)) { + if (!WIFEXITED(reason) || status != 0) { if (errCheck) { #ifdef USE_META - if (useMeta) { - meta_job_error(NULL, gn, 0, status); - } + if (useMeta) { + meta_job_error(NULL, gn, FALSE, status); + } #endif - gn->made = ERROR; - if (keepgoing) { - /* - * Abort the current target, but let others - * continue. - */ - printf(" (continuing)\n"); - } + gn->made = ERROR; + if (opts.keepgoing) { + /* + * Abort the current target, + * but let others continue. + */ + printf(" (continuing)\n"); + } else { + printf("\n"); + } + if (deleteOnError) + CompatDeleteTarget(gn); } else { - /* - * Continue executing commands for this target. - * If we return 0, this will happen... - */ - printf(" (ignored)\n"); - status = 0; + /* + * Continue executing commands for this target. + * If we return 0, this will happen... + */ + printf(" (ignored)\n"); + status = 0; } - } - break; - } else { - Fatal("error in wait: %d: %s", retstat, strerror(errno)); - /*NOTREACHED*/ } - } - free(cmdStart); - return (status); + free(cmdStart); + compatChild = 0; + if (compatSigno != 0) { + bmake_signal(compatSigno, SIG_DFL); + kill(myPid, compatSigno); + } + + return status; } - -/*- - *----------------------------------------------------------------------- - * Compat_Make -- - * Make a target. - * - * Input: - * gnp The node to make - * pgnp Parent to abort if necessary - * - * Results: - * 0 - * - * Side Effects: - * If an error is detected and not being ignored, the process exits. - * - *----------------------------------------------------------------------- - */ -int -Compat_Make(void *gnp, void *pgnp) -{ - GNode *gn = (GNode *)gnp; - GNode *pgn = (GNode *)pgnp; - if (!shellName) /* we came here from jobs */ - Shell_Init(); - if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) { +static void +RunCommands(GNode *gn) +{ + StringListNode *ln; + + for (ln = gn->commands.first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + if (Compat_RunCommand(cmd, gn, ln) != 0) + break; + } +} + +static void +MakeNodes(GNodeList *gnodes, GNode *pgn) +{ + GNodeListNode *ln; + + for (ln = gnodes->first; ln != NULL; ln = ln->next) { + GNode *cohort = ln->datum; + Compat_Make(cohort, pgn); + } +} + +static Boolean +MakeUnmade(GNode *gn, GNode *pgn) +{ + + assert(gn->made == UNMADE); + /* * First mark ourselves to be made, then apply whatever transformations * the suffix module thinks are necessary. Once that's done, we can * descend and make all our children. If any of them has an error - * but the -k flag was given, our 'make' field will be set FALSE again. - * This is our signal to not attempt to do anything but abort our - * parent as well. + * but the -k flag was given, our 'make' field will be set to FALSE + * again. This is our signal to not attempt to do anything but abort + * our parent as well. */ gn->flags |= REMAKE; gn->made = BEINGMADE; - if ((gn->type & OP_MADE) == 0) - Suff_FindDeps(gn); - Lst_ForEach(gn->children, Compat_Make, gn); - if ((gn->flags & REMAKE) == 0) { - gn->made = ABORTED; - pgn->flags &= ~REMAKE; - goto cohorts; + + if (!(gn->type & OP_MADE)) + Suff_FindDeps(gn); + + MakeNodes(&gn->children, gn); + + if (!(gn->flags & REMAKE)) { + gn->made = ABORTED; + pgn->flags &= ~(unsigned)REMAKE; + return FALSE; } - if (Lst_Member(gn->iParents, pgn) != NULL) { - char *p1; - Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0); - if (p1) - free(p1); - } + if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) + Var_Set(pgn, IMPSRC, GNode_VarTarget(gn)); /* - * All the children were made ok. Now cmgn->mtime contains the + * All the children were made ok. Now youngestChild->mtime contains the * modification time of the newest child, we need to find out if we * exist and when we were modified last. The criteria for datedness - * are defined by the Make_OODate function. + * are defined by GNode_IsOODate. */ - if (DEBUG(MAKE)) { - fprintf(debug_file, "Examining %s...", gn->name); - } - if (! Make_OODate(gn)) { - gn->made = UPTODATE; - if (DEBUG(MAKE)) { - fprintf(debug_file, "up-to-date.\n"); - } - goto cohorts; - } else if (DEBUG(MAKE)) { - fprintf(debug_file, "out-of-date.\n"); + DEBUG1(MAKE, "Examining %s...", gn->name); + if (!GNode_IsOODate(gn)) { + gn->made = UPTODATE; + DEBUG0(MAKE, "up-to-date.\n"); + return FALSE; } /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ - if (queryFlag) { - exit(1); - } + DEBUG0(MAKE, "out-of-date.\n"); + if (opts.queryFlag) + exit(1); /* - * We need to be re-made. We also have to make sure we've got a $? - * variable. To be nice, we also define the $> variable using - * Make_DoAllVar(). + * We need to be re-made. + * Ensure that $? (.OODATE) and $> (.ALLSRC) are both set. */ Make_DoAllVar(gn); /* * Alter our type to tell if errors should be ignored or things - * should not be printed so CompatRunCommand knows what to do. + * should not be printed so Compat_RunCommand knows what to do. */ - if (Targ_Ignore(gn)) { - gn->type |= OP_IGNORE; - } - if (Targ_Silent(gn)) { - gn->type |= OP_SILENT; - } + if (opts.ignoreErrors) + gn->type |= OP_IGNORE; + if (opts.beSilent) + gn->type |= OP_SILENT; if (Job_CheckCommands(gn, Fatal)) { - /* - * Our commands are ok, but we still have to worry about the -t - * flag... - */ - if (!touchFlag || (gn->type & OP_MAKE)) { - curTarg = gn; + /* + * Our commands are ok, but we still have to worry about + * the -t flag. + */ + if (!opts.touchFlag || (gn->type & OP_MAKE)) { + curTarg = gn; #ifdef USE_META - if (useMeta && !NoExecute(gn)) { - meta_job_start(NULL, gn); - } + if (useMeta && GNode_ShouldExecute(gn)) { + meta_job_start(NULL, gn); + } #endif - Lst_ForEach(gn->commands, CompatRunCommand, gn); - curTarg = NULL; - } else { - Job_Touch(gn, gn->type & OP_SILENT); - } + RunCommands(gn); + curTarg = NULL; + } else { + Job_Touch(gn, (gn->type & OP_SILENT) != 0); + } } else { - gn->made = ERROR; + gn->made = ERROR; } #ifdef USE_META - if (useMeta && !NoExecute(gn)) { - meta_job_finish(NULL); + if (useMeta && GNode_ShouldExecute(gn)) { + if (meta_job_finish(NULL) != 0) + gn->made = ERROR; } #endif if (gn->made != ERROR) { - /* - * If the node was made successfully, mark it so, update - * its modification time and timestamp all its parents. Note - * that for .ZEROTIME targets, the timestamping isn't done. - * This is to keep its state from affecting that of its parent. - */ - gn->made = MADE; - pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0; - if (!(gn->type & OP_EXEC)) { - pgn->flags |= CHILDMADE; - Make_TimeStamp(pgn, gn); - } - } else if (keepgoing) { - pgn->flags &= ~REMAKE; + /* + * If the node was made successfully, mark it so, update + * its modification time and timestamp all its parents. + * This is to keep its state from affecting that of its parent. + */ + gn->made = MADE; + if (Make_Recheck(gn) == 0) + pgn->flags |= FORCE; + if (!(gn->type & OP_EXEC)) { + pgn->flags |= CHILDMADE; + GNode_UpdateYoungestChild(pgn, gn); + } + } else if (opts.keepgoing) { + pgn->flags &= ~(unsigned)REMAKE; } else { - PrintOnError(gn, "\n\nStop."); - exit(1); + PrintOnError(gn, "\nStop."); + exit(1); } - } else if (gn->made == ERROR) { - /* - * Already had an error when making this beastie. Tell the parent - * to abort. - */ - pgn->flags &= ~REMAKE; - } else { - if (Lst_Member(gn->iParents, pgn) != NULL) { - char *p1; - Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0); - if (p1) - free(p1); + return TRUE; +} + +static void +MakeOther(GNode *gn, GNode *pgn) +{ + + if (Lst_FindDatum(&gn->implicitParents, pgn) != NULL) { + const char *target = GNode_VarTarget(gn); + Var_Set(pgn, IMPSRC, target != NULL ? target : ""); } - switch(gn->made) { - case BEINGMADE: + + switch (gn->made) { + case BEINGMADE: Error("Graph cycles through %s", gn->name); gn->made = ERROR; - pgn->flags &= ~REMAKE; + pgn->flags &= ~(unsigned)REMAKE; break; - case MADE: - if ((gn->type & OP_EXEC) == 0) { - pgn->flags |= CHILDMADE; - Make_TimeStamp(pgn, gn); + case MADE: + if (!(gn->type & OP_EXEC)) { + pgn->flags |= CHILDMADE; + GNode_UpdateYoungestChild(pgn, gn); } break; - case UPTODATE: - if ((gn->type & OP_EXEC) == 0) { - Make_TimeStamp(pgn, gn); - } + case UPTODATE: + if (!(gn->type & OP_EXEC)) + GNode_UpdateYoungestChild(pgn, gn); break; - default: + default: break; } - } - -cohorts: - Lst_ForEach(gn->cohorts, Compat_Make, pgnp); - return (0); } - -/*- - *----------------------------------------------------------------------- - * Compat_Run -- - * Initialize this mode and start making. + +/* + * Make a target. + * + * If an error is detected and not being ignored, the process exits. * * Input: - * targs List of target nodes to re-create + * gn The node to make + * pgn Parent to abort if necessary * - * Results: - * None. - * - * Side Effects: - * Guess what? - * - *----------------------------------------------------------------------- + * Output: + * gn->made + * UPTODATE gn was already up-to-date. + * MADE gn was recreated successfully. + * ERROR An error occurred while gn was being created, + * either due to missing commands or in -k mode. + * ABORTED gn was not remade because one of its + * dependencies could not be made due to errors. */ void -Compat_Run(Lst targs) +Compat_Make(GNode *gn, GNode *pgn) { - GNode *gn = NULL;/* Current root target */ - int errors; /* Number of targets not remade due to errors */ + if (shellName == NULL) /* we came here from jobs */ + Shell_Init(); - if (!shellName) - Shell_Init(); + if (gn->made == UNMADE && (gn == pgn || !(pgn->type & OP_MADE))) { + if (!MakeUnmade(gn, pgn)) + goto cohorts; - if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) { - bmake_signal(SIGINT, CompatInterrupt); - } - if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) { - bmake_signal(SIGTERM, CompatInterrupt); - } - if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) { - bmake_signal(SIGHUP, CompatInterrupt); - } - if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) { - bmake_signal(SIGQUIT, CompatInterrupt); - } - - ENDNode = Targ_FindNode(".END", TARG_CREATE); - ENDNode->type = OP_SPECIAL; - /* - * If the user has defined a .BEGIN target, execute the commands attached - * to it. - */ - if (!queryFlag) { - gn = Targ_FindNode(".BEGIN", TARG_NOCREATE); - if (gn != NULL) { - Compat_Make(gn, gn); - if (gn->made == ERROR) { - PrintOnError(gn, "\n\nStop."); - exit(1); - } + /* XXX: Replace with GNode_IsError(gn) */ + } else if (gn->made == ERROR) { + /* + * Already had an error when making this. + * Tell the parent to abort. + */ + pgn->flags &= ~(unsigned)REMAKE; + } else { + MakeOther(gn, pgn); } - } - /* - * Expand .USE nodes right now, because they can modify the structure - * of the tree. - */ - Make_ExpandUse(targs); - - /* - * For each entry in the list of targets to create, call Compat_Make on - * it to create the thing. Compat_Make will leave the 'made' field of gn - * in one of several states: - * UPTODATE gn was already up-to-date - * MADE gn was recreated successfully - * ERROR An error occurred while gn was being created - * ABORTED gn was not remade because one of its inferiors - * could not be made due to errors. - */ - errors = 0; - while (!Lst_IsEmpty (targs)) { - gn = (GNode *)Lst_DeQueue(targs); - Compat_Make(gn, gn); - - if (gn->made == UPTODATE) { - printf("`%s' is up to date.\n", gn->name); - } else if (gn->made == ABORTED) { - printf("`%s' not remade because of errors.\n", gn->name); - errors += 1; - } - } - - /* - * If the user has defined a .END target, run its commands. - */ - if (errors == 0) { - Compat_Make(ENDNode, ENDNode); - if (gn->made == ERROR) { - PrintOnError(gn, "\n\nStop."); - exit(1); - } - } +cohorts: + MakeNodes(&gn->cohorts, pgn); +} + +static void +MakeBeginNode(void) +{ + GNode *gn = Targ_FindNode(".BEGIN"); + if (gn == NULL) + return; + + Compat_Make(gn, gn); + if (GNode_IsError(gn)) { + PrintOnError(gn, "\nStop."); + exit(1); + } +} + +static void +InitSignals(void) +{ + if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) + bmake_signal(SIGINT, CompatInterrupt); + if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) + bmake_signal(SIGTERM, CompatInterrupt); + if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) + bmake_signal(SIGHUP, CompatInterrupt); + if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) + bmake_signal(SIGQUIT, CompatInterrupt); +} + +/* + * Initialize this module and start making. + * + * Input: + * targs The target nodes to re-create + */ +void +Compat_Run(GNodeList *targs) +{ + GNode *errorNode = NULL; + + if (shellName == NULL) + Shell_Init(); + + InitSignals(); + + /* Create the .END node now, to keep the (debug) output of the + * counter.mk test the same as before 2020-09-23. This implementation + * detail probably doesn't matter though. */ + (void)Targ_GetEndNode(); + + if (!opts.queryFlag) + MakeBeginNode(); + + /* + * Expand .USE nodes right now, because they can modify the structure + * of the tree. + */ + Make_ExpandUse(targs); + + while (!Lst_IsEmpty(targs)) { + GNode *gn = Lst_Dequeue(targs); + Compat_Make(gn, gn); + + if (gn->made == UPTODATE) { + printf("`%s' is up to date.\n", gn->name); + } else if (gn->made == ABORTED) { + printf("`%s' not remade because of errors.\n", + gn->name); + } + if (GNode_IsError(gn) && errorNode == NULL) + errorNode = gn; + } + + /* If the user has defined a .END target, run its commands. */ + if (errorNode == NULL) { + GNode *endNode = Targ_GetEndNode(); + Compat_Make(endNode, endNode); + if (GNode_IsError(endNode)) + errorNode = endNode; + } + + if (errorNode != NULL) { + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + else if (DEBUG(GRAPH3)) + Targ_PrintGraph(3); + PrintOnError(errorNode, "\nStop."); + exit(1); + } } diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c index b05a56cbc..8de220ecb 100644 --- a/usr.bin/make/cond.c +++ b/usr.bin/make/cond.c @@ -1,4 +1,4 @@ -/* $NetBSD: cond.c,v 1.68 2015/05/05 21:51:09 sjg Exp $ */ +/* $NetBSD: cond.c,v 1.259 2021/03/15 12:15:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,1366 +69,1294 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: cond.c,v 1.68 2015/05/05 21:51:09 sjg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)cond.c 8.2 (Berkeley) 1/2/94"; -#else -__RCSID("$NetBSD: cond.c,v 1.68 2015/05/05 21:51:09 sjg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * cond.c -- - * Functions to handle conditionals in a makefile. +/* + * Handling of conditionals in a makefile. * * Interface: - * Cond_Eval Evaluate the conditional in the passed line. + * Cond_EvalLine Evaluate the conditional directive, such as + * '.if ', '.elifnmake ', '.else', '.endif'. * + * Cond_EvalCondition + * Evaluate the conditional, which is either the argument + * of one of the .if directives or the condition in a + * ':?then:else' variable modifier. + * + * Cond_save_depth + * Cond_restore_depth + * Save and restore the nesting of the conditions, at + * the start and end of including another makefile, to + * ensure that in each makefile the conditional + * directives are well-balanced. */ -#include -#include /* For strtoul() error checking */ +#include -#include "make.h" -#include "hash.h" -#include "dir.h" -#include "buf.h" +#include "make.h" +#include "dir.h" + +/* "@(#)cond.c 8.2 (Berkeley) 1/2/94" */ +MAKE_RCSID("$NetBSD: cond.c,v 1.259 2021/03/15 12:15:03 rillig Exp $"); /* * The parsing of conditional expressions is based on this grammar: - * E -> F || E - * E -> F - * F -> T && F - * F -> T - * T -> defined(variable) - * T -> make(target) - * T -> exists(file) - * T -> empty(varspec) - * T -> target(name) - * T -> commands(name) - * T -> symbol - * T -> $(varspec) op value - * T -> $(varspec) == "string" - * T -> $(varspec) != "string" - * T -> "string" - * T -> ( E ) - * T -> ! T - * op -> == | != | > | < | >= | <= + * Or -> And '||' Or + * Or -> And + * And -> Term '&&' And + * And -> Term + * Term -> Function '(' Argument ')' + * Term -> Leaf Operator Leaf + * Term -> Leaf + * Term -> '(' Or ')' + * Term -> '!' Term + * Leaf -> "string" + * Leaf -> Number + * Leaf -> VariableExpression + * Leaf -> Symbol + * Operator -> '==' | '!=' | '>' | '<' | '>=' | '<=' * - * 'symbol' is some other symbol to which the default function (condDefProc) - * is applied. + * 'Symbol' is an unquoted string literal to which the default function is + * applied. * - * Tokens are scanned from the 'condExpr' string. The scanner (CondToken) - * will return TOK_AND for '&' and '&&', TOK_OR for '|' and '||', - * TOK_NOT for '!', TOK_LPAREN for '(', TOK_RPAREN for ')' and will evaluate - * the other terminal symbols, using either the default function or the - * function given in the terminal, and return the result as either TOK_TRUE - * or TOK_FALSE. + * The tokens are scanned by CondToken, which returns: + * TOK_AND for '&&' + * TOK_OR for '||' + * TOK_NOT for '!' + * TOK_LPAREN for '(' + * TOK_RPAREN for ')' * - * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons. - * - * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on - * error. + * Other terminal symbols are evaluated using either the default function or + * the function given in the terminal, they return either TOK_TRUE or + * TOK_FALSE. */ -typedef enum { - TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT, - TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR +typedef enum Token { + TOK_FALSE, TOK_TRUE, TOK_AND, TOK_OR, TOK_NOT, + TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR } Token; -/*- - * Structures to handle elegantly the different forms of #if's. The - * last two fields are stored in condInvert and condDefProc, respectively. - */ -static void CondPushBack(Token); -static int CondGetArg(char **, char **, const char *); -static Boolean CondDoDefined(int, const char *); -static int CondStrMatch(const void *, const void *); -static Boolean CondDoMake(int, const char *); -static Boolean CondDoExists(int, const char *); -static Boolean CondDoTarget(int, const char *); -static Boolean CondDoCommands(int, const char *); -static Boolean CondCvtArg(char *, double *); -static Token CondToken(Boolean); -static Token CondT(Boolean); -static Token CondF(Boolean); -static Token CondE(Boolean); -static int do_Cond_EvalExpression(Boolean *); +typedef enum CondResult { + CR_FALSE, CR_TRUE, CR_ERROR +} CondResult; -static const struct If { - const char *form; /* Form of if */ - int formlen; /* Length of form */ - Boolean doNot; /* TRUE if default function should be negated */ - Boolean (*defProc)(int, const char *); /* Default function to apply */ -} ifs[] = { - { "def", 3, FALSE, CondDoDefined }, - { "ndef", 4, TRUE, CondDoDefined }, - { "make", 4, FALSE, CondDoMake }, - { "nmake", 5, TRUE, CondDoMake }, - { "", 0, FALSE, CondDoDefined }, - { NULL, 0, FALSE, NULL } -}; +typedef enum ComparisonOp { + LT, LE, GT, GE, EQ, NE +} ComparisonOp; -static const struct If *if_info; /* Info for current statement */ -static char *condExpr; /* The expression to parse */ -static Token condPushBack=TOK_NONE; /* Single push-back token used in - * parsing */ +typedef struct CondParser { -static unsigned int cond_depth = 0; /* current .if nesting level */ -static unsigned int cond_min_depth = 0; /* depth at makefile open */ + /* + * The plain '.if ${VAR}' evaluates to true if the value of the + * expression has length > 0. The other '.if' variants delegate + * to evalBare instead. + */ + Boolean plain; + + /* The function to apply on unquoted bare words. */ + Boolean (*evalBare)(size_t, const char *); + Boolean negateEvalBare; + + const char *p; /* The remaining condition to parse */ + Token curr; /* Single push-back token used in parsing */ + + /* + * Whether an error message has already been printed for this + * condition. The first available error message is usually the most + * specific one, therefore it makes sense to suppress the standard + * "Malformed conditional" message. + */ + Boolean printedError; +} CondParser; + +static CondResult CondParser_Or(CondParser *par, Boolean); + +static unsigned int cond_depth = 0; /* current .if nesting level */ +static unsigned int cond_min_depth = 0; /* depth at makefile open */ + +static const char *opname[] = { "<", "<=", ">", ">=", "==", "!=" }; /* * Indicate when we should be strict about lhs of comparisons. - * TRUE when Cond_EvalExpression is called from Cond_Eval (.if etc) - * FALSE when Cond_EvalExpression is called from var.c:ApplyModifiers - * since lhs is already expanded and we cannot tell if + * In strict mode, the lhs must be a variable expression or a string literal + * in quotes. In non-strict mode it may also be an unquoted string literal. + * + * TRUE when CondEvalExpression is called from Cond_EvalLine (.if etc) + * FALSE when CondEvalExpression is called from ApplyModifier_IfElse + * since lhs is already expanded, and at that point we cannot tell if * it was a variable reference or not. */ static Boolean lhsStrict; -static int -istoken(const char *str, const char *tok, size_t len) +static Boolean +is_token(const char *str, const char *tok, size_t len) { - return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]); + return strncmp(str, tok, len) == 0 && !ch_isalpha(str[len]); } -/*- - *----------------------------------------------------------------------- - * CondPushBack -- - * Push back the most recent token read. We only need one level of - * this, so the thing is just stored in 'condPushback'. - * - * Input: - * t Token to push back into the "stream" - * - * Results: - * None. - * - * Side Effects: - * condPushback is overwritten. - * - *----------------------------------------------------------------------- - */ +static Token +ToToken(Boolean cond) +{ + return cond ? TOK_TRUE : TOK_FALSE; +} + +/* Push back the most recent token read. We only need one level of this. */ static void -CondPushBack(Token t) +CondParser_PushBack(CondParser *par, Token t) { - condPushBack = t; + assert(par->curr == TOK_NONE); + assert(t != TOK_NONE); + + par->curr = t; } - -/*- - *----------------------------------------------------------------------- - * CondGetArg -- - * Find the argument of a built-in function. - * - * Input: - * parens TRUE if arg should be bounded by parens - * - * Results: - * The length of the argument and the address of the argument. - * - * Side Effects: - * The pointer is set to point to the closing parenthesis of the - * function call. - * - *----------------------------------------------------------------------- - */ -static int -CondGetArg(char **linePtr, char **argPtr, const char *func) + +static void +CondParser_SkipWhitespace(CondParser *par) { - char *cp; - int argLen; - Buffer buf; - int paren_depth; - char ch; + cpp_skip_whitespace(&par->p); +} - cp = *linePtr; - if (func != NULL) - /* Skip opening '(' - verfied by caller */ - cp++; +/* + * Parse the argument of a built-in function. + * + * Arguments: + * *pp initially points at the '(', + * upon successful return it points right after the ')'. + * + * *out_arg receives the argument as string. + * + * func says whether the argument belongs to an actual function, or + * whether the parsed argument is passed to the default function. + * + * Return the length of the argument, or 0 on error. + */ +static size_t +ParseFuncArg(CondParser *par, const char **pp, Boolean doEval, const char *func, + char **out_arg) +{ + const char *p = *pp; + Buffer argBuf; + int paren_depth; + size_t argLen; - if (*cp == '\0') { - /* - * No arguments whatsoever. Because 'make' and 'defined' aren't really - * "reserved words", we don't print a message. I think this is better - * than hitting the user with a warning message every time s/he uses - * the word 'make' or 'defined' at the beginning of a symbol... - */ - *argPtr = NULL; - return (0); - } + if (func != NULL) + p++; /* Skip opening '(' - verified by caller */ - while (*cp == ' ' || *cp == '\t') { - cp++; - } - - /* - * Create a buffer for the argument and start it out at 16 characters - * long. Why 16? Why not? - */ - Buf_Init(&buf, 16); - - paren_depth = 0; - for (;;) { - ch = *cp; - if (ch == 0 || ch == ' ' || ch == '\t') - break; - if ((ch == '&' || ch == '|') && paren_depth == 0) - break; - if (*cp == '$') { - /* - * Parse the variable spec and install it as part of the argument - * if it's valid. We tell Var_Parse to complain on an undefined - * variable, so we don't do it too. Nor do we return an error, - * though perhaps we should... - */ - char *cp2; - int len; - void *freeIt; - - cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &freeIt); - Buf_AddBytes(&buf, strlen(cp2), cp2); - if (freeIt) - free(freeIt); - cp += len; - continue; + if (*p == '\0') { + *out_arg = NULL; /* Missing closing parenthesis: */ + return 0; /* .if defined( */ } - if (ch == '(') - paren_depth++; - else - if (ch == ')' && --paren_depth < 0) - break; - Buf_AddByte(&buf, *cp); - cp++; - } - *argPtr = Buf_GetAll(&buf, &argLen); - Buf_Destroy(&buf, FALSE); + cpp_skip_hspace(&p); - while (*cp == ' ' || *cp == '\t') { - cp++; - } + Buf_InitSize(&argBuf, 16); - if (func != NULL && *cp++ != ')') { - Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", - func); - return (0); - } + paren_depth = 0; + for (;;) { + char ch = *p; + if (ch == '\0' || ch == ' ' || ch == '\t') + break; + if ((ch == '&' || ch == '|') && paren_depth == 0) + break; + if (*p == '$') { + /* + * Parse the variable expression and install it as + * part of the argument if it's valid. We tell + * Var_Parse to complain on an undefined variable, + * (XXX: but Var_Parse ignores that request) + * so we don't need to do it. Nor do we return an + * error, though perhaps we should. + */ + VarEvalFlags eflags = doEval + ? VARE_UNDEFERR + : VARE_PARSE_ONLY; + FStr nestedVal; + (void)Var_Parse(&p, SCOPE_CMDLINE, eflags, &nestedVal); + /* TODO: handle errors */ + Buf_AddStr(&argBuf, nestedVal.str); + FStr_Done(&nestedVal); + continue; + } + if (ch == '(') + paren_depth++; + else if (ch == ')' && --paren_depth < 0) + break; + Buf_AddByte(&argBuf, *p); + p++; + } - *linePtr = cp; - return (argLen); + argLen = argBuf.len; + *out_arg = Buf_DoneData(&argBuf); + + cpp_skip_hspace(&p); + + if (func != NULL && *p++ != ')') { + Parse_Error(PARSE_FATAL, + "Missing closing parenthesis for %s()", func); + par->printedError = TRUE; + return 0; + } + + *pp = p; + return argLen; } - -/*- - *----------------------------------------------------------------------- - * CondDoDefined -- - * Handle the 'defined' function for conditionals. - * - * Results: - * TRUE if the given variable is defined. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ + +/* Test whether the given variable is defined. */ +/*ARGSUSED*/ static Boolean -CondDoDefined(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncDefined(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - char *p1; - Boolean result; + FStr value = Var_Value(SCOPE_CMDLINE, arg); + Boolean result = value.str != NULL; + FStr_Done(&value); + return result; +} - if (Var_Value(arg, VAR_CMD, &p1) != NULL) { - result = TRUE; - } else { - result = FALSE; - } - if (p1) - free(p1); - return (result); -} - -/*- - *----------------------------------------------------------------------- - * CondStrMatch -- - * Front-end for Str_Match so it returns 0 on match and non-zero - * on mismatch. Callback function for CondDoMake via Lst_Find - * - * Results: - * 0 if string matches pattern - * - * Side Effects: - * None - * - *----------------------------------------------------------------------- - */ -static int -CondStrMatch(const void *string, const void *pattern) -{ - return(!Str_Match(string, pattern)); -} - -/*- - *----------------------------------------------------------------------- - * CondDoMake -- - * Handle the 'make' function for conditionals. - * - * Results: - * TRUE if the given target is being made. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ +/* See if the given target is being made. */ +/*ARGSUSED*/ static Boolean -CondDoMake(int argLen MAKE_ATTR_UNUSED, const char *arg) +FuncMake(size_t argLen MAKE_ATTR_UNUSED, const char *arg) { - return Lst_Find(create, arg, CondStrMatch) != NULL; -} - -/*- - *----------------------------------------------------------------------- - * CondDoExists -- - * See if the given file exists. - * - * Results: - * TRUE if the file exists and FALSE if it does not. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -static Boolean -CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg) -{ - Boolean result; - char *path; + StringListNode *ln; - path = Dir_FindFile(arg, dirSearchPath); - if (DEBUG(COND)) { - fprintf(debug_file, "exists(%s) result is \"%s\"\n", - arg, path ? path : ""); - } - if (path != NULL) { - result = TRUE; + for (ln = opts.create.first; ln != NULL; ln = ln->next) + if (Str_Match(ln->datum, arg)) + return TRUE; + return FALSE; +} + +/* See if the given file exists. */ +/*ARGSUSED*/ +static Boolean +FuncExists(size_t argLen MAKE_ATTR_UNUSED, const char *arg) +{ + Boolean result; + char *path; + + path = Dir_FindFile(arg, &dirSearchPath); + DEBUG2(COND, "exists(%s) result is \"%s\"\n", + arg, path != NULL ? path : ""); + result = path != NULL; free(path); - } else { - result = FALSE; - } - return (result); + return result; } - -/*- - *----------------------------------------------------------------------- - * CondDoTarget -- - * See if the given node exists and is an actual target. + +/* See if the given node exists and is an actual target. */ +/*ARGSUSED*/ +static Boolean +FuncTarget(size_t argLen MAKE_ATTR_UNUSED, const char *arg) +{ + GNode *gn = Targ_FindNode(arg); + return gn != NULL && GNode_IsTarget(gn); +} + +/* + * See if the given node exists and is an actual target with commands + * associated with it. + */ +/*ARGSUSED*/ +static Boolean +FuncCommands(size_t argLen MAKE_ATTR_UNUSED, const char *arg) +{ + GNode *gn = Targ_FindNode(arg); + return gn != NULL && GNode_IsTarget(gn) && !Lst_IsEmpty(&gn->commands); +} + +/* + * Convert the given number into a double. + * We try a base 10 or 16 integer conversion first, if that fails + * then we try a floating point conversion instead. * * Results: - * TRUE if the node exists as a target and FALSE if it does not. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- + * Returns TRUE if the conversion succeeded. + * Sets 'out_value' to the converted number. */ static Boolean -CondDoTarget(int argLen MAKE_ATTR_UNUSED, const char *arg) +TryParseNumber(const char *str, double *out_value) { - GNode *gn; + char *end; + unsigned long ul_val; + double dbl_val; - gn = Targ_FindNode(arg, TARG_NOCREATE); - return (gn != NULL) && !OP_NOP(gn->type); + errno = 0; + if (str[0] == '\0') { /* XXX: why is an empty string a number? */ + *out_value = 0.0; + return TRUE; + } + + ul_val = strtoul(str, &end, str[1] == 'x' ? 16 : 10); + if (*end == '\0' && errno != ERANGE) { + *out_value = str[0] == '-' ? -(double)-ul_val : (double)ul_val; + return TRUE; + } + + if (*end != '\0' && *end != '.' && *end != 'e' && *end != 'E') + return FALSE; /* skip the expensive strtod call */ + dbl_val = strtod(str, &end); + if (*end != '\0') + return FALSE; + + *out_value = dbl_val; + return TRUE; } -/*- - *----------------------------------------------------------------------- - * CondDoCommands -- - * See if the given node exists and is an actual target with commands - * associated with it. +static Boolean +is_separator(char ch) +{ + return ch == '\0' || ch_isspace(ch) || strchr("!=><)", ch) != NULL; +} + +/* + * In a quoted or unquoted string literal or a number, parse a variable + * expression. * - * Results: - * TRUE if the node exists as a target and has commands associated with - * it and FALSE if it does not. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- + * Example: .if x${CENTER}y == "${PREFIX}${SUFFIX}" || 0x${HEX} */ static Boolean -CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg) +CondParser_StringExpr(CondParser *par, const char *start, + Boolean const doEval, Boolean const quoted, + Buffer *buf, FStr *const inout_str) { - GNode *gn; + VarEvalFlags eflags; + const char *nested_p; + Boolean atStart; + VarParseResult parseResult; - gn = Targ_FindNode(arg, TARG_NOCREATE); - return (gn != NULL) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands); -} - -/*- - *----------------------------------------------------------------------- - * CondCvtArg -- - * Convert the given number into a double. - * We try a base 10 or 16 integer conversion first, if that fails - * then we try a floating point conversion instead. - * - * Results: - * Sets 'value' to double value of string. - * Returns 'true' if the convertion suceeded - * - *----------------------------------------------------------------------- - */ -static Boolean -CondCvtArg(char *str, double *value) -{ - char *eptr, ech; - unsigned long l_val; - double d_val; + /* if we are in quotes, an undefined variable is ok */ + eflags = doEval && !quoted ? VARE_UNDEFERR + : doEval ? VARE_WANTRES + : VARE_PARSE_ONLY; - errno = 0; - l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10); - ech = *eptr; - if (ech == 0 && errno != ERANGE) { - d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; - } else { - if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E') - return FALSE; - d_val = strtod(str, &eptr); - if (*eptr) - return FALSE; - } - - *value = d_val; - return TRUE; -} - -/*- - *----------------------------------------------------------------------- - * CondGetString -- - * Get a string from a variable reference or an optionally quoted - * string. This is called for the lhs and rhs of string compares. - * - * Results: - * Sets freeIt if needed, - * Sets quoted if string was quoted, - * Returns NULL on error, - * else returns string - absent any quotes. - * - * Side Effects: - * Moves condExpr to end of this token. - * - * - *----------------------------------------------------------------------- - */ -/* coverity:[+alloc : arg-*2] */ -static char * -CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS) -{ - Buffer buf; - char *cp; - char *str; - int len; - int qt; - char *start; - - Buf_Init(&buf, 0); - str = NULL; - *freeIt = NULL; - *quoted = qt = *condExpr == '"' ? 1 : 0; - if (qt) - condExpr++; - for (start = condExpr; *condExpr && str == NULL; condExpr++) { - switch (*condExpr) { - case '\\': - if (condExpr[1] != '\0') { - condExpr++; - Buf_AddByte(&buf, *condExpr); - } - break; - case '"': - if (qt) { - condExpr++; /* we don't want the quotes */ - goto got_str; - } else - Buf_AddByte(&buf, *condExpr); /* likely? */ - break; - case ')': - case '!': - case '=': - case '>': - case '<': - case ' ': - case '\t': - if (!qt) - goto got_str; - else - Buf_AddByte(&buf, *condExpr); - break; - case '$': - /* if we are in quotes, then an undefined variable is ok */ - str = Var_Parse(condExpr, VAR_CMD, (qt ? 0 : doEval), - &len, freeIt); - if (str == var_Error) { - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; + nested_p = par->p; + atStart = nested_p == start; + parseResult = Var_Parse(&nested_p, SCOPE_CMDLINE, eflags, inout_str); + /* TODO: handle errors */ + if (inout_str->str == var_Error) { + if (parseResult == VPR_ERR) { + /* + * FIXME: Even if an error occurs, there is no + * guarantee that it is reported. + * + * See cond-token-plain.mk $$$$$$$$. + */ + par->printedError = TRUE; } /* - * Even if !doEval, we still report syntax errors, which - * is what getting var_Error back with !doEval means. + * XXX: Can there be any situation in which a returned + * var_Error needs to be freed? */ - str = NULL; - goto cleanup; - } - condExpr += len; - /* - * If the '$' was first char (no quotes), and we are - * followed by space, the operator or end of expression, - * we are done. - */ - if ((condExpr == start + len) && - (*condExpr == '\0' || - isspace((unsigned char) *condExpr) || - strchr("!=><)", *condExpr))) { - goto cleanup; - } - /* - * Nope, we better copy str to buf - */ - for (cp = str; *cp; cp++) { - Buf_AddByte(&buf, *cp); - } - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; - } - str = NULL; /* not finished yet */ - condExpr--; /* don't skip over next char */ - break; - default: - if (strictLHS && !qt && *start != '$' && - !isdigit((unsigned char) *start)) { - /* lhs must be quoted, a variable reference or number */ - if (*freeIt) { - free(*freeIt); - *freeIt = NULL; - } - str = NULL; - goto cleanup; - } - Buf_AddByte(&buf, *condExpr); - break; + FStr_Done(inout_str); + /* + * Even if !doEval, we still report syntax errors, which is + * what getting var_Error back with !doEval means. + */ + *inout_str = FStr_InitRefer(NULL); + return FALSE; } - } - got_str: - str = Buf_GetAll(&buf, NULL); - *freeIt = str; - cleanup: - Buf_Destroy(&buf, FALSE); - return str; + par->p = nested_p; + + /* + * If the '$' started the string literal (which means no quotes), and + * the variable expression is followed by a space, looks like a + * comparison operator or is the end of the expression, we are done. + */ + if (atStart && is_separator(par->p[0])) + return FALSE; + + Buf_AddStr(buf, inout_str->str); + FStr_Done(inout_str); + *inout_str = FStr_InitRefer(NULL); /* not finished yet */ + return TRUE; } - -/*- - *----------------------------------------------------------------------- - * CondToken -- - * Return the next token from the input. + +/* + * Parse a string from a variable reference or an optionally quoted + * string. This is called for the lhs and rhs of string comparisons. * * Results: - * A Token for the next lexical token in the stream. - * - * Side Effects: - * condPushback will be set back to TOK_NONE if it is used. - * - *----------------------------------------------------------------------- + * Returns the string, absent any quotes, or NULL on error. + * Sets out_quoted if the string was quoted. */ -static Token -compare_expression(Boolean doEval) +static void +CondParser_String(CondParser *par, Boolean doEval, Boolean strictLHS, + FStr *out_str, Boolean *out_quoted) { - Token t; - char *lhs; - char *rhs; - char *op; - void *lhsFree; - void *rhsFree; - Boolean lhsQuoted; - Boolean rhsQuoted; - double left, right; + Buffer buf; + FStr str; + Boolean quoted; + const char *start; - t = TOK_ERROR; - rhs = NULL; - lhsFree = rhsFree = FALSE; - lhsQuoted = rhsQuoted = FALSE; - - /* - * Parse the variable spec and skip over it, saving its - * value in lhs. - */ - lhs = CondGetString(doEval, &lhsQuoted, &lhsFree, lhsStrict); - if (!lhs) - goto done; + Buf_Init(&buf); + str = FStr_InitRefer(NULL); + *out_quoted = quoted = par->p[0] == '"'; + start = par->p; + if (quoted) + par->p++; - /* - * Skip whitespace to get to the operator - */ - while (isspace((unsigned char) *condExpr)) - condExpr++; - - /* - * Make sure the operator is a valid one. If it isn't a - * known relational operator, pretend we got a - * != 0 comparison. - */ - op = condExpr; - switch (*condExpr) { - case '!': - case '=': - case '<': - case '>': - if (condExpr[1] == '=') { - condExpr += 2; - } else { - condExpr += 1; - } - break; - default: - if (!doEval) { - t = TOK_FALSE; - goto done; - } - /* For .ifxxx "..." check for non-empty string. */ - if (lhsQuoted) { - t = lhs[0] != 0; - goto done; - } - /* For .ifxxx compare against zero */ - if (CondCvtArg(lhs, &left)) { - t = left != 0.0; - goto done; - } - /* For .if ${...} check for non-empty string (defProc is ifdef). */ - if (if_info->form[0] == 0) { - t = lhs[0] != 0; - goto done; - } - /* Otherwise action default test ... */ - t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot; - goto done; - } - - while (isspace((unsigned char)*condExpr)) - condExpr++; - - if (*condExpr == '\0') { - Parse_Error(PARSE_WARNING, - "Missing right-hand-side of operator"); - goto done; - } - - rhs = CondGetString(doEval, &rhsQuoted, &rhsFree, FALSE); - if (!rhs) - goto done; - - if (rhsQuoted || lhsQuoted) { -do_string_compare: - if (((*op != '!') && (*op != '=')) || (op[1] != '=')) { - Parse_Error(PARSE_WARNING, - "String comparison operator should be either == or !="); - goto done; + while (par->p[0] != '\0' && str.str == NULL) { + switch (par->p[0]) { + case '\\': + par->p++; + if (par->p[0] != '\0') { + Buf_AddByte(&buf, par->p[0]); + par->p++; + } + continue; + case '"': + par->p++; + if (quoted) + goto got_str; /* skip the closing quote */ + Buf_AddByte(&buf, '"'); + continue; + case ')': /* see is_separator */ + case '!': + case '=': + case '>': + case '<': + case ' ': + case '\t': + if (!quoted) + goto got_str; + Buf_AddByte(&buf, par->p[0]); + par->p++; + continue; + case '$': + if (!CondParser_StringExpr(par, + start, doEval, quoted, &buf, &str)) + goto cleanup; + continue; + default: + if (strictLHS && !quoted && *start != '$' && + !ch_isdigit(*start)) { + /* + * The left-hand side must be quoted, + * a variable reference or a number. + */ + str = FStr_InitRefer(NULL); + goto cleanup; + } + Buf_AddByte(&buf, par->p[0]); + par->p++; + continue; + } } - - if (DEBUG(COND)) { - fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", - lhs, rhs, op); - } - /* - * Null-terminate rhs and perform the comparison. - * t is set to the result. - */ - if (*op == '=') { - t = strcmp(lhs, rhs) == 0; - } else { - t = strcmp(lhs, rhs) != 0; - } - } else { - /* - * rhs is either a float or an integer. Convert both the - * lhs and the rhs to a double and compare the two. - */ - - if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right)) - goto do_string_compare; - - if (DEBUG(COND)) { - fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left, - right, op); - } - switch(op[0]) { - case '!': - if (op[1] != '=') { - Parse_Error(PARSE_WARNING, - "Unknown operator"); - goto done; - } - t = (left != right); - break; - case '=': - if (op[1] != '=') { - Parse_Error(PARSE_WARNING, - "Unknown operator"); - goto done; - } - t = (left == right); - break; - case '<': - if (op[1] == '=') { - t = (left <= right); - } else { - t = (left < right); - } - break; - case '>': - if (op[1] == '=') { - t = (left >= right); - } else { - t = (left > right); - } - break; - } - } - -done: - if (lhsFree) - free(lhsFree); - if (rhsFree) - free(rhsFree); - return t; -} - -static int -get_mpt_arg(char **linePtr, char **argPtr, const char *func MAKE_ATTR_UNUSED) -{ - /* - * Use Var_Parse to parse the spec in parens and return - * TOK_TRUE if the resulting string is empty. - */ - int length; - void *freeIt; - char *val; - char *cp = *linePtr; - - /* We do all the work here and return the result as the length */ - *argPtr = NULL; - - val = Var_Parse(cp - 1, VAR_CMD, FALSE, &length, &freeIt); - /* - * Advance *linePtr to beyond the closing ). Note that - * we subtract one because 'length' is calculated from 'cp - 1'. - */ - *linePtr = cp - 1 + length; - - if (val == var_Error) { - free(freeIt); - return -1; - } - - /* A variable is empty when it just contains spaces... 4/15/92, christos */ - while (isspace(*(unsigned char *)val)) - val++; - - /* - * For consistency with the other functions we can't generate the - * true/false here. - */ - length = *val ? 2 : 1; - if (freeIt) - free(freeIt); - return length; +got_str: + str = FStr_InitOwn(buf.data); +cleanup: + Buf_DoneData(&buf); + *out_str = str; } static Boolean -CondDoEmpty(int arglen, const char *arg MAKE_ATTR_UNUSED) +If_Eval(const CondParser *par, const char *arg, size_t arglen) { - return arglen == 1; + Boolean res = par->evalBare(arglen, arg); + return par->negateEvalBare ? !res : res; +} + +/* + * Evaluate a "comparison without operator", such as in ".if ${VAR}" or + * ".if 0". + */ +static Boolean +EvalNotEmpty(CondParser *par, const char *value, Boolean quoted) +{ + double num; + + /* For .ifxxx "...", check for non-empty string. */ + if (quoted) + return value[0] != '\0'; + + /* For .ifxxx , compare against zero */ + if (TryParseNumber(value, &num)) + return num != 0.0; + + /* For .if ${...}, check for non-empty string. This is different from + * the evaluation function from that .if variant, which would test + * whether a variable of the given name were defined. */ + /* XXX: Whitespace should count as empty, just as in ParseEmptyArg. */ + if (par->plain) + return value[0] != '\0'; + + return If_Eval(par, value, strlen(value)); +} + +/* Evaluate a numerical comparison, such as in ".if ${VAR} >= 9". */ +static Boolean +EvalCompareNum(double lhs, ComparisonOp op, double rhs) +{ + DEBUG3(COND, "lhs = %f, rhs = %f, op = %.2s\n", lhs, rhs, opname[op]); + + switch (op) { + case LT: + return lhs < rhs; + case LE: + return lhs <= rhs; + case GT: + return lhs > rhs; + case GE: + return lhs >= rhs; + case NE: + return lhs != rhs; + default: + return lhs == rhs; + } } static Token -compare_function(Boolean doEval) +EvalCompareStr(CondParser *par, const char *lhs, + ComparisonOp op, const char *rhs) { - static const struct fn_def { - const char *fn_name; - int fn_name_len; - int (*fn_getarg)(char **, char **, const char *); - Boolean (*fn_proc)(int, const char *); - } fn_defs[] = { - { "defined", 7, CondGetArg, CondDoDefined }, - { "make", 4, CondGetArg, CondDoMake }, - { "exists", 6, CondGetArg, CondDoExists }, - { "empty", 5, get_mpt_arg, CondDoEmpty }, - { "target", 6, CondGetArg, CondDoTarget }, - { "commands", 8, CondGetArg, CondDoCommands }, - { NULL, 0, NULL, NULL }, - }; - const struct fn_def *fn_def; - Token t; - char *arg = NULL; - int arglen; - char *cp = condExpr; - char *cp1; - - for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { - if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len)) - continue; - cp += fn_def->fn_name_len; - /* There can only be whitespace before the '(' */ - while (isspace(*(unsigned char *)cp)) - cp++; - if (*cp != '(') - break; - - arglen = fn_def->fn_getarg(&cp, &arg, fn_def->fn_name); - if (arglen <= 0) { - condExpr = cp; - return arglen < 0 ? TOK_ERROR : TOK_FALSE; + if (op != EQ && op != NE) { + Parse_Error(PARSE_FATAL, + "String comparison operator must be either == or !="); + par->printedError = TRUE; + return TOK_ERROR; } - /* Evaluate the argument using the required function. */ - t = !doEval || fn_def->fn_proc(arglen, arg); - if (arg) - free(arg); - condExpr = cp; + + DEBUG3(COND, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", + lhs, rhs, opname[op]); + return ToToken((op == EQ) == (strcmp(lhs, rhs) == 0)); +} + +/* Evaluate a comparison, such as "${VAR} == 12345". */ +static Token +EvalCompare(CondParser *par, const char *lhs, Boolean lhsQuoted, + ComparisonOp op, const char *rhs, Boolean rhsQuoted) +{ + double left, right; + + if (!rhsQuoted && !lhsQuoted) + if (TryParseNumber(lhs, &left) && TryParseNumber(rhs, &right)) + return ToToken(EvalCompareNum(left, op, right)); + + return EvalCompareStr(par, lhs, op, rhs); +} + +static Boolean +CondParser_ComparisonOp(CondParser *par, ComparisonOp *out_op) +{ + const char *p = par->p; + + if (p[0] == '<' && p[1] == '=') { + *out_op = LE; + goto length_2; + } else if (p[0] == '<') { + *out_op = LT; + goto length_1; + } else if (p[0] == '>' && p[1] == '=') { + *out_op = GE; + goto length_2; + } else if (p[0] == '>') { + *out_op = GT; + goto length_1; + } else if (p[0] == '=' && p[1] == '=') { + *out_op = EQ; + goto length_2; + } else if (p[0] == '!' && p[1] == '=') { + *out_op = NE; + goto length_2; + } + return FALSE; + +length_2: + par->p = p + 2; + return TRUE; +length_1: + par->p = p + 1; + return TRUE; +} + +/* + * Parse a comparison condition such as: + * + * 0 + * ${VAR:Mpattern} + * ${VAR} == value + * ${VAR:U0} < 12345 + */ +static Token +CondParser_Comparison(CondParser *par, Boolean doEval) +{ + Token t = TOK_ERROR; + FStr lhs, rhs; + ComparisonOp op; + Boolean lhsQuoted, rhsQuoted; + + /* + * Parse the variable spec and skip over it, saving its + * value in lhs. + */ + CondParser_String(par, doEval, lhsStrict, &lhs, &lhsQuoted); + if (lhs.str == NULL) + goto done_lhs; + + CondParser_SkipWhitespace(par); + + if (!CondParser_ComparisonOp(par, &op)) { + /* Unknown operator, compare against an empty string or 0. */ + t = ToToken(doEval && EvalNotEmpty(par, lhs.str, lhsQuoted)); + goto done_lhs; + } + + CondParser_SkipWhitespace(par); + + if (par->p[0] == '\0') { + Parse_Error(PARSE_FATAL, + "Missing right-hand-side of operator '%s'", opname[op]); + par->printedError = TRUE; + goto done_lhs; + } + + CondParser_String(par, doEval, FALSE, &rhs, &rhsQuoted); + if (rhs.str == NULL) + goto done_rhs; + + if (!doEval) { + t = TOK_FALSE; + goto done_rhs; + } + + t = EvalCompare(par, lhs.str, lhsQuoted, op, rhs.str, rhsQuoted); + +done_rhs: + FStr_Done(&rhs); +done_lhs: + FStr_Done(&lhs); return t; - } +} - /* Push anything numeric through the compare expression */ - cp = condExpr; - if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0])) - return compare_expression(doEval); +/* + * The argument to empty() is a variable name, optionally followed by + * variable modifiers. + */ +/*ARGSUSED*/ +static size_t +ParseEmptyArg(CondParser *par MAKE_ATTR_UNUSED, const char **pp, + Boolean doEval, const char *func MAKE_ATTR_UNUSED, + char **out_arg) +{ + FStr val; + size_t magic_res; - /* - * 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. - * 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. - */ - arglen = CondGetArg(&cp, &arg, NULL); - for (cp1 = cp; isspace(*(unsigned char *)cp1); cp1++) - continue; - if (*cp1 == '=' || *cp1 == '!') - return compare_expression(doEval); - condExpr = cp; + /* We do all the work here and return the result as the length */ + *out_arg = NULL; - /* - * 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. - */ - t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot; - if (arg) + (*pp)--; /* Make (*pp)[1] point to the '('. */ + (void)Var_Parse(pp, SCOPE_CMDLINE, + doEval ? VARE_WANTRES : VARE_PARSE_ONLY, &val); + /* TODO: handle errors */ + /* If successful, *pp points beyond the closing ')' now. */ + + if (val.str == var_Error) { + FStr_Done(&val); + return (size_t)-1; + } + + /* + * A variable is empty when it just contains spaces... + * 4/15/92, christos + */ + cpp_skip_whitespace(&val.str); + + /* + * For consistency with the other functions we can't generate the + * true/false here. + */ + magic_res = val.str[0] != '\0' ? 2 : 1; + FStr_Done(&val); + return magic_res; +} + +/*ARGSUSED*/ +static Boolean +FuncEmpty(size_t arglen, const char *arg MAKE_ATTR_UNUSED) +{ + /* Magic values ahead, see ParseEmptyArg. */ + return arglen == 1; +} + +static Boolean +CondParser_Func(CondParser *par, Boolean doEval, Token *out_token) +{ + static const struct fn_def { + const char *fn_name; + size_t fn_name_len; + size_t (*fn_parse)(CondParser *, const char **, Boolean, + const char *, char **); + Boolean (*fn_eval)(size_t, const char *); + } fns[] = { + { "defined", 7, ParseFuncArg, FuncDefined }, + { "make", 4, ParseFuncArg, FuncMake }, + { "exists", 6, ParseFuncArg, FuncExists }, + { "empty", 5, ParseEmptyArg, FuncEmpty }, + { "target", 6, ParseFuncArg, FuncTarget }, + { "commands", 8, ParseFuncArg, FuncCommands } + }; + const struct fn_def *fn; + char *arg = NULL; + size_t arglen; + const char *cp = par->p; + const struct fn_def *fns_end = fns + sizeof fns / sizeof fns[0]; + + for (fn = fns; fn != fns_end; fn++) { + if (!is_token(cp, fn->fn_name, fn->fn_name_len)) + continue; + + cp += fn->fn_name_len; + cpp_skip_whitespace(&cp); + if (*cp != '(') + break; + + arglen = fn->fn_parse(par, &cp, doEval, fn->fn_name, &arg); + if (arglen == 0 || arglen == (size_t)-1) { + par->p = cp; + *out_token = arglen == 0 ? TOK_FALSE : TOK_ERROR; + return TRUE; + } + + /* Evaluate the argument using the required function. */ + *out_token = ToToken(!doEval || fn->fn_eval(arglen, arg)); + free(arg); + par->p = cp; + return TRUE; + } + + return FALSE; +} + +/* + * Parse a function call, a number, a variable expression or a string + * literal. + */ +static Token +CondParser_LeafToken(CondParser *par, Boolean doEval) +{ + Token t; + char *arg = NULL; + size_t arglen; + const char *cp; + const char *cp1; + + if (CondParser_Func(par, doEval, &t)) + return t; + + /* Push anything numeric through the compare expression */ + cp = par->p; + if (ch_isdigit(cp[0]) || cp[0] == '-' || cp[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. + * 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. + */ + arglen = ParseFuncArg(par, &cp, doEval, NULL, &arg); + cp1 = cp; + cpp_skip_whitespace(&cp1); + if (*cp1 == '=' || *cp1 == '!') + return CondParser_Comparison(par, doEval); + par->p = cp; + + /* + * 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. + */ + t = ToToken(!doEval || If_Eval(par, arg, arglen)); free(arg); - return t; -} - -static Token -CondToken(Boolean doEval) -{ - Token t; - - t = condPushBack; - if (t != TOK_NONE) { - condPushBack = TOK_NONE; return t; - } - - while (*condExpr == ' ' || *condExpr == '\t') { - condExpr++; - } - - switch (*condExpr) { - - case '(': - condExpr++; - return TOK_LPAREN; - - case ')': - condExpr++; - return TOK_RPAREN; - - case '|': - if (condExpr[1] == '|') { - condExpr++; - } - condExpr++; - return TOK_OR; - - case '&': - if (condExpr[1] == '&') { - condExpr++; - } - condExpr++; - return TOK_AND; - - case '!': - condExpr++; - return TOK_NOT; - - case '#': - case '\n': - case '\0': - return TOK_EOF; - - case '"': - case '$': - return compare_expression(doEval); - - default: - return compare_function(doEval); - } } -/*- - *----------------------------------------------------------------------- - * CondT -- - * Parse a single term in the expression. This consists of a terminal - * symbol or TOK_NOT and a terminal symbol (not including the binary - * operators): - * T -> defined(variable) | make(target) | exists(file) | symbol - * T -> ! T | ( E ) - * - * Results: - * TOK_TRUE, TOK_FALSE or TOK_ERROR. - * - * Side Effects: - * Tokens are consumed. - * - *----------------------------------------------------------------------- - */ +/* Return the next token or comparison result from the parser. */ static Token -CondT(Boolean doEval) +CondParser_Token(CondParser *par, Boolean doEval) { - Token t; + Token t; - t = CondToken(doEval); + t = par->curr; + if (t != TOK_NONE) { + par->curr = TOK_NONE; + return t; + } - if (t == TOK_EOF) { - /* - * If we reached the end of the expression, the expression - * is malformed... - */ - t = TOK_ERROR; - } else if (t == TOK_LPAREN) { - /* - * T -> ( E ) - */ - t = CondE(doEval); - if (t != TOK_ERROR) { - if (CondToken(doEval) != TOK_RPAREN) { - t = TOK_ERROR; - } + cpp_skip_hspace(&par->p); + + switch (par->p[0]) { + + case '(': + par->p++; + return TOK_LPAREN; + + case ')': + par->p++; + return TOK_RPAREN; + + case '|': + par->p++; + if (par->p[0] == '|') + par->p++; + else if (opts.strict) { + Parse_Error(PARSE_FATAL, "Unknown operator '|'"); + par->printedError = TRUE; + return TOK_ERROR; + } + return TOK_OR; + + case '&': + par->p++; + if (par->p[0] == '&') + par->p++; + else if (opts.strict) { + Parse_Error(PARSE_FATAL, "Unknown operator '&'"); + par->printedError = TRUE; + return TOK_ERROR; + } + return TOK_AND; + + case '!': + par->p++; + return TOK_NOT; + + case '#': /* XXX: see unit-tests/cond-token-plain.mk */ + case '\n': /* XXX: why should this end the condition? */ + /* Probably obsolete now, from 1993-03-21. */ + case '\0': + return TOK_EOF; + + case '"': + case '$': + return CondParser_Comparison(par, doEval); + + default: + return CondParser_LeafToken(par, doEval); } - } else if (t == TOK_NOT) { - t = CondT(doEval); - if (t == TOK_TRUE) { - t = TOK_FALSE; - } else if (t == TOK_FALSE) { - t = TOK_TRUE; - } - } - return (t); } - -/*- - *----------------------------------------------------------------------- - * CondF -- - * Parse a conjunctive factor (nice name, wot?) - * F -> T && F | T - * - * Results: - * TOK_TRUE, TOK_FALSE or TOK_ERROR - * - * Side Effects: - * Tokens are consumed. - * - *----------------------------------------------------------------------- + +/* + * Term -> '(' Or ')' + * Term -> '!' Term + * Term -> Leaf Operator Leaf + * Term -> Leaf */ -static Token -CondF(Boolean doEval) +static CondResult +CondParser_Term(CondParser *par, Boolean doEval) { - Token l, o; + CondResult res; + Token t; - l = CondT(doEval); - if (l != TOK_ERROR) { - o = CondToken(doEval); + t = CondParser_Token(par, doEval); + if (t == TOK_TRUE) + return CR_TRUE; + if (t == TOK_FALSE) + return CR_FALSE; - if (o == TOK_AND) { - /* - * F -> T && F - * - * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to - * parse the r.h.s. anyway (to throw it away). - * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no. - */ - if (l == TOK_TRUE) { - l = CondF(doEval); - } else { - (void)CondF(FALSE); - } - } else { - /* - * F -> T - */ - CondPushBack(o); + if (t == TOK_LPAREN) { + res = CondParser_Or(par, doEval); + if (res == CR_ERROR) + return CR_ERROR; + if (CondParser_Token(par, doEval) != TOK_RPAREN) + return CR_ERROR; + return res; } - } - return (l); + + if (t == TOK_NOT) { + res = CondParser_Term(par, doEval); + if (res == CR_TRUE) + res = CR_FALSE; + else if (res == CR_FALSE) + res = CR_TRUE; + return res; + } + + return CR_ERROR; } - -/*- - *----------------------------------------------------------------------- - * CondE -- - * Main expression production. - * E -> F || E | F - * - * Results: - * TOK_TRUE, TOK_FALSE or TOK_ERROR. - * - * Side Effects: - * Tokens are, of course, consumed. - * - *----------------------------------------------------------------------- + +/* + * And -> Term '&&' And + * And -> Term */ -static Token -CondE(Boolean doEval) +static CondResult +CondParser_And(CondParser *par, Boolean doEval) { - Token l, o; + CondResult res; + Token op; - l = CondF(doEval); - if (l != TOK_ERROR) { - o = CondToken(doEval); + res = CondParser_Term(par, doEval); + if (res == CR_ERROR) + return CR_ERROR; - if (o == TOK_OR) { - /* - * E -> F || E - * - * A similar thing occurs for ||, except that here we make sure - * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s. - * Once again, if l is TOK_FALSE, the result is the r.h.s. and once - * again if l is TOK_TRUE, we parse the r.h.s. to throw it away. - */ - if (l == TOK_FALSE) { - l = CondE(doEval); - } else { - (void)CondE(FALSE); - } - } else { - /* - * E -> F - */ - CondPushBack(o); + op = CondParser_Token(par, doEval); + if (op == TOK_AND) { + if (res == CR_TRUE) + return CondParser_And(par, doEval); + if (CondParser_And(par, FALSE) == CR_ERROR) + return CR_ERROR; + return res; } - } - return (l); + + CondParser_PushBack(par, op); + return res; } -/*- - *----------------------------------------------------------------------- - * Cond_EvalExpression -- - * Evaluate an expression in the passed line. The expression - * consists of &&, ||, !, make(target), defined(variable) - * and parenthetical groupings thereof. +/* + * Or -> And '||' Or + * Or -> And + */ +static CondResult +CondParser_Or(CondParser *par, Boolean doEval) +{ + CondResult res; + Token op; + + res = CondParser_And(par, doEval); + if (res == CR_ERROR) + return CR_ERROR; + + op = CondParser_Token(par, doEval); + if (op == TOK_OR) { + if (res == CR_FALSE) + return CondParser_Or(par, doEval); + if (CondParser_Or(par, FALSE) == CR_ERROR) + return CR_ERROR; + return res; + } + + CondParser_PushBack(par, op); + return res; +} + +static CondEvalResult +CondParser_Eval(CondParser *par, Boolean *out_value) +{ + CondResult res; + + DEBUG1(COND, "CondParser_Eval: %s\n", par->p); + + res = CondParser_Or(par, TRUE); + if (res == CR_ERROR) + return COND_INVALID; + + if (CondParser_Token(par, FALSE) != TOK_EOF) + return COND_INVALID; + + *out_value = res == CR_TRUE; + return COND_PARSE; +} + +/* + * Evaluate the condition, including any side effects from the variable + * expressions in the condition. The condition consists of &&, ||, !, + * function(arg), comparisons and parenthetical groupings thereof. * * Results: * COND_PARSE if the condition was valid grammatically - * COND_INVALID if not a valid conditional. + * COND_INVALID if not a valid conditional. * * (*value) is set to the boolean value of the condition - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- */ -int -Cond_EvalExpression(const struct If *info, char *line, Boolean *value, int eprint, Boolean strictLHS) +static CondEvalResult +CondEvalExpression(const char *cond, Boolean *out_value, Boolean plain, + Boolean (*evalBare)(size_t, const char *), Boolean negate, + Boolean eprint, Boolean strictLHS) { - static const struct If *dflt_info; - const struct If *sv_if_info = if_info; - char *sv_condExpr = condExpr; - Token sv_condPushBack = condPushBack; - int rval; + CondParser par; + CondEvalResult rval; - lhsStrict = strictLHS; + lhsStrict = strictLHS; - while (*line == ' ' || *line == '\t') - line++; + cpp_skip_hspace(&cond); - if (info == NULL && (info = dflt_info) == NULL) { - /* Scan for the entry for .if - it can't be first */ - for (info = ifs; ; info++) - if (info->form[0] == 0) - break; - dflt_info = info; - } + par.plain = plain; + par.evalBare = evalBare; + par.negateEvalBare = negate; + par.p = cond; + par.curr = TOK_NONE; + par.printedError = FALSE; - if_info = info != NULL ? info : ifs + 4; - condExpr = line; - condPushBack = TOK_NONE; + rval = CondParser_Eval(&par, out_value); - rval = do_Cond_EvalExpression(value); + if (rval == COND_INVALID && eprint && !par.printedError) + Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", cond); - if (rval == COND_INVALID && eprint) - Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line); - - if_info = sv_if_info; - condExpr = sv_condExpr; - condPushBack = sv_condPushBack; - - return rval; + return rval; } -static int -do_Cond_EvalExpression(Boolean *value) +/* + * Evaluate a condition in a :? modifier, such as + * ${"${VAR}" == value:?yes:no}. + */ +CondEvalResult +Cond_EvalCondition(const char *cond, Boolean *out_value) { - - switch (CondE(TRUE)) { - case TOK_TRUE: - if (CondToken(TRUE) == TOK_EOF) { - *value = TRUE; - return COND_PARSE; - } - break; - case TOK_FALSE: - if (CondToken(TRUE) == TOK_EOF) { - *value = FALSE; - return COND_PARSE; - } - break; - default: - case TOK_ERROR: - break; - } - - return COND_INVALID; + return CondEvalExpression(cond, out_value, TRUE, + FuncDefined, FALSE, FALSE, FALSE); } - -/*- - *----------------------------------------------------------------------- - * Cond_Eval -- - * Evaluate the conditional in the passed line. The line - * looks like this: - * . - * where is any of if, ifmake, ifnmake, ifdef, - * ifndef, elif, elifmake, elifnmake, elifdef, elifndef - * and consists of &&, ||, !, make(target), defined(variable) - * and parenthetical groupings thereof. +static Boolean +IsEndif(const char *p) +{ + return p[0] == 'e' && p[1] == 'n' && p[2] == 'd' && + p[3] == 'i' && p[4] == 'f' && !ch_isalpha(p[5]); +} + +static Boolean +DetermineKindOfConditional(const char **pp, Boolean *out_plain, + Boolean (**out_evalBare)(size_t, const char *), + Boolean *out_negate) +{ + const char *p = *pp; + + p += 2; + *out_plain = FALSE; + *out_evalBare = FuncDefined; + *out_negate = FALSE; + if (*p == 'n') { + p++; + *out_negate = TRUE; + } + if (is_token(p, "def", 3)) { /* .ifdef and .ifndef */ + p += 3; + } else if (is_token(p, "make", 4)) { /* .ifmake and .ifnmake */ + p += 4; + *out_evalBare = FuncMake; + } else if (is_token(p, "", 0) && !*out_negate) { /* plain .if */ + *out_plain = TRUE; + } else { + /* + * 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; + } + + *pp = p; + return TRUE; +} + +/* + * Evaluate the conditional directive in the line, which is one of: * - * Input: - * line Line to parse + * .if + * .ifmake + * .ifnmake + * .ifdef + * .ifndef + * .elif + * .elifmake + * .elifnmake + * .elifdef + * .elifndef + * .else + * .endif + * + * In these directives, consists of &&, ||, !, function(arg), + * comparisons, expressions, bare words, numbers and strings, and + * parenthetical groupings thereof. * * Results: - * COND_PARSE if should parse lines after the conditional - * COND_SKIP if should skip lines after the conditional - * COND_INVALID if not a valid conditional. - * - * Side Effects: - * None. - * - * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order - * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF) - * otherwise .else could be treated as '.elif 1'. - * - *----------------------------------------------------------------------- + * COND_PARSE to continue parsing the lines that follow the + * conditional (when evaluates to TRUE) + * COND_SKIP to skip the lines after the conditional + * (when evaluates to FALSE, or when a previous + * branch has already been taken) + * COND_INVALID 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 */ -int -Cond_Eval(char *line) +CondEvalResult +Cond_EvalLine(const char *line) { -#define MAXIF 128 /* maximum depth of .if'ing */ -#define MAXIF_BUMP 32 /* how much to grow by */ - enum if_states { - IF_ACTIVE, /* .if or .elif part active */ - ELSE_ACTIVE, /* .else part active */ - SEARCH_FOR_ELIF, /* searching for .elif/else to execute */ - SKIP_TO_ELSE, /* has been true, but not seen '.else' */ - SKIP_TO_ENDIF /* nothing else to execute */ - }; - static enum if_states *cond_state = NULL; - static unsigned int max_if_depth = MAXIF; + typedef enum IfState { - const struct If *ifp; - Boolean isElif; - Boolean value; - int level; /* Level at which to report errors. */ - enum if_states state; + /* None of the previous evaluated to TRUE. */ + IFS_INITIAL = 0, - level = PARSE_FATAL; - if (!cond_state) { - cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); - cond_state[0] = IF_ACTIVE; - } - /* skip leading character (the '.') and any whitespace */ - for (line++; *line == ' ' || *line == '\t'; line++) - continue; + /* The previous evaluated to TRUE. + * The lines following this condition are interpreted. */ + IFS_ACTIVE = 1 << 0, - /* Find what type of if we're dealing with. */ - if (line[0] == 'e') { - if (line[1] != 'l') { - if (!istoken(line + 1, "ndif", 4)) + /* The previous directive was an '.else'. */ + IFS_SEEN_ELSE = 1 << 1, + + /* One of the previous evaluated to TRUE. */ + IFS_WAS_ACTIVE = 1 << 2 + + } IfState; + + static enum IfState *cond_states = NULL; + static unsigned int cond_states_cap = 128; + + Boolean plain; + Boolean (*evalBare)(size_t, const char *); + Boolean negate; + Boolean isElif; + Boolean value; + IfState state; + const char *p = line; + + if (cond_states == NULL) { + cond_states = bmake_malloc( + cond_states_cap * sizeof *cond_states); + cond_states[0] = IFS_ACTIVE; + } + + p++; /* skip the leading '.' */ + cpp_skip_hspace(&p); + + if (IsEndif(p)) { /* It is an '.endif'. */ + if (p[5] != '\0') { + Parse_Error(PARSE_FATAL, + "The .endif directive does not take arguments."); + } + + if (cond_depth == cond_min_depth) { + Parse_Error(PARSE_FATAL, "if-less endif"); + return COND_PARSE; + } + + /* Return state for previous conditional */ + cond_depth--; + return cond_states[cond_depth] & IFS_ACTIVE + ? COND_PARSE : COND_SKIP; + } + + /* 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 '.elisp.scm', + * therefore no error message here. + */ + return COND_INVALID; + } + + /* Quite likely this is 'else' or 'elif' */ + p += 2; + if (is_token(p, "se", 2)) { /* It is an 'else'. */ + + if (p[2] != '\0') + Parse_Error(PARSE_FATAL, + "The .else directive " + "does not take arguments."); + + if (cond_depth == cond_min_depth) { + Parse_Error(PARSE_FATAL, "if-less else"); + return COND_PARSE; + } + + state = cond_states[cond_depth]; + if (state == IFS_INITIAL) { + state = IFS_ACTIVE | IFS_SEEN_ELSE; + } else { + if (state & IFS_SEEN_ELSE) + Parse_Error(PARSE_WARNING, + "extra else"); + state = IFS_WAS_ACTIVE | IFS_SEEN_ELSE; + } + cond_states[cond_depth] = state; + + return state & IFS_ACTIVE ? COND_PARSE : COND_SKIP; + } + /* Assume for now it is an elif */ + isElif = TRUE; + } 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 COND_INVALID; /* Not an ifxxx or elifxxx line */ + } + + if (!DetermineKindOfConditional(&p, &plain, &evalBare, &negate)) return COND_INVALID; - /* End of conditional section */ - if (cond_depth == cond_min_depth) { - Parse_Error(level, "if-less endif"); - return COND_PARSE; - } - /* Return state for previous conditional */ - cond_depth--; - return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; + + if (isElif) { + if (cond_depth == cond_min_depth) { + Parse_Error(PARSE_FATAL, "if-less elif"); + return COND_PARSE; + } + state = cond_states[cond_depth]; + if (state & IFS_SEEN_ELSE) { + Parse_Error(PARSE_WARNING, "extra elif"); + cond_states[cond_depth] = + IFS_WAS_ACTIVE | IFS_SEEN_ELSE; + return COND_SKIP; + } + if (state != IFS_INITIAL) { + cond_states[cond_depth] = IFS_WAS_ACTIVE; + return COND_SKIP; + } + } else { + /* Normal .if */ + if (cond_depth + 1 >= cond_states_cap) { + /* + * This is rare, but not impossible. + * In meta mode, dirdeps.mk (only runs at level 0) + * can need more than the default. + */ + cond_states_cap += 32; + cond_states = bmake_realloc(cond_states, + cond_states_cap * + sizeof *cond_states); + } + state = cond_states[cond_depth]; + cond_depth++; + if (!(state & IFS_ACTIVE)) { + /* + * If we aren't parsing the data, + * treat as always false. + */ + cond_states[cond_depth] = IFS_WAS_ACTIVE; + return COND_SKIP; + } } - /* Quite likely this is 'else' or 'elif' */ - line += 2; - if (istoken(line, "se", 2)) { - /* It is else... */ - if (cond_depth == cond_min_depth) { - Parse_Error(level, "if-less else"); - return COND_PARSE; - } - - state = cond_state[cond_depth]; - switch (state) { - case SEARCH_FOR_ELIF: - state = ELSE_ACTIVE; - break; - case ELSE_ACTIVE: - case SKIP_TO_ENDIF: - Parse_Error(PARSE_WARNING, "extra else"); - /* FALLTHROUGH */ - default: - case IF_ACTIVE: - case SKIP_TO_ELSE: - state = SKIP_TO_ENDIF; - break; - } - cond_state[cond_depth] = state; - return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; + /* And evaluate the conditional expression */ + if (CondEvalExpression(p, &value, plain, evalBare, negate, + TRUE, TRUE) == COND_INVALID) { + /* Syntax error in conditional, error message already output. */ + /* Skip everything to matching .endif */ + /* XXX: An extra '.else' is not detected in this case. */ + cond_states[cond_depth] = IFS_WAS_ACTIVE; + return COND_SKIP; } - /* Assume for now it is an elif */ - isElif = TRUE; - } else - isElif = FALSE; - if (line[0] != 'i' || line[1] != 'f') - /* Not an ifxxx or elifxxx line */ - return COND_INVALID; - - /* - * Figure out what sort of conditional it is -- what its default - * function is, etc. -- by looking in the table of valid "ifs" - */ - line += 2; - for (ifp = ifs; ; ifp++) { - if (ifp->form == NULL) - return COND_INVALID; - if (istoken(ifp->form, line, ifp->formlen)) { - line += ifp->formlen; - break; + if (!value) { + cond_states[cond_depth] = IFS_INITIAL; + return COND_SKIP; } - } - - /* Now we know what sort of 'if' it is... */ - - if (isElif) { - if (cond_depth == cond_min_depth) { - Parse_Error(level, "if-less elif"); - return COND_PARSE; - } - state = cond_state[cond_depth]; - if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) { - Parse_Error(PARSE_WARNING, "extra elif"); - cond_state[cond_depth] = SKIP_TO_ENDIF; - return COND_SKIP; - } - if (state != SEARCH_FOR_ELIF) { - /* Either just finished the 'true' block, or already SKIP_TO_ELSE */ - cond_state[cond_depth] = SKIP_TO_ELSE; - return COND_SKIP; - } - } else { - /* Normal .if */ - if (cond_depth + 1 >= max_if_depth) { - /* - * This is rare, but not impossible. - * In meta mode, dirdeps.mk (only runs at level 0) - * can need more than the default. - */ - max_if_depth += MAXIF_BUMP; - cond_state = bmake_realloc(cond_state, max_if_depth * - sizeof(*cond_state)); - } - state = cond_state[cond_depth]; - cond_depth++; - if (state > ELSE_ACTIVE) { - /* If we aren't parsing the data, treat as always false */ - cond_state[cond_depth] = SKIP_TO_ELSE; - return COND_SKIP; - } - } - - /* And evaluate the conditional expresssion */ - if (Cond_EvalExpression(ifp, line, &value, 1, TRUE) == COND_INVALID) { - /* Syntax error in conditional, error message already output. */ - /* Skip everything to matching .endif */ - cond_state[cond_depth] = SKIP_TO_ELSE; - return COND_SKIP; - } - - if (!value) { - cond_state[cond_depth] = SEARCH_FOR_ELIF; - return COND_SKIP; - } - cond_state[cond_depth] = IF_ACTIVE; - return COND_PARSE; + cond_states[cond_depth] = IFS_ACTIVE; + return COND_PARSE; } - - -/*- - *----------------------------------------------------------------------- - * Cond_End -- - * Make sure everything's clean at the end of a makefile. - * - * Results: - * None. - * - * Side Effects: - * Parse_Error will be called if open conditionals are around. - * - *----------------------------------------------------------------------- - */ void Cond_restore_depth(unsigned int saved_depth) { - int open_conds = cond_depth - cond_min_depth; + unsigned int open_conds = cond_depth - cond_min_depth; - if (open_conds != 0 || saved_depth > cond_depth) { - Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds, - open_conds == 1 ? "" : "s"); - cond_depth = cond_min_depth; - } + if (open_conds != 0 || saved_depth > cond_depth) { + Parse_Error(PARSE_FATAL, "%u open conditional%s", + open_conds, open_conds == 1 ? "" : "s"); + cond_depth = cond_min_depth; + } - cond_min_depth = saved_depth; + cond_min_depth = saved_depth; } unsigned int Cond_save_depth(void) { - int depth = cond_min_depth; + unsigned int depth = cond_min_depth; - cond_min_depth = cond_depth; - return depth; + cond_min_depth = cond_depth; + return depth; } diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h index 06af09c2d..dcf5b3162 100644 --- a/usr.bin/make/config.h +++ b/usr.bin/make/config.h @@ -1,4 +1,4 @@ -/* $NetBSD: config.h,v 1.21 2012/03/31 00:12:24 christos Exp $ */ +/* $NetBSD: config.h,v 1.28 2020/12/11 22:53:08 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -72,26 +72,16 @@ * from: @(#)config.h 8.1 (Berkeley) 6/6/93 */ -/* - * DEFMAXJOBS - * DEFMAXLOCAL - * These control the default concurrency. On no occasion will more - * than DEFMAXJOBS targets be created at once (locally or remotely) - * DEFMAXLOCAL is the highest number of targets which will be - * created on the local machine at once. Note that if you set this - * to 0, nothing will ever happen... - */ -#define DEFMAXJOBS 4 -#define DEFMAXLOCAL 1 - /* * INCLUDES * LIBRARIES * These control the handling of the .INCLUDES and .LIBS variables. + * * If INCLUDES is defined, the .INCLUDES variable will be filled * from the search paths of those suffixes which are marked by - * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS - * See suff.c for more details. + * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS. + * + * See varname-dot-include.mk and varname-dot-libs.mk for more details. */ #define INCLUDES #define LIBRARIES @@ -100,34 +90,39 @@ * LIBSUFF * Is the suffix used to denote libraries and is used by the Suff module * to find the search path on which to seek any -l targets. - * + */ +#define LIBSUFF ".a" + +/* * RECHECK * If defined, Make_Update will check a target for its current * modification time after it has been re-made, setting it to the * starting time of the make only if the target still doesn't exist. + * * Unfortunately, under NFS the modification time often doesn't * get updated in time, so a target will appear to not have been - * re-made, causing later targets to appear up-to-date. On systems - * that don't have this problem, you should defined this. Under - * NFS you probably should not, unless you aren't exporting jobs. + * re-made, causing later targets to appear up-to-date. + * + * On systems that don't have this problem, you should define this. + * Under NFS you probably should not, unless you aren't exporting jobs. */ -#define LIBSUFF ".a" -#define RECHECK +#define RECHECK /* * POSIX * Adhere to the POSIX 1003.2 draft for the make(1) program. * - Use MAKEFLAGS instead of MAKE to pick arguments from the * environment. - * - Allow empty command lines if starting with tab. */ #define POSIX /* * SYSVINCLUDE * Recognize system V like include directives [include "filename"] + * (required by POSIX 2018) * SYSVVARSUB * Recognize system V like ${VAR:x=y} variable substitutions + * (required by POSIX 2018) */ #define SYSVINCLUDE #define SYSVVARSUB @@ -147,12 +142,6 @@ */ #define SUNSHCMD -/* - * USE_IOVEC - * We have writev(2) - */ -#define USE_IOVEC - #if defined(MAKE_NATIVE) && !defined(__ELF__) # ifndef RANLIBMAG # define RANLIBMAG "__.SYMDEF" diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c index 7b12769c2..026983e3e 100644 --- a/usr.bin/make/dir.c +++ b/usr.bin/make/dir.c @@ -1,4 +1,4 @@ -/* $NetBSD: dir.c,v 1.67 2013/03/05 22:01:43 christos Exp $ */ +/* $NetBSD: dir.c,v 1.270 2021/02/05 05:48:19 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,70 +69,62 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: dir.c,v 1.67 2013/03/05 22:01:43 christos Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)dir.c 8.2 (Berkeley) 1/2/94"; -#else -__RCSID("$NetBSD: dir.c,v 1.67 2013/03/05 22:01:43 christos Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * dir.c -- - * Directory searching using wildcards and/or normal names... - * Used both for source wildcarding in the Makefile and for finding - * implicit sources. +/* + * Directory searching using wildcards and/or normal names. + * Used both for source wildcarding in the makefile and for finding + * implicit sources. * * The interface for this module is: - * Dir_Init Initialize the module. + * Dir_Init Initialize the module. * - * Dir_InitCur Set the cur Path. + * Dir_InitCur Set the cur CachedDir. * - * Dir_InitDot Set the dot Path. + * Dir_InitDot Set the dot CachedDir. * - * Dir_End Cleanup the module. + * Dir_End Clean up the module. * - * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. + * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. * - * Dir_HasWildcards Returns TRUE if the name given it needs to - * be wildcard-expanded. + * Dir_HasWildcards + * Returns TRUE if the name given it needs to + * be wildcard-expanded. * - * Dir_Expand Given a pattern and a path, return a Lst of names - * which match the pattern on the search path. + * SearchPath_Expand + * Expand a filename pattern to find all matching files + * 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. + * Dir_FindFile Searches for a file on a given search path. + * If it exists, the entire path is returned. + * Otherwise NULL is returned. * - * 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 ("/"). - * - * Dir_MTime Return the modification time of a node. The file - * is searched for along the default search path. - * The path and mtime fields of the node are filled - * in. + * 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 ("/"). * - * Dir_AddDir Add a directory to a search path. + * Dir_UpdateMTime + * Update the modification time and path of a node with + * data from the file corresponding to the node. * - * Dir_MakeFlags Given a search path and a command flag, create - * a string with each of the directories in the path - * preceded by the command flag and all of them - * separated by a space. + * SearchPath_Add Add a directory to a search path. * - * 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. - * Dir_ClearPath Resets a search path to the empty list. + * SearchPath_ToFlags + * Given a search path and a command flag, create + * a string with each of the directories in the path + * 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. * * For debugging: - * Dir_PrintDirectories Print stats about the directory cache. + * Dir_PrintDirectories + * Print stats about the directory cache. */ #include @@ -140,244 +132,409 @@ __RCSID("$NetBSD: dir.c,v 1.67 2013/03/05 22:01:43 christos Exp $"); #include #include -#include #include "make.h" -#include "hash.h" #include "dir.h" #include "job.h" +/* "@(#)dir.c 8.2 (Berkeley) 1/2/94" */ +MAKE_RCSID("$NetBSD: dir.c,v 1.270 2021/02/05 05:48:19 rillig Exp $"); + /* - * A search path consists of a Lst of Path structures. A Path structure - * has in it the name of the directory and a hash table of all the files - * in the directory. This is used to cut down on the number of system - * calls necessary to find implicit dependents and their like. Since - * these searches are made before any actions are taken, we need not - * worry about the directory changing due to creation commands. If this - * hampers the style of some makefiles, they must be changed. + * A search path is a list of CachedDir structures. A CachedDir has in it the + * name of the directory and the names of all the files in the directory. + * This is used to cut down on the number of system calls necessary to find + * implicit dependents and their like. Since these searches are made before + * any actions are taken, we need not worry about the directory changing due + * to creation commands. If this hampers the style of some makefiles, they + * must be changed. * - * A list of all previously-read directories is kept in the - * openDirectories Lst. This list is checked first before a directory - * is opened. + * 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 hashing 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: + * 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: * - * 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 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 - * 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. - * 3) record the mtime of the directory in the Path structure and - * verify the directory hasn't changed since the contents were - * hashed. This will catch the creation or deletion of files, - * but not the updating of files. However, since it is the - * creation and deletion that is the problem, this could be - * a good thing to do. Unfortunately, if the directory (say ".") - * were fairly large and changed fairly frequently, the constant - * rehashing could seriously degrade performance. It might be - * good in such cases to keep track of the number of rehashes - * and if the number goes over a (small) limit, resort to using - * stat in its place. + * 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 + * the multi-level transformation code. * - * An additional thing to consider is that pmake is used primarily - * to create C programs and until recently pcc-based compilers refused - * to allow you 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. + * 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. * - * One more note: the location of a target's file is only performed - * on the downward traversal of the graph and then only for terminal - * nodes in the graph. This could be construed as wrong in some cases, - * but prevents inadvertent modification of files when the "installed" - * directory for a file is provided in the search path. + * 3) record the mtime of the directory in the CachedDir structure and + * verify the directory hasn't changed since the contents were cached. + * This will catch the creation or deletion of files, but not the + * updating of files. However, since it is the creation and deletion + * that is the problem, this could be a good thing to do. Unfortunately, + * if the directory (say ".") were fairly large and changed fairly + * frequently, the constant reloading could seriously degrade + * performance. It might be good in such cases to keep track of the + * number of reloadings and if the number goes over a (small) limit, + * resort to using stat in its place. * - * Another data structure maintained by this module is an mtime - * cache used when the searching of cached directories fails to find - * a file. In the past, Dir_FindFile would simply perform an access() - * call in such a case to determine if the file could be found using - * just the name given. When this hit, however, all that was gained - * was the knowledge that the file existed. Given that an access() is - * essentially a stat() without the copyout() call, and that the same - * filesystem overhead would have to be incurred in Dir_MTime, it made - * sense to replace the access() with a stat() and record the mtime - * in a cache for when Dir_MTime was actually called. + * 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 + * 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. + * + * One more note: the location of a target's file is only performed on the + * downward traversal of the graph and then only for terminal nodes in the + * graph. This could be construed as wrong in some cases, but prevents + * inadvertent modification of files when the "installed" directory for a + * file is provided in the search path. + * + * Another data structure maintained by this module is an mtime cache used + * when the searching of cached directories fails to find a file. In the past, + * Dir_FindFile would simply perform an access() call in such a case to + * determine if the file could be found using just the name given. When this + * hit, however, all that was gained was the knowledge that the file existed. + * Given that an access() is essentially a stat() without the copyout() call, + * and that the same filesystem overhead would have to be incurred in + * Dir_MTime, it made sense to replace the access() with a stat() and record + * the mtime in a cache for when Dir_UpdateMTime was actually called. */ -Lst dirSearchPath; /* main search path */ -static Lst openDirectories; /* the list of all open directories */ +/* A cache for the filenames in a directory. */ +struct CachedDir { + /* + * Name of directory, either absolute or relative to the current + * directory. The name is not normalized in any way, that is, "." + * and "./." are different. + * + * Not sure what happens when .CURDIR is assigned a new value; see + * Parse_DoVar. + */ + char *name; + + /* + * The number of SearchPaths that refer to this directory. + * Plus the number of global variables that refer to this directory. + * References from openDirs do not count though. + */ + int refCount; + + /* The number of times a file in this directory has been found. */ + int hits; + + /* The names of the directory entries. */ + HashSet files; +}; + +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; + HashTable /* of CachedDirListNode */ table; +} OpenDirs; + +typedef enum CachedStatsFlags { + CST_NONE = 0, + CST_LSTAT = 1 << 0, /* call lstat(2) instead of stat(2) */ + CST_UPDATE = 1 << 1 /* ignore existing cached entry */ +} CachedStatsFlags; + + +SearchPath dirSearchPath = { LST_INIT }; /* main search path */ + +static OpenDirs openDirs; /* all cached directories */ /* - * Variables for gathering statistics on the efficiency of the hashing + * Variables for gathering statistics on the efficiency of the caching * mechanism. */ -static int hits, /* Found in directory cache */ - misses, /* Sad, but not evil misses */ - nearmisses, /* Found under search path */ - bigmisses; /* Sought by itself */ +static int hits; /* Found in directory cache */ +static int misses; /* Sad, but not evil misses */ +static int nearmisses; /* Found under search path */ +static int bigmisses; /* Sought by itself */ -static Path *dot; /* contents of current directory */ -static Path *cur; /* contents of current directory, if not dot */ -static Path *dotLast; /* a fake path entry indicating we need to - * look for . last */ -static Hash_Table mtimes; /* Results of doing a last-resort stat in - * Dir_FindFile -- if we have to go to the - * system to find the file, we might as well - * have its mtime on record. 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... */ +/* The cached contents of ".", the relative current directory. */ +static CachedDir *dot = NULL; +/* The cached contents of the absolute current directory. */ +static CachedDir *cur = NULL; +/* A fake path entry indicating we need to look for '.' last. */ +static CachedDir *dotLast = NULL; - -static int DirFindName(const void *, const void *); -static int DirMatchFiles(const char *, Path *, Lst); -static void DirExpandCurly(const char *, const char *, Lst, Lst); -static void DirExpandInt(const char *, Lst, Lst); -static int DirPrintWord(void *, void *); -static int DirPrintDir(void *, void *); -static char *DirLookup(Path *, const char *, const char *, Boolean); -static char *DirLookupSubdir(Path *, const char *); -static char *DirFindDot(Boolean, const char *, const char *); -static char *DirLookupAbs(Path *, const char *, const char *); - -/*- - *----------------------------------------------------------------------- - * Dir_Init -- - * initialize things for this module +/* + * Results of doing a last-resort stat in Dir_FindFile -- if we have to go to + * the system to find the file, we might as well have its mtime on record. * - * Results: - * none - * - * Side Effects: - * some directories may be opened. - *----------------------------------------------------------------------- + * 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... */ -void -Dir_Init(const char *cdname) +static HashTable mtimes; + +static HashTable lmtimes; /* same as mtimes but for lstat */ + + +static void OpenDirs_Remove(OpenDirs *, const char *); + + +static CachedDir * +CachedDir_New(const char *name) { - dirSearchPath = Lst_Init(FALSE); - openDirectories = Lst_Init(FALSE); - Hash_InitTable(&mtimes, 0); + CachedDir *dir = bmake_malloc(sizeof *dir); - Dir_InitCur(cdname); + dir->name = bmake_strdup(name); + dir->refCount = 0; + dir->hits = 0; + HashSet_Init(&dir->files); - dotLast = bmake_malloc(sizeof(Path)); - dotLast->refCount = 1; - dotLast->hits = 0; - dotLast->name = bmake_strdup(".DOTLAST"); - Hash_InitTable(&dotLast->files, -1); +#ifdef DEBUG_REFCNT + DEBUG2(DIR, "CachedDir %p new for \"%s\"\n", dir, dir->name); +#endif + + return dir; +} + +static CachedDir * +CachedDir_Ref(CachedDir *dir) +{ + dir->refCount++; + +#ifdef DEBUG_REFCNT + DEBUG3(DIR, "CachedDir %p ++ %d for \"%s\"\n", + dir, dir->refCount, dir->name); +#endif + + return dir; +} + +static void +CachedDir_Unref(CachedDir *dir) +{ + dir->refCount--; + +#ifdef DEBUG_REFCNT + DEBUG3(DIR, "CachedDir %p -- %d for \"%s\"\n", + dir, dir->refCount, dir->name); +#endif + + if (dir->refCount > 0) + return; + +#ifdef DEBUG_REFCNT + DEBUG2(DIR, "CachedDir %p free for \"%s\"\n", dir, dir->name); +#endif + + OpenDirs_Remove(&openDirs, dir->name); + + free(dir->name); + HashSet_Done(&dir->files); + free(dir); +} + +/* Update the value of the CachedDir variable, updating the reference counts. */ +static void +CachedDir_Assign(CachedDir **var, CachedDir *dir) +{ + CachedDir *prev; + + prev = *var; + *var = dir; + if (dir != NULL) + CachedDir_Ref(dir); + if (prev != NULL) + CachedDir_Unref(prev); +} + +static void +OpenDirs_Init(OpenDirs *odirs) +{ + Lst_Init(&odirs->list); + HashTable_Init(&odirs->table); +} + +#ifdef CLEANUP +static void +OpenDirs_Done(OpenDirs *odirs) +{ + CachedDirListNode *ln = odirs->list.first; + DEBUG1(DIR, "OpenDirs_Done: %u entries to remove\n", + odirs->table.numEntries); + while (ln != NULL) { + CachedDirListNode *next = ln->next; + CachedDir *dir = ln->datum; + DEBUG2(DIR, "OpenDirs_Done: refCount %d for \"%s\"\n", + dir->refCount, dir->name); + CachedDir_Unref(dir); /* removes the dir from odirs->list */ + ln = next; + } + Lst_Done(&odirs->list); + HashTable_Done(&odirs->table); +} +#endif + +static CachedDir * +OpenDirs_Find(OpenDirs *odirs, const char *name) +{ + CachedDirListNode *ln = HashTable_FindValue(&odirs->table, name); + return ln != NULL ? ln->datum : NULL; +} + +static void +OpenDirs_Add(OpenDirs *odirs, CachedDir *cdir) +{ + if (HashTable_FindEntry(&odirs->table, cdir->name) != NULL) + return; + Lst_Append(&odirs->list, cdir); + HashTable_Set(&odirs->table, cdir->name, odirs->list.last); +} + +static void +OpenDirs_Remove(OpenDirs *odirs, const char *name) +{ + HashEntry *he = HashTable_FindEntry(&odirs->table, name); + CachedDirListNode *ln; + if (he == NULL) + return; + ln = HashEntry_Get(he); + HashTable_DeleteEntry(&odirs->table, he); + Lst_Remove(&odirs->list, ln); } /* - * Called by Dir_Init() and whenever .CURDIR is assigned to. + * Returns 0 and the result of stat(2) or lstat(2) in *out_cst, + * or -1 on error. + */ +static int +cached_stats(const char *pathname, struct cached_stat *out_cst, + CachedStatsFlags flags) +{ + HashTable *tbl = flags & CST_LSTAT ? &lmtimes : &mtimes; + struct stat sys_st; + struct cached_stat *cst; + int rc; + + if (pathname == NULL || pathname[0] == '\0') + return -1; /* This can happen in meta mode. */ + + cst = HashTable_FindValue(tbl, pathname); + if (cst != NULL && !(flags & CST_UPDATE)) { + *out_cst = *cst; + DEBUG2(DIR, "Using cached time %s for %s\n", + Targ_FmtTime(cst->cst_mtime), pathname); + return 0; + } + + rc = (flags & CST_LSTAT ? lstat : stat)(pathname, &sys_st); + if (rc == -1) + return -1; /* don't cache negative lookups */ + + if (sys_st.st_mtime == 0) + sys_st.st_mtime = 1; /* avoid confusion with missing file */ + + if (cst == NULL) { + cst = bmake_malloc(sizeof *cst); + HashTable_Set(tbl, pathname, cst); + } + + cst->cst_mtime = sys_st.st_mtime; + cst->cst_mode = sys_st.st_mode; + + *out_cst = *cst; + DEBUG2(DIR, " Caching %s for %s\n", + Targ_FmtTime(sys_st.st_mtime), pathname); + + return 0; +} + +int +cached_stat(const char *pathname, struct cached_stat *cst) +{ + return cached_stats(pathname, cst, CST_NONE); +} + +int +cached_lstat(const char *pathname, struct cached_stat *cst) +{ + return cached_stats(pathname, cst, CST_LSTAT); +} + +/* Initialize the directories module. */ +void +Dir_Init(void) +{ + OpenDirs_Init(&openDirs); + HashTable_Init(&mtimes); + HashTable_Init(&lmtimes); + CachedDir_Assign(&dotLast, CachedDir_New(".DOTLAST")); +} + +/* + * Called by Dir_InitDir and whenever .CURDIR is assigned to. */ void -Dir_InitCur(const char *cdname) +Dir_InitCur(const char *newCurdir) { - Path *p; - - if (cdname != NULL) { + CachedDir *dir; + + if (newCurdir == NULL) + return; + /* * Our build directory is not the same as our source directory. * Keep this one around too. */ - if ((p = Dir_AddDir(NULL, cdname))) { - p->refCount += 1; - if (cur && cur != p) { - /* - * We've been here before, cleanup. - */ - cur->refCount -= 1; - Dir_Destroy(cur); - } - cur = p; - } - } + dir = SearchPath_Add(NULL, newCurdir); + if (dir == NULL) + return; + + CachedDir_Assign(&cur, dir); } -/*- - *----------------------------------------------------------------------- - * Dir_InitDot -- - * (re)initialize "dot" (current/object directory) path hash - * - * Results: - * none - * - * Side Effects: - * some directories may be opened. - *----------------------------------------------------------------------- +/* + * (Re)initialize "dot" (current/object directory) path hash. + * Some directories may be cached. */ void Dir_InitDot(void) { - if (dot != NULL) { - LstNode ln; + CachedDir *dir; - /* Remove old entry from openDirectories, but do not destroy. */ - ln = Lst_Member(openDirectories, dot); - (void)Lst_Remove(openDirectories, ln); - } + dir = SearchPath_Add(NULL, "."); + if (dir == NULL) { + Error("Cannot open `.' (%s)", strerror(errno)); + exit(2); /* Not 1 so -q can distinguish error */ + } - dot = Dir_AddDir(NULL, "."); + CachedDir_Assign(&dot, dir); - if (dot == NULL) { - Error("Cannot open `.' (%s)", strerror(errno)); - exit(1); - } - - /* - * We always need to have dot around, so we increment its reference count - * to make sure it's not destroyed. - */ - dot->refCount += 1; - Dir_SetPATH(); /* initialize */ + Dir_SetPATH(); /* initialize */ } -/*- - *----------------------------------------------------------------------- - * Dir_End -- - * cleanup things for this module - * - * Results: - * none - * - * Side Effects: - * none - *----------------------------------------------------------------------- - */ +/* Clean up the directories module. */ void Dir_End(void) { #ifdef CLEANUP - if (cur) { - cur->refCount -= 1; - Dir_Destroy(cur); - } - dot->refCount -= 1; - dotLast->refCount -= 1; - Dir_Destroy(dotLast); - Dir_Destroy(dot); - Dir_ClearPath(dirSearchPath); - Lst_Destroy(dirSearchPath, NULL); - Dir_ClearPath(openDirectories); - Lst_Destroy(openDirectories, NULL); - Hash_DeleteTable(&mtimes); + CachedDir_Assign(&cur, NULL); + CachedDir_Assign(&dot, NULL); + CachedDir_Assign(&dotLast, NULL); + SearchPath_Clear(&dirSearchPath); + OpenDirs_Done(&openDirs); + HashTable_Done(&mtimes); + HashTable_Done(&lmtimes); #endif } @@ -389,578 +546,475 @@ Dir_End(void) void Dir_SetPATH(void) { - LstNode ln; /* a list element */ - Path *p; - Boolean hasLastDot = FALSE; /* true we should search dot last */ + CachedDirListNode *ln; + Boolean seenDotLast = FALSE; /* true if we should search '.' last */ - Var_Delete(".PATH", VAR_GLOBAL); - - if (Lst_Open(dirSearchPath) == SUCCESS) { - if ((ln = Lst_First(dirSearchPath)) != NULL) { - p = (Path *)Lst_Datum(ln); - if (p == dotLast) { - hasLastDot = TRUE; - Var_Append(".PATH", dotLast->name, VAR_GLOBAL); - } + Global_Delete(".PATH"); + + if ((ln = dirSearchPath.dirs.first) != NULL) { + CachedDir *dir = ln->datum; + if (dir == dotLast) { + seenDotLast = TRUE; + Global_Append(".PATH", dotLast->name); + } } - if (!hasLastDot) { - if (dot) - Var_Append(".PATH", dot->name, VAR_GLOBAL); - if (cur) - Var_Append(".PATH", cur->name, VAR_GLOBAL); + if (!seenDotLast) { + if (dot != NULL) + Global_Append(".PATH", dot->name); + if (cur != NULL) + Global_Append(".PATH", cur->name); } - while ((ln = Lst_Next(dirSearchPath)) != NULL) { - p = (Path *)Lst_Datum(ln); - if (p == dotLast) - continue; - if (p == dot && hasLastDot) - continue; - Var_Append(".PATH", p->name, VAR_GLOBAL); + for (ln = dirSearchPath.dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) + continue; + if (dir == dot && seenDotLast) + continue; + Global_Append(".PATH", dir->name); } - if (hasLastDot) { - if (dot) - Var_Append(".PATH", dot->name, VAR_GLOBAL); - if (cur) - Var_Append(".PATH", cur->name, VAR_GLOBAL); + if (seenDotLast) { + if (dot != NULL) + Global_Append(".PATH", dot->name); + if (cur != NULL) + Global_Append(".PATH", cur->name); } - Lst_Close(dirSearchPath); - } } -/*- - *----------------------------------------------------------------------- - * DirFindName -- - * See if the Path structure describes the same directory as the - * given one by comparing their names. Called from Dir_AddDir via - * Lst_Find when searching the list of open directories. +/* + * See if the given name has any wildcard characters in it and all braces and + * brackets are properly balanced. * - * Input: - * p Current name - * dname Desired name + * 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! * - * Results: - * 0 if it is the same. Non-zero otherwise - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -static int -DirFindName(const void *p, const void *dname) -{ - return (strcmp(((const Path *)p)->name, dname)); -} - -/*- - *----------------------------------------------------------------------- - * Dir_HasWildcards -- - * see if the given name has any wildcard characters in it - * be careful not to expand unmatching brackets or braces. - * 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! - * - * Input: - * name name to check - * - * Results: - * returns TRUE if the word should be expanded, FALSE otherwise - * - * Side Effects: - * none - *----------------------------------------------------------------------- + * Return TRUE if the word should be expanded, FALSE otherwise. */ Boolean -Dir_HasWildcards(char *name) +Dir_HasWildcards(const char *name) { - char *cp; - int wild = 0, brace = 0, bracket = 0; + const char *p; + Boolean wild = FALSE; + int braces = 0, brackets = 0; - for (cp = name; *cp; cp++) { - switch(*cp) { - case '{': - brace++; - wild = 1; - break; - case '}': - brace--; - break; - case '[': - bracket++; - wild = 1; - break; - case ']': - bracket--; - break; - case '?': - case '*': - wild = 1; - break; - default: - break; + for (p = name; *p != '\0'; p++) { + switch (*p) { + case '{': + braces++; + wild = TRUE; + break; + case '}': + braces--; + break; + case '[': + brackets++; + wild = TRUE; + break; + case ']': + brackets--; + break; + case '?': + case '*': + wild = TRUE; + break; + default: + break; + } } - } - return wild && bracket == 0 && brace == 0; + return wild && brackets == 0 && braces == 0; } -/*- - *----------------------------------------------------------------------- - * DirMatchFiles -- - * Given a pattern and a Path structure, see if any files - * match the pattern and add their names to the 'expansions' list if - * any do. This is incomplete -- it doesn't take care of patterns like - * src / *src / *.c properly (just *.c on any of the directories), but it - * will do for now. +/* + * See if any files match the pattern and add their names to the 'expansions' + * list 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, + * 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 - * p Directory to search + * dir Directory to search * expansion Place to store the results - * - * Results: - * Always returns 0 - * - * Side Effects: - * File names are added to the expansions lst. The directory will be - * fully hashed when this is done. - *----------------------------------------------------------------------- */ -static int -DirMatchFiles(const char *pattern, Path *p, Lst expansions) +static void +DirMatchFiles(const char *pattern, CachedDir *dir, StringList *expansions) { - Hash_Search search; /* Index into the directory's table */ - Hash_Entry *entry; /* Current entry in the table */ - Boolean isDot; /* TRUE if the directory being searched is . */ + const char *dirName = dir->name; + Boolean isDot = dirName[0] == '.' && dirName[1] == '\0'; + HashIter hi; - isDot = (*p->name == '.' && p->name[1] == '\0'); - - for (entry = Hash_EnumFirst(&p->files, &search); - entry != NULL; - entry = Hash_EnumNext(&search)) - { /* - * See if the file matches the given pattern. Note we follow the UNIX - * convention that dot files will only be found if the pattern - * begins with a dot (note also that as a side effect of the hashing - * scheme, .* won't match . or .. since they aren't hashed). + * XXX: Iterating over all hash entries is inefficient. If the + * pattern is a plain string without any wildcards, a direct lookup + * is faster. */ - if (Str_Match(entry->name, pattern) && - ((entry->name[0] != '.') || - (pattern[0] == '.'))) - { - (void)Lst_AtEnd(expansions, - (isDot ? bmake_strdup(entry->name) : - str_concat(p->name, entry->name, - STR_ADDSLASH))); + + HashIter_InitSet(&hi, &dir->files); + while (HashIter_Next(&hi) != NULL) { + const char *base = hi.entry->key; + + if (!Str_Match(base, pattern)) + continue; + + /* + * Follow the UNIX convention that dot files are only found + * if the pattern begins with a dot. The pattern '.*' does + * not match '.' or '..' since these are not included in the + * directory cache. + * + * This means that the pattern '[a-z.]*' does not find + * '.file', which is consistent with NetBSD sh, NetBSD ksh, + * bash, dash, csh and probably many other shells as well. + */ + if (base[0] == '.' && pattern[0] != '.') + continue; + + { + char *fullName = isDot + ? bmake_strdup(base) + : str_concat3(dirName, "/", base); + Lst_Append(expansions, fullName); + } } - } - return (0); } -/*- - *----------------------------------------------------------------------- - * DirExpandCurly -- - * Expand curly braces like the C shell. Does this recursively. - * Note the special case: if after the piece of the curly brace is - * done there are no wildcard characters in the result, the result is - * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE. +/* + * Find the next closing brace in the string, taking nested braces into + * account. + */ +static const char * +closing_brace(const char *p) +{ + int nest = 0; + while (*p != '\0') { + if (*p == '}' && nest == 0) + break; + if (*p == '{') + nest++; + if (*p == '}') + nest--; + p++; + } + return p; +} + +/* + * Find the next closing brace or comma in the string, taking nested braces + * into account. + */ +static const char * +separator_comma(const char *p) +{ + int nest = 0; + while (*p != '\0') { + if ((*p == '}' || *p == ',') && nest == 0) + break; + if (*p == '{') + nest++; + if (*p == '}') + nest--; + p++; + } + return p; +} + +static Boolean +contains_wildcard(const char *p) +{ + for (; *p != '\0'; p++) { + switch (*p) { + case '*': + case '?': + case '{': + case '[': + return TRUE; + } + } + return FALSE; +} + +static char * +concat3(const char *a, size_t a_len, const char *b, size_t b_len, + const char *c, size_t c_len) +{ + size_t s_len = a_len + b_len + c_len; + char *s = bmake_malloc(s_len + 1); + memcpy(s, a, a_len); + memcpy(s + a_len, b, b_len); + memcpy(s + a_len + b_len, c, c_len); + s[s_len] = '\0'; + return s; +} + +/* + * Expand curly braces like the C shell. Brace expansion by itself is purely + * textual, the expansions are not looked up in the file system. But if an + * expanded word contains wildcard characters, it is expanded further, + * matching only the actually existing files. + * + * Example: "{a{b,c}}" expands to "ab" and "ac". + * Example: "{a}" expands to "a". + * Example: "{a,*.c}" expands to "a" and all "*.c" files that exist. * * Input: * word Entire word to expand * brace First curly brace in it * path Search path to use * expansions Place to store the expansions - * - * Results: - * None. - * - * Side Effects: - * The given list is filled with the expansions... - * - *----------------------------------------------------------------------- */ static void -DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions) +DirExpandCurly(const char *word, const char *brace, SearchPath *path, + StringList *expansions) { - const char *end; /* Character after the closing brace */ - const char *cp; /* Current position in brace clause */ - const char *start; /* Start of current piece of brace clause */ - int bracelevel; /* Number of braces we've seen. If we see a - * right brace when this is 0, we've hit the - * end of the clause. */ - char *file; /* Current expansion */ - int otherLen; /* The length of the other pieces of the - * expansion (chars before and after the - * clause in 'word') */ - char *cp2; /* Pointer for checking for wildcards in - * expansion before calling Dir_Expand */ + const char *prefix, *middle, *piece, *middle_end, *suffix; + size_t prefix_len, suffix_len; - start = brace+1; + /* Split the word into prefix '{' middle '}' suffix. */ - /* - * Find the end of the brace clause first, being wary of nested brace - * clauses. - */ - for (end = start, bracelevel = 0; *end != '\0'; end++) { - if (*end == '{') { - bracelevel++; - } else if ((*end == '}') && (bracelevel-- == 0)) { - break; + middle = brace + 1; + middle_end = closing_brace(middle); + if (*middle_end == '\0') { + Error("Unterminated {} clause \"%s\"", middle); + return; } - } - if (*end == '\0') { - Error("Unterminated {} clause \"%s\"", start); - return; - } else { - end++; - } - otherLen = brace - word + strlen(end); - for (cp = start; cp < end; cp++) { - /* - * Find the end of this piece of the clause. - */ - bracelevel = 0; - while (*cp != ',') { - if (*cp == '{') { - bracelevel++; - } else if ((*cp == '}') && (bracelevel-- <= 0)) { - break; - } - cp++; - } - /* - * Allocate room for the combination and install the three pieces. - */ - file = bmake_malloc(otherLen + cp - start + 1); - if (brace != word) { - strncpy(file, word, brace-word); - } - if (cp != start) { - strncpy(&file[brace-word], start, cp-start); - } - strcpy(&file[(brace-word)+(cp-start)], end); + prefix = word; + prefix_len = (size_t)(brace - prefix); + suffix = middle_end + 1; + suffix_len = strlen(suffix); - /* - * See if the result has any wildcards in it. If we find one, call - * Dir_Expand right away, telling it to place the result on our list - * of expansions. - */ - for (cp2 = file; *cp2 != '\0'; cp2++) { - switch(*cp2) { - case '*': - case '?': - case '{': - case '[': - Dir_Expand(file, path, expansions); - goto next; - } + /* Split the middle into pieces, separated by commas. */ + + piece = middle; + while (piece < middle_end + 1) { + const char *piece_end = separator_comma(piece); + size_t piece_len = (size_t)(piece_end - piece); + + char *file = concat3(prefix, prefix_len, piece, piece_len, + suffix, suffix_len); + + if (contains_wildcard(file)) { + SearchPath_Expand(path, file, expansions); + free(file); + } else { + Lst_Append(expansions, file); + } + + /* skip over the comma or closing brace */ + piece = piece_end + 1; } - if (*cp2 == '\0') { - /* - * Hit the end w/o finding any wildcards, so stick the expansion - * on the end of the list. - */ - (void)Lst_AtEnd(expansions, file); - } else { - next: - free(file); - } - start = cp+1; - } } -/*- - *----------------------------------------------------------------------- - * DirExpandInt -- - * Internal expand routine. Passes through the directories in the - * path one by one, calling DirMatchFiles for each. NOTE: This still - * doesn't handle patterns in directories... - * - * Input: - * word Word to expand - * path Path on which to look - * expansions Place to store the result - * - * Results: - * None. - * - * Side Effects: - * Things are added to the expansions list. - * - *----------------------------------------------------------------------- +/* Expand the word in each of the directories from the path. */ +static void +DirExpandPath(const char *word, SearchPath *path, StringList *expansions) +{ + SearchPathNode *ln; + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + DirMatchFiles(word, dir, expansions); + } +} + +static void +PrintExpansions(StringList *expansions) +{ + const char *sep = ""; + StringListNode *ln; + for (ln = expansions->first; ln != NULL; ln = ln->next) { + const char *word = ln->datum; + debug_printf("%s%s", sep, word); + sep = " "; + } + debug_printf("\n"); +} + +/* + * The wildcard isn't in the first component. + * Find all the components up to the one with the wildcard. */ static void -DirExpandInt(const char *word, Lst path, Lst expansions) +SearchPath_ExpandMiddle(SearchPath *path, const char *pattern, + const char *wildcardComponent, StringList *expansions) { - LstNode ln; /* Current node */ - Path *p; /* Directory in the node */ + char *prefix, *dirpath, *end; + SearchPath *partPath; - if (Lst_Open(path) == SUCCESS) { - while ((ln = Lst_Next(path)) != NULL) { - p = (Path *)Lst_Datum(ln); - DirMatchFiles(word, p, expansions); - } - Lst_Close(path); - } + 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 + * that the pattern is expanded in the whole path. + */ + dirpath = Dir_FindFile(prefix, path); + free(prefix); + + /* + * dirpath is null if can't find the leading component + * + * XXX: Dir_FindFile won't find internal components. i.e. if the + * 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. + */ + if (dirpath == NULL) + return; + + end = &dirpath[strlen(dirpath) - 1]; + /* XXX: What about multiple trailing slashes? */ + if (*end == '/') + *end = '\0'; + + partPath = SearchPath_New(); + (void)SearchPath_Add(partPath, dirpath); + DirExpandPath(wildcardComponent + 1, partPath, expansions); + SearchPath_Free(partPath); } -/*- - *----------------------------------------------------------------------- - * DirPrintWord -- - * Print a word in the list of expansions. Callback for Dir_Expand - * when DEBUG(DIR), via Lst_ForEach. - * - * Results: - * === 0 - * - * Side Effects: - * The passed word is printed, followed by a space. - * - *----------------------------------------------------------------------- - */ -static int -DirPrintWord(void *word, void *dummy) -{ - fprintf(debug_file, "%s ", (char *)word); - - return(dummy ? 0 : 0); -} - -/*- - *----------------------------------------------------------------------- - * Dir_Expand -- - * Expand the given word into a list of words by globbing it looking - * in the directories on the given search path. +/* + * Expand the given pattern into a list of existing filenames by globbing it, + * looking in each directory from the search path. * * Input: - * word the word to expand - * path the list of directories in which to find the - * resulting files + * path the directories in which to find the files + * pattern the pattern to expand * expansions the list on which to place the results - * - * Results: - * A list of words consisting of the files which exist along the search - * path matching the given pattern. - * - * Side Effects: - * Directories may be opened. Who knows? - *----------------------------------------------------------------------- */ void -Dir_Expand(const char *word, Lst path, Lst expansions) +SearchPath_Expand(SearchPath *path, const char *pattern, StringList *expansions) { - const char *cp; + const char *brace, *slash, *wildcard, *wildcardComponent; - if (DEBUG(DIR)) { - fprintf(debug_file, "Expanding \"%s\"... ", word); - } + assert(path != NULL); + assert(expansions != NULL); - cp = strchr(word, '{'); - if (cp) { - DirExpandCurly(word, cp, path, expansions); - } else { - cp = strchr(word, '/'); - if (cp) { - /* - * The thing has a directory component -- find the first wildcard - * in the string. - */ - for (cp = word; *cp; cp++) { - if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') { - break; - } - } - if (*cp == '{') { - /* - * This one will be fun. - */ - DirExpandCurly(word, cp, path, expansions); - return; - } else if (*cp != '\0') { - /* - * Back up to the start of the component - */ - char *dirpath; + DEBUG1(DIR, "Expanding \"%s\"... ", pattern); - while (cp > word && *cp != '/') { - cp--; - } - if (cp != word) { - char sc; - /* - * If the glob isn't in the first component, try and find - * all the components up to the one with a wildcard. - */ - sc = cp[1]; - ((char *)UNCONST(cp))[1] = '\0'; - dirpath = Dir_FindFile(word, path); - ((char *)UNCONST(cp))[1] = sc; - /* - * dirpath is null if can't find the leading component - * XXX: Dir_FindFile won't find internal components. - * i.e. if the path contains ../Etc/Object and we're - * looking for Etc, it won't be found. Ah well. - * Probably not important. - */ - if (dirpath != NULL) { - char *dp = &dirpath[strlen(dirpath) - 1]; - if (*dp == '/') - *dp = '\0'; - path = Lst_Init(FALSE); - (void)Dir_AddDir(path, dirpath); - DirExpandInt(cp+1, path, expansions); - Lst_Destroy(path, NULL); - } - } else { - /* - * Start the search from the local directory - */ - DirExpandInt(word, path, expansions); - } - } else { + brace = strchr(pattern, '{'); + if (brace != NULL) { + DirExpandCurly(pattern, brace, path, 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; + } + + /* At this point, the pattern has a directory component. */ + + /* Find the first wildcard in the pattern. */ + for (wildcard = pattern; *wildcard != '\0'; wildcard++) + if (*wildcard == '?' || *wildcard == '[' || *wildcard == '*') + break; + + if (*wildcard == '\0') { /* - * Return the file -- this should never happen. + * No directory component and no wildcard at all -- this + * should never happen as in such a simple case there is no + * need to expand anything. */ - DirExpandInt(word, path, expansions); - } + DirExpandPath(pattern, path, expansions); + goto done; + } + + /* Back up to the start of the component containing the wildcard. */ + /* XXX: This handles '///' and '/' differently. */ + wildcardComponent = wildcard; + while (wildcardComponent > pattern && *wildcardComponent != '/') + wildcardComponent--; + + if (wildcardComponent == pattern) { + /* The first component contains the wildcard. */ + /* Start the search from the local directory */ + DirExpandPath(pattern, path, expansions); } else { - /* - * First the files in dot - */ - DirMatchFiles(word, dot, expansions); - - /* - * Then the files in every other directory on the path. - */ - DirExpandInt(word, path, expansions); + SearchPath_ExpandMiddle(path, pattern, wildcardComponent, + expansions); } - } - if (DEBUG(DIR)) { - Lst_ForEach(expansions, DirPrintWord, NULL); - fprintf(debug_file, "\n"); - } + +done: + if (DEBUG(DIR)) + PrintExpansions(expansions); } -/*- - *----------------------------------------------------------------------- - * DirLookup -- - * Find if the file with the given name exists in the given path. - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * None. - *----------------------------------------------------------------------- +/* + * Find if the file with the given name exists in the given path. + * Return the freshly allocated path to the file, or NULL. */ static char * -DirLookup(Path *p, const char *name MAKE_ATTR_UNUSED, const char *cp, - Boolean hasSlash MAKE_ATTR_UNUSED) +DirLookup(CachedDir *dir, const char *base) { - char *file; /* the current filename to check */ + char *file; /* the current filename to check */ - if (DEBUG(DIR)) { - fprintf(debug_file, " %s ...\n", p->name); - } + DEBUG1(DIR, " %s ...\n", dir->name); - if (Hash_FindEntry(&p->files, cp) == NULL) + if (!HashSet_Contains(&dir->files, base)) + return NULL; + + file = str_concat3(dir->name, "/", base); + DEBUG1(DIR, " returning %s\n", file); + dir->hits++; + hits++; + return file; +} + + +/* + * Find if the file with the given name exists in the given directory. + * Return the freshly allocated path to the file, or NULL. + */ +static char * +DirLookupSubdir(CachedDir *dir, const char *name) +{ + struct cached_stat cst; + char *file = dir == dot ? bmake_strdup(name) + : str_concat3(dir->name, "/", name); + + DEBUG1(DIR, "checking %s ...\n", file); + + if (cached_stat(file, &cst) == 0) { + nearmisses++; + return file; + } + free(file); return NULL; - - file = str_concat(p->name, cp, STR_ADDSLASH); - if (DEBUG(DIR)) { - fprintf(debug_file, " returning %s\n", file); - } - p->hits += 1; - hits += 1; - return file; } - -/*- - *----------------------------------------------------------------------- - * DirLookupSubdir -- - * Find if the file with the given name exists in the given path. - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * If the file is found, it is added in the modification times hash - * table. - *----------------------------------------------------------------------- +/* + * 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. */ static char * -DirLookupSubdir(Path *p, const char *name) +DirLookupAbs(CachedDir *dir, const char *name, const char *cp) { - struct stat stb; /* Buffer for stat, if necessary */ - Hash_Entry *entry; /* Entry for mtimes table */ - char *file; /* the current filename to check */ + const char *dnp; /* pointer into dir->name */ + const char *np; /* pointer into name */ - if (p != dot) { - file = str_concat(p->name, name, STR_ADDSLASH); - } else { - /* - * Checking in dot -- DON'T put a leading ./ on the thing. - */ - file = bmake_strdup(name); - } - - if (DEBUG(DIR)) { - fprintf(debug_file, "checking %s ...\n", file); - } - - if (stat(file, &stb) == 0) { - if (stb.st_mtime == 0) - stb.st_mtime = 1; - /* - * Save the modification time so if it's needed, we don't have - * to fetch it again. - */ - if (DEBUG(DIR)) { - fprintf(debug_file, " Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), - file); - } - entry = Hash_CreateEntry(&mtimes, file, NULL); - Hash_SetTimeValue(entry, stb.st_mtime); - nearmisses += 1; - return (file); - } - free(file); - return NULL; -} - -/*- - *----------------------------------------------------------------------- - * DirLookupAbs -- - * Find if the file with the given name exists in the given path. - * - * Results: - * The path to the file, the empty string or NULL. If the file is - * the empty string, the search should be terminated. - * This path is guaranteed to be in a different part of memory - * than name and so may be safely free'd. - * - * Side Effects: - * None. - *----------------------------------------------------------------------- - */ -static char * -DirLookupAbs(Path *p, const char *name, const char *cp) -{ - char *p1; /* pointer into p->name */ - const char *p2; /* pointer into name */ - - if (DEBUG(DIR)) { - fprintf(debug_file, " %s ...\n", p->name); - } + DEBUG1(DIR, " %s ...\n", dir->name); /* * If the file has a leading path component and that component @@ -968,838 +1022,709 @@ DirLookupAbs(Path *p, const char *name, const char *cp) * directory, we can attempt another cache lookup. And if we don't * have a hit, we can safely assume the file does not exist at all. */ - for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) { + for (dnp = dir->name, np = name; + *dnp != '\0' && *dnp == *np; dnp++, np++) continue; - } - if (*p1 != '\0' || p2 != cp - 1) { + if (*dnp != '\0' || np != cp - 1) return NULL; + + if (!HashSet_Contains(&dir->files, cp)) { + DEBUG0(DIR, " must be here but isn't -- returning\n"); + return bmake_strdup(""); /* to terminate the search */ } - if (Hash_FindEntry(&p->files, cp) == NULL) { - if (DEBUG(DIR)) { - fprintf(debug_file, " must be here but isn't -- returning\n"); - } - /* Return empty string: terminates search */ - return bmake_strdup(""); - } - - p->hits += 1; - hits += 1; - if (DEBUG(DIR)) { - fprintf(debug_file, " returning %s\n", name); - } - return (bmake_strdup(name)); + dir->hits++; + hits++; + DEBUG1(DIR, " returning %s\n", name); + return bmake_strdup(name); } -/*- - *----------------------------------------------------------------------- - * DirFindDot -- - * Find the file given on "." or curdir - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * Hit counts change - *----------------------------------------------------------------------- +/* + * Find the file given on "." or curdir. + * Return the freshly allocated path to the file, or NULL. */ static char * -DirFindDot(Boolean hasSlash MAKE_ATTR_UNUSED, const char *name, const char *cp) +DirFindDot(const char *name, const char *base) { - if (Hash_FindEntry(&dot->files, cp) != NULL) { - if (DEBUG(DIR)) { - fprintf(debug_file, " in '.'\n"); - } - hits += 1; - dot->hits += 1; - return (bmake_strdup(name)); + if (HashSet_Contains(&dot->files, base)) { + DEBUG0(DIR, " in '.'\n"); + hits++; + dot->hits++; + return bmake_strdup(name); } - if (cur && - Hash_FindEntry(&cur->files, cp) != NULL) { - if (DEBUG(DIR)) { - fprintf(debug_file, " in ${.CURDIR} = %s\n", cur->name); - } - hits += 1; - cur->hits += 1; - return str_concat(cur->name, cp, STR_ADDSLASH); + + if (cur != NULL && HashSet_Contains(&cur->files, base)) { + DEBUG1(DIR, " in ${.CURDIR} = %s\n", cur->name); + hits++; + cur->hits++; + return str_concat3(cur->name, "/", base); } return NULL; } -/*- - *----------------------------------------------------------------------- - * Dir_FindFile -- - * Find the file with the given name along the given search path. - * - * Input: - * name the file to find - * path the Lst of directories to search - * - * Results: - * The path to the file or NULL. This path is guaranteed to be in a - * different part of memory than name and so may be safely free'd. - * - * Side Effects: - * If the file is found in a directory which 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. - *----------------------------------------------------------------------- - */ -char * -Dir_FindFile(const char *name, Lst path) +static Boolean +FindFileRelative(SearchPath *path, Boolean seenDotLast, + const char *name, char **out_file) { - LstNode ln; /* a list element */ - char *file; /* the current filename to check */ - Path *p; /* current path member */ - const char *cp; /* Terminal name of file */ - Boolean hasLastDot = FALSE; /* true we should search dot last */ - Boolean hasSlash; /* true if 'name' contains a / */ - struct stat stb; /* Buffer for stat, if necessary */ - Hash_Entry *entry; /* Entry for mtimes table */ - const char *trailing_dot = "."; + SearchPathNode *ln; + Boolean checkedDot = FALSE; + char *file; - /* - * Find the final component of the name and note whether it has a - * slash in it (the name, I mean) - */ - cp = strrchr(name, '/'); - if (cp) { - hasSlash = TRUE; - cp += 1; - } else { - hasSlash = FALSE; - cp = name; - } + DEBUG0(DIR, " Trying subdirectories...\n"); - if (DEBUG(DIR)) { - fprintf(debug_file, "Searching for %s ...", name); - } - - if (Lst_Open(path) == FAILURE) { - if (DEBUG(DIR)) { - fprintf(debug_file, "couldn't open path, file not found\n"); - } - misses += 1; - return NULL; - } - - if ((ln = Lst_First(path)) != NULL) { - p = (Path *)Lst_Datum(ln); - if (p == dotLast) { - hasLastDot = TRUE; - if (DEBUG(DIR)) - fprintf(debug_file, "[dot last]..."); - } - } - if (DEBUG(DIR)) { - fprintf(debug_file, "\n"); - } - - /* - * If there's no leading directory components or if the leading - * directory component is exactly `./', consult the cached contents - * of each of the directories on the search path. - */ - if (!hasSlash || (cp - name == 2 && *name == '.')) { - /* - * We look through all the directories on the path seeking one which - * contains the final component of the given name. If such a beast - * 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... - * - * 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. - * This is so there are no conflicts between what the user - * specifies (fish.c) and what pmake finds (./fish.c). - */ - if (!hasLastDot && - (file = DirFindDot(hasSlash, name, cp)) != NULL) { - Lst_Close(path); - return file; - } - - while ((ln = Lst_Next(path)) != NULL) { - p = (Path *)Lst_Datum(ln); - if (p == dotLast) - continue; - if ((file = DirLookup(p, name, cp, hasSlash)) != NULL) { - Lst_Close(path); - return file; - } - } - - if (hasLastDot && - (file = DirFindDot(hasSlash, name, cp)) != NULL) { - Lst_Close(path); - return file; - } - } - Lst_Close(path); - - /* - * 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 beast, 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 (!hasSlash) { - if (DEBUG(DIR)) { - fprintf(debug_file, " failed.\n"); - } - misses += 1; - return NULL; - } - - if (*cp == '\0') { - /* we were given a trailing "/" */ - cp = trailing_dot; - } - - if (name[0] != '/') { - Boolean checkedDot = FALSE; - - if (DEBUG(DIR)) { - fprintf(debug_file, " Trying subdirectories...\n"); - } - - if (!hasLastDot) { - if (dot) { + if (!seenDotLast) { + if (dot != NULL) { checkedDot = TRUE; if ((file = DirLookupSubdir(dot, name)) != NULL) - return file; + goto found; } - if (cur && (file = DirLookupSubdir(cur, name)) != NULL) - return file; + if (cur != NULL && + (file = DirLookupSubdir(cur, name)) != NULL) + goto found; } - (void)Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { - p = (Path *)Lst_Datum(ln); - if (p == dotLast) - continue; - if (p == dot) { - if (checkedDot) - continue; - checkedDot = TRUE; - } - if ((file = DirLookupSubdir(p, name)) != NULL) { - Lst_Close(path); - return file; - } + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) + continue; + if (dir == dot) { + if (checkedDot) + continue; + checkedDot = TRUE; + } + if ((file = DirLookupSubdir(dir, name)) != NULL) + goto found; } - Lst_Close(path); - if (hasLastDot) { - if (dot && !checkedDot) { + if (seenDotLast) { + if (dot != NULL && !checkedDot) { checkedDot = TRUE; if ((file = DirLookupSubdir(dot, name)) != NULL) - return file; + goto found; } - if (cur && (file = DirLookupSubdir(cur, name)) != NULL) - return file; + if (cur != NULL && + (file = DirLookupSubdir(cur, name)) != NULL) + goto found; } if (checkedDot) { - /* - * Already checked by the given name, since . was in the path, - * so no point in proceeding... - */ - if (DEBUG(DIR)) { - fprintf(debug_file, " Checked . already, returning NULL\n"); - } - return NULL; + /* + * Already checked by the given name, since . was in + * the path, so no point in proceeding. + */ + DEBUG0(DIR, " Checked . already, returning NULL\n"); + file = NULL; + goto found; } - } else { /* name[0] == '/' */ + return FALSE; + +found: + *out_file = file; + return TRUE; +} + +static Boolean +FindFileAbsolute(SearchPath *path, Boolean const seenDotLast, + const char *const name, const char *const base, + char **out_file) +{ + char *file; + SearchPathNode *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. + * 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. */ - if (DEBUG(DIR)) { - fprintf(debug_file, " Trying exact path matches...\n"); + DEBUG0(DIR, " Trying exact path matches...\n"); + + if (!seenDotLast && cur != NULL && + ((file = DirLookupAbs(cur, name, base)) != NULL)) + goto found; + + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) + continue; + if ((file = DirLookupAbs(dir, name, base)) != NULL) + goto found; } - if (!hasLastDot && cur && (file = DirLookupAbs(cur, name, cp)) != NULL) - return *file?file:NULL; + if (seenDotLast && cur != NULL && + ((file = DirLookupAbs(cur, name, base)) != NULL)) + goto found; - (void)Lst_Open(path); - while ((ln = Lst_Next(path)) != NULL) { - p = (Path *)Lst_Datum(ln); - if (p == dotLast) - continue; - if ((file = DirLookupAbs(p, name, cp)) != NULL) { - Lst_Close(path); - return *file?file:NULL; - } + return FALSE; + +found: + if (file[0] == '\0') { + free(file); + file = NULL; } - Lst_Close(path); + *out_file = file; + return TRUE; +} - if (hasLastDot && cur && (file = DirLookupAbs(cur, name, cp)) != NULL) - return *file?file:NULL; - } +/* + * 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 + * + * Results: + * The freshly allocated path to the file, or NULL. + */ +char * +Dir_FindFile(const char *name, SearchPath *path) +{ + char *file; /* the current filename to check */ + Boolean seenDotLast = FALSE; /* true if we should search dot last */ + struct cached_stat cst; /* Buffer for stat, if necessary */ + const char *trailing_dot = "."; + const char *base = str_basename(name); - /* - * 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 - * of this amusing case: - * $(INSTALLDIR)/$(FILE): $(FILE) - * - * $(FILE) exists in $(INSTALLDIR) but not in the current one. - * When searching for $(FILE), we will find it in $(INSTALLDIR) - * b/c we added it here. This is not good... - */ -#ifdef notdef - if (cp == traling_dot) { - cp = strrchr(name, '/'); - cp += 1; - } - cp[-1] = '\0'; - (void)Dir_AddDir(path, name); - cp[-1] = '/'; + DEBUG1(DIR, "Searching for %s ...", name); - bigmisses += 1; - ln = Lst_Last(path); - if (ln == NULL) { + if (path == NULL) { + DEBUG0(DIR, "couldn't open path, file not found\n"); + misses++; + return NULL; + } + + if (path->dirs.first != NULL) { + CachedDir *dir = path->dirs.first->datum; + if (dir == dotLast) { + seenDotLast = TRUE; + DEBUG0(DIR, "[dot last]..."); + } + } + DEBUG0(DIR, "\n"); + + /* + * If there's no leading directory components or if the leading + * directory component is exactly `./', consult the cached contents + * of each of the directories on the search path. + */ + if (base == name || (base - name == 2 && *name == '.')) { + SearchPathNode *ln; + + /* + * We 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. + * + * 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. + * This is so there are no conflicts between what the user + * specifies (fish.c) and what pmake finds (./fish.c). + */ + if (!seenDotLast && (file = DirFindDot(name, base)) != NULL) + return file; + + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (dir == dotLast) + continue; + if ((file = DirLookup(dir, base)) != NULL) + return file; + } + + if (seenDotLast && (file = DirFindDot(name, base)) != NULL) + 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 (name[0] != '/') { + if (FindFileRelative(path, seenDotLast, name, &file)) + return file; + } else { + if (FindFileAbsolute(path, seenDotLast, name, base, &file)) + return file; + } + + /* + * 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 + * of this amusing case: + * $(INSTALLDIR)/$(FILE): $(FILE) + * + * $(FILE) exists in $(INSTALLDIR) but not in the current one. + * 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) { + return bmake_strdup(name); + } + + DEBUG0(DIR, " failed. Returning NULL\n"); return NULL; - } else { - p = (Path *)Lst_Datum(ln); - } - - if (Hash_FindEntry(&p->files, cp) != NULL) { - return (bmake_strdup(name)); - } else { - return NULL; - } -#else /* !notdef */ - if (DEBUG(DIR)) { - fprintf(debug_file, " Looking for \"%s\" ...\n", name); - } - - bigmisses += 1; - entry = Hash_FindEntry(&mtimes, name); - if (entry != NULL) { - if (DEBUG(DIR)) { - fprintf(debug_file, " got it (in mtime cache)\n"); - } - return(bmake_strdup(name)); - } else if (stat(name, &stb) == 0) { - if (stb.st_mtime == 0) - stb.st_mtime = 1; - entry = Hash_CreateEntry(&mtimes, name, NULL); - if (DEBUG(DIR)) { - fprintf(debug_file, " Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), - name); - } - Hash_SetTimeValue(entry, stb.st_mtime); - return (bmake_strdup(name)); - } else { - if (DEBUG(DIR)) { - fprintf(debug_file, " failed. Returning NULL\n"); - } - return NULL; - } -#endif /* notdef */ +#endif } -/*- - *----------------------------------------------------------------------- - * Dir_FindHereOrAbove -- - * search for a path starting at a given directory and then working - * our way up towards the root. +/* + * 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 path we are looking for - * result the result of a successful search is placed here - * rlen the length of the result buffer - * (typically MAXPATHLEN + 1) + * search_path the relative path we are looking for * * Results: - * 0 on failure, 1 on success [in which case the found path is put - * in the result buffer]. - * - * Side Effects: - *----------------------------------------------------------------------- + * The found path, or NULL. */ -int -Dir_FindHereOrAbove(char *here, char *search_path, char *result, int rlen) { - - struct stat st; - char dirbase[MAXPATHLEN + 1], *db_end; - char try[MAXPATHLEN + 1], *try_end; +char * +Dir_FindHereOrAbove(const char *here, const char *search_path) +{ + struct cached_stat cst; + char *dirbase, *dirbase_end; + char *try, *try_end; /* copy out our starting point */ - snprintf(dirbase, sizeof(dirbase), "%s", here); - db_end = dirbase + strlen(dirbase); + dirbase = bmake_strdup(here); + dirbase_end = dirbase + strlen(dirbase); /* loop until we determine a result */ - while (1) { + for (;;) { /* try and stat(2) it ... */ - snprintf(try, sizeof(try), "%s/%s", dirbase, search_path); - if (stat(try, &st) != -1) { + try = str_concat3(dirbase, "/", search_path); + if (cached_stat(try, &cst) != -1) { /* * success! if we found a file, chop off * the filename so we return a directory. */ - if ((st.st_mode & S_IFMT) != S_IFDIR) { + if ((cst.cst_mode & S_IFMT) != S_IFDIR) { try_end = try + strlen(try); while (try_end > try && *try_end != '/') try_end--; - if (try_end > try) - *try_end = 0; /* chop! */ + if (try_end > try) + *try_end = '\0'; /* chop! */ } - /* - * done! - */ - snprintf(result, rlen, "%s", try); - return(1); + free(dirbase); + return try; } + free(try); - /* + /* * nope, we didn't find it. if we used up dirbase we've * reached the root and failed. */ - if (db_end == dirbase) - break; /* failed! */ + if (dirbase_end == dirbase) + break; /* failed! */ /* * truncate dirbase from the end to move up a dir */ - while (db_end > dirbase && *db_end != '/') - db_end--; - *db_end = 0; /* chop! */ + while (dirbase_end > dirbase && *dirbase_end != '/') + dirbase_end--; + *dirbase_end = '\0'; /* chop! */ + } - } /* while (1) */ + free(dirbase); + return NULL; +} + +/* + * This is an implied source, and it may have moved, + * see if we can find it via the current .PATH + */ +static char * +ResolveMovedDepends(GNode *gn) +{ + char *fullName; + + const char *base = str_basename(gn->name); + if (base == gn->name) + return NULL; + + fullName = Dir_FindFile(base, Suff_FindPath(gn)); + if (fullName == NULL) + return NULL; /* - * we failed... + * Put the found file in gn->path so that we give that to the compiler. */ - return(0); + /* + * XXX: Better just reset gn->path to NULL; updating it is already done + * by Dir_UpdateMTime. + */ + gn->path = bmake_strdup(fullName); + if (!Job_RunTarget(".STALE", gn->fname)) + fprintf(stdout, /* XXX: Why stdout? */ + "%s: %s, %d: ignoring stale %s for %s, found %s\n", + progname, gn->fname, gn->lineno, + makeDependfile, gn->name, fullName); + + return fullName; } -/*- - *----------------------------------------------------------------------- - * Dir_MTime -- - * Find the modification time of the file described by gn along the - * search path dirSearchPath. - * - * Input: - * gn the file whose modification time is desired - * - * Results: - * The modification time or 0 if it doesn't exist - * - * Side Effects: - * The modification time is placed in the node's mtime slot. - * If the node didn't have a path entry before, and Dir_FindFile - * found one for it, the full name is placed in the path slot. - *----------------------------------------------------------------------- - */ -int -Dir_MTime(GNode *gn, Boolean recheck) +static char * +ResolveFullName(GNode *gn) { - char *fullName; /* the full pathname of name */ - struct stat stb; /* buffer for finding the mod time */ - Hash_Entry *entry; + char *fullName; - if (gn->type & OP_ARCHV) { - return Arch_MTime(gn); - } else if (gn->type & OP_PHONY) { - gn->mtime = 0; - return 0; - } else if (gn->path == NULL) { - if (gn->type & OP_NOPATH) - fullName = NULL; - else { - fullName = Dir_FindFile(gn->name, Suff_FindPath(gn)); - if (fullName == NULL && gn->flags & FROM_DEPEND && - !Lst_IsEmpty(gn->iParents)) { - char *cp; - - cp = strrchr(gn->name, '/'); - if (cp) { - /* - * This is an implied source, and it may have moved, - * see if we can find it via the current .PATH - */ - cp++; - - fullName = Dir_FindFile(cp, Suff_FindPath(gn)); - if (fullName) { - /* - * Put the found file in gn->path - * so that we give that to the compiler. - */ - gn->path = bmake_strdup(fullName); - if (!Job_RunTarget(".STALE", gn->fname)) - fprintf(stdout, - "%s: %s, %d: ignoring stale %s for %s, " - "found %s\n", progname, gn->fname, gn->lineno, - makeDependfile, gn->name, fullName); - } - } - } - if (DEBUG(DIR)) - fprintf(debug_file, "Found '%s' as '%s'\n", - gn->name, fullName ? fullName : "(not found)" ); - } - } else { fullName = gn->path; - } + if (fullName == NULL && !(gn->type & OP_NOPATH)) { - if (fullName == NULL) { - fullName = bmake_strdup(gn->name); - } + fullName = Dir_FindFile(gn->name, Suff_FindPath(gn)); - if (!recheck) - entry = Hash_FindEntry(&mtimes, fullName); - else - entry = NULL; - if (entry != NULL) { - if (DEBUG(DIR)) { - fprintf(debug_file, "Using cached time %s for %s\n", - Targ_FmtTime(Hash_GetTimeValue(entry)), fullName); - } - stb.st_mtime = Hash_GetTimeValue(entry); - } else if (stat(fullName, &stb) < 0) { - if (gn->type & OP_MEMBER) { - if (fullName != gn->path) - free(fullName); - return Arch_MemMTime(gn); - } else { - stb.st_mtime = 0; - } - } else { - if (stb.st_mtime == 0) { - /* - * 0 handled specially by the code, if the time is really 0, - * return something else instead - */ - stb.st_mtime = 1; - } - entry = Hash_CreateEntry(&mtimes, fullName, NULL); - Hash_SetTimeValue(entry, stb.st_mtime); - } - - if (fullName && gn->path == NULL) { - gn->path = fullName; - } + if (fullName == NULL && gn->flags & FROM_DEPEND && + !Lst_IsEmpty(&gn->implicitParents)) + fullName = ResolveMovedDepends(gn); - gn->mtime = stb.st_mtime; - return (gn->mtime); + DEBUG2(DIR, "Found '%s' as '%s'\n", + gn->name, fullName != NULL ? fullName : "(not found)"); + } + + if (fullName == NULL) + fullName = bmake_strdup(gn->name); + + /* XXX: Is every piece of memory freed as it should? */ + + return fullName; } -/*- - *----------------------------------------------------------------------- - * Dir_AddDir -- - * Add the given name to the end of the given path. The order of - * the arguments is backwards so ParseDoDependency can do a - * Lst_ForEach of its list of paths... +/* + * Search gn along dirSearchPath and store its modification time in gn->mtime. + * If no file is found, store 0 instead. * - * Input: - * path the path to which the directory should be - * added - * name the name of the directory to add - * - * Results: - * none - * - * Side Effects: - * A structure is added to the list and the directory is - * read and hashed. - *----------------------------------------------------------------------- + * The found file is stored in gn->path, unless the node already had a path. */ -Path * -Dir_AddDir(Lst path, const char *name) +void +Dir_UpdateMTime(GNode *gn, Boolean recheck) { - LstNode ln = NULL; /* node in case Path structure is found */ - Path *p = NULL; /* pointer to new Path structure */ - DIR *d; /* for reading directory */ - struct dirent *dp; /* entry in directory */ + char *fullName; + struct cached_stat cst; - if (strcmp(name, ".DOTLAST") == 0) { - ln = Lst_Find(path, name, DirFindName); - if (ln != NULL) - return (Path *)Lst_Datum(ln); - else { - dotLast->refCount += 1; - (void)Lst_AtFront(path, dotLast); - } - } - - if (path) - ln = Lst_Find(openDirectories, name, DirFindName); - if (ln != NULL) { - p = (Path *)Lst_Datum(ln); - if (path && Lst_Member(path, p) == NULL) { - p->refCount += 1; - (void)Lst_AtEnd(path, p); - } - } else { - if (DEBUG(DIR)) { - fprintf(debug_file, "Caching %s ...", name); + if (gn->type & OP_ARCHV) { + Arch_UpdateMTime(gn); + return; } - if ((d = opendir(name)) != NULL) { - p = bmake_malloc(sizeof(Path)); - p->name = bmake_strdup(name); - p->hits = 0; - p->refCount = 1; - Hash_InitTable(&p->files, -1); + if (gn->type & OP_PHONY) { + gn->mtime = 0; + return; + } + + fullName = ResolveFullName(gn); + + if (cached_stats(fullName, &cst, recheck ? CST_UPDATE : CST_NONE) < 0) { + if (gn->type & OP_MEMBER) { + if (fullName != gn->path) + free(fullName); + Arch_UpdateMemberMTime(gn); + return; + } + + cst.cst_mtime = 0; + } + + if (fullName != NULL && gn->path == NULL) + gn->path = fullName; + /* XXX: else free(fullName)? */ + + gn->mtime = cst.cst_mtime; +} + +/* + * Read the directory and add it to the cache in openDirs. + * If a path is given, add the directory to that path as well. + */ +static CachedDir * +CacheNewDir(const char *name, SearchPath *path) +{ + CachedDir *dir = NULL; + DIR *d; + struct dirent *dp; + + if ((d = opendir(name)) == NULL) { + DEBUG1(DIR, "Caching %s ... not found\n", name); + return dir; + } + + DEBUG1(DIR, "Caching %s ...\n", name); + + dir = CachedDir_New(name); + + while ((dp = readdir(d)) != NULL) { - while ((dp = readdir(d)) != NULL) { #if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */ /* * The sun directory library doesn't check for a 0 inode * (0-inode slots just take up space), so we have to do * it ourselves. */ - if (dp->d_fileno == 0) { - continue; - } + if (dp->d_fileno == 0) + continue; #endif /* sun && d_ino */ - (void)Hash_CreateEntry(&p->files, dp->d_name, NULL); - } - (void)closedir(d); - (void)Lst_AtEnd(openDirectories, p); - if (path != NULL) - (void)Lst_AtEnd(path, p); + + (void)HashSet_Add(&dir->files, dp->d_name); } - if (DEBUG(DIR)) { - fprintf(debug_file, "done\n"); - } - } - return p; + (void)closedir(d); + + OpenDirs_Add(&openDirs, dir); + if (path != NULL) + Lst_Append(&path->dirs, CachedDir_Ref(dir)); + + DEBUG1(DIR, "Caching %s done\n", name); + return dir; } -/*- - *----------------------------------------------------------------------- - * Dir_CopyDir -- - * Callback function for duplicating a search path via Lst_Duplicate. - * Ups the reference count for the directory. +/* + * Read the list of filenames in the directory and store the result + * in openDirs. * - * Results: - * Returns the Path it was given. + * If a path is given, append the directory to that path. * - * Side Effects: - * The refCount of the path is incremented. - * - *----------------------------------------------------------------------- + * Input: + * path The path to which the directory should be + * 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: + * result If no path is given and the directory exists, the + * returned CachedDir has a reference count of 0. It + * must either be assigned to a variable using + * CachedDir_Assign or be appended to a SearchPath using + * Lst_Append and CachedDir_Ref. */ -void * -Dir_CopyDir(void *p) +CachedDir * +SearchPath_Add(SearchPath *path, const char *name) { - ((Path *)p)->refCount += 1; - return (p); + if (path != NULL && strcmp(name, ".DOTLAST") == 0) { + SearchPathNode *ln; + + /* XXX: Linear search gets slow with thousands of entries. */ + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *pathDir = ln->datum; + if (strcmp(pathDir->name, name) == 0) + return pathDir; + } + + Lst_Prepend(&path->dirs, CachedDir_Ref(dotLast)); + } + + if (path != NULL) { + /* XXX: Why is OpenDirs only checked if path != NULL? */ + CachedDir *dir = OpenDirs_Find(&openDirs, name); + if (dir != NULL) { + if (Lst_FindDatum(&path->dirs, dir) == NULL) + Lst_Append(&path->dirs, CachedDir_Ref(dir)); + return dir; + } + } + + return CacheNewDir(name, path); } -/*- - *----------------------------------------------------------------------- - * Dir_MakeFlags -- - * 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. +/* + * Return a copy of dirSearchPath, incrementing the reference counts for + * the contained directories. + */ +SearchPath * +Dir_CopyDirSearchPath(void) +{ + SearchPath *path = SearchPath_New(); + SearchPathNode *ln; + for (ln = dirSearchPath.dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + Lst_Append(&path->dirs, CachedDir_Ref(dir)); + } + return path; +} + +/* + * 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. - * - * Side Effects: - * None - *----------------------------------------------------------------------- + * 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. */ char * -Dir_MakeFlags(const char *flag, Lst path) +SearchPath_ToFlags(SearchPath *path, const char *flag) { - char *str; /* the string which will be returned */ - char *s1, *s2;/* the current directory preceded by 'flag' */ - LstNode ln; /* the node of the current directory */ - Path *p; /* the structure describing the current directory */ + Buffer buf; + SearchPathNode *ln; - str = bmake_strdup(""); + Buf_Init(&buf); - if (Lst_Open(path) == SUCCESS) { - while ((ln = Lst_Next(path)) != NULL) { - p = (Path *)Lst_Datum(ln); - s2 = str_concat(flag, p->name, 0); - str = str_concat(s1 = str, s2, STR_ADDSPACE); - free(s1); - free(s2); + if (path != NULL) { + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + Buf_AddStr(&buf, " "); + Buf_AddStr(&buf, flag); + Buf_AddStr(&buf, dir->name); + } } - Lst_Close(path); - } - return (str); + return Buf_DoneData(&buf); } -/*- - *----------------------------------------------------------------------- - * Dir_Destroy -- - * Nuke a directory descriptor, if possible. Callback procedure - * for the suffixes module when destroying a search path. - * - * Input: - * pp The directory descriptor to nuke - * - * Results: - * None. - * - * Side Effects: - * If no other path references this directory (refCount == 0), - * the Path and all its data are freed. - * - *----------------------------------------------------------------------- - */ +/* Free the search path and all directories mentioned in it. */ void -Dir_Destroy(void *pp) +SearchPath_Free(SearchPath *path) { - Path *p = (Path *)pp; - p->refCount -= 1; + SearchPathNode *ln; - if (p->refCount == 0) { - LstNode ln; - - ln = Lst_Member(openDirectories, p); - (void)Lst_Remove(openDirectories, ln); - - Hash_DeleteTable(&p->files); - free(p->name); - free(p); - } -} - -/*- - *----------------------------------------------------------------------- - * Dir_ClearPath -- - * Clear out all elements of the given search path. This is different - * from destroying the list, notice. - * - * Input: - * path Path to clear - * - * Results: - * None. - * - * Side Effects: - * The path is set to the empty list. - * - *----------------------------------------------------------------------- - */ -void -Dir_ClearPath(Lst path) -{ - Path *p; - while (!Lst_IsEmpty(path)) { - p = (Path *)Lst_DeQueue(path); - Dir_Destroy(p); - } -} - - -/*- - *----------------------------------------------------------------------- - * Dir_Concat -- - * Concatenate two paths, adding the second to the end of the first. - * Makes sure to avoid duplicates. - * - * Input: - * path1 Dest - * path2 Source - * - * Results: - * None - * - * Side Effects: - * Reference counts for added dirs are upped. - * - *----------------------------------------------------------------------- - */ -void -Dir_Concat(Lst path1, Lst path2) -{ - LstNode ln; - Path *p; - - for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) { - p = (Path *)Lst_Datum(ln); - if (Lst_Member(path1, p) == NULL) { - p->refCount += 1; - (void)Lst_AtEnd(path1, p); + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + CachedDir_Unref(dir); } - } + Lst_Done(&path->dirs); + free(path); +} + +/* + * Clear out all elements from the given search path. + * The path is set to the empty list but is not destroyed. + */ +void +SearchPath_Clear(SearchPath *path) +{ + while (!Lst_IsEmpty(&path->dirs)) { + CachedDir *dir = Lst_Dequeue(&path->dirs); + CachedDir_Unref(dir); + } +} + + +/* + * Concatenate two paths, adding the second to the end of the first, + * skipping duplicates. + */ +void +SearchPath_AddAll(SearchPath *dst, SearchPath *src) +{ + SearchPathNode *ln; + + for (ln = src->dirs.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + if (Lst_FindDatum(&dst->dirs, dir) == NULL) + Lst_Append(&dst->dirs, CachedDir_Ref(dir)); + } +} + +static int +percentage(int num, int den) +{ + return den != 0 ? num * 100 / den : 0; } /********** DEBUG INFO **********/ void Dir_PrintDirectories(void) { - LstNode ln; - Path *p; + CachedDirListNode *ln; - fprintf(debug_file, "#*** Directory Cache:\n"); - fprintf(debug_file, "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", - hits, misses, nearmisses, bigmisses, - (hits+bigmisses+nearmisses ? - hits * 100 / (hits + bigmisses + nearmisses) : 0)); - fprintf(debug_file, "# %-20s referenced\thits\n", "directory"); - if (Lst_Open(openDirectories) == SUCCESS) { - while ((ln = Lst_Next(openDirectories)) != NULL) { - p = (Path *)Lst_Datum(ln); - fprintf(debug_file, "# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits); + debug_printf("#*** Directory Cache:\n"); + debug_printf( + "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", + hits, misses, nearmisses, bigmisses, + percentage(hits, hits + bigmisses + nearmisses)); + debug_printf("# refs hits directory\n"); + + for (ln = openDirs.list.first; ln != NULL; ln = ln->next) { + CachedDir *dir = ln->datum; + debug_printf("# %4d %4d %s\n", + dir->refCount, dir->hits, dir->name); } - Lst_Close(openDirectories); - } -} - -static int -DirPrintDir(void *p, void *dummy) -{ - fprintf(debug_file, "%s ", ((Path *)p)->name); - return (dummy ? 0 : 0); } void -Dir_PrintPath(Lst path) +SearchPath_Print(const SearchPath *path) { - Lst_ForEach(path, DirPrintDir, NULL); + SearchPathNode *ln; + + for (ln = path->dirs.first; ln != NULL; ln = ln->next) { + const CachedDir *dir = ln->datum; + debug_printf("%s ", dir->name); + } } diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h index aa004504a..f4bf32ab5 100644 --- a/usr.bin/make/dir.h +++ b/usr.bin/make/dir.h @@ -1,4 +1,4 @@ -/* $NetBSD: dir.h,v 1.15 2012/04/07 18:29:08 christos Exp $ */ +/* $NetBSD: dir.h,v 1.43 2021/02/05 05:48:19 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -72,37 +72,36 @@ * from: @(#)dir.h 8.1 (Berkeley) 6/6/93 */ -/* dir.h -- - */ +#ifndef MAKE_DIR_H +#define MAKE_DIR_H -#ifndef _DIR -#define _DIR +typedef struct CachedDir CachedDir; -typedef struct Path { - char *name; /* Name of directory */ - int refCount; /* Number of paths with this directory */ - int hits; /* the number of times a file in this - * directory has been found */ - Hash_Table files; /* Hash table of files in directory */ -} Path; - -void Dir_Init(const char *); +void Dir_Init(void); void Dir_InitCur(const char *); void Dir_InitDot(void); void Dir_End(void); void Dir_SetPATH(void); -Boolean Dir_HasWildcards(char *); -void Dir_Expand(const char *, Lst, Lst); -char *Dir_FindFile(const char *, Lst); -int Dir_FindHereOrAbove(char *, char *, char *, int); -int Dir_MTime(GNode *, Boolean); -Path *Dir_AddDir(Lst, const char *); -char *Dir_MakeFlags(const char *, Lst); -void Dir_ClearPath(Lst); -void Dir_Concat(Lst, Lst); +Boolean Dir_HasWildcards(const char *); +void SearchPath_Expand(SearchPath *, const char *, StringList *); +char *Dir_FindFile(const char *, SearchPath *); +char *Dir_FindHereOrAbove(const char *, const char *); +void Dir_UpdateMTime(GNode *, Boolean); +CachedDir *SearchPath_Add(SearchPath *, const char *); +char *SearchPath_ToFlags(SearchPath *, const char *); +void SearchPath_Clear(SearchPath *); +void SearchPath_AddAll(SearchPath *, SearchPath *); void Dir_PrintDirectories(void); -void Dir_PrintPath(Lst); -void Dir_Destroy(void *); -void * Dir_CopyDir(void *); +void SearchPath_Print(const SearchPath *); +SearchPath *Dir_CopyDirSearchPath(void); -#endif /* _DIR */ +/* Stripped-down variant of struct stat. */ +struct cached_stat { + time_t cst_mtime; + mode_t cst_mode; +}; + +int cached_lstat(const char *, struct cached_stat *); +int cached_stat(const char *, struct cached_stat *); + +#endif /* MAKE_DIR_H */ diff --git a/usr.bin/make/enum.c b/usr.bin/make/enum.c new file mode 100644 index 000000000..a0ae8569b --- /dev/null +++ b/usr.bin/make/enum.c @@ -0,0 +1,80 @@ +/* $NetBSD: enum.c,v 1.15 2021/02/02 17:56:31 rillig Exp $ */ + +/* + Copyright (c) 2020 Roland Illig + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#include "make.h" + +MAKE_RCSID("$NetBSD: enum.c,v 1.15 2021/02/02 17:56:31 rillig Exp $"); + +/* + * Convert a bitset into a string representation, showing the names of the + * individual bits. + * + * Optionally, shortcuts for groups of bits can be added. To have an effect, + * they need to be listed before their individual bits. + */ +const char * +Enum_FlagsToString(char *buf, size_t buf_size, + int value, const EnumToStringSpec *spec) +{ + const char *buf_start = buf; + const char *sep = ""; + size_t sep_len = 0; + + for (; spec->es_value != 0; spec++) { + size_t name_len; + + if ((value & spec->es_value) != spec->es_value) + continue; + value &= ~spec->es_value; + + assert(buf_size >= sep_len + 1); + memcpy(buf, sep, sep_len); + buf += sep_len; + buf_size -= sep_len; + + name_len = strlen(spec->es_name); + assert(buf_size >= name_len + 1); + memcpy(buf, spec->es_name, name_len); + buf += name_len; + buf_size -= name_len; + + sep = ENUM__SEP; + sep_len = sizeof ENUM__SEP - 1; + } + + /* If this assertion fails, the listed enum values are incomplete. */ + assert(value == 0); + + if (buf == buf_start) + return "none"; + + assert(buf_size >= 1); + buf[0] = '\0'; + return buf_start; +} diff --git a/usr.bin/make/enum.h b/usr.bin/make/enum.h new file mode 100644 index 000000000..e10fcae04 --- /dev/null +++ b/usr.bin/make/enum.h @@ -0,0 +1,179 @@ +/* $NetBSD: enum.h,v 1.19 2021/03/15 16:00:05 rillig Exp $ */ + +/* + Copyright (c) 2020 Roland Illig + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAKE_ENUM_H +#define MAKE_ENUM_H + +/* Generate string representations for bitmasks and simple enums. */ + +#include + +typedef struct EnumToStringSpec { + int es_value; + const char *es_name; +} EnumToStringSpec; + + +const char *Enum_FlagsToString(char *, size_t, int, const EnumToStringSpec *); + + +/* For Enum_FlagsToString, the separator between flags. */ +#define ENUM__SEP "|" + +/* + * Generate the string that joins all possible flags, to see how large the + * buffer must be. + */ +#define ENUM__JOIN_STR_1(v1) \ + #v1 +#define ENUM__JOIN_STR_2(v1, v2) \ + ENUM__JOIN_STR_1(v1) ENUM__SEP \ + ENUM__JOIN_STR_1(v2) +#define ENUM__JOIN_STR_4(v1, v2, v3, v4) \ + ENUM__JOIN_STR_2(v1, v2) ENUM__SEP \ + ENUM__JOIN_STR_2(v3, v4) +#define ENUM__JOIN_STR_8(v1, v2, v3, v4, v5, v6, v7, v8) \ + ENUM__JOIN_STR_4(v1, v2, v3, v4) ENUM__SEP \ + ENUM__JOIN_STR_4(v5, v6, v7, v8) +#define ENUM__JOIN_STR_16(v01, v02, v03, v04, v05, v06, v07, v08, \ + v09, v10, v11, v12, v13, v14, v15, v16) \ + ENUM__JOIN_STR_8(v01, v02, v03, v04, v05, v06, v07, v08) ENUM__SEP \ + ENUM__JOIN_STR_8(v09, v10, v11, v12, v13, v14, v15, v16) + +#define ENUM__JOIN_2(part1, part2) \ + part1 ENUM__SEP part2 +#define ENUM__JOIN_3(part1, part2, part3) \ + part1 ENUM__SEP part2 ENUM__SEP part3 +#define ENUM__JOIN_4(part1, part2, part3, part4) \ + part1 ENUM__SEP part2 ENUM__SEP part3 ENUM__SEP part4 +#define ENUM__JOIN_5(part1, part2, part3, part4, part5) \ + part1 ENUM__SEP part2 ENUM__SEP part3 ENUM__SEP part4 ENUM__SEP part5 + +/* List the pairs of enum value and corresponding name. */ +#define ENUM__SPEC_1(v1) \ + { v1, #v1 } +#define ENUM__SPEC_2(v1, v2) \ + ENUM__SPEC_1(v1), \ + ENUM__SPEC_1(v2) +#define ENUM__SPEC_4(v1, v2, v3, v4) \ + ENUM__SPEC_2(v1, v2), \ + ENUM__SPEC_2(v3, v4) +#define ENUM__SPEC_8(v1, v2, v3, v4, v5, v6, v7, v8) \ + ENUM__SPEC_4(v1, v2, v3, v4), \ + ENUM__SPEC_4(v5, v6, v7, v8) +#define ENUM__SPEC_16(v01, v02, v03, v04, v05, v06, v07, v08, \ + v09, v10, v11, v12, v13, v14, v15, v16) \ + ENUM__SPEC_8(v01, v02, v03, v04, v05, v06, v07, v08), \ + ENUM__SPEC_8(v09, v10, v11, v12, v13, v14, v15, v16) + +#define ENUM__SPECS_2(part1, part2) \ + { part1, part2, { 0, "" } } +#define ENUM__SPECS_3(part1, part2, part3) \ + { part1, part2, part3, { 0, "" } } +#define ENUM__SPECS_4(part1, part2, part3, part4) \ + { part1, part2, part3, part4, { 0, "" } } +#define ENUM__SPECS_5(part1, part2, part3, part4, part5) \ + { part1, part2, part3, part4, part5, { 0, "" } } + + +/* Declare the necessary data structures for calling Enum_FlagsToString. */ +#define ENUM__FLAGS_RTTI(typnam, specs, joined) \ + static const EnumToStringSpec typnam ## _ ## ToStringSpecs[] = specs; \ + enum { typnam ## _ ## ToStringSize = sizeof (joined) }; \ + MAKE_INLINE const char *typnam ## _ToString(char *buf, typnam value) \ + { return Enum_FlagsToString(buf, typnam ## _ ## ToStringSize, \ + value, typnam ## _ ## ToStringSpecs); \ + } \ + extern void enum_flags_rtti_dummy(void) + +/* + * Declare the necessary data structures for calling Enum_FlagsToString + * for an enum with 3 flags. + */ +#define ENUM_FLAGS_RTTI_3(typnam, v1, v2, v3) \ + ENUM__FLAGS_RTTI(typnam, \ + ENUM__SPECS_2( \ + ENUM__SPEC_2(v1, v2), \ + ENUM__SPEC_1(v3)), \ + ENUM__JOIN_2( \ + ENUM__JOIN_STR_2(v1, v2), \ + ENUM__JOIN_STR_1(v3))) + +/* + * Declare the necessary data structures for calling Enum_FlagsToString + * for an enum with 6 flags. + */ +#define ENUM_FLAGS_RTTI_6(typnam, v1, v2, v3, v4, v5, v6) \ + ENUM__FLAGS_RTTI(typnam, \ + ENUM__SPECS_2( \ + ENUM__SPEC_4(v1, v2, v3, v4), \ + ENUM__SPEC_2(v5, v6)), \ + ENUM__JOIN_2( \ + ENUM__JOIN_STR_4(v1, v2, v3, v4), \ + ENUM__JOIN_STR_2(v5, v6))) + +/* + * Declare the necessary data structures for calling Enum_FlagsToString + * for an enum with 9 flags. + */ +#define ENUM_FLAGS_RTTI_9(typnam, v1, v2, v3, v4, v5, v6, v7, v8, v9) \ + ENUM__FLAGS_RTTI(typnam, \ + ENUM__SPECS_2( \ + ENUM__SPEC_8(v1, v2, v3, v4, v5, v6, v7, v8), \ + ENUM__SPEC_1(v9)), \ + ENUM__JOIN_2( \ + ENUM__JOIN_STR_8(v1, v2, v3, v4, v5, v6, v7, v8), \ + ENUM__JOIN_STR_1(v9))) + +/* + * Declare the necessary data structures for calling Enum_FlagsToString + * for an enum with 31 flags. + */ +#define ENUM_FLAGS_RTTI_31(typnam, \ + v01, v02, v03, v04, v05, v06, v07, v08, \ + v09, v10, v11, v12, v13, v14, v15, v16, \ + v17, v18, v19, v20, v21, v22, v23, v24, \ + v25, v26, v27, v28, v29, v30, v31) \ + ENUM__FLAGS_RTTI(typnam, \ + ENUM__SPECS_5( \ + ENUM__SPEC_16(v01, v02, v03, v04, v05, v06, v07, v08, \ + v09, v10, v11, v12, v13, v14, v15, v16), \ + ENUM__SPEC_8(v17, v18, v19, v20, v21, v22, v23, v24), \ + ENUM__SPEC_4(v25, v26, v27, v28), \ + ENUM__SPEC_2(v29, v30), \ + ENUM__SPEC_1(v31)), \ + ENUM__JOIN_5( \ + ENUM__JOIN_STR_16(v01, v02, v03, v04, v05, v06, v07, v08, \ + v09, v10, v11, v12, v13, v14, v15, v16), \ + ENUM__JOIN_STR_8(v17, v18, v19, v20, v21, v22, v23, v24), \ + ENUM__JOIN_STR_4(v25, v26, v27, v28), \ + ENUM__JOIN_STR_2(v29, v30), \ + ENUM__JOIN_STR_1(v31))) + +#endif diff --git a/usr.bin/make/strlist.h b/usr.bin/make/filemon/filemon.h similarity index 56% rename from usr.bin/make/strlist.h rename to usr.bin/make/filemon/filemon.h index 2fc049e86..139d62f1a 100644 --- a/usr.bin/make/strlist.h +++ b/usr.bin/make/filemon/filemon.h @@ -1,11 +1,11 @@ -/* $NetBSD: strlist.h,v 1.3 2009/01/16 21:15:34 dsl Exp $ */ +/* $NetBSD: filemon.h,v 1.5 2021/01/19 20:51:46 rillig Exp $ */ -/*- - * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc. +/* + * Copyright (c) 2019 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation - * by David Laight. + * by Taylor R. Campbell. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -15,9 +15,6 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of The NetBSD Foundation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED @@ -32,31 +29,25 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _STRLIST_H -#define _STRLIST_H +#ifndef MAKE_FILEMON_H +#define MAKE_FILEMON_H -typedef struct { - char *si_str; - unsigned int si_info; -} strlist_item_t; +#include -typedef struct { - unsigned int sl_num; - unsigned int sl_max; - strlist_item_t *sl_items; -} strlist_t; +struct filemon; -void strlist_init(strlist_t *); -void strlist_clean(strlist_t *); -void strlist_add_str(strlist_t *, char *, unsigned int); +const char * + filemon_path(void); -#define strlist_num(sl) ((sl)->sl_num) -#define strlist_str(sl, n) ((sl)->sl_items[n].si_str) -#define strlist_info(sl, n) ((sl)->sl_items[n].si_info) -#define strlist_set_info(sl, n, v) ((void)((sl)->sl_items[n].si_info = (v))) +struct filemon * + filemon_open(void); +int filemon_close(struct filemon *); -#define STRLIST_FOREACH(v, sl, index) \ - if ((sl)->sl_items != NULL) \ - for (index = 0; (v = strlist_str(sl, index)) != NULL; index++) +int filemon_setfd(struct filemon *, int); +void filemon_setpid_parent(struct filemon *, pid_t); +int filemon_setpid_child(const struct filemon *, pid_t); -#endif /* _STRLIST_H */ +int filemon_readfd(const struct filemon *); +int filemon_process(struct filemon *); + +#endif /* MAKE_FILEMON_H */ diff --git a/usr.bin/make/filemon/filemon_dev.c b/usr.bin/make/filemon/filemon_dev.c new file mode 100644 index 000000000..728d84c1f --- /dev/null +++ b/usr.bin/make/filemon/filemon_dev.c @@ -0,0 +1,151 @@ +/* $NetBSD: filemon_dev.c,v 1.8 2021/02/01 21:09:25 rillig Exp $ */ + +/* + * Copyright (c) 2020 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "filemon.h" + +#include + +#include +#include +#include +#include + +#ifdef HAVE_FILEMON_H +# include +#endif + +#ifndef _PATH_FILEMON +#define _PATH_FILEMON "/dev/filemon" +#endif + +struct filemon { + int fd; +}; + +const char * +filemon_path(void) +{ + + return _PATH_FILEMON; +} + +struct filemon * +filemon_open(void) +{ + struct filemon *F; + unsigned i; + int error; + + /* Allocate and zero a struct filemon object. */ + F = calloc(1, sizeof *F); + if (F == NULL) + return NULL; + + /* Try opening /dev/filemon, up to six times (cargo cult!). */ + for (i = 0; (F->fd = open(_PATH_FILEMON, O_RDWR|O_CLOEXEC)) == -1; i++) { + if (i == 5) { + error = errno; + goto fail0; + } + } + + /* Success! */ + return F; + +fail0: free(F); + errno = error; + return NULL; +} + +int +filemon_setfd(struct filemon *F, int fd) +{ + + /* Point the kernel at this file descriptor. */ + if (ioctl(F->fd, FILEMON_SET_FD, &fd) == -1) + return -1; + + /* No need for it in userland any more; close it. */ + (void)close(fd); + + /* Success! */ + return 0; +} + +void +filemon_setpid_parent(struct filemon *F, pid_t pid) +{ + /* Nothing to do! */ +} + +int +filemon_setpid_child(const struct filemon *F, pid_t pid) +{ + + /* Just pass it on to the kernel. */ + return ioctl(F->fd, FILEMON_SET_PID, &pid); +} + +int +filemon_close(struct filemon *F) +{ + int error = 0; + + /* Close the filemon device fd. */ + if (close(F->fd) == -1 && error == 0) + error = errno; + + /* Free the filemon descriptor. */ + free(F); + + /* Set errno and return -1 if anything went wrong. */ + if (error != 0) { + errno = error; + return -1; + } + + /* Success! */ + return 0; +} + +int +filemon_readfd(const struct filemon *F) +{ + + return -1; +} + +int +filemon_process(struct filemon *F) +{ + + return 0; +} diff --git a/usr.bin/make/filemon/filemon_ktrace.c b/usr.bin/make/filemon/filemon_ktrace.c new file mode 100644 index 000000000..1abef7e78 --- /dev/null +++ b/usr.bin/make/filemon/filemon_ktrace.c @@ -0,0 +1,894 @@ +/* $NetBSD: filemon_ktrace.c,v 1.14 2021/02/01 21:34:41 rillig Exp $ */ + +/* + * Copyright (c) 2019 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Taylor R. Campbell. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _KERNTYPES /* register_t */ + +#include "filemon.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef AT_CWD +#define AT_CWD -1 +#endif + +struct filemon; +struct filemon_key; +struct filemon_state; + +typedef struct filemon_state *filemon_syscall_t(struct filemon *, + const struct filemon_key *, const struct ktr_syscall *); + +static filemon_syscall_t filemon_sys_chdir; +static filemon_syscall_t filemon_sys_execve; +static filemon_syscall_t filemon_sys_exit; +static filemon_syscall_t filemon_sys_fork; +static filemon_syscall_t filemon_sys_link; +static filemon_syscall_t filemon_sys_open; +static filemon_syscall_t filemon_sys_openat; +static filemon_syscall_t filemon_sys_symlink; +static filemon_syscall_t filemon_sys_unlink; +static filemon_syscall_t filemon_sys_rename; + +static filemon_syscall_t *const filemon_syscalls[] = { + [SYS_chdir] = &filemon_sys_chdir, + [SYS_execve] = &filemon_sys_execve, + [SYS_exit] = &filemon_sys_exit, + [SYS_fork] = &filemon_sys_fork, + [SYS_link] = &filemon_sys_link, + [SYS_open] = &filemon_sys_open, + [SYS_openat] = &filemon_sys_openat, + [SYS_symlink] = &filemon_sys_symlink, + [SYS_unlink] = &filemon_sys_unlink, + [SYS_rename] = &filemon_sys_rename, +}; + +struct filemon { + int ktrfd; /* kernel writes ktrace events here */ + FILE *in; /* we read ktrace events from here */ + FILE *out; /* we write filemon events to here */ + rb_tree_t active; + pid_t child; + + /* I/O state machine. */ + enum { + FILEMON_START = 0, + FILEMON_HEADER, + FILEMON_PAYLOAD, + FILEMON_ERROR, + } state; + unsigned char *p; + size_t resid; + + /* I/O buffer. */ + struct ktr_header hdr; + union { + struct ktr_syscall syscall; + struct ktr_sysret sysret; + char namei[PATH_MAX]; + unsigned char buf[4096]; + } payload; +}; + +struct filemon_state { + struct filemon_key { + pid_t pid; + lwpid_t lid; + } key; + struct rb_node node; + int syscode; + void (*show)(struct filemon *, const struct filemon_state *, + const struct ktr_sysret *); + unsigned i; + unsigned npath; + char *path[/*npath*/]; +}; + +/*ARGSUSED*/ +static int +compare_filemon_states(void *cookie, const void *na, const void *nb) +{ + const struct filemon_state *Sa = na; + const struct filemon_state *Sb = nb; + + if (Sa->key.pid < Sb->key.pid) + return -1; + if (Sa->key.pid > Sb->key.pid) + return +1; + if (Sa->key.lid < Sb->key.lid) + return -1; + if (Sa->key.lid > Sb->key.lid) + return +1; + return 0; +} + +/*ARGSUSED*/ +static int +compare_filemon_key(void *cookie, const void *n, const void *k) +{ + const struct filemon_state *S = n; + const struct filemon_key *key = k; + + if (S->key.pid < key->pid) + return -1; + if (S->key.pid > key->pid) + return +1; + if (S->key.lid < key->lid) + return -1; + if (S->key.lid > key->lid) + return +1; + return 0; +} + +static const rb_tree_ops_t filemon_rb_ops = { + .rbto_compare_nodes = &compare_filemon_states, + .rbto_compare_key = &compare_filemon_key, + .rbto_node_offset = offsetof(struct filemon_state, node), + .rbto_context = NULL, +}; + +/* + * filemon_path() + * + * Return a pointer to a constant string denoting the `path' of + * the filemon. + */ +const char * +filemon_path(void) +{ + + return "ktrace"; +} + +/* + * filemon_open() + * + * Allocate a filemon descriptor. Returns NULL and sets errno on + * failure. + */ +struct filemon * +filemon_open(void) +{ + struct filemon *F; + int ktrpipe[2]; + int error; + + /* Allocate and zero a struct filemon object. */ + F = calloc(1, sizeof *F); + if (F == NULL) + return NULL; + + /* Create a pipe for ktrace events. */ + if (pipe2(ktrpipe, O_CLOEXEC|O_NONBLOCK) == -1) { + error = errno; + goto fail0; + } + + /* Create a file stream for reading the ktrace events. */ + if ((F->in = fdopen(ktrpipe[0], "r")) == NULL) { + error = errno; + goto fail1; + } + ktrpipe[0] = -1; /* claimed by fdopen */ + + /* + * Set the fd for writing ktrace events and initialize the + * rbtree. The rest can be safely initialized to zero. + */ + F->ktrfd = ktrpipe[1]; + rb_tree_init(&F->active, &filemon_rb_ops); + + /* Success! */ + return F; + + (void)fclose(F->in); +fail1: (void)close(ktrpipe[0]); + (void)close(ktrpipe[1]); +fail0: free(F); + errno = error; + return NULL; +} + +/* + * filemon_closefd(F) + * + * Internal subroutine to try to flush and close the output file. + * If F is not open for output, do nothing. Never leaves F open + * for output even on failure. Returns 0 on success; sets errno + * and return -1 on failure. + */ +static int +filemon_closefd(struct filemon *F) +{ + int error = 0; + + /* If we're not open, nothing to do. */ + if (F->out == NULL) + return 0; + + /* + * Flush it, close it, and null it unconditionally, but be + * careful to return the earliest error in errno. + */ + if (fflush(F->out) == EOF && error == 0) + error = errno; + if (fclose(F->out) == EOF && error == 0) + error = errno; + F->out = NULL; + + /* Set errno and return -1 if anything went wrong. */ + if (error != 0) { + errno = error; + return -1; + } + + /* Success! */ + return 0; +} + +/* + * filemon_setfd(F, fd) + * + * Cause filemon activity on F to be sent to fd. Claims ownership + * of fd; caller should not use fd afterward, and any duplicates + * of fd may see their file positions changed. + */ +int +filemon_setfd(struct filemon *F, int fd) +{ + + /* + * Close an existing output file if done. Fail now if there's + * an error closing. + */ + if ((filemon_closefd(F)) == -1) + return -1; + assert(F->out == NULL); + + /* Open a file stream and claim ownership of the fd. */ + if ((F->out = fdopen(fd, "a")) == NULL) + return -1; + + /* + * Print the opening output. Any failure will be deferred + * until closing. For hysterical raisins, we show the parent + * pid, not the child pid. + */ + fprintf(F->out, "# filemon version 4\n"); + fprintf(F->out, "# Target pid %jd\n", (intmax_t)getpid()); + fprintf(F->out, "V 4\n"); + + /* Success! */ + return 0; +} + +/* + * filemon_setpid_parent(F, pid) + * + * Set the traced pid, from the parent. Never fails. + */ +void +filemon_setpid_parent(struct filemon *F, pid_t pid) +{ + + F->child = pid; +} + +/* + * filemon_setpid_child(F, pid) + * + * Set the traced pid, from the child. Returns 0 on success; sets + * errno and returns -1 on failure. + */ +int +filemon_setpid_child(const struct filemon *F, pid_t pid) +{ + int ops, trpoints; + + ops = KTROP_SET|KTRFLAG_DESCEND; + trpoints = KTRFACv2; + trpoints |= KTRFAC_SYSCALL|KTRFAC_NAMEI|KTRFAC_SYSRET; + trpoints |= KTRFAC_INHERIT; + if (fktrace(F->ktrfd, ops, trpoints, pid) == -1) + return -1; + + return 0; +} + +/* + * filemon_close(F) + * + * Close F for output if necessary, and free a filemon descriptor. + * Returns 0 on success; sets errno and returns -1 on failure, but + * frees the filemon descriptor either way; + */ +int +filemon_close(struct filemon *F) +{ + struct filemon_state *S; + int error = 0; + + /* Close for output. */ + if (filemon_closefd(F) == -1 && error == 0) + error = errno; + + /* Close the ktrace pipe. */ + if (fclose(F->in) == EOF && error == 0) + error = errno; + if (close(F->ktrfd) == -1 && error == 0) + error = errno; + + /* Free any active records. */ + while ((S = RB_TREE_MIN(&F->active)) != NULL) { + rb_tree_remove_node(&F->active, S); + free(S); + } + + /* Free the filemon descriptor. */ + free(F); + + /* Set errno and return -1 if anything went wrong. */ + if (error != 0) { + errno = error; + return -1; + } + + /* Success! */ + return 0; +} + +/* + * filemon_readfd(F) + * + * Returns a file descriptor which will select/poll ready for read + * when there are filemon events to be processed by + * filemon_process, or -1 if anything has gone wrong. + */ +int +filemon_readfd(const struct filemon *F) +{ + + if (F->state == FILEMON_ERROR) + return -1; + return fileno(F->in); +} + +/* + * filemon_dispatch(F) + * + * Internal subroutine to dispatch a filemon ktrace event. + * Silently ignore events that we don't recognize. + */ +static void +filemon_dispatch(struct filemon *F) +{ + const struct filemon_key key = { + .pid = F->hdr.ktr_pid, + .lid = F->hdr.ktr_lid, + }; + struct filemon_state *S; + + switch (F->hdr.ktr_type) { + case KTR_SYSCALL: { + struct ktr_syscall *call = &F->payload.syscall; + struct filemon_state *S1; + + /* Validate the syscall code. */ + if (call->ktr_code < 0 || + (size_t)call->ktr_code >= __arraycount(filemon_syscalls) || + filemon_syscalls[call->ktr_code] == NULL) + break; + + /* + * Invoke the syscall-specific logic to create a new + * active state. + */ + S = (*filemon_syscalls[call->ktr_code])(F, &key, call); + if (S == NULL) + break; + + /* + * Insert the active state, or ignore it if there + * already is one. + * + * Collisions shouldn't happen because the states are + * keyed by , in which syscalls should happen + * sequentially in CALL/RET pairs, but let's be + * defensive. + */ + S1 = rb_tree_insert_node(&F->active, S); + if (S1 != S) { + /* XXX Which one to drop? */ + free(S); + break; + } + break; + } + case KTR_NAMEI: + /* Find an active syscall state, or drop it. */ + S = rb_tree_find_node(&F->active, &key); + if (S == NULL) + break; + /* Find the position of the next path, or drop it. */ + if (S->i >= S->npath) + break; + /* Record the path. */ + S->path[S->i++] = strndup(F->payload.namei, + sizeof F->payload.namei); + break; + case KTR_SYSRET: { + struct ktr_sysret *ret = &F->payload.sysret; + unsigned i; + + /* Find and remove an active syscall state, or drop it. */ + S = rb_tree_find_node(&F->active, &key); + if (S == NULL) + break; + rb_tree_remove_node(&F->active, S); + + /* + * If the active syscall state matches this return, + * invoke the syscall-specific logic to show a filemon + * event. + */ + /* XXX What to do if syscall code doesn't match? */ + if (S->i == S->npath && S->syscode == ret->ktr_code) + S->show(F, S, ret); + + /* Free the state now that it is no longer active. */ + for (i = 0; i < S->i; i++) + free(S->path[i]); + free(S); + break; + } + default: + /* Ignore all other ktrace events. */ + break; + } +} + +/* + * filemon_process(F) + * + * Process all pending events after filemon_readfd(F) has + * selected/polled ready for read. + * + * Returns -1 on failure, 0 on end of events, and anything else if + * there may be more events. + * + * XXX What about fairness to other activities in the event loop? + * If we stop while there's events buffered in F->in, then select + * or poll may not return ready even though there's work queued up + * in the buffer of F->in, but if we don't stop then ktrace events + * may overwhelm all other activity in the event loop. + */ +int +filemon_process(struct filemon *F) +{ + size_t nread; + +top: /* If the child has exited, nothing to do. */ + /* XXX What if one thread calls exit while another is running? */ + if (F->child == 0) + return 0; + + /* If we're waiting for input, read some. */ + if (F->resid > 0) { + nread = fread(F->p, 1, F->resid, F->in); + if (nread == 0) { + if (feof(F->in) != 0) + return 0; + assert(ferror(F->in) != 0); + /* + * If interrupted or would block, there may be + * more events. Otherwise fail. + */ + if (errno == EAGAIN || errno == EINTR) + return 1; + F->state = FILEMON_ERROR; + F->p = NULL; + F->resid = 0; + return -1; + } + assert(nread <= F->resid); + F->p += nread; + F->resid -= nread; + if (F->resid > 0) /* may be more events */ + return 1; + } + + /* Process a state transition now that we've read a buffer. */ + switch (F->state) { + case FILEMON_START: /* just started filemon; read header next */ + F->state = FILEMON_HEADER; + F->p = (void *)&F->hdr; + F->resid = sizeof F->hdr; + goto top; + case FILEMON_HEADER: /* read header */ + /* Sanity-check ktrace header; then read payload. */ + if (F->hdr.ktr_len < 0 || + (size_t)F->hdr.ktr_len > sizeof F->payload) { + F->state = FILEMON_ERROR; + F->p = NULL; + F->resid = 0; + errno = EIO; + return -1; + } + F->state = FILEMON_PAYLOAD; + F->p = (void *)&F->payload; + F->resid = (size_t)F->hdr.ktr_len; + goto top; + case FILEMON_PAYLOAD: /* read header and payload */ + /* Dispatch ktrace event; then read next header. */ + filemon_dispatch(F); + F->state = FILEMON_HEADER; + F->p = (void *)&F->hdr; + F->resid = sizeof F->hdr; + goto top; + default: /* paranoia */ + F->state = FILEMON_ERROR; + /*FALLTHROUGH*/ + case FILEMON_ERROR: /* persistent error indicator */ + F->p = NULL; + F->resid = 0; + errno = EIO; + return -1; + } +} + +static struct filemon_state * +syscall_enter( + const struct filemon_key *key, const struct ktr_syscall *call, + unsigned npath, + void (*show)(struct filemon *, const struct filemon_state *, + const struct ktr_sysret *)) +{ + struct filemon_state *S; + unsigned i; + + S = calloc(1, offsetof(struct filemon_state, path[npath])); + if (S == NULL) + return NULL; + S->key = *key; + S->show = show; + S->syscode = call->ktr_code; + S->i = 0; + S->npath = npath; + for (i = 0; i < npath; i++) + S->path[i] = NULL; /* paranoia */ + + return S; +} + +static void +show_paths(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret, const char *prefix) +{ + unsigned i; + + /* Caller must ensure all paths have been specified. */ + assert(S->i == S->npath); + + /* + * Ignore it if it failed or yielded EJUSTRETURN (-2), or if + * we're not producing output. + */ + if (ret->ktr_error != 0 && ret->ktr_error != -2) + return; + if (F->out == NULL) + return; + + /* + * Print the prefix, pid, and paths -- with the paths quoted if + * there's more than one. + */ + fprintf(F->out, "%s %jd", prefix, (intmax_t)S->key.pid); + for (i = 0; i < S->npath; i++) { + const char *q = S->npath > 1 ? "'" : ""; + fprintf(F->out, " %s%s%s", q, S->path[i], q); + } + fprintf(F->out, "\n"); +} + +static void +show_retval(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret, const char *prefix) +{ + + /* + * Ignore it if it failed or yielded EJUSTRETURN (-2), or if + * we're not producing output. + */ + if (ret->ktr_error != 0 && ret->ktr_error != -2) + return; + if (F->out == NULL) + return; + + fprintf(F->out, "%s %jd %jd\n", prefix, (intmax_t)S->key.pid, + (intmax_t)ret->ktr_retval); +} + +static void +show_chdir(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "C"); +} + +static void +show_execve(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "E"); +} + +static void +show_fork(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_retval(F, S, ret, "F"); +} + +static void +show_link(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "L"); /* XXX same as symlink */ +} + +static void +show_open_read(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "R"); +} + +static void +show_open_write(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "W"); +} + +static void +show_open_readwrite(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "R"); + show_paths(F, S, ret, "W"); +} + +static void +show_openat_read(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + if (S->path[0][0] != '/') + show_paths(F, S, ret, "A"); + show_paths(F, S, ret, "R"); +} + +static void +show_openat_write(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + if (S->path[0][0] != '/') + show_paths(F, S, ret, "A"); + show_paths(F, S, ret, "W"); +} + +static void +show_openat_readwrite(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + if (S->path[0][0] != '/') + show_paths(F, S, ret, "A"); + show_paths(F, S, ret, "R"); + show_paths(F, S, ret, "W"); +} + +static void +show_symlink(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "L"); /* XXX same as link */ +} + +static void +show_unlink(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "D"); +} + +static void +show_rename(struct filemon *F, const struct filemon_state *S, + const struct ktr_sysret *ret) +{ + show_paths(F, S, ret, "M"); +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_chdir(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 1, &show_chdir); +} + +/* TODO: monitor fchdir as well */ + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_execve(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 1, &show_execve); +} + +static struct filemon_state * +filemon_sys_exit(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + const register_t *args = (const void *)&call[1]; + int status = (int)args[0]; + + if (F->out != NULL) { + fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status); + if (key->pid == F->child) { + fprintf(F->out, "# Bye bye\n"); + F->child = 0; + } + } + return NULL; +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_fork(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 0, &show_fork); +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_link(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 2, &show_link); +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_open(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + const register_t *args = (const void *)&call[1]; + int flags; + + if (call->ktr_argsize < 2) + return NULL; + flags = (int)args[1]; + + if ((flags & O_RDWR) == O_RDWR) + return syscall_enter(key, call, 1, &show_open_readwrite); + else if ((flags & O_WRONLY) == O_WRONLY) + return syscall_enter(key, call, 1, &show_open_write); + else if ((flags & O_RDONLY) == O_RDONLY) + return syscall_enter(key, call, 1, &show_open_read); + else + return NULL; /* XXX Do we care if no read or write? */ +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_openat(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + const register_t *args = (const void *)&call[1]; + int flags, fd; + + /* + * XXX: In the .meta log, the base directory is missing, which makes + * all references to relative pathnames useless. + */ + + if (call->ktr_argsize < 3) + return NULL; + fd = (int)args[0]; + flags = (int)args[2]; + + if (fd == AT_CWD) { + if ((flags & O_RDWR) == O_RDWR) + return syscall_enter(key, call, 1, + &show_open_readwrite); + else if ((flags & O_WRONLY) == O_WRONLY) + return syscall_enter(key, call, 1, &show_open_write); + else if ((flags & O_RDONLY) == O_RDONLY) + return syscall_enter(key, call, 1, &show_open_read); + else + return NULL; + } else { + if ((flags & O_RDWR) == O_RDWR) + return syscall_enter(key, call, 1, + &show_openat_readwrite); + else if ((flags & O_WRONLY) == O_WRONLY) + return syscall_enter(key, call, 1, &show_openat_write); + else if ((flags & O_RDONLY) == O_RDONLY) + return syscall_enter(key, call, 1, &show_openat_read); + else + return NULL; + } +} + +/* TODO: monitor the other *at syscalls as well, not only openat. */ + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_symlink(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 2, &show_symlink); +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_unlink(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 1, &show_unlink); +} + +/*ARGSUSED*/ +static struct filemon_state * +filemon_sys_rename(struct filemon *F, const struct filemon_key *key, + const struct ktr_syscall *call) +{ + return syscall_enter(key, call, 2, &show_rename); +} diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c index 33bcf1392..5705d9c5d 100644 --- a/usr.bin/make/for.c +++ b/usr.bin/make/for.c @@ -1,4 +1,4 @@ -/* $NetBSD: for.c,v 1.49 2012/06/03 04:29:40 sjg Exp $ */ +/* $NetBSD: for.c,v 1.141 2021/02/04 21:33:13 rillig Exp $ */ /* * Copyright (c) 1992, The Regents of the University of California. @@ -29,106 +29,188 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: for.c,v 1.49 2012/06/03 04:29:40 sjg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: for.c,v 1.49 2012/06/03 04:29:40 sjg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * for.c -- - * Functions to handle loops in a makefile. +/* + * Handling of .for/.endfor loops in a makefile. + * + * For loops have the form: + * + * .for in + * # the body + * .endfor + * + * When a .for line is parsed, the following lines are copied to the body of + * the .for loop, until the corresponding .endfor line is reached. In this + * phase, the body is not yet evaluated. This also applies to any nested + * .for loops. + * + * 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 variable + * names are replaced with expressions of the form ${:U...} or $(:U...). + * After that, the body is treated like a file from an .include directive. * * Interface: - * For_Eval Evaluate the loop in the passed line. + * For_Eval Evaluate the loop in the passed line. + * * For_Run Run accumulated loop - * */ -#include -#include +#include "make.h" -#include "make.h" -#include "hash.h" -#include "dir.h" -#include "buf.h" -#include "strlist.h" +/* "@(#)for.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: for.c,v 1.141 2021/02/04 21:33:13 rillig Exp $"); -#define FOR_SUB_ESCAPE_CHAR 1 -#define FOR_SUB_ESCAPE_BRACE 2 -#define FOR_SUB_ESCAPE_PAREN 4 -/* - * For statements are of the form: - * - * .for in - * ... - * .endfor - * - * The trick is to look for the matching end inside for for loop - * To do that, we count the current nesting level of the for loops. - * and the .endfor statements, accumulating all the statements between - * the initial .for loop and the matching .endfor; - * then we evaluate the for loop for each variable in the varlist. - * - * Note that any nested fors are just passed through; they get handled - * recursively in For_Eval when we're expanding the enclosing for in - * For_Run. - */ +/* One of the variables to the left of the "in" in a .for loop. */ +typedef struct ForVar { + char *name; + size_t nameLen; +} ForVar; -static int forLevel = 0; /* Nesting level */ +typedef struct ForLoop { + Buffer body; /* Unexpanded body of the loop */ + Vector /* of ForVar */ vars; /* Iteration variables */ + Words items; /* Substitution items */ + Buffer curBody; /* Expanded body of the current iteration */ + /* Is any of the names 1 character long? If so, when the variable values + * are substituted, the parser must handle $V expressions as well, not + * only ${V} and $(V). */ + Boolean short_var; + unsigned int sub_next; /* Where to continue iterating */ +} ForLoop; -/* - * State of a for loop. - */ -typedef struct _For { - Buffer buf; /* Body of loop */ - strlist_t vars; /* Iteration variables */ - strlist_t items; /* Substitution items */ - char *parse_buf; - int short_var; - int sub_next; -} For; -static For *accumFor; /* Loop being accumulated */ +static ForLoop *accumFor; /* Loop being accumulated */ +static int forLevel = 0; /* Nesting level */ - -static char * -make_str(const char *ptr, int len) +static ForLoop * +ForLoop_New(void) { - char *new_ptr; + ForLoop *f = bmake_malloc(sizeof *f); - new_ptr = bmake_malloc(len + 1); - memcpy(new_ptr, ptr, len); - new_ptr[len] = 0; - return new_ptr; + Buf_Init(&f->body); + Vector_Init(&f->vars, sizeof(ForVar)); + f->items.words = NULL; + f->items.freeIt = NULL; + Buf_Init(&f->curBody); + f->short_var = FALSE; + f->sub_next = 0; + + return f; } static void -For_Free(For *arg) +ForLoop_Free(ForLoop *f) { - Buf_Destroy(&arg->buf, TRUE); - strlist_clean(&arg->vars); - strlist_clean(&arg->items); - free(arg->parse_buf); + Buf_Done(&f->body); - free(arg); + while (f->vars.len > 0) { + ForVar *var = Vector_Pop(&f->vars); + free(var->name); + } + Vector_Done(&f->vars); + + Words_Free(f->items); + Buf_Done(&f->curBody); + + free(f); } -/*- - *----------------------------------------------------------------------- - * For_Eval -- - * Evaluate the for loop in the passed line. The line - * looks like this: - * .for in +static void +ForLoop_AddVar(ForLoop *f, const char *name, size_t len) +{ + ForVar *var = Vector_Push(&f->vars); + var->name = bmake_strldup(name, len); + var->nameLen = len; +} + +static Boolean +ForLoop_ParseVarnames(ForLoop *f, const char **pp) +{ + const char *p = *pp; + + for (;;) { + size_t len; + + cpp_skip_whitespace(&p); + if (*p == '\0') { + Parse_Error(PARSE_FATAL, "missing `in' in for"); + return FALSE; + } + + /* + * XXX: This allows arbitrary variable names; + * see directive-for.mk. + */ + for (len = 1; p[len] != '\0' && !ch_isspace(p[len]); len++) + continue; + + if (len == 2 && p[0] == 'i' && p[1] == 'n') { + p += 2; + break; + } + if (len == 1) + f->short_var = TRUE; + + ForLoop_AddVar(f, p, len); + p += len; + } + + if (f->vars.len == 0) { + Parse_Error(PARSE_FATAL, "no iteration variables in for"); + return FALSE; + } + + *pp = p; + return TRUE; +} + +static Boolean +ForLoop_ParseItems(ForLoop *f, const char *p) +{ + char *items; + + cpp_skip_whitespace(&p); + + if (Var_Subst(p, SCOPE_GLOBAL, VARE_WANTRES, &items) != VPR_OK) { + Parse_Error(PARSE_FATAL, "Error in .for loop items"); + return FALSE; + } + + f->items = Str_Words(items, FALSE); + free(items); + + if (f->items.len == 1 && f->items.words[0][0] == '\0') + f->items.len = 0; /* .for var in ${:U} */ + + if (f->items.len != 0 && f->items.len % f->vars.len != 0) { + Parse_Error(PARSE_FATAL, + "Wrong number of words (%u) in .for " + "substitution list with %u variables", + (unsigned)f->items.len, (unsigned)f->vars.len); + return FALSE; + } + + return TRUE; +} + +static Boolean +IsFor(const char *p) +{ + return p[0] == 'f' && p[1] == 'o' && p[2] == 'r' && ch_isspace(p[3]); +} + +static Boolean +IsEndfor(const char *p) +{ + return p[0] == 'e' && strncmp(p, "endfor", 6) == 0 && + (p[6] == '\0' || ch_isspace(p[6])); +} + +/* + * Evaluate the for loop in the passed line. The line looks like this: + * .for in * * Input: * line Line to parse @@ -137,360 +219,299 @@ For_Free(For *arg) * 0: Not a .for statement, parse the line * 1: We found a for loop * -1: A .for statement with a bad syntax error, discard. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- */ int -For_Eval(char *line) +For_Eval(const char *line) { - For *new_for; - char *ptr = line, *sub; - int len; - int escapes; - unsigned char ch; - char **words, *word_buf; - int n, nwords; + ForLoop *f; + const char *p; - /* Skip the '.' and any following whitespace */ - for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++) - continue; + p = line + 1; /* skip the '.' */ + cpp_skip_whitespace(&p); - /* - * If we are not in a for loop quickly determine if the statement is - * a for. - */ - if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || - !isspace((unsigned char) ptr[3])) { - if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) { - Parse_Error(PARSE_FATAL, "for-less endfor"); - return -1; - } - return 0; - } - ptr += 3; - - /* - * we found a for loop, and now we are going to parse it. - */ - - new_for = bmake_malloc(sizeof *new_for); - memset(new_for, 0, sizeof *new_for); - - /* Grab the variables. Terminate on "in". */ - for (;; ptr += len) { - while (*ptr && isspace((unsigned char) *ptr)) - ptr++; - if (*ptr == '\0') { - Parse_Error(PARSE_FATAL, "missing `in' in for"); - For_Free(new_for); - return -1; - } - for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++) - continue; - if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') { - ptr += 2; - break; - } - if (len == 1) - new_for->short_var = 1; - strlist_add_str(&new_for->vars, make_str(ptr, len), len); - } - - if (strlist_num(&new_for->vars) == 0) { - Parse_Error(PARSE_FATAL, "no iteration variables in for"); - For_Free(new_for); - return -1; - } - - while (*ptr && isspace((unsigned char) *ptr)) - ptr++; - - /* - * Make a list with the remaining words - * The values are substituted as ${:U...} so we must \ escape - * characters that break that syntax. - * Variables are fully expanded - so it is safe for escape $. - * We can't do the escapes here - because we don't know whether - * we are substuting into ${...} or $(...). - */ - sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE); - - /* - * Split into words allowing for quoted strings. - */ - words = brk_string(sub, &nwords, FALSE, &word_buf); - - free(sub); - - if (words != NULL) { - for (n = 0; n < nwords; n++) { - ptr = words[n]; - if (!*ptr) - continue; - escapes = 0; - while ((ch = *ptr++)) { - switch(ch) { - case ':': - case '$': - case '\\': - escapes |= FOR_SUB_ESCAPE_CHAR; - break; - case ')': - escapes |= FOR_SUB_ESCAPE_PAREN; - break; - case /*{*/ '}': - escapes |= FOR_SUB_ESCAPE_BRACE; - break; + if (!IsFor(p)) { + if (IsEndfor(p)) { + Parse_Error(PARSE_FATAL, "for-less endfor"); + return -1; } - } - /* - * We have to dup words[n] to maintain the semantics of - * strlist. - */ - strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes); + return 0; + } + p += 3; + + f = ForLoop_New(); + + if (!ForLoop_ParseVarnames(f, &p)) { + ForLoop_Free(f); + return -1; } - free(words); - free(word_buf); - - if ((len = strlist_num(&new_for->items)) > 0 && - len % (n = strlist_num(&new_for->vars))) { - Parse_Error(PARSE_FATAL, - "Wrong number of words (%d) in .for substitution list" - " with %d vars", len, n); - /* - * Return 'success' so that the body of the .for loop is - * accumulated. - * Remove all items so that the loop doesn't iterate. - */ - strlist_clean(&new_for->items); + if (!ForLoop_ParseItems(f, p)) { + /* Continue parsing the .for loop, but don't iterate. */ + f->items.len = 0; } - } - Buf_Init(&new_for->buf, 0); - accumFor = new_for; - forLevel = 1; - return 1; + accumFor = f; + forLevel = 1; + return 1; } /* - * Add another line to a .for loop. - * Returns 0 when the matching .endfor is reached. + * Add another line to the .for loop that is being built up. + * Returns FALSE when the matching .endfor is reached. */ - -int -For_Accum(char *line) +Boolean +For_Accum(const char *line) { - char *ptr = line; + const char *p = line; - if (*ptr == '.') { + if (*p == '.') { + p++; + cpp_skip_whitespace(&p); - for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++) - continue; - - if (strncmp(ptr, "endfor", 6) == 0 && - (isspace((unsigned char) ptr[6]) || !ptr[6])) { - if (DEBUG(FOR)) - (void)fprintf(debug_file, "For: end for %d\n", forLevel); - if (--forLevel <= 0) - return 0; - } else if (strncmp(ptr, "for", 3) == 0 && - isspace((unsigned char) ptr[3])) { - forLevel++; - if (DEBUG(FOR)) - (void)fprintf(debug_file, "For: new loop %d\n", forLevel); + if (IsEndfor(p)) { + DEBUG1(FOR, "For: end for %d\n", forLevel); + if (--forLevel <= 0) + return FALSE; + } else if (IsFor(p)) { + forLevel++; + DEBUG1(FOR, "For: new loop %d\n", forLevel); + } } - } - Buf_AddBytes(&accumFor->buf, strlen(line), line); - Buf_AddByte(&accumFor->buf, '\n'); - return 1; + Buf_AddStr(&accumFor->body, line); + Buf_AddByte(&accumFor->body, '\n'); + return TRUE; } - -/*- - *----------------------------------------------------------------------- - * For_Run -- - * Run the for loop, imitating the actions of an include file - * - * Results: - * None. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -static int +static size_t for_var_len(const char *var) { - char ch, var_start, var_end; - int depth; - int len; + char ch, var_start, var_end; + int depth; + size_t len; - var_start = *var; - if (var_start == 0) - /* just escape the $ */ + var_start = *var; + if (var_start == '\0') + /* just escape the $ */ + return 0; + + if (var_start == '(') + var_end = ')'; + else if (var_start == '{') + var_end = '}'; + else + return 1; /* Single char variable */ + + depth = 1; + for (len = 1; (ch = var[len++]) != '\0';) { + if (ch == var_start) + depth++; + else if (ch == var_end && --depth == 0) + return len; + } + + /* Variable end not found, escape the $ */ return 0; - - if (var_start == '(') - var_end = ')'; - else if (var_start == '{') - var_end = '}'; - else - /* Single char variable */ - return 1; - - depth = 1; - for (len = 1; (ch = var[len++]) != 0;) { - if (ch == var_start) - depth++; - else if (ch == var_end && --depth == 0) - return len; - } - - /* Variable end not found, escape the $ */ - return 0; } +/* + * The .for loop substitutes the items as ${:U...}, which means + * that characters that break this syntax must be backslash-escaped. + */ +static Boolean +NeedsEscapes(const char *value, char endc) +{ + const char *p; + + for (p = value; *p != '\0'; p++) { + if (*p == ':' || *p == '$' || *p == '\\' || *p == endc) + return TRUE; + } + return FALSE; +} + +/* + * While expanding the body of a .for loop, write the item in the ${:U...} + * expression, escaping characters as needed. + * + * The result is later unescaped by ApplyModifier_Defined. + */ static void -for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech) +Buf_AddEscaped(Buffer *cmds, const char *item, char endc) { - const char *item = strlist_str(items, item_no); - int len; - char ch; + char ch; - /* If there were no escapes, or the only escape is the other variable - * terminator, then just substitute the full string */ - if (!(strlist_info(items, item_no) & - (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) { - Buf_AddBytes(cmds, strlen(item), item); + if (!NeedsEscapes(item, endc)) { + Buf_AddStr(cmds, item); + return; + } + + /* Escape ':', '$', '\\' and 'endc' - these will be removed later by + * :U processing, see ApplyModifier_Defined. */ + while ((ch = *item++) != '\0') { + if (ch == '$') { + size_t len = for_var_len(item); + if (len != 0) { + Buf_AddBytes(cmds, item - 1, len + 1); + item += len; + continue; + } + Buf_AddByte(cmds, '\\'); + } else if (ch == ':' || ch == '\\' || ch == endc) + Buf_AddByte(cmds, '\\'); + Buf_AddByte(cmds, ch); + } +} + +/* + * While expanding the body of a .for loop, replace the variable name of an + * expression like ${i} or ${i:...} or $(i) or $(i:...) with ":Uvalue". + */ +static void +ForLoop_SubstVarLong(ForLoop *f, const char **pp, const char *bodyEnd, + char endc, const char **inout_mark) +{ + size_t i; + const char *p = *pp; + + for (i = 0; i < f->vars.len; i++) { + ForVar *forVar = Vector_Get(&f->vars, i); + char *varname = forVar->name; + size_t varnameLen = forVar->nameLen; + + if (varnameLen >= (size_t)(bodyEnd - p)) + continue; + if (memcmp(p, varname, varnameLen) != 0) + continue; + /* XXX: why test for backslash here? */ + if (p[varnameLen] != ':' && p[varnameLen] != endc && + p[varnameLen] != '\\') + continue; + + /* + * Found a variable match. Skip over the variable name and + * instead add ':U' to the current body. + */ + Buf_AddBytesBetween(&f->curBody, *inout_mark, p); + Buf_AddStr(&f->curBody, ":U"); + Buf_AddEscaped(&f->curBody, + f->items.words[f->sub_next + i], endc); + + p += varnameLen; + *inout_mark = p; + *pp = p; + return; + } +} + +/* + * While expanding the body of a .for loop, replace single-character + * variable expressions like $i with their ${:U...} expansion. + */ +static void +ForLoop_SubstVarShort(ForLoop *f, const char *p, const char **inout_mark) +{ + const char ch = *p; + ForVar *vars; + size_t i; + + /* Skip $$ and stupid ones. */ + if (!f->short_var || strchr("}):$", ch) != NULL) + return; + + vars = Vector_Get(&f->vars, 0); + for (i = 0; i < f->vars.len; i++) { + const char *varname = vars[i].name; + if (varname[0] == ch && varname[1] == '\0') + goto found; + } return; - } - /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */ - while ((ch = *item++) != 0) { - if (ch == '$') { - len = for_var_len(item); - if (len != 0) { - Buf_AddBytes(cmds, len + 1, item - 1); - item += len; - continue; - } - Buf_AddByte(cmds, '\\'); - } else if (ch == ':' || ch == '\\' || ch == ech) - Buf_AddByte(cmds, '\\'); - Buf_AddByte(cmds, ch); - } +found: + /* Replace $ with ${:U} */ + Buf_AddBytesBetween(&f->curBody, *inout_mark, p), *inout_mark = p + 1; + Buf_AddStr(&f->curBody, "{:U"); + Buf_AddEscaped(&f->curBody, f->items.words[f->sub_next + i], '}'); + Buf_AddByte(&f->curBody, '}'); } -static char * -For_Iterate(void *v_arg, size_t *ret_len) +/* + * 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 + * syntax, and that the later parsing will still see a variable. + * This code assumes that the variable with the empty name will never be + * defined, see unit-tests/varname-empty.mk for more details. + * + * The detection of substitutions of the loop control variables is naive. + * Many of the modifiers use '\' to escape '$' (not '$'), so it is possible + * to contrive a makefile where an unwanted substitution happens. + */ +static void +ForLoop_SubstBody(ForLoop *f) { - For *arg = v_arg; - int i, len; - char *var; - char *cp; - char *cmd_cp; - char *body_end; - char ch; - Buffer cmds; + const char *p, *bodyEnd; + const char *mark; /* where the last replacement left off */ - if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) { - /* No more iterations */ - For_Free(arg); - return NULL; - } + Buf_Empty(&f->curBody); - free(arg->parse_buf); - arg->parse_buf = NULL; - - /* - * Scan the for loop body and replace references to the loop variables - * with variable references that expand to the required text. - * Using variable expansions ensures that the .for loop can't generate - * syntax, and that the later parsing will still see a variable. - * We assume that the null variable will never be defined. - * - * The detection of substitions of the loop control variable is naive. - * Many of the modifiers use \ to escape $ (not $) so it is possible - * to contrive a makefile where an unwanted substitution happens. - */ - - cmd_cp = Buf_GetAll(&arg->buf, &len); - body_end = cmd_cp + len; - Buf_Init(&cmds, len + 256); - for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) { - char ech; - ch = *++cp; - if ((ch == '(' && (ech = ')')) || (ch == '{' && (ech = '}'))) { - cp++; - /* Check variable name against the .for loop variables */ - STRLIST_FOREACH(var, &arg->vars, i) { - len = strlist_info(&arg->vars, i); - if (memcmp(cp, var, len) != 0) - continue; - if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\') - continue; - /* Found a variable match. Replace with :U */ - Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp); - Buf_AddBytes(&cmds, 2, ":U"); - cp += len; - cmd_cp = cp; - for_substitute(&cmds, &arg->items, arg->sub_next + i, ech); - break; - } - continue; + mark = f->body.data; + bodyEnd = f->body.data + f->body.len; + for (p = mark; (p = strchr(p, '$')) != NULL;) { + if (p[1] == '{' || p[1] == '(') { + p += 2; + ForLoop_SubstVarLong(f, &p, bodyEnd, + p[-1] == '{' ? '}' : ')', &mark); + } else if (p[1] != '\0') { + ForLoop_SubstVarShort(f, p + 1, &mark); + p += 2; + } else + break; } - if (ch == 0) - break; - /* Probably a single character name, ignore $$ and stupid ones. {*/ - if (!arg->short_var || strchr("}):$", ch) != NULL) { - cp++; - continue; - } - STRLIST_FOREACH(var, &arg->vars, i) { - if (var[0] != ch || var[1] != 0) - continue; - /* Found a variable match. Replace with ${:U} */ - Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp); - Buf_AddBytes(&cmds, 3, "{:U"); - cmd_cp = ++cp; - for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}'); - Buf_AddBytes(&cmds, 1, "}"); - break; - } - } - Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp); - cp = Buf_Destroy(&cmds, FALSE); - if (DEBUG(FOR)) - (void)fprintf(debug_file, "For: loop body:\n%s", cp); - - arg->sub_next += strlist_num(&arg->vars); - - arg->parse_buf = cp; - *ret_len = strlen(cp); - return cp; + Buf_AddBytesBetween(&f->curBody, mark, bodyEnd); } +/* + * Compute the body for the current iteration by copying the unexpanded body, + * replacing the expressions for the iteration variables on the way. + */ +static char * +ForReadMore(void *v_arg, size_t *out_len) +{ + ForLoop *f = v_arg; + + if (f->sub_next == f->items.len) { + /* No more iterations */ + ForLoop_Free(f); + return NULL; + } + + ForLoop_SubstBody(f); + DEBUG1(FOR, "For: loop body:\n%s", f->curBody.data); + f->sub_next += (unsigned int)f->vars.len; + + *out_len = f->curBody.len; + return f->curBody.data; +} + +/* Run the .for loop, imitating the actions of an include file. */ void For_Run(int lineno) -{ - For *arg; - - arg = accumFor; - accumFor = NULL; +{ + ForLoop *f = accumFor; + accumFor = NULL; - if (strlist_num(&arg->items) == 0) { - /* Nothing to expand - possibly due to an earlier syntax error. */ - For_Free(arg); - return; - } - - Parse_SetInput(NULL, lineno, -1, For_Iterate, arg); + if (f->items.len == 0) { + /* + * Nothing to expand - possibly due to an earlier syntax + * error. + */ + ForLoop_Free(f); + return; + } + + Parse_SetInput(NULL, lineno, -1, ForReadMore, f); } diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c index ed2364443..3afc4ac7e 100644 --- a/usr.bin/make/hash.c +++ b/usr.bin/make/hash.c @@ -1,4 +1,4 @@ -/* $NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $ */ +/* $NetBSD: hash.c,v 1.61 2021/02/01 17:32:10 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,398 +69,264 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)hash.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $"); -#endif -#endif /* not lint */ -#endif +/* Hash tables with string keys. */ -/* hash.c -- - * - * This module contains routines to manipulate a hash table. - * See hash.h for a definition of the structure of the hash - * table. Hash tables grow automatically as the amount of - * information increases. - */ -#include "sprite.h" #include "make.h" -#include "hash.h" + +/* "@(#)hash.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: hash.c,v 1.61 2021/02/01 17:32:10 rillig Exp $"); /* - * Forward references to local procedures that are used before they're - * defined: + * The ratio of # entries to # buckets at which we rebuild the table to + * make it larger. */ - -static void RebuildTable(Hash_Table *); - -/* - * The following defines the ratio of # entries to # buckets - * at which we rebuild the table to make it larger. - */ - #define rebuildLimit 3 -/* - *--------------------------------------------------------- - * - * Hash_InitTable -- - * - * This routine just sets up the hash table. - * - * Input: - * t Structure to to hold table. - * numBuckets How many buckets to create for starters. This - * number is rounded up to a power of two. If - * <= 0, a reasonable default is chosen. The - * table will grow in size later as needed. - * - * Results: - * None. - * - * Side Effects: - * Memory is allocated for the initial bucket area. - * - *--------------------------------------------------------- - */ - -void -Hash_InitTable(Hash_Table *t, int numBuckets) +/* This hash function matches Gosling's Emacs and java.lang.String. */ +static unsigned int +hash(const char *key, size_t *out_keylen) { - int i; - struct Hash_Entry **hp; + unsigned int h; + const char *p; - /* - * Round up the size to a power of two. - */ - if (numBuckets <= 0) - i = 16; - else { - for (i = 2; i < numBuckets; i <<= 1) - continue; + h = 0; + for (p = key; *p != '\0'; p++) + h = 31 * h + (unsigned char)*p; + + if (out_keylen != NULL) + *out_keylen = (size_t)(p - key); + return h; +} + +unsigned int +Hash_Hash(const char *key) +{ + return hash(key, NULL); +} + +static HashEntry * +HashTable_Find(HashTable *t, unsigned int h, const char *key) +{ + HashEntry *e; + unsigned int chainlen = 0; + +#ifdef DEBUG_HASH_LOOKUP + DEBUG4(HASH, "%s: %p h=%08x key=%s\n", __func__, t, h, key); +#endif + + for (e = t->buckets[h & t->bucketsMask]; e != NULL; e = e->next) { + chainlen++; + if (e->key_hash == h && strcmp(e->key, key) == 0) + break; } + + if (chainlen > t->maxchain) + t->maxchain = chainlen; + + return e; +} + +/* Set up the hash table. */ +void +HashTable_Init(HashTable *t) +{ + unsigned int n = 16, i; + HashEntry **buckets = bmake_malloc(sizeof *buckets * n); + for (i = 0; i < n; i++) + buckets[i] = NULL; + + t->buckets = buckets; + t->bucketsSize = n; t->numEntries = 0; - t->size = i; - t->mask = i - 1; - t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i); - while (--i >= 0) - *hp++ = NULL; + t->bucketsMask = n - 1; + t->maxchain = 0; } /* - *--------------------------------------------------------- - * - * Hash_DeleteTable -- - * - * This routine removes everything from a hash table - * and frees up the memory space it occupied (except for - * the space in the Hash_Table structure). - * - * Results: - * None. - * - * Side Effects: - * Lots of memory is freed up. - * - *--------------------------------------------------------- + * Remove everything from the hash table and free up the memory for the keys + * of the hash table, but not for the values associated to these keys. */ - void -Hash_DeleteTable(Hash_Table *t) +HashTable_Done(HashTable *t) { - struct Hash_Entry **hp, *h, *nexth = NULL; - int i; + HashEntry **buckets = t->buckets; + size_t i, n = t->bucketsSize; - for (hp = t->bucketPtr, i = t->size; --i >= 0;) { - for (h = *hp++; h != NULL; h = nexth) { - nexth = h->next; - free(h); - } - } - free(t->bucketPtr); - - /* - * Set up the hash table to cause memory faults on any future access - * attempts until re-initialization. - */ - t->bucketPtr = NULL; -} - -/* - *--------------------------------------------------------- - * - * Hash_FindEntry -- - * - * Searches a hash table for an entry corresponding to key. - * - * Input: - * t Hash table to search. - * key A hash key. - * - * Results: - * The return value is a pointer to the entry for key, - * if key was present in the table. If key was not - * present, NULL is returned. - * - * Side Effects: - * None. - * - *--------------------------------------------------------- - */ - -Hash_Entry * -Hash_FindEntry(Hash_Table *t, const char *key) -{ - Hash_Entry *e; - unsigned h; - const char *p; - - if (t == NULL || t->bucketPtr == NULL) { - return NULL; - } - for (h = 0, p = key; *p;) - h = (h << 5) - h + *p++; - p = key; - for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) - if (e->namehash == h && strcmp(e->name, p) == 0) - return (e); - return NULL; -} - -/* - *--------------------------------------------------------- - * - * Hash_CreateEntry -- - * - * Searches a hash table for an entry corresponding to - * key. If no entry is found, then one is created. - * - * Input: - * t Hash table to search. - * key A hash key. - * newPtr Filled in with TRUE if new entry created, - * FALSE otherwise. - * - * Results: - * The return value is a pointer to the entry. If *newPtr - * isn't NULL, then *newPtr is filled in with TRUE if a - * new entry was created, and FALSE if an entry already existed - * with the given key. - * - * Side Effects: - * Memory may be allocated, and the hash buckets may be modified. - *--------------------------------------------------------- - */ - -Hash_Entry * -Hash_CreateEntry(Hash_Table *t, const char *key, Boolean *newPtr) -{ - Hash_Entry *e; - unsigned h; - const char *p; - int keylen; - struct Hash_Entry **hp; - - /* - * Hash the key. As a side effect, save the length (strlen) of the - * key in case we need to create the entry. - */ - for (h = 0, p = key; *p;) - h = (h << 5) - h + *p++; - keylen = p - key; - p = key; - for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) { - if (e->namehash == h && strcmp(e->name, p) == 0) { - if (newPtr != NULL) - *newPtr = FALSE; - return (e); + for (i = 0; i < n; i++) { + HashEntry *he = buckets[i]; + while (he != NULL) { + HashEntry *next = he->next; + free(he); + he = next; } } - /* - * The desired entry isn't there. Before allocating a new entry, - * expand the table if necessary (and this changes the resulting - * bucket chain). - */ - if (t->numEntries >= rebuildLimit * t->size) - RebuildTable(t); - e = bmake_malloc(sizeof(*e) + keylen); - hp = &t->bucketPtr[h & t->mask]; - e->next = *hp; - *hp = e; - Hash_SetValue(e, NULL); - e->namehash = h; - (void)strcpy(e->name, p); + free(t->buckets); +#ifdef CLEANUP + t->buckets = NULL; +#endif +} + +/* Find the entry corresponding to the key, or return NULL. */ +HashEntry * +HashTable_FindEntry(HashTable *t, const char *key) +{ + unsigned int h = hash(key, NULL); + return HashTable_Find(t, h, key); +} + +/* Find the value corresponding to the key, or return NULL. */ +void * +HashTable_FindValue(HashTable *t, const char *key) +{ + HashEntry *he = HashTable_FindEntry(t, key); + return he != NULL ? he->value : NULL; +} + +/* + * Find the value corresponding to the key and the precomputed hash, + * or return NULL. + */ +void * +HashTable_FindValueHash(HashTable *t, const char *key, unsigned int h) +{ + HashEntry *he = HashTable_Find(t, h, key); + return he != NULL ? he->value : NULL; +} + +/* + * Make the hash table larger. Any bucket numbers from the old table become + * invalid; the hash codes stay valid though. + */ +static void +HashTable_Enlarge(HashTable *t) +{ + unsigned int oldSize = t->bucketsSize; + HashEntry **oldBuckets = t->buckets; + unsigned int newSize = 2 * oldSize; + unsigned int newMask = newSize - 1; + HashEntry **newBuckets = bmake_malloc(sizeof *newBuckets * newSize); + size_t i; + + for (i = 0; i < newSize; i++) + newBuckets[i] = NULL; + + for (i = 0; i < oldSize; i++) { + 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; + } + } + + free(oldBuckets); + + t->bucketsSize = newSize; + t->bucketsMask = newMask; + t->buckets = newBuckets; + DEBUG5(HASH, "%s: %p size=%d entries=%d maxchain=%d\n", + __func__, t, t->bucketsSize, t->numEntries, t->maxchain); + t->maxchain = 0; +} + +/* + * Find or create an entry corresponding to the key. + * Return in out_isNew whether a new entry has been created. + */ +HashEntry * +HashTable_CreateEntry(HashTable *t, const char *key, Boolean *out_isNew) +{ + size_t keylen; + unsigned int h = hash(key, &keylen); + HashEntry *he = HashTable_Find(t, h, key); + + if (he != NULL) { + if (out_isNew != NULL) + *out_isNew = FALSE; + return he; + } + + if (t->numEntries >= rebuildLimit * t->bucketsSize) + HashTable_Enlarge(t); + + he = bmake_malloc(sizeof *he + keylen); + he->value = NULL; + he->key_hash = h; + memcpy(he->key, key, keylen + 1); + + he->next = t->buckets[h & t->bucketsMask]; + t->buckets[h & t->bucketsMask] = he; t->numEntries++; - if (newPtr != NULL) - *newPtr = TRUE; - return (e); + if (out_isNew != NULL) + *out_isNew = TRUE; + return he; } -/* - *--------------------------------------------------------- - * - * Hash_DeleteEntry -- - * - * Delete the given hash table entry and free memory associated with - * it. - * - * Results: - * None. - * - * Side Effects: - * Hash chain that entry lives in is modified and memory is freed. - * - *--------------------------------------------------------- - */ - -void -Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e) +HashEntry * +HashTable_Set(HashTable *t, const char *key, void *value) { - Hash_Entry **hp, *p; + HashEntry *he = HashTable_CreateEntry(t, key, NULL); + HashEntry_Set(he, value); + return he; +} - if (e == NULL) - return; - for (hp = &t->bucketPtr[e->namehash & t->mask]; - (p = *hp) != NULL; hp = &p->next) { - if (p == e) { - *hp = p->next; +/* Delete the entry from the table and free the associated memory. */ +void +HashTable_DeleteEntry(HashTable *t, HashEntry *he) +{ + HashEntry **ref = &t->buckets[he->key_hash & t->bucketsMask]; + HashEntry *p; + + for (; (p = *ref) != NULL; ref = &p->next) { + if (p == he) { + *ref = p->next; free(p); t->numEntries--; return; } } - (void)write(2, "bad call to Hash_DeleteEntry\n", 29); abort(); } -/* - *--------------------------------------------------------- - * - * Hash_EnumFirst -- - * This procedure sets things up for a complete search - * of all entries recorded in the hash table. - * - * Input: - * t Table to be searched. - * searchPtr Area in which to keep state about search. - * - * Results: - * The return value is the address of the first entry in - * the hash table, or NULL if the table is empty. - * - * Side Effects: - * The information in searchPtr is initialized so that successive - * calls to Hash_Next will return successive HashEntry's - * from the table. - * - *--------------------------------------------------------- - */ - -Hash_Entry * -Hash_EnumFirst(Hash_Table *t, Hash_Search *searchPtr) +/* Set things up for iterating over all entries in the hash table. */ +void +HashIter_Init(HashIter *hi, HashTable *t) { - searchPtr->tablePtr = t; - searchPtr->nextIndex = 0; - searchPtr->hashEntryPtr = NULL; - return Hash_EnumNext(searchPtr); + hi->table = t; + hi->nextBucket = 0; + hi->entry = NULL; } /* - *--------------------------------------------------------- - * - * Hash_EnumNext -- - * This procedure returns successive entries in the hash table. - * - * Input: - * searchPtr Area used to keep state about search. - * - * Results: - * The return value is a pointer to the next HashEntry - * in the table, or NULL when the end of the table is - * reached. - * - * Side Effects: - * The information in searchPtr is modified to advance to the - * next entry. - * - *--------------------------------------------------------- + * Return the next entry in the hash table, or NULL if the end of the table + * is reached. */ - -Hash_Entry * -Hash_EnumNext(Hash_Search *searchPtr) +HashEntry * +HashIter_Next(HashIter *hi) { - Hash_Entry *e; - Hash_Table *t = searchPtr->tablePtr; + HashTable *t = hi->table; + HashEntry *he = hi->entry; + HashEntry **buckets = t->buckets; + unsigned int bucketsSize = t->bucketsSize; - /* - * The hashEntryPtr field points to the most recently returned - * entry, or is nil if we are starting up. If not nil, we have - * to start at the next one in the chain. - */ - e = searchPtr->hashEntryPtr; - if (e != NULL) - e = e->next; - /* - * If the chain ran out, or if we are starting up, we need to - * find the next nonempty chain. - */ - while (e == NULL) { - if (searchPtr->nextIndex >= t->size) + if (he != NULL) + he = he->next; /* skip the most recently returned entry */ + + while (he == NULL) { /* find the next nonempty chain */ + if (hi->nextBucket >= bucketsSize) return NULL; - e = t->bucketPtr[searchPtr->nextIndex++]; + he = buckets[hi->nextBucket++]; } - searchPtr->hashEntryPtr = e; - return (e); + hi->entry = he; + return he; } -/* - *--------------------------------------------------------- - * - * RebuildTable -- - * This local routine makes a new hash table that - * is larger than the old one. - * - * Results: - * None. - * - * Side Effects: - * The entire hash table is moved, so any bucket numbers - * from the old table are invalid. - * - *--------------------------------------------------------- - */ - -static void -RebuildTable(Hash_Table *t) +void +HashTable_DebugStats(HashTable *t, const char *name) { - Hash_Entry *e, *next = NULL, **hp, **xp; - int i, mask; - Hash_Entry **oldhp; - int oldsize; - - oldhp = t->bucketPtr; - oldsize = i = t->size; - i <<= 1; - t->size = i; - t->mask = mask = i - 1; - t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i); - while (--i >= 0) - *hp++ = NULL; - for (hp = oldhp, i = oldsize; --i >= 0;) { - for (e = *hp++; e != NULL; e = next) { - next = e->next; - xp = &t->bucketPtr[e->namehash & mask]; - e->next = *xp; - *xp = e; - } - } - free(oldhp); + DEBUG4(HASH, "HashTable %s: size=%u numEntries=%u maxchain=%u\n", + name, t->bucketsSize, t->numEntries, t->maxchain); } diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h index 31d2ff1ea..b101137aa 100644 --- a/usr.bin/make/hash.h +++ b/usr.bin/make/hash.h @@ -1,4 +1,4 @@ -/* $NetBSD: hash.h,v 1.10 2009/01/24 10:59:09 dsl Exp $ */ +/* $NetBSD: hash.h,v 1.38 2020/12/15 01:23:55 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -72,83 +72,99 @@ * from: @(#)hash.h 8.1 (Berkeley) 6/6/93 */ -/* hash.h -- - * - * This file contains definitions used by the hash module, - * which maintains hash tables. - */ +/* Hash tables with strings as keys and arbitrary pointers as values. */ -#ifndef _HASH -#define _HASH +#ifndef MAKE_HASH_H +#define MAKE_HASH_H -/* - * The following defines one entry in the hash table. - */ +/* A single key-value entry in the hash table. */ +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 */ + char key[1]; /* key string, variable length */ +} HashEntry; -typedef struct Hash_Entry { - struct Hash_Entry *next; /* Used to link together all the - * entries associated with the same - * bucket. */ - union { - void *clientPtr; /* Arbitrary pointer */ - time_t clientTime; /* Arbitrary Time */ - } clientInfo; - unsigned namehash; /* hash value of key */ - char name[1]; /* key string */ -} Hash_Entry; +/* The hash table containing the entries. */ +typedef struct HashTable { + HashEntry **buckets; /* Pointers to HashEntry, one + * for each bucket in the table. */ + unsigned int bucketsSize; + unsigned int numEntries; /* Number of entries in the table. */ + unsigned int bucketsMask; /* Used to select the bucket for a hash. */ + unsigned int maxchain; /* max length of chain detected */ +} HashTable; -typedef struct Hash_Table { - struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one - * for each bucket in the table. */ - int size; /* Actual size of array. */ - int numEntries; /* Number of entries in the table. */ - int mask; /* Used to select bits for hashing. */ -} Hash_Table; +/* State of an iteration over all entries in a table. */ +typedef struct HashIter { + HashTable *table; /* Table being searched. */ + unsigned int nextBucket; /* Next bucket to check (after current). */ + HashEntry *entry; /* Next entry to check in current bucket. */ +} HashIter; -/* - * The following structure is used by the searching routines - * to record where we are in the search. - */ +/* A set of strings. */ +typedef struct HashSet { + HashTable tbl; +} HashSet; -typedef struct Hash_Search { - Hash_Table *tablePtr; /* Table being searched. */ - int nextIndex; /* Next bucket to check (after current). */ - Hash_Entry *hashEntryPtr; /* Next entry to check in current bucket. */ -} Hash_Search; +MAKE_INLINE void * +HashEntry_Get(HashEntry *h) +{ + return h->value; +} -/* - * Macros. - */ +MAKE_INLINE void +HashEntry_Set(HashEntry *h, void *datum) +{ + h->value = datum; +} -/* - * void * Hash_GetValue(h) - * Hash_Entry *h; - */ +void HashTable_Init(HashTable *); +void HashTable_Done(HashTable *); +HashEntry *HashTable_FindEntry(HashTable *, const char *); +void *HashTable_FindValue(HashTable *, const char *); +unsigned int Hash_Hash(const char *); +void *HashTable_FindValueHash(HashTable *, const char *, unsigned int); +HashEntry *HashTable_CreateEntry(HashTable *, const char *, Boolean *); +HashEntry *HashTable_Set(HashTable *, const char *, void *); +void HashTable_DeleteEntry(HashTable *, HashEntry *); +void HashTable_DebugStats(HashTable *, const char *); -#define Hash_GetValue(h) ((h)->clientInfo.clientPtr) -#define Hash_GetTimeValue(h) ((h)->clientInfo.clientTime) +void HashIter_Init(HashIter *, HashTable *); +HashEntry *HashIter_Next(HashIter *); -/* - * Hash_SetValue(h, val); - * Hash_Entry *h; - * char *val; - */ +MAKE_INLINE void +HashSet_Init(HashSet *set) +{ + HashTable_Init(&set->tbl); +} -#define Hash_SetValue(h, val) ((h)->clientInfo.clientPtr = (val)) -#define Hash_SetTimeValue(h, val) ((h)->clientInfo.clientTime = (val)) +MAKE_INLINE void +HashSet_Done(HashSet *set) +{ + HashTable_Done(&set->tbl); +} -/* - * Hash_Size(n) returns the number of words in an object of n bytes - */ +MAKE_INLINE Boolean +HashSet_Add(HashSet *set, const char *key) +{ + Boolean isNew; -#define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int)) + (void)HashTable_CreateEntry(&set->tbl, key, &isNew); + return isNew; +} -void Hash_InitTable(Hash_Table *, int); -void Hash_DeleteTable(Hash_Table *); -Hash_Entry *Hash_FindEntry(Hash_Table *, const char *); -Hash_Entry *Hash_CreateEntry(Hash_Table *, const char *, Boolean *); -void Hash_DeleteEntry(Hash_Table *, Hash_Entry *); -Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *); -Hash_Entry *Hash_EnumNext(Hash_Search *); +MAKE_INLINE Boolean +HashSet_Contains(HashSet *set, const char *key) +{ + return HashTable_FindEntry(&set->tbl, key) != NULL; +} -#endif /* _HASH */ +MAKE_INLINE void +HashIter_InitSet(HashIter *hi, HashSet *set) +{ + HashIter_Init(hi, &set->tbl); +} + +#endif /* MAKE_HASH_H */ diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c index 1cd3b0c76..e9c6803a2 100644 --- a/usr.bin/make/job.c +++ b/usr.bin/make/job.c @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $ */ +/* $NetBSD: job.c,v 1.420 2021/02/05 22:15:44 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -69,68 +69,57 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- +/* * job.c -- * handle the creation etc. of our child processes. * * Interface: - * Job_Make Start the creation of the given target. + * Job_Init Called to initialize this module. In addition, + * the .BEGIN target is made including all of its + * dependencies before this function returns. + * Hence, the makefiles must have been parsed + * before this function is called. * - * Job_CatchChildren Check for and handle the termination of any - * children. This must be called reasonably - * frequently to keep the whole make going at - * a decent clip, since job table entries aren't - * removed until their process is caught this way. + * Job_End Clean up any memory used. * - * Job_CatchOutput Print any output our children have produced. - * Should also be called fairly frequently to - * keep the user informed of what's going on. - * If no output is waiting, it will block for - * a time given by the SEL_* constants, below, - * or until output is ready. + * Job_Make Start the creation of the given target. * - * Job_Init Called to intialize this module. in addition, - * any commands attached to the .BEGIN target - * are executed before this function returns. - * Hence, the makefile must have been parsed - * before this function is called. + * Job_CatchChildren + * Check for and handle the termination of any + * children. This must be called reasonably + * frequently to keep the whole make going at + * a decent clip, since job table entries aren't + * removed until their process is caught this way. * - * Job_End Cleanup any memory used. + * Job_CatchOutput + * Print any output our children have produced. + * Should also be called fairly frequently to + * keep the user informed of what's going on. + * If no output is waiting, it will block for + * a time given by the SEL_* constants, below, + * or until output is ready. * - * Job_ParseShell Given the line following a .SHELL target, parse - * the line as a shell specification. Returns - * FAILURE if the spec was incorrect. + * Job_ParseShell Given a special dependency line with target '.SHELL', + * define the shell that is used for the creation + * commands in jobs mode. * - * Job_Finish Perform any final processing which needs doing. - * This includes the execution of any commands - * which have been/were attached to the .END - * target. It should only be called when the - * job table is empty. + * Job_Finish Perform any final processing which needs doing. + * This includes the execution of any commands + * which have been/were attached to the .END + * target. It should only be called when the + * job table is empty. * - * Job_AbortAll Abort all currently running jobs. It doesn't - * handle output or do anything for the jobs, - * just kills them. It should only be called in - * an emergency, as it were. + * Job_AbortAll Abort all currently running jobs. Do not handle + * output or do anything for the jobs, just kill them. + * Should only be called in an emergency. * - * Job_CheckCommands Verify that the commands for a target are - * ok. Provide them if necessary and possible. + * Job_CheckCommands + * Verify that the commands for a target are + * ok. Provide them if necessary and possible. * - * Job_Touch Update a target without really updating it. + * Job_Touch Update a target without really updating it. * - * Job_Wait Wait for all currently-running jobs to finish. + * Job_Wait Wait for all currently-running jobs to finish. */ #include @@ -139,79 +128,159 @@ __RCSID("$NetBSD: job.c,v 1.180 2015/04/16 13:31:03 joerg Exp $"); #include #include -#include #include -#include #ifndef USE_SELECT #include #endif #include -#include -#include #include #include "make.h" -#include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" -# define STATIC static + +/* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: job.c,v 1.420 2021/02/05 22:15:44 sjg Exp $"); + +/* + * A shell defines how the commands are run. All commands for a target are + * written into a single file, which is then given to the shell to execute + * the commands from it. The commands are written to the file using a few + * templates for echo control and error control. + * + * The name of the shell is the basename for the predefined shells, such as + * "sh", "csh", "bash". For custom shells, it is the full pathname, and its + * basename is used to select the type of shell; the longest match wins. + * So /usr/pkg/bin/bash has type sh, /usr/local/bin/tcsh has type csh. + * + * The echoing of command lines is controlled using hasEchoCtl, echoOff, + * echoOn, noPrint and noPrintLen. When echoOff is executed by the shell, it + * still outputs something, but this something is not interesting, therefore + * it is filtered out using noPrint and noPrintLen. + * + * The error checking for individual commands is controlled using hasErrCtl, + * errOn, errOff and runChkTmpl. + * + * In case a shell doesn't have error control, echoTmpl is a printf template + * for echoing the command, should echoing be on; runIgnTmpl is another + * printf template for executing the command while ignoring the return + * status. Finally runChkTmpl is a printf template for running the command and + * causing the shell to exit on error. If any of these strings are empty when + * hasErrCtl is FALSE, the command will be executed anyway as is, and if it + * causes an error, so be it. Any templates set up to echo the command will + * escape any '$ ` \ "' characters in the command string to avoid unwanted + * shell code injection, the escaped command is safe to use in double quotes. + * + * The command-line flags "echo" and "exit" also control the behavior. The + * "echo" flag causes the shell to start echoing commands right away. The + * "exit" flag causes the shell to exit when an error is detected in one of + * the commands. + */ +typedef struct Shell { + + /* + * The name of the shell. For Bourne and C shells, this is used only + * to find the shell description when used as the single source of a + * .SHELL target. For user-defined shells, this is the full path of + * the shell. + */ + const char *name; + + Boolean hasEchoCtl; /* whether both echoOff and echoOn are there */ + const char *echoOff; /* command to turn echoing off */ + const char *echoOn; /* command to turn echoing back on */ + const char *noPrint; /* text to skip when printing output from the + * shell. This is usually the same as echoOff */ + size_t noPrintLen; /* length of noPrint command */ + + Boolean hasErrCtl; /* whether error checking can be controlled + * for individual commands */ + const char *errOn; /* command to turn on error checking */ + const char *errOff; /* command to turn off error checking */ + + const char *echoTmpl; /* template to echo a command */ + const char *runIgnTmpl; /* template to run a command + * without error checking */ + const char *runChkTmpl; /* template to run a command + * with error checking */ + + /* string literal that results in a newline character when it appears + * outside of any 'quote' or "quote" characters */ + const char *newline; + char commentChar; /* character used by shell for comment lines */ + + const char *echoFlag; /* shell flag to echo commands */ + const char *errFlag; /* shell flag to exit on error */ +} Shell; + +typedef struct CommandFlags { + /* Whether to echo the command before or instead of running it. */ + Boolean echo; + + /* Run the command even in -n or -N mode. */ + Boolean always; + + /* + * true if we turned error checking off before printing the command + * and need to turn it back on + */ + Boolean ignerr; +} CommandFlags; + +/* + * Write shell commands to a file. + * + * TODO: keep track of whether commands are echoed. + * TODO: keep track of whether error checking is active. + */ +typedef struct ShellWriter { + FILE *f; + + /* we've sent 'set -x' */ + Boolean xtraced; + +} ShellWriter; /* * error handling variables */ -static int errors = 0; /* number of errors reported */ -static int aborting = 0; /* why is the make aborting? */ -#define ABORT_ERROR 1 /* Because of an error */ -#define ABORT_INTERRUPT 2 /* Because it was interrupted */ -#define ABORT_WAIT 3 /* Waiting for jobs to finish */ -#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ +static int job_errors = 0; /* number of errors reported */ +typedef enum AbortReason { /* why is the make aborting? */ + ABORT_NONE, + ABORT_ERROR, /* Because of an error */ + ABORT_INTERRUPT, /* Because it was interrupted */ + ABORT_WAIT /* Waiting for jobs to finish */ + /* XXX: "WAIT" is not a _reason_ for aborting, it's rather a status. */ +} AbortReason; +static AbortReason 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. */ int jobTokensRunning = 0; -int not_parallel = 0; /* set if .NOT_PARALLEL */ -/* - * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file - * is a char! So when we go above 127 we turn negative! - */ -#define FILENO(a) ((unsigned) fileno(a)) - -/* - * post-make command processing. The node postCommands is really just the - * .END target but we keep it around to avoid having to search for it - * all the time. - */ -static GNode *postCommands = NULL; - /* node containing commands to execute when - * everything else is done */ -static int numCommands; /* The number of commands actually printed - * for a target. Should this number be - * 0, no shell will be executed. */ - -/* - * Return values from JobStart. - */ -#define JOB_RUNNING 0 /* Job is running */ -#define JOB_ERROR 1 /* Error in starting the job */ -#define JOB_FINISHED 2 /* The job is already finished */ +typedef enum JobStartResult { + JOB_RUNNING, /* Job is running */ + JOB_ERROR, /* Error in starting the job */ + JOB_FINISHED /* The job is already finished */ +} JobStartResult; /* * Descriptions for various shells. * * The build environment may set DEFSHELL_INDEX to one of * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to - * select one of the prefedined shells as the default shell. + * select one of the predefined shells as the default shell. * * Alternatively, the build environment may set DEFSHELL_CUSTOM to the * name or the full path of a sh-compatible shell, which will be used as * the default shell. * * ".SHELL" lines in Makefiles can choose the default shell from the - # set defined here, or add additional shells. + * set defined here, or add additional shells. */ #ifdef DEFSHELL_CUSTOM @@ -229,7 +298,7 @@ static int numCommands; /* The number of commands actually printed #define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */ #endif /* !DEFSHELL_INDEX */ -static Shell shells[] = { +static Shell shells[] = { #ifdef DEFSHELL_CUSTOM /* * An sh-compatible shell with a non-standard name. @@ -238,141 +307,213 @@ static Shell shells[] = { * non-portable features that might not be supplied by all * sh-compatible shells. */ -{ - DEFSHELL_CUSTOM, - FALSE, "", "", "", 0, - FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', - "", - "", -}, + { + DEFSHELL_CUSTOM, /* .name */ + FALSE, /* .hasEchoCtl */ + "", /* .echoOff */ + "", /* .echoOn */ + "", /* .noPrint */ + 0, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "%s\n", /* .runIgnTmpl */ + "{ %s \n} || exit $?\n", /* .runChkTmpl */ + "'\n'", /* .newline */ + '#', /* .commentChar */ + "", /* .echoFlag */ + "", /* .errFlag */ + }, #endif /* DEFSHELL_CUSTOM */ /* * SH description. Echo control is also possible and, under * sun UNIX anyway, one can even control error checking. */ -{ - "sh", - FALSE, "", "", "", 0, - FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', + { + "sh", /* .name */ + FALSE, /* .hasEchoCtl */ + "", /* .echoOff */ + "", /* .echoOn */ + "", /* .noPrint */ + 0, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "%s\n", /* .runIgnTmpl */ + "{ %s \n} || exit $?\n", /* .runChkTmpl */ + "'\n'", /* .newline */ + '#', /* .commentChar*/ #if defined(MAKE_NATIVE) && defined(__NetBSD__) - "q", + /* XXX: -q is not really echoFlag, it's more like noEchoInSysFlag. */ + "q", /* .echoFlag */ #else - "", + "", /* .echoFlag */ #endif - "", -}, + "", /* .errFlag */ + }, /* - * KSH description. + * KSH description. */ -{ - "ksh", - TRUE, "set +v", "set -v", "set +v", 6, - FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', - "v", - "", -}, + { + "ksh", /* .name */ + TRUE, /* .hasEchoCtl */ + "set +v", /* .echoOff */ + "set -v", /* .echoOn */ + "set +v", /* .noPrint */ + 6, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "%s\n", /* .runIgnTmpl */ + "{ %s \n} || exit $?\n", /* .runChkTmpl */ + "'\n'", /* .newline */ + '#', /* .commentChar */ + "v", /* .echoFlag */ + "", /* .errFlag */ + }, /* * CSH description. The csh can do echo control by playing * with the setting of the 'echo' shell variable. Sadly, * however, it is unable to do error control nicely. */ -{ - "csh", - TRUE, "unset verbose", "set verbose", "unset verbose", 10, - FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#', - "v", "e", -}, - /* - * UNKNOWN. - */ -{ - NULL, - FALSE, NULL, NULL, NULL, 0, - FALSE, NULL, NULL, NULL, NULL, 0, - NULL, NULL, -} + { + "csh", /* .name */ + TRUE, /* .hasEchoCtl */ + "unset verbose", /* .echoOff */ + "set verbose", /* .echoOn */ + "unset verbose", /* .noPrint */ + 13, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "csh -c \"%s || exit 0\"\n", /* .runIgnTmpl */ + "", /* .runChkTmpl */ + "'\\\n'", /* .newline */ + '#', /* .commentChar */ + "v", /* .echoFlag */ + "e", /* .errFlag */ + } }; -static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to - * which we pass all - * commands in the Makefile. - * It is set by the - * Job_ParseShell function */ -const char *shellPath = NULL, /* full pathname of - * executable image */ - *shellName = NULL; /* last component of shell */ + +/* + * This is the shell to which we pass all commands in the Makefile. + * It is set by the Job_ParseShell function. + */ +static Shell *shell = &shells[DEFSHELL_INDEX]; +const char *shellPath = NULL; /* full pathname of executable image */ +const char *shellName = NULL; /* last component of shellPath */ char *shellErrFlag = NULL; -static const char *shellArgv = NULL; /* Custom shell args */ +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 int wantToken; /* we want a token */ -static int lurking_children = 0; -static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ +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 Boolean lurking_children = FALSE; +static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */ /* * Set of descriptors of pipes connected to * the output channels of children */ static struct pollfd *fds = NULL; -static Job **jobfds = NULL; -static int nfds = 0; +static Job **jobByFdIndex = NULL; +static nfds_t fdsLen = 0; static void watchfd(Job *); static void clearfd(Job *); -static int readyfd(Job *); +static Boolean readyfd(Job *); -STATIC GNode *lastNode; /* The node for which output was most recently - * produced. */ -static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */ +static char *targPrefix = NULL; /* To identify a job change in the output. */ static Job tokenWaitJob; /* token wait pseudo-job */ static Job childExitJob; /* child exit pseudo-job */ -#define CHILD_EXIT "." -#define DO_JOB_RESUME "R" +#define CHILD_EXIT "." +#define DO_JOB_RESUME "R" -#define TARG_FMT "%s %s ---\n" /* Default format */ -#define MESSAGE(fp, gn) \ - if (maxJobs != 1 && targPrefix && *targPrefix) \ - (void)fprintf(fp, TARG_FMT, targPrefix, gn->name) +enum { + npseudojobs = 2 /* number of pseudo-jobs */ +}; static sigset_t caught_signals; /* Set of signals we handle */ -#if defined(SYSV) -#define KILLPG(pid, sig) kill(-(pid), (sig)) -#else -#define KILLPG(pid, sig) killpg((pid), (sig)) -#endif +static volatile sig_atomic_t caught_sigchld; -static void JobChildSig(int); -static void JobContinueSig(int); -static Job *JobFindPid(int, int, Boolean); -static int JobPrintCommand(void *, void *); -static int JobSaveCommand(void *, void *); -static void JobClose(Job *); -static void JobExec(Job *, char **); -static void JobMakeArgv(Job *, char **); -static int JobStart(GNode *, int); -static char *JobOutput(Job *, char *, char *, int); static void JobDoOutput(Job *, Boolean); -static Shell *JobMatchShell(const char *); -static void JobInterrupt(int, int) MAKE_ATTR_DEAD; +static void JobInterrupt(Boolean, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); -static void JobTokenAdd(void); -static void JobSigLock(sigset_t *); -static void JobSigUnlock(sigset_t *); static void JobSigReset(void); -const char *malloc_options="A"; +static void +SwitchOutputTo(GNode *gn) +{ + /* The node for which output was most recently produced. */ + static GNode *lastNode = NULL; + + if (gn == lastNode) + return; + lastNode = gn; + + if (opts.maxJobs != 1 && targPrefix != NULL && targPrefix[0] != '\0') + (void)fprintf(stdout, "%s %s ---\n", targPrefix, gn->name); +} + +static unsigned +nfds_per_job(void) +{ +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + if (useMeta) + return 2; +#endif + return 1; +} + +void +Job_FlagsToString(const Job *job, char *buf, size_t bufsize) +{ + snprintf(buf, bufsize, "%c%c%c", + job->ignerr ? 'i' : '-', + !job->echo ? 's' : '-', + job->special ? 'S' : '-'); +} static void job_table_dump(const char *where) { - Job *job; + Job *job; + char flags[4]; - fprintf(debug_file, "job table @ %s\n", where); - for (job = job_table; job < job_table_end; job++) { - fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n", - (int)(job - job_table), job->job_state, job->flags, job->pid); - } + debug_printf("job table @ %s\n", where); + for (job = job_table; job < job_table_end; job++) { + Job_FlagsToString(job, flags, sizeof flags); + debug_printf("job %d, status %d, flags %s, pid %d\n", + (int)(job - job_table), job->status, flags, job->pid); + } +} + +/* + * Delete the target of a failed, interrupted, or otherwise + * unsuccessful job unless inhibited by .PRECIOUS. + */ +static void +JobDeleteTarget(GNode *gn) +{ + const char *file; + + if (gn->type & OP_JOIN) + return; + if (gn->type & OP_PHONY) + return; + if (Targ_Precious(gn)) + return; + if (opts.noExecute) + return; + + file = GNode_Path(gn); + if (eunlink(file) != -1) + Error("*** %s removed", file); } /* @@ -381,7 +522,8 @@ job_table_dump(const char *where) * Signal lock routines to get exclusive access. Currently used to * protect `jobs' and `stoppedJobs' list manipulations. */ -static void JobSigLock(sigset_t *omaskp) +static void +JobSigLock(sigset_t *omaskp) { if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) { Punt("JobSigLock: sigprocmask: %s", strerror(errno)); @@ -389,7 +531,8 @@ static void JobSigLock(sigset_t *omaskp) } } -static void JobSigUnlock(sigset_t *omaskp) +static void +JobSigUnlock(sigset_t *omaskp) { (void)sigprocmask(SIG_SETMASK, omaskp, NULL); } @@ -397,793 +540,748 @@ static void JobSigUnlock(sigset_t *omaskp) static void JobCreatePipe(Job *job, int minfd) { - int i, fd; + int i, fd, flags; + int pipe_fds[2]; - if (pipe(job->jobPipe) == -1) - Punt("Cannot create pipe: %s", strerror(errno)); + if (pipe(pipe_fds) == -1) + Punt("Cannot create pipe: %s", strerror(errno)); - for (i = 0; i < 2; i++) { - /* Avoid using low numbered fds */ - fd = fcntl(job->jobPipe[i], F_DUPFD, minfd); - if (fd != -1) { - close(job->jobPipe[i]); - job->jobPipe[i] = fd; - } - } - - /* Set close-on-exec flag for both */ - (void)fcntl(job->jobPipe[0], F_SETFD, 1); - (void)fcntl(job->jobPipe[1], F_SETFD, 1); + for (i = 0; i < 2; i++) { + /* Avoid using low numbered fds */ + fd = fcntl(pipe_fds[i], F_DUPFD, minfd); + if (fd != -1) { + close(pipe_fds[i]); + pipe_fds[i] = fd; + } + } - /* - * We mark the input side of the pipe non-blocking; we poll(2) the - * pipe when we're waiting for a job token, but we might lose the - * race for the token when a new one becomes available, so the read - * from the pipe should not block. - */ - fcntl(job->jobPipe[0], F_SETFL, - fcntl(job->jobPipe[0], F_GETFL, 0) | O_NONBLOCK); + 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) + Punt("Cannot set close-on-exec: %s", strerror(errno)); + + /* + * We mark the input side of the pipe non-blocking; we poll(2) the + * pipe when we're waiting for a job token, but we might lose the + * race for the token when a new one becomes available, so the read + * from the pipe should not block. + */ + flags = fcntl(job->inPipe, F_GETFL, 0); + if (flags == -1) + Punt("Cannot get flags: %s", strerror(errno)); + flags |= O_NONBLOCK; + if (fcntl(job->inPipe, F_SETFL, flags) == -1) + Punt("Cannot set flags: %s", strerror(errno)); } -/*- - *----------------------------------------------------------------------- - * JobCondPassSig -- - * Pass a signal to a job - * - * Input: - * signop Signal to send it - * - * Side Effects: - * None, except the job may bite it. - * - *----------------------------------------------------------------------- - */ +/* Pass the signal to each running job. */ static void JobCondPassSig(int signo) { - Job *job; + Job *job; - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo); - } + DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); - for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) - continue; - if (DEBUG(JOB)) { - (void)fprintf(debug_file, - "JobCondPassSig passing signal %d to child %d.\n", - signo, job->pid); + for (job = job_table; job < job_table_end; job++) { + if (job->status != JOB_ST_RUNNING) + continue; + DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", + signo, job->pid); + KILLPG(job->pid, signo); } - KILLPG(job->pid, signo); - } } -/*- - *----------------------------------------------------------------------- - * JobChldSig -- - * SIGCHLD handler. +/* + * SIGCHLD handler. * - * Input: - * signo The signal number we've received - * - * Results: - * None. - * - * Side Effects: - * Sends a token on the child exit pipe to wake us up from - * select()/poll(). - * - *----------------------------------------------------------------------- + * Sends a token on the child exit pipe to wake us up from select()/poll(). */ +/*ARGSUSED*/ static void JobChildSig(int signo MAKE_ATTR_UNUSED) { - while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN) - continue; + caught_sigchld = 1; + while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && + errno == EAGAIN) + continue; } -/*- - *----------------------------------------------------------------------- - * JobContinueSig -- - * Resume all stopped jobs. - * - * Input: - * signo The signal number we've received - * - * Results: - * None. - * - * Side Effects: - * Jobs start running again. - * - *----------------------------------------------------------------------- - */ +/* Resume all stopped jobs. */ +/*ARGSUSED*/ static void JobContinueSig(int signo MAKE_ATTR_UNUSED) { - /* - * Defer sending to SIGCONT to our stopped children until we return - * from the signal handler. - */ - while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && - errno == EAGAIN) - continue; + /* + * Defer sending SIGCONT to our stopped children until we return + * from the signal handler. + */ + while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && + errno == EAGAIN) + continue; } -/*- - *----------------------------------------------------------------------- - * JobPassSig -- - * Pass a signal on to all jobs, then resend to ourselves. - * - * Input: - * signo The signal number we've received - * - * Results: - * None. - * - * Side Effects: - * We die by the same signal. - * - *----------------------------------------------------------------------- +/* + * Pass a signal on to all jobs, then resend to ourselves. + * We die by the same signal. */ MAKE_ATTR_DEAD static void JobPassSig_int(int signo) { - /* Run .INTERRUPT target then exit */ - JobInterrupt(TRUE, signo); + /* Run .INTERRUPT target then exit */ + JobInterrupt(TRUE, signo); } +/* + * Pass a signal on to all jobs, then resend to ourselves. + * We die by the same signal. + */ MAKE_ATTR_DEAD static void JobPassSig_term(int signo) { - /* Dont run .INTERRUPT target then exit */ - JobInterrupt(FALSE, signo); + /* Dont run .INTERRUPT target then exit */ + JobInterrupt(FALSE, signo); } static void JobPassSig_suspend(int signo) { - sigset_t nmask, omask; - struct sigaction act; + sigset_t nmask, omask; + struct sigaction act; - /* Suppress job started/continued messages */ - make_suspended = 1; + /* Suppress job started/continued messages */ + make_suspended = TRUE; - /* Pass the signal onto every job */ - JobCondPassSig(signo); + /* Pass the signal onto every job */ + JobCondPassSig(signo); - /* - * Send ourselves the signal now we've given the message to everyone else. - * Note we block everything else possible while we're getting the signal. - * This ensures that all our jobs get continued when we wake up before - * we take any other signal. - */ - sigfillset(&nmask); - sigdelset(&nmask, signo); - (void)sigprocmask(SIG_SETMASK, &nmask, &omask); + /* + * Send ourselves the signal now we've given the message to everyone + * else. Note we block everything else possible while we're getting + * the signal. This ensures that all our jobs get continued when we + * wake up before we take any other signal. + */ + sigfillset(&nmask); + sigdelset(&nmask, signo); + (void)sigprocmask(SIG_SETMASK, &nmask, &omask); - act.sa_handler = SIG_DFL; - sigemptyset(&act.sa_mask); - act.sa_flags = 0; - (void)sigaction(signo, &act, NULL); + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + (void)sigaction(signo, &act, NULL); - if (DEBUG(JOB)) { - (void)fprintf(debug_file, - "JobPassSig passing signal %d to self.\n", signo); - } + DEBUG1(JOB, "JobPassSig passing signal %d to self.\n", signo); - (void)kill(getpid(), signo); + (void)kill(getpid(), signo); - /* - * We've been continued. - * - * A whole host of signals continue 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. - * - * Since we defer passing the SIGCONT on to our children until - * the main processing loop, we can be sure that all the SIGCHLD - * events will have happened by then - and that the waitpid() will - * collect the child 'suspended' events. - * For correct sequencing we just need to ensure we process the - * waitpid() before passign on the SIGCONT. - * - * In any case nothing else is needed here. - */ + /* + * We've been continued. + * + * A whole host of signals continue 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. + * + * Since we defer passing the SIGCONT on to our children until + * the main processing loop, we can be sure that all the SIGCHLD + * events will have happened by then - and that the waitpid() will + * collect the child 'suspended' events. + * For correct sequencing we just need to ensure we process the + * waitpid() before passing on the SIGCONT. + * + * In any case nothing else is needed here. + */ - /* Restore handler and signal mask */ - act.sa_handler = JobPassSig_suspend; - (void)sigaction(signo, &act, NULL); - (void)sigprocmask(SIG_SETMASK, &omask, NULL); + /* Restore handler and signal mask */ + act.sa_handler = JobPassSig_suspend; + (void)sigaction(signo, &act, NULL); + (void)sigprocmask(SIG_SETMASK, &omask, NULL); } -/*- - *----------------------------------------------------------------------- - * JobFindPid -- - * Compare the pid of the job with the given pid and return 0 if they - * are equal. This function is called from Job_CatchChildren - * to find the job descriptor of the finished job. - * - * Input: - * job job to examine - * pid process id desired - * - * Results: - * Job with matching pid - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ static Job * -JobFindPid(int pid, int status, Boolean isJobs) +JobFindPid(int pid, JobStatus status, Boolean isJobs) { - Job *job; + Job *job; - for (job = job_table; job < job_table_end; job++) { - if ((job->job_state == status) && job->pid == pid) - return job; - } - if (DEBUG(JOB) && isJobs) - job_table_dump("no pid"); - return NULL; + for (job = job_table; job < job_table_end; job++) { + if (job->status == status && job->pid == pid) + return job; + } + if (DEBUG(JOB) && isJobs) + job_table_dump("no pid"); + return NULL; } -/*- - *----------------------------------------------------------------------- - * JobPrintCommand -- - * Put out another command for the given job. If the command starts - * with an @ or a - we process it specially. In the former case, - * so long as the -s and -n flags weren't given to make, we stick - * a shell-specific echoOff command in the script. In the latter, - * we ignore errors for the entire job, unless the shell has error - * control. - * If the command is just "..." we take all future commands for this - * job to be commands to be executed once the entire graph has been - * made and return non-zero to signal that the end of the commands - * was reached. These commands are later attached to the postCommands - * node and executed by Job_End when all things are done. - * This function is called from JobStart via Lst_ForEach. - * - * Input: - * cmdp command string to print - * jobp job for which to print it - * - * Results: - * Always 0, unless the command was "..." - * - * Side Effects: - * If the command begins with a '-' and the shell has no error control, - * the JOB_IGNERR flag is set in the job descriptor. - * If the command is "..." and we're not ignoring such things, - * tailCmds is set to the successor node of the cmd. - * numCommands is incremented if the command is actually printed. - *----------------------------------------------------------------------- - */ -static int -JobPrintCommand(void *cmdp, void *jobp) +/* Parse leading '@', '-' and '+', which control the exact execution mode. */ +static void +ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags) { - Boolean noSpecials; /* true if we shouldn't worry about - * inserting special commands into - * the input stream. */ - Boolean shutUp = FALSE; /* true if we put a no echo command - * into the command file */ - Boolean errOff = FALSE; /* true if we turned error checking - * off before printing the command - * and need to turn it back on */ - const char *cmdTemplate; /* Template to use when printing the - * command */ - char *cmdStart; /* Start of expanded command */ - char *escCmd = NULL; /* Command with quotes/backticks escaped */ - char *cmd = (char *)cmdp; - Job *job = (Job *)jobp; - int i, j; + char *p = *pp; + out_cmdFlags->echo = TRUE; + out_cmdFlags->ignerr = FALSE; + out_cmdFlags->always = FALSE; - noSpecials = NoExecute(job->node); - - if (strcmp(cmd, "...") == 0) { - job->node->type |= OP_SAVE_CMDS; - if ((job->flags & JOB_IGNDOTS) == 0) { - job->tailCmds = Lst_Succ(Lst_Member(job->node->commands, - cmd)); - return 1; + for (;;) { + if (*p == '@') + out_cmdFlags->echo = DEBUG(LOUD); + else if (*p == '-') + out_cmdFlags->ignerr = TRUE; + else if (*p == '+') + out_cmdFlags->always = TRUE; + else + break; + p++; } - return 0; - } -#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \ - (void)fprintf(debug_file, fmt, arg); \ - } \ - (void)fprintf(job->cmdFILE, fmt, arg); \ - (void)fflush(job->cmdFILE); + pp_skip_whitespace(&p); - numCommands += 1; + *pp = p; +} - cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE); +/* Escape a string for a double-quoted string literal in sh, csh and ksh. */ +static char * +EscapeShellDblQuot(const char *cmd) +{ + size_t i, j; - cmdTemplate = "%s\n"; + /* Worst that could happen is every char needs escaping. */ + char *esc = bmake_malloc(strlen(cmd) * 2 + 1); + for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) { + if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || + cmd[i] == '"') + esc[j++] = '\\'; + esc[j] = cmd[i]; + } + esc[j] = '\0'; - /* - * Check for leading @' and -'s to control echoing and error checking. - */ - while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) { - switch (*cmd) { - case '@': - shutUp = DEBUG(LOUD) ? FALSE : TRUE; - break; - case '-': - errOff = TRUE; - break; - case '+': - if (noSpecials) { + return esc; +} + +static void +ShellWriter_PrintFmt(ShellWriter *wr, const char *fmt, const char *arg) +{ + DEBUG1(JOB, fmt, arg); + + (void)fprintf(wr->f, fmt, arg); + /* XXX: Is flushing needed in any case, or only if f == stdout? */ + (void)fflush(wr->f); +} + +static void +ShellWriter_Println(ShellWriter *wr, const char *line) +{ + ShellWriter_PrintFmt(wr, "%s\n", line); +} + +static void +ShellWriter_EchoOff(ShellWriter *wr) +{ + if (shell->hasEchoCtl) + ShellWriter_Println(wr, shell->echoOff); +} + +static void +ShellWriter_EchoCmd(ShellWriter *wr, const char *escCmd) +{ + ShellWriter_PrintFmt(wr, shell->echoTmpl, escCmd); +} + +static void +ShellWriter_EchoOn(ShellWriter *wr) +{ + if (shell->hasEchoCtl) + ShellWriter_Println(wr, shell->echoOn); +} + +static void +ShellWriter_TraceOn(ShellWriter *wr) +{ + if (!wr->xtraced) { + ShellWriter_Println(wr, "set -x"); + wr->xtraced = TRUE; + } +} + +static void +ShellWriter_ErrOff(ShellWriter *wr, Boolean echo) +{ + if (echo) + ShellWriter_EchoOff(wr); + ShellWriter_Println(wr, shell->errOff); + if (echo) + ShellWriter_EchoOn(wr); +} + +static void +ShellWriter_ErrOn(ShellWriter *wr, Boolean echo) +{ + if (echo) + ShellWriter_EchoOff(wr); + ShellWriter_Println(wr, shell->errOn); + if (echo) + ShellWriter_EchoOn(wr); +} + +/* + * The shell has no built-in error control, so emulate error control by + * enclosing each shell command in a template like "{ %s \n } || exit $?" + * (configurable per shell). + */ +static void +JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, + const char *escCmd, const char **inout_cmdTemplate) +{ + /* XXX: Why is the job modified at this point? */ + job->ignerr = TRUE; + + if (job->echo && inout_cmdFlags->echo) { + ShellWriter_EchoOff(wr); + ShellWriter_EchoCmd(wr, escCmd); + + /* + * Leave echoing off so the user doesn't see the commands + * for toggling the error checking. + */ + inout_cmdFlags->echo = FALSE; + } else { + if (inout_cmdFlags->echo) + ShellWriter_EchoCmd(wr, escCmd); + } + *inout_cmdTemplate = shell->runIgnTmpl; + + /* + * The template runIgnTmpl already takes care of ignoring errors, + * so pretend error checking is still on. + * XXX: What effects does this have, and why is it necessary? + */ + inout_cmdFlags->ignerr = FALSE; +} + +static void +JobPrintSpecials(Job *job, ShellWriter *wr, const char *escCmd, Boolean 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. + */ + inout_cmdFlags->ignerr = FALSE; + } else if (shell->hasErrCtl) + ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo); + else if (shell->runIgnTmpl != NULL && shell->runIgnTmpl[0] != '\0') { + JobPrintSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, + inout_cmdTemplate); + } else + inout_cmdFlags->ignerr = FALSE; +} + +/* + * Put out another command for the given job. + * + * If the command starts with '@' and neither the -s nor the -n flag was + * given to make, we stick a shell-specific echoOff command in the script. + * + * If the command starts with '-' and the shell has no error control (none + * of the predefined shells has that), we ignore errors for the entire job. + * XXX: Why ignore errors for the entire job? + * XXX: Even ignore errors for the commands before this command? + * + * If the command is just "...", all further commands of this job are skipped + * for now. They are attached to the .END node and will be run by Job_Finish + * after all other targets have been made. + */ +static void +JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) +{ + Boolean run; + + CommandFlags cmdFlags; + /* Template for printing a command to the shell file */ + const char *cmdTemplate; + char *xcmd; /* The expanded command */ + char *xcmdStart; + char *escCmd; /* xcmd escaped to be used in double quotes */ + + run = GNode_ShouldExecute(job->node); + + Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd); + /* TODO: handle errors */ + xcmdStart = xcmd; + + cmdTemplate = "%s\n"; + + ParseCommandFlags(&xcmd, &cmdFlags); + + /* The '+' command flag overrides the -n or -N options. */ + if (cmdFlags.always && !run) { /* * We're not actually executing anything... * but this one needs to be - use compat mode just for it. */ - CompatRunCommand(cmdp, job->node); - return 0; - } - break; + Compat_RunCommand(ucmd, job->node, ln); + free(xcmdStart); + return; } - cmd++; - } - while (isspace((unsigned char) *cmd)) - cmd++; - - /* - * If the shell doesn't have error control the alternate echo'ing will - * be done (to avoid showing additional error checking code) - * and this will need the characters '$ ` \ "' escaped - */ - - if (!commandShell->hasErrCtl) { - /* Worst that could happen is every char needs escaping. */ - escCmd = bmake_malloc((strlen(cmd) * 2) + 1); - for (i = 0, j= 0; cmd[i] != '\0'; i++, j++) { - if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || - cmd[i] == '"') - escCmd[j++] = '\\'; - escCmd[j] = cmd[i]; - } - escCmd[j] = 0; - } - - if (shutUp) { - if (!(job->flags & JOB_SILENT) && !noSpecials && - commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - } else { - if (commandShell->hasErrCtl) - shutUp = FALSE; - } - } - - if (errOff) { - if (!noSpecials) { - if (commandShell->hasErrCtl) { - /* - * we don't want the error-control commands showing - * up either, so we turn off echoing while executing - * them. We could put another field in the shell - * structure to tell JobDoOutput to look for this - * string too, but why make it any more complex than - * it already is? - */ - if (!(job->flags & JOB_SILENT) && !shutUp && - commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - DBPRINTF("%s\n", commandShell->ignErr); - DBPRINTF("%s\n", commandShell->echoOn); - } else { - DBPRINTF("%s\n", commandShell->ignErr); - } - } else if (commandShell->ignErr && - (*commandShell->ignErr != '\0')) - { - /* - * The shell has no error control, so we need to be - * weird to get it to ignore any errors from the command. - * If echoing is turned on, we turn it off and use the - * errCheck template to echo the command. Leave echoing - * off so the user doesn't see the weirdness we go through - * to ignore errors. Set cmdTemplate to use the weirdness - * instead of the simple "%s\n" template. - */ - job->flags |= JOB_IGNERR; - if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - } - DBPRINTF(commandShell->errCheck, escCmd); - shutUp = TRUE; - } else { - if (!shutUp) { - DBPRINTF(commandShell->errCheck, escCmd); - } - } - cmdTemplate = commandShell->ignErr; - /* - * The error ignoration (hee hee) is already taken care - * of by the ignErr template, so pretend error checking - * is still on. - */ - errOff = FALSE; - } else { - errOff = FALSE; - } - } else { - errOff = FALSE; - } - } else { - - /* - * If errors are being checked and the shell doesn't have error control - * but does supply an errOut template, then setup commands to run - * through it. - */ - - if (!commandShell->hasErrCtl && commandShell->errOut && - (*commandShell->errOut != '\0')) { - if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOff); - } - DBPRINTF(commandShell->errCheck, escCmd); - shutUp = TRUE; - } - /* If it's a comment line or blank, treat as an ignored error */ - if ((escCmd[0] == commandShell->commentChar) || - (escCmd[0] == 0)) - cmdTemplate = commandShell->ignErr; - else - cmdTemplate = commandShell->errOut; - errOff = FALSE; - } - } - - if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && - (job->flags & JOB_TRACED) == 0) { - DBPRINTF("set -%s\n", "x"); - job->flags |= JOB_TRACED; - } - - DBPRINTF(cmdTemplate, cmd); - free(cmdStart); - if (escCmd) - free(escCmd); - if (errOff) { /* - * If echoing is already off, there's no point in issuing the - * echoOff command. Otherwise we issue it and pretend it was on - * for the whole command... + * If the shell doesn't have error control, the alternate echoing + * will be done (to avoid showing additional error checking code) + * and this needs some characters escaped. */ - if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){ - DBPRINTF("%s\n", commandShell->echoOff); - shutUp = TRUE; + escCmd = shell->hasErrCtl ? NULL : EscapeShellDblQuot(xcmd); + + if (!cmdFlags.echo) { + if (job->echo && run && shell->hasEchoCtl) { + ShellWriter_EchoOff(wr); + } else { + if (shell->hasErrCtl) + cmdFlags.echo = TRUE; + } } - DBPRINTF("%s\n", commandShell->errCheck); - } - if (shutUp && commandShell->hasEchoCtl) { - DBPRINTF("%s\n", commandShell->echoOn); - } - return 0; + + if (cmdFlags.ignerr) { + JobPrintSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); + } else { + + /* + * If errors are being checked and the shell doesn't have + * error control but does supply an runChkTmpl template, then + * set up commands to run through it. + */ + + if (!shell->hasErrCtl && shell->runChkTmpl != NULL && + shell->runChkTmpl[0] != '\0') { + if (job->echo && cmdFlags.echo) { + ShellWriter_EchoOff(wr); + ShellWriter_EchoCmd(wr, escCmd); + cmdFlags.echo = FALSE; + } + /* + * If it's a comment line or blank, avoid the possible + * syntax error generated by "{\n} || exit $?". + */ + cmdTemplate = escCmd[0] == shell->commentChar || + escCmd[0] == '\0' + ? shell->runIgnTmpl + : shell->runChkTmpl; + cmdFlags.ignerr = FALSE; + } + } + + if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0) + ShellWriter_TraceOn(wr); + + ShellWriter_PrintFmt(wr, cmdTemplate, xcmd); + free(xcmdStart); + free(escCmd); + + if (cmdFlags.ignerr) + ShellWriter_ErrOn(wr, cmdFlags.echo && job->echo); + + if (!cmdFlags.echo) + ShellWriter_EchoOn(wr); } -/*- - *----------------------------------------------------------------------- - * JobSaveCommand -- - * Save a command to be executed when everything else is done. - * Callback function for JobFinish... +/* + * Print all commands to the shell file that is later executed. * - * Results: - * Always returns 0 + * The special command "..." stops printing and saves the remaining commands + * to be executed later, when the target '.END' is made. * - * Side Effects: - * The command is tacked onto the end of postCommands's commands list. - * - *----------------------------------------------------------------------- + * Return whether at least one command was written to the shell file. */ -static int -JobSaveCommand(void *cmd, void *gn) +static Boolean +JobPrintCommands(Job *job) { - cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, FALSE); - (void)Lst_AtEnd(postCommands->commands, cmd); - return(0); + StringListNode *ln; + Boolean seen = FALSE; + ShellWriter wr = { job->cmdFILE, FALSE }; + + for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + + if (strcmp(cmd, "...") == 0) { + job->node->type |= OP_SAVE_CMDS; + job->tailCmds = ln->next; + break; + } + + JobPrintCommand(job, &wr, ln, ln->datum); + seen = TRUE; + } + + return seen; } - -/*- - *----------------------------------------------------------------------- - * JobClose -- - * Called to close both input and output pipes when a job is finished. - * - * Results: - * Nada - * - * Side Effects: - * The file descriptors associated with the job are closed. - * - *----------------------------------------------------------------------- +/* + * Save the delayed commands (those after '...'), to be executed later in + * the '.END' node, when everything else is done. */ static void -JobClose(Job *job) +JobSaveCommands(Job *job) { - clearfd(job); - (void)close(job->outPipe); - job->outPipe = -1; + StringListNode *ln; - JobDoOutput(job, TRUE); - (void)close(job->inPipe); - job->inPipe = -1; + for (ln = job->tailCmds; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + char *expanded_cmd; + /* + * XXX: This Var_Subst is only intended to expand the dynamic + * variables such as .TARGET, .IMPSRC. It is not intended to + * expand the other variables as well; see deptgt-end.mk. + */ + (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd); + /* TODO: handle errors */ + Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd); + } } -/*- - *----------------------------------------------------------------------- - * JobFinish -- - * Do final processing for the given job including updating - * parents and starting new jobs as available/necessary. Note - * that we pay no attention to the JOB_IGNERR flag here. - * This is because when we're called because of a noexecute flag - * or something, jstat.w_status is 0 and when called from - * Job_CatchChildren, the status is zeroed if it s/b ignored. + +/* Called to close both input and output pipes when a job is finished. */ +static void +JobClosePipes(Job *job) +{ + clearfd(job); + (void)close(job->outPipe); + job->outPipe = -1; + + JobDoOutput(job, TRUE); + (void)close(job->inPipe); + job->inPipe = -1; +} + +static void +JobFinishDoneExitedError(Job *job, int *inout_status) +{ + SwitchOutputTo(job->node); +#ifdef USE_META + if (useMeta) { + meta_job_error(job, job->node, + job->ignerr, WEXITSTATUS(*inout_status)); + } +#endif + if (!shouldDieQuietly(job->node, -1)) { + (void)printf("*** [%s] Error code %d%s\n", + job->node->name, WEXITSTATUS(*inout_status), + job->ignerr ? " (ignored)" : ""); + } + + if (job->ignerr) + *inout_status = 0; + else { + if (deleteOnError) + JobDeleteTarget(job->node); + PrintOnError(job->node, NULL); + } +} + +static void +JobFinishDoneExited(Job *job, int *inout_status) +{ + DEBUG2(JOB, "Process %d [%s] exited.\n", job->pid, job->node->name); + + if (WEXITSTATUS(*inout_status) != 0) + JobFinishDoneExitedError(job, inout_status); + else if (DEBUG(JOB)) { + SwitchOutputTo(job->node); + (void)printf("*** [%s] Completed successfully\n", + job->node->name); + } +} + +static void +JobFinishDoneSignaled(Job *job, int status) +{ + SwitchOutputTo(job->node); + (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); + if (deleteOnError) + JobDeleteTarget(job->node); +} + +static void +JobFinishDone(Job *job, int *inout_status) +{ + if (WIFEXITED(*inout_status)) + JobFinishDoneExited(job, inout_status); + else + JobFinishDoneSignaled(job, *inout_status); + + (void)fflush(stdout); +} + +/* + * Do final processing for the given job including updating parent nodes and + * starting new jobs as available/necessary. + * + * Deferred commands for the job are placed on the .END node. + * + * If there was a serious error (job_errors != 0; not an ignored one), no more + * jobs will be started. * * Input: * job job to finish * status sub-why job went away - * - * Results: - * None - * - * Side Effects: - * Final commands for the job are placed on postCommands. - * - * If we got an error and are aborting (aborting == ABORT_ERROR) and - * the job list is now empty, we are done for the day. - * If we recognized an error (errors !=0), we set the aborting flag - * to ABORT_ERROR so no more jobs will be started. - *----------------------------------------------------------------------- */ -/*ARGSUSED*/ static void JobFinish(Job *job, int status) { - Boolean done, return_job_token; + Boolean done, return_job_token; - if (DEBUG(JOB)) { - fprintf(debug_file, "Jobfinish: %d [%s], status %d\n", - job->pid, job->node->name, status); - } + DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", + job->pid, job->node->name, status); - if ((WIFEXITED(status) && - (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || - WIFSIGNALED(status)) - { - /* - * If it exited non-zero and either we're doing things our - * way or we're not ignoring errors, the job is finished. - * Similarly, if the shell died because of a signal - * the job is also finished. In these - * cases, finish out the job's output before printing the exit - * status... - */ - JobClose(job); - if (job->cmdFILE != NULL && job->cmdFILE != stdout) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; - } - done = TRUE; - } else if (WIFEXITED(status)) { - /* - * Deal with ignored errors in -B mode. We need to print a message - * telling of the ignored error as well as setting status.w_status - * to 0 so the next command gets run. To do this, we set done to be - * TRUE if in -B mode and the job exited non-zero. - */ - done = WEXITSTATUS(status) != 0; - /* - * Old comment said: "Note we don't - * want to close down any of the streams until we know we're at the - * end." - * But we do. Otherwise when are we going to print the rest of the - * stuff? - */ - JobClose(job); - } else { - /* - * No need to close things down or anything. - */ - done = FALSE; - } + if ((WIFEXITED(status) && + ((WEXITSTATUS(status) != 0 && !job->ignerr))) || + WIFSIGNALED(status)) { + /* Finished because of an error. */ - if (done) { - if (WIFEXITED(status)) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Process %d [%s] exited.\n", - job->pid, job->node->name); - } - if (WEXITSTATUS(status) != 0) { - if (job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; + JobClosePipes(job); + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; } -#ifdef USE_META - if (useMeta) { - meta_job_error(job, job->node, job->flags, WEXITSTATUS(status)); - } -#endif - (void)printf("*** [%s] Error code %d%s\n", - job->node->name, - WEXITSTATUS(status), - (job->flags & JOB_IGNERR) ? " (ignored)" : ""); - if (job->flags & JOB_IGNERR) { - status = 0; - } else { - PrintOnError(job->node, NULL); - } - } else if (DEBUG(JOB)) { - if (job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } - (void)printf("*** [%s] Completed successfully\n", - job->node->name); - } + done = TRUE; + + } else if (WIFEXITED(status)) { + /* + * Deal with ignored errors in -B mode. We need to print a + * message telling of the ignored error as well as to run + * the next command. + */ + done = WEXITSTATUS(status) != 0; + + JobClosePipes(job); + } else { - if (job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } - (void)printf("*** [%s] Signal %d\n", - job->node->name, WTERMSIG(status)); + /* No need to close things down or anything. */ + done = FALSE; } - (void)fflush(stdout); - } + + if (done) + JobFinishDone(job, &status); #ifdef USE_META - if (useMeta) { - meta_job_finish(job); - } -#endif - - return_job_token = FALSE; - - Trace_Log(JOBEND, job); - if (!(job->flags & JOB_SPECIAL)) { - if ((status != 0) || - (aborting == ABORT_ERROR) || - (aborting == ABORT_INTERRUPT)) - return_job_token = TRUE; - } - - if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (status == 0)) { - /* - * As long as we aren't aborting and the job didn't return a non-zero - * status that we shouldn't ignore, we call Make_Update to update - * the parents. In addition, any saved commands for the node are placed - * on the .END target. - */ - if (job->tailCmds != NULL) { - Lst_ForEachFrom(job->node->commands, job->tailCmds, - JobSaveCommand, - job->node); + if (useMeta) { + int meta_status = meta_job_finish(job); + if (meta_status != 0 && status == 0) + status = meta_status; } - job->node->made = MADE; - if (!(job->flags & JOB_SPECIAL)) - return_job_token = TRUE; - Make_Update(job->node); - job->job_state = JOB_ST_FREE; - } else if (status != 0) { - errors += 1; - job->job_state = JOB_ST_FREE; - } +#endif - /* - * Set aborting if any error. - */ - if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) { - /* - * If we found any errors in this batch of children and the -k flag - * wasn't given, we set the aborting flag so no more jobs get - * started. - */ - aborting = ABORT_ERROR; - } + return_job_token = FALSE; - if (return_job_token) - Job_TokenReturn(); + Trace_Log(JOBEND, job); + if (!job->special) { + if (status != 0 || + (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) + return_job_token = TRUE; + } - if (aborting == ABORT_ERROR && jobTokensRunning == 0) { - /* - * If we are aborting and the job table is now empty, we finish. - */ - Finish(errors); - } + if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && + (status == 0)) { + /* + * As long as we aren't aborting and the job didn't return a + * non-zero status that we shouldn't ignore, we call + * Make_Update to update the parents. + */ + JobSaveCommands(job); + job->node->made = MADE; + if (!job->special) + return_job_token = TRUE; + Make_Update(job->node); + job->status = JOB_ST_FREE; + } else if (status != 0) { + job_errors++; + job->status = JOB_ST_FREE; + } + + if (job_errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) { + /* Prevent more jobs from getting started. */ + aborting = ABORT_ERROR; + } + + if (return_job_token) + Job_TokenReturn(); + + if (aborting == ABORT_ERROR && jobTokensRunning == 0) + Finish(job_errors); } -/*- - *----------------------------------------------------------------------- - * Job_Touch -- - * Touch the given target. Called by JobStart when the -t flag was - * given +static void +TouchRegular(GNode *gn) +{ + const char *file = GNode_Path(gn); + struct utimbuf times = { now, now }; + int fd; + char c; + + if (utime(file, ×) >= 0) + return; + + fd = open(file, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + (void)fprintf(stderr, "*** couldn't touch %s: %s\n", + file, strerror(errno)); + (void)fflush(stderr); + return; /* XXX: What about propagating the error? */ + } + + /* Last resort: update the file's time stamps in the traditional way. + * XXX: This doesn't work for empty files, which are sometimes used + * as marker files. */ + if (read(fd, &c, 1) == 1) { + (void)lseek(fd, 0, SEEK_SET); + while (write(fd, &c, 1) == -1 && errno == EAGAIN) + continue; + } + (void)close(fd); /* XXX: What about propagating the error? */ +} + +/* + * Touch the given target. Called by JobStart when the -t flag was given. * - * Input: - * gn the node of the file to touch - * silent TRUE if should not print message - * - * Results: - * None - * - * Side Effects: - * The data modification of the file is changed. In addition, if the - * file did not exist, it is created. - *----------------------------------------------------------------------- + * The modification date of the file is changed. + * If the file did not exist, it is created. */ void -Job_Touch(GNode *gn, Boolean silent) +Job_Touch(GNode *gn, Boolean echo) { - int streamID; /* ID of stream opened to do the touch */ - struct utimbuf times; /* Times for utime() call */ - - if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| - OP_SPECIAL|OP_PHONY)) { - /* - * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets - * and, as such, shouldn't really be created. - */ - return; - } - - if (!silent || NoExecute(gn)) { - (void)fprintf(stdout, "touch %s\n", gn->name); - (void)fflush(stdout); - } - - if (NoExecute(gn)) { - return; - } - - if (gn->type & OP_ARCHV) { - Arch_Touch(gn); - } else if (gn->type & OP_LIB) { - Arch_TouchLib(gn); - } else { - char *file = gn->path ? gn->path : gn->name; - - times.actime = times.modtime = now; - if (utime(file, ×) < 0){ - streamID = open(file, O_RDWR | O_CREAT, 0666); - - if (streamID >= 0) { - char c; - + if (gn->type & + (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_OPTIONAL | + OP_SPECIAL | OP_PHONY)) { /* - * Read and write a byte to the file to change the - * modification time, then close the file. + * These are "virtual" targets and should not really be + * created. */ - if (read(streamID, &c, 1) == 1) { - (void)lseek(streamID, (off_t)0, SEEK_SET); - while (write(streamID, &c, 1) == -1 && errno == EAGAIN) - continue; - } - - (void)close(streamID); - } else { - (void)fprintf(stdout, "*** couldn't touch %s: %s", - file, strerror(errno)); - (void)fflush(stdout); - } + return; } - } + + if (echo || !GNode_ShouldExecute(gn)) { + (void)fprintf(stdout, "touch %s\n", gn->name); + (void)fflush(stdout); + } + + if (!GNode_ShouldExecute(gn)) + return; + + if (gn->type & OP_ARCHV) + Arch_Touch(gn); + else if (gn->type & OP_LIB) + Arch_TouchLib(gn); + else + TouchRegular(gn); } -/*- - *----------------------------------------------------------------------- - * Job_CheckCommands -- - * Make sure the given node has all the commands it needs. +/* + * Make sure the given node has all the commands it needs. + * + * The node will have commands from the .DEFAULT rule added to it if it + * needs them. * * Input: * gn The target whose commands need verifying @@ -1191,302 +1289,324 @@ Job_Touch(GNode *gn, Boolean silent) * * Results: * TRUE if the commands list is/was ok. - * - * Side Effects: - * The node will have commands from the .DEFAULT rule added to it - * if it needs them. - *----------------------------------------------------------------------- */ Boolean Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { - if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) && - ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) { + if (GNode_IsTarget(gn)) + return TRUE; + if (!Lst_IsEmpty(&gn->commands)) + return TRUE; + if ((gn->type & OP_LIB) && !Lst_IsEmpty(&gn->children)) + return TRUE; + /* * No commands. Look for .DEFAULT rule from which we might infer - * commands + * commands. */ - if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && - (gn->type & OP_SPECIAL) == 0) { - char *p1; - /* - * Make only looks for a .DEFAULT if the node was never the - * target of an operator, so that's what we do too. If - * a .DEFAULT was given, we substitute its commands for gn's - * commands and set the IMPSRC variable to be the target's name - * The DEFAULT node acts like a transformation rule, in that - * gn also inherits any attributes or sources attached to - * .DEFAULT itself. - */ - Make_HandleUse(DEFAULT, gn); - Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn, 0); - if (p1) - free(p1); - } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) { - /* - * The node wasn't the target of an operator we have no .DEFAULT - * rule to go on and the target doesn't already exist. There's - * nothing more we can do for this branch. If the -k flag wasn't - * given, we stop in our tracks, otherwise we just don't update - * this node's parents so they never get examined. - */ - static const char msg[] = ": don't know how to make"; - - if (gn->flags & FROM_DEPEND) { - if (!Job_RunTarget(".STALE", gn->fname)) - fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n", - progname, gn->fname, gn->lineno, makeDependfile, - gn->name); + if (defaultNode != NULL && !Lst_IsEmpty(&defaultNode->commands) && + !(gn->type & OP_SPECIAL)) { + /* + * The traditional Make only looks for a .DEFAULT if the node + * was never the target of an operator, so that's what we do + * too. + * + * The .DEFAULT node acts like a transformation rule, in that + * gn also inherits any attributes or sources attached to + * .DEFAULT itself. + */ + Make_HandleUse(defaultNode, gn); + Var_Set(gn, IMPSRC, GNode_VarTarget(gn)); return TRUE; - } - - if (gn->type & OP_OPTIONAL) { - (void)fprintf(stdout, "%s%s %s (ignored)\n", progname, - msg, gn->name); - (void)fflush(stdout); - } else if (keepgoing) { - (void)fprintf(stdout, "%s%s %s (continuing)\n", progname, - msg, gn->name); - (void)fflush(stdout); - return FALSE; - } else { - (*abortProc)("%s%s %s. Stop", progname, msg, gn->name); - return FALSE; - } } - } - return TRUE; + + Dir_UpdateMTime(gn, FALSE); + if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) + return TRUE; + + /* + * The node wasn't the target of an operator. We have no .DEFAULT + * rule to go on and the target doesn't already exist. There's + * nothing more we can do for this branch. If the -k flag wasn't + * given, we stop in our tracks, otherwise we just don't update + * this node's parents so they never get examined. + */ + + if (gn->flags & FROM_DEPEND) { + if (!Job_RunTarget(".STALE", gn->fname)) + fprintf(stdout, + "%s: %s, %d: ignoring stale %s for %s\n", + progname, gn->fname, gn->lineno, makeDependfile, + gn->name); + return TRUE; + } + + if (gn->type & OP_OPTIONAL) { + (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", + progname, gn->name, "ignored"); + (void)fflush(stdout); + return TRUE; + } + + if (opts.keepgoing) { + (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", + progname, gn->name, "continuing"); + (void)fflush(stdout); + return FALSE; + } + + abortProc("%s: don't know how to make %s. Stop", progname, gn->name); + return FALSE; } -/*- - *----------------------------------------------------------------------- - * JobExec -- - * Execute the shell for the given job. Called from JobStart +/* + * Execute the shell for the given job. * - * Input: - * job Job to execute - * - * Results: - * None. - * - * Side Effects: - * A shell is executed, outputs is altered and the Job structure added - * to the job table. - * - *----------------------------------------------------------------------- + * See Job_CatchOutput for handling the output of the shell. */ static void JobExec(Job *job, char **argv) { - int cpid; /* ID of new child */ - sigset_t mask; + int cpid; /* ID of new child */ + sigset_t mask; - job->flags &= ~JOB_TRACED; + if (DEBUG(JOB)) { + int i; - if (DEBUG(JOB)) { - int i; - - (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local"); - (void)fprintf(debug_file, "\tCommand: "); - for (i = 0; argv[i] != NULL; i++) { - (void)fprintf(debug_file, "%s ", argv[i]); + debug_printf("Running %s\n", job->node->name); + debug_printf("\tCommand: "); + for (i = 0; argv[i] != NULL; i++) { + debug_printf("%s ", argv[i]); + } + debug_printf("\n"); } - (void)fprintf(debug_file, "\n"); - } - /* - * Some jobs produce no output and it's disconcerting to have - * no feedback of their running (since they produce no output, the - * banner with their name in it never appears). This is an attempt to - * provide that feedback, even if nothing follows it. - */ - if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } + /* + * Some jobs produce no output and it's disconcerting to have + * no feedback of their running (since they produce no output, the + * banner with their name in it never appears). This is an attempt to + * provide that feedback, even if nothing follows it. + */ + if (job->echo) + SwitchOutputTo(job->node); - /* No interruptions until this job is on the `jobs' list */ - JobSigLock(&mask); + /* No interruptions until this job is on the `jobs' list */ + JobSigLock(&mask); - /* Pre-emptively mark job running, pid still zero though */ - job->job_state = JOB_ST_RUNNING; + /* Pre-emptively mark job running, pid still zero though */ + job->status = JOB_ST_RUNNING; - cpid = vFork(); - if (cpid == -1) - Punt("Cannot vfork: %s", strerror(errno)); + Var_ReexportVars(); - if (cpid == 0) { - /* Child */ - sigset_t tmask; + cpid = vfork(); + if (cpid == -1) + Punt("Cannot vfork: %s", strerror(errno)); + + if (cpid == 0) { + /* Child */ + sigset_t tmask; + +#ifdef USE_META + if (useMeta) { + meta_job_child(job); + } +#endif + /* + * Reset all signal handlers; this is necessary because we + * also need to unblock signals before we exec(2). + */ + JobSigReset(); + + /* Now unblock signals */ + sigemptyset(&tmask); + JobSigUnlock(&tmask); + + /* + * Must duplicate the input stream down to the child's input + * and reset it to the beginning (again). Since the stream + * was marked close-on-exec, we must clear that bit in the + * new input. + */ + if (dup2(fileno(job->cmdFILE), 0) == -1) + execDie("dup2", "job->cmdFILE"); + if (fcntl(0, F_SETFD, 0) == -1) + execDie("fcntl clear close-on-exec", "stdin"); + if (lseek(0, 0, SEEK_SET) == -1) + execDie("lseek to 0", "stdin"); + + if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { + /* + * Pass job token pipe to submakes. + */ + if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", + "tokenWaitJob.inPipe"); + if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", + "tokenWaitJob.outPipe"); + } + + /* + * Set up the child's output to be routed through the pipe + * we've created for it. + */ + if (dup2(job->outPipe, 1) == -1) + execDie("dup2", "job->outPipe"); + + /* + * The output channels are marked close on exec. This bit + * was duplicated by the dup2(on some systems), so we have + * to clear it before routing the shell's error output to + * the same place as its standard output. + */ + if (fcntl(1, F_SETFD, 0) == -1) + execDie("clear close-on-exec", "stdout"); + if (dup2(1, 2) == -1) + execDie("dup2", "1, 2"); + + /* + * We want to switch the child into a different process + * family so we can kill it and all its descendants in + * one fell swoop, by killing its process family, but not + * commit suicide. + */ +#if defined(MAKE_NATIVE) || defined(HAVE_SETPGID) +# if defined(SYSV) + /* XXX: dsl - I'm sure this should be setpgrp()... */ + (void)setsid(); +# else + (void)setpgid(0, getpid()); +# endif +#endif + + (void)execv(shellPath, argv); + execDie("exec", shellPath); + } + + /* Parent, continuing after the child exec */ + job->pid = cpid; + + Trace_Log(JOBSTART, job); #ifdef USE_META if (useMeta) { - meta_job_child(job); + meta_job_parent(job, cpid); } #endif - /* - * Reset all signal handlers; this is necessary because we also - * need to unblock signals before we exec(2). - */ - JobSigReset(); - - /* Now unblock signals */ - sigemptyset(&tmask); - JobSigUnlock(&tmask); /* - * Must duplicate the input stream down to the child's input and - * reset it to the beginning (again). Since the stream was marked - * close-on-exec, we must clear that bit in the new input. + * Set the current position in the buffer to the beginning + * and mark another stream to watch in the outputs mask */ - if (dup2(FILENO(job->cmdFILE), 0) == -1) { - execError("dup2", "job->cmdFILE"); - _exit(1); - } - (void)fcntl(0, F_SETFD, 0); - (void)lseek(0, (off_t)0, SEEK_SET); + job->curPos = 0; - if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { - /* - * Pass job token pipe to submakes. - */ - fcntl(tokenWaitJob.inPipe, F_SETFD, 0); - fcntl(tokenWaitJob.outPipe, F_SETFD, 0); - } - - /* - * Set up the child's output to be routed through the pipe - * we've created for it. - */ - if (dup2(job->outPipe, 1) == -1) { - execError("dup2", "job->outPipe"); - _exit(1); - } - /* - * The output channels are marked close on exec. This bit was - * duplicated by the dup2(on some systems), so we have to clear - * it before routing the shell's error output to the same place as - * its standard output. - */ - (void)fcntl(1, F_SETFD, 0); - if (dup2(1, 2) == -1) { - execError("dup2", "1, 2"); - _exit(1); + watchfd(job); + + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; } - /* - * We want to switch the child into a different process family so - * we can kill it and all its descendants in one fell swoop, - * by killing its process family, but not commit suicide. - */ -#if defined(MAKE_NATIVE) || defined(HAVE_SETPGID) -#if defined(SYSV) - /* XXX: dsl - I'm sure this should be setpgrp()... */ - (void)setsid(); -#else - (void)setpgid(0, getpid()); -#endif -#endif - - Var_ExportVars(); - - (void)execv(shellPath, argv); - execError("exec", shellPath); - _exit(1); - } - - /* Parent, continuing after the child exec */ - job->pid = cpid; - - Trace_Log(JOBSTART, job); - - /* - * Set the current position in the buffer to the beginning - * and mark another stream to watch in the outputs mask - */ - job->curPos = 0; - - watchfd(job); - - if (job->cmdFILE != NULL && job->cmdFILE != stdout) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; - } - - /* - * Now the job is actually running, add it to the table. - */ - if (DEBUG(JOB)) { - fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n", - job->node->name, job->pid); - job_table_dump("job started"); - } - JobSigUnlock(&mask); + /* Now that the job is actually running, add it to the table. */ + if (DEBUG(JOB)) { + debug_printf("JobExec(%s): pid %d added to jobs table\n", + job->node->name, job->pid); + job_table_dump("job started"); + } + JobSigUnlock(&mask); } -/*- - *----------------------------------------------------------------------- - * JobMakeArgv -- - * Create the argv needed to execute the shell for a given job. - * - * - * Results: - * - * Side Effects: - * - *----------------------------------------------------------------------- - */ +/* Create the argv needed to execute the shell for a given job. */ static void JobMakeArgv(Job *job, char **argv) { - int argc; - static char args[10]; /* For merged arguments */ + int argc; + static char args[10]; /* For merged arguments */ - argv[0] = UNCONST(shellName); - argc = 1; + argv[0] = UNCONST(shellName); + argc = 1; - if ((commandShell->exit && (*commandShell->exit != '-')) || - (commandShell->echo && (*commandShell->echo != '-'))) - { - /* - * At least one of the flags doesn't have a minus before it, so - * merge them together. Have to do this because the *(&(@*#*&#$# - * Bourne shell thinks its second argument is a file to source. - * Grrrr. Note the ten-character limitation on the combined arguments. - */ - (void)snprintf(args, sizeof(args), "-%s%s", - ((job->flags & JOB_IGNERR) ? "" : - (commandShell->exit ? commandShell->exit : "")), - ((job->flags & JOB_SILENT) ? "" : - (commandShell->echo ? commandShell->echo : ""))); + if ((shell->errFlag != NULL && shell->errFlag[0] != '-') || + (shell->echoFlag != NULL && shell->echoFlag[0] != '-')) { + /* + * At least one of the flags doesn't have a minus before it, + * so merge them together. Have to do this because the Bourne + * shell thinks its second argument is a file to source. + * Grrrr. Note the ten-character limitation on the combined + * arguments. + * + * TODO: Research until when the above comments were + * practically relevant. + */ + (void)snprintf(args, sizeof args, "-%s%s", + (job->ignerr ? "" : + (shell->errFlag != NULL ? shell->errFlag : "")), + (!job->echo ? "" : + (shell->echoFlag != NULL ? shell->echoFlag : ""))); - if (args[1]) { - argv[argc] = args; - argc++; + if (args[1] != '\0') { + argv[argc] = args; + argc++; + } + } else { + if (!job->ignerr && shell->errFlag != NULL) { + argv[argc] = UNCONST(shell->errFlag); + argc++; + } + if (job->echo && shell->echoFlag != NULL) { + argv[argc] = UNCONST(shell->echoFlag); + argc++; + } } - } else { - if (!(job->flags & JOB_IGNERR) && commandShell->exit) { - argv[argc] = UNCONST(commandShell->exit); - argc++; - } - if (!(job->flags & JOB_SILENT) && commandShell->echo) { - argv[argc] = UNCONST(commandShell->echo); - argc++; - } - } - argv[argc] = NULL; + argv[argc] = NULL; } -/*- - *----------------------------------------------------------------------- - * JobStart -- - * Start a target-creation process going for the target described - * by the graph node gn. +static void +JobWriteShellCommands(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run) +{ + /* + * tfile is the name of a file into which all shell commands + * are put. It is removed before the child shell is executed, + * unless DEBUG(SCRIPT) is set. + */ + char tfile[MAXPATHLEN]; + int tfd; /* File descriptor to the temp file */ + + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); + } + + tfd = Job_TempFile(TMPPAT, tfile, sizeof tfile); + + job->cmdFILE = fdopen(tfd, "w+"); + if (job->cmdFILE == NULL) + Punt("Could not fdopen %s", tfile); + + (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); + +#ifdef USE_META + if (useMeta) { + meta_job_start(job, gn); + if (gn->type & OP_SILENT) /* might have changed */ + job->echo = FALSE; + } +#endif + + *out_run = JobPrintCommands(job); +} + +/* + * Start a target-creation process going for the target described by the + * graph node gn. * * Input: * gn target to create * flags flags for the job to override normal ones. - * e.g. JOB_SPECIAL or JOB_IGNDOTS * previous The previous Job structure for this node, if any. * * Results: @@ -1498,479 +1618,357 @@ JobMakeArgv(Job *job, char **argv) * A new Job node is created and added to the list of running * jobs. PMake is forked and a child shell created. * - * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set - * JOB_IGNDOTS is never set (dsl) - * Also the return value is ignored by everyone. - *----------------------------------------------------------------------- + * NB: The return value is ignored by everyone. */ -static int -JobStart(GNode *gn, int flags) +static JobStartResult +JobStart(GNode *gn, Boolean special) { - Job *job; /* new job descriptor */ - char *argv[10]; /* Argument vector to shell */ - Boolean cmdsOK; /* true if the nodes commands were all right */ - Boolean noExec; /* Set true if we decide not to run the job */ - int tfd; /* File descriptor to the temp file */ + Job *job; /* new job descriptor */ + char *argv[10]; /* Argument vector to shell */ + Boolean cmdsOK; /* true if the nodes commands were all right */ + Boolean run; - for (job = job_table; job < job_table_end; job++) { - if (job->job_state == JOB_ST_FREE) - break; - } - if (job >= job_table_end) - Punt("JobStart no job slots vacant"); + for (job = job_table; job < job_table_end; job++) { + if (job->status == JOB_ST_FREE) + break; + } + if (job >= job_table_end) + Punt("JobStart no job slots vacant"); - memset(job, 0, sizeof *job); - job->job_state = JOB_ST_SETUP; - if (gn->type & OP_SPECIAL) - flags |= JOB_SPECIAL; + memset(job, 0, sizeof *job); + job->node = gn; + job->tailCmds = NULL; + job->status = JOB_ST_SET_UP; - job->node = gn; - job->tailCmds = NULL; + job->special = special || gn->type & OP_SPECIAL; + job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE; + job->echo = !(opts.beSilent || gn->type & OP_SILENT); - /* - * Set the initial value of the flags for this job based on the global - * ones and the node's attributes... Any flags supplied by the caller - * are also added to the field. - */ - job->flags = 0; - if (Targ_Ignore(gn)) { - job->flags |= JOB_IGNERR; - } - if (Targ_Silent(gn)) { - job->flags |= JOB_SILENT; - } - job->flags |= flags; - - /* - * Check the commands now so any attributes from .DEFAULT have a chance - * to migrate to the node - */ - cmdsOK = Job_CheckCommands(gn, Error); - - job->inPollfd = NULL; - /* - * If the -n flag wasn't given, we open up OUR (not the child's) - * temporary file to stuff commands in it. The thing is rd/wr so we don't - * need to reopen it to feed it to the shell. If the -n flag *was* given, - * we just set the file to be stdout. Cute, huh? - */ - if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) || - (!noExecute && !touchFlag)) { /* - * tfile is the name of a file into which all shell commands are - * put. It is removed before the child shell is executed, unless - * DEBUG(SCRIPT) is set. + * Check the commands now so any attributes from .DEFAULT have a + * chance to migrate to the node. */ - char *tfile; - sigset_t mask; - /* - * We're serious here, but if the commands were bogus, we're - * also dead... - */ - if (!cmdsOK) { - PrintOnError(gn, NULL); /* provide some clue */ - DieHorribly(); + cmdsOK = Job_CheckCommands(gn, Error); + + job->inPollfd = NULL; + + if (Lst_IsEmpty(&gn->commands)) { + job->cmdFILE = stdout; + run = FALSE; + } else if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || + (!opts.noExecute && !opts.touchFlag)) { + /* + * The above condition looks very similar to + * GNode_ShouldExecute but is subtly different. It prevents + * that .MAKE targets are touched since these are usually + * virtual targets. + */ + + JobWriteShellCommands(job, gn, cmdsOK, &run); + (void)fflush(job->cmdFILE); + } else if (!GNode_ShouldExecute(gn)) { + /* + * Just print all the commands to stdout in one fell swoop. + * This still sets up job->tailCmds correctly. + */ + SwitchOutputTo(gn); + job->cmdFILE = stdout; + if (cmdsOK) + JobPrintCommands(job); + run = FALSE; + (void)fflush(job->cmdFILE); + } else { + Job_Touch(gn, job->echo); + run = FALSE; } - JobSigLock(&mask); - tfd = mkTempFile(TMPPAT, &tfile); - if (!DEBUG(SCRIPT)) - (void)eunlink(tfile); - JobSigUnlock(&mask); + /* If we're not supposed to execute a shell, don't. */ + if (!run) { + if (!job->special) + Job_TokenReturn(); + /* Unlink and close the command file if we opened one */ + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; + } - job->cmdFILE = fdopen(tfd, "w+"); - if (job->cmdFILE == NULL) { - Punt("Could not fdopen %s", tfile); - } - (void)fcntl(FILENO(job->cmdFILE), F_SETFD, 1); - /* - * Send the commands to the command file, flush all its buffers then - * rewind and remove the thing. - */ - noExec = FALSE; - -#ifdef USE_META - if (useMeta) { - meta_job_start(job, gn); - if (Targ_Silent(gn)) { /* might have changed */ - job->flags |= JOB_SILENT; - } - } -#endif - /* - * We can do all the commands at once. hooray for sanity - */ - numCommands = 0; - Lst_ForEach(gn->commands, JobPrintCommand, job); - - /* - * If we didn't print out any commands to the shell script, - * there's not much point in executing the shell, is there? - */ - if (numCommands == 0) { - noExec = TRUE; - } - - free(tfile); - } else if (NoExecute(gn)) { - /* - * Not executing anything -- just print all the commands to stdout - * in one fell swoop. This will still set up job->tailCmds correctly. - */ - if (lastNode != gn) { - MESSAGE(stdout, gn); - lastNode = gn; - } - job->cmdFILE = stdout; - /* - * Only print the commands if they're ok, but don't die if they're - * not -- just let the user know they're bad and keep going. It - * doesn't do any harm in this case and may do some good. - */ - if (cmdsOK) { - Lst_ForEach(gn->commands, JobPrintCommand, job); - } - /* - * Don't execute the shell, thank you. - */ - noExec = TRUE; - } else { - /* - * Just touch the target and note that no shell should be executed. - * Set cmdFILE to stdout to make life easier. Check the commands, too, - * but don't die if they're no good -- it does no harm to keep working - * up the graph. - */ - job->cmdFILE = stdout; - Job_Touch(gn, job->flags&JOB_SILENT); - noExec = TRUE; - } - /* Just in case it isn't already... */ - (void)fflush(job->cmdFILE); - - /* - * If we're not supposed to execute a shell, don't. - */ - if (noExec) { - if (!(job->flags & JOB_SPECIAL)) - Job_TokenReturn(); - /* - * Unlink and close the command file if we opened one - */ - if (job->cmdFILE != stdout) { - if (job->cmdFILE != NULL) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; - } + /* + * We only want to work our way up the graph if we aren't + * here because the commands for the job were no good. + */ + if (cmdsOK && aborting == ABORT_NONE) { + JobSaveCommands(job); + job->node->made = MADE; + Make_Update(job->node); + } + job->status = JOB_ST_FREE; + return cmdsOK ? JOB_FINISHED : JOB_ERROR; } /* - * We only want to work our way up the graph if we aren't here because - * the commands for the job were no good. + * Set up the control arguments to the shell. This is based on the + * flags set earlier for this job. */ - if (cmdsOK && aborting == 0) { - if (job->tailCmds != NULL) { - Lst_ForEachFrom(job->node->commands, job->tailCmds, - JobSaveCommand, - job->node); - } - job->node->made = MADE; - Make_Update(job->node); - } - job->job_state = JOB_ST_FREE; - return cmdsOK ? JOB_FINISHED : JOB_ERROR; - } + JobMakeArgv(job, argv); - /* - * Set up the control arguments to the shell. This is based on the flags - * set earlier for this job. - */ - JobMakeArgv(job, argv); + /* Create the pipe by which we'll get the shell's output. */ + JobCreatePipe(job, 3); - /* Create the pipe by which we'll get the shell's output. */ - JobCreatePipe(job, 3); - - JobExec(job, argv); - return(JOB_RUNNING); + JobExec(job, argv); + return JOB_RUNNING; } +/* + * Print the output of the shell command, skipping the noPrint text of the + * shell, if any. The default shell does not have noPrint though, which means + * that in all practical cases, handling the output is left to the caller. + */ static char * -JobOutput(Job *job, char *cp, char *endp, int msg) +JobOutput(char *cp, char *endp) /* XXX: should all be const */ { - char *ecp; + char *ecp; /* XXX: should be const */ - if (commandShell->noPrint) { - ecp = Str_FindSubstring(cp, commandShell->noPrint); - while (ecp != NULL) { - if (cp != ecp) { - *ecp = '\0'; - if (!beSilent && msg && job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } - /* - * The only way there wouldn't be a newline after - * this line is if it were the last in the buffer. - * however, since the non-printable comes after it, - * there must be a newline, so we don't print one. - */ - (void)fprintf(stdout, "%s", cp); - (void)fflush(stdout); - } - cp = ecp + commandShell->noPLen; - if (cp != endp) { - /* - * Still more to print, look again after skipping - * the whitespace following the non-printable - * command.... - */ - cp++; - while (*cp == ' ' || *cp == '\t' || *cp == '\n') { - cp++; - } - ecp = Str_FindSubstring(cp, commandShell->noPrint); - } else { + if (shell->noPrint == NULL || shell->noPrint[0] == '\0') return cp; - } + + /* + * XXX: What happens if shell->noPrint occurs on the boundary of + * the buffer? To work correctly in all cases, this should rather + * be a proper stream filter instead of doing string matching on + * selected chunks of the output. + */ + while ((ecp = strstr(cp, shell->noPrint)) != NULL) { + if (ecp != cp) { + *ecp = '\0'; /* XXX: avoid writing to the buffer */ + /* + * The only way there wouldn't be a newline after + * this line is if it were the last in the buffer. + * however, since the noPrint output comes after it, + * there must be a newline, so we don't print one. + */ + /* XXX: What about null bytes in the output? */ + (void)fprintf(stdout, "%s", cp); + (void)fflush(stdout); + } + cp = ecp + shell->noPrintLen; + if (cp == endp) + break; + cp++; /* skip over the (XXX: assumed) newline */ + pp_skip_whitespace(&cp); } - } - return cp; + return cp; } -/*- - *----------------------------------------------------------------------- - * JobDoOutput -- - * This function is called at different times depending on - * whether the user has specified that output is to be collected - * via pipes or temporary files. In the former case, we are called - * whenever there is something to read on the pipe. We collect more - * output from the given job and store it in the job's outBuf. If - * this makes up a line, we print it tagged by the job's identifier, - * as necessary. - * If output has been collected in a temporary file, we open the - * file and read it line by line, transfering it to our own - * output channel until the file is empty. At which point we - * remove the temporary file. - * In both cases, however, we keep our figurative eye out for the - * 'noPrint' line for the shell from which the output came. If - * we recognize a line, we don't print it. If the command is not - * alone on the line (the character after it is not \0 or \n), we - * do print whatever follows it. +/* + * This function is called whenever there is something to read on the pipe. + * We collect more output from the given job and store it in the job's + * outBuf. If this makes up a line, we print it tagged by the job's + * identifier, as necessary. + * + * In the output of the shell, the 'noPrint' lines are removed. If the + * command is not alone on the line (the character after it is not \0 or + * \n), we do print whatever follows it. * * Input: * job the job whose output needs printing * finish TRUE if this is the last time we'll be called * for this job - * - * Results: - * None - * - * Side Effects: - * curPos may be shifted as may the contents of outBuf. - *----------------------------------------------------------------------- */ -STATIC void +static void JobDoOutput(Job *job, Boolean finish) { - Boolean gotNL = FALSE; /* true if got a newline */ - Boolean fbuf; /* true if our buffer filled up */ - int nr; /* number of bytes read */ - int i; /* auxiliary index into outBuf */ - int max; /* limit for i (end of current data) */ - int nRead; /* (Temporary) number of bytes read */ + Boolean gotNL; /* true if got a newline */ + Boolean fbuf; /* true if our buffer filled up */ + size_t nr; /* number of bytes read */ + size_t i; /* auxiliary index into outBuf */ + size_t max; /* limit for i (end of current data) */ + ssize_t nRead; /* (Temporary) number of bytes read */ - /* - * Read as many bytes as will fit in the buffer. - */ -end_loop: - gotNL = FALSE; - fbuf = FALSE; + /* Read as many bytes as will fit in the buffer. */ +again: + gotNL = FALSE; + fbuf = FALSE; - nRead = read(job->inPipe, &job->outBuf[job->curPos], - JOB_BUFSIZE - job->curPos); - if (nRead < 0) { - if (errno == EAGAIN) - return; - if (DEBUG(JOB)) { - perror("JobDoOutput(piperead)"); - } - nr = 0; - } else { - nr = nRead; - } - - /* - * If we hit the end-of-file (the job is dead), we must flush its - * remaining output, so pretend we read a newline if there's any - * output remaining in the buffer. - * Also clear the 'finish' flag so we stop looping. - */ - if ((nr == 0) && (job->curPos != 0)) { - job->outBuf[job->curPos] = '\n'; - nr = 1; - finish = FALSE; - } else if (nr == 0) { - finish = FALSE; - } - - /* - * Look for the last newline in the bytes we just got. If there is - * one, break out of the loop with 'i' as its index and gotNL set - * TRUE. - */ - max = job->curPos + nr; - for (i = job->curPos + nr - 1; i >= job->curPos; i--) { - if (job->outBuf[i] == '\n') { - gotNL = TRUE; - break; - } else if (job->outBuf[i] == '\0') { - /* - * Why? - */ - job->outBuf[i] = ' '; - } - } - - if (!gotNL) { - job->curPos += nr; - if (job->curPos == JOB_BUFSIZE) { - /* - * If we've run out of buffer space, we have no choice - * but to print the stuff. sigh. - */ - fbuf = TRUE; - i = job->curPos; - } - } - if (gotNL || fbuf) { - /* - * Need to send the output to the screen. Null terminate it - * first, overwriting the newline character if there was one. - * So long as the line isn't one we should filter (according - * to the shell description), we print the line, preceded - * by a target banner if this target isn't the same as the - * one for which we last printed something. - * The rest of the data in the buffer are then shifted down - * to the start of the buffer and curPos is set accordingly. - */ - job->outBuf[i] = '\0'; - if (i >= job->curPos) { - char *cp; - - cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE); - - /* - * There's still more in that thar 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 (!beSilent && job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; + nRead = read(job->inPipe, &job->outBuf[job->curPos], + JOB_BUFSIZE - job->curPos); + if (nRead < 0) { + if (errno == EAGAIN) + return; + if (DEBUG(JOB)) { + perror("JobDoOutput(piperead)"); } -#ifdef USE_META - if (useMeta) { - meta_job_output(job, cp, gotNL ? "\n" : ""); - } -#endif - (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); - (void)fflush(stdout); - } - } - /* - * max is the last offset still in the buffer. Move any remaining - * characters to the start of the buffer and update the end marker - * curPos. - */ - if (i < max) { - (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); - job->curPos = max - (i + 1); + nr = 0; } else { - assert(i == max); - job->curPos = 0; + nr = (size_t)nRead; } - } - if (finish) { + /* - * If the finish flag is true, we must loop until we hit - * end-of-file on the pipe. This is guaranteed to happen - * eventually since the other end of the pipe is now closed - * (we closed it explicitly and the child has exited). When - * we do get an EOF, finish will be set FALSE and we'll fall - * through and out. + * If we hit the end-of-file (the job is dead), we must flush its + * remaining output, so pretend we read a newline if there's any + * output remaining in the buffer. + * Also clear the 'finish' flag so we stop looping. */ - goto end_loop; - } + if (nr == 0 && job->curPos != 0) { + job->outBuf[job->curPos] = '\n'; + nr = 1; + finish = FALSE; + } else if (nr == 0) { + finish = FALSE; + } + + /* + * Look for the last newline in the bytes we just got. If there is + * one, break out of the loop with 'i' as its index and gotNL set + * TRUE. + */ + max = job->curPos + nr; + for (i = job->curPos + nr - 1; + i >= job->curPos && i != (size_t)-1; i--) { + if (job->outBuf[i] == '\n') { + gotNL = TRUE; + break; + } else if (job->outBuf[i] == '\0') { + /* + * Why? + */ + job->outBuf[i] = ' '; + } + } + + if (!gotNL) { + job->curPos += nr; + if (job->curPos == JOB_BUFSIZE) { + /* + * If we've run out of buffer space, we have no choice + * but to print the stuff. sigh. + */ + fbuf = TRUE; + i = job->curPos; + } + } + if (gotNL || fbuf) { + /* + * Need to send the output to the screen. Null terminate it + * first, overwriting the newline character if there was one. + * So long as the line isn't one we should filter (according + * to the shell description), we print the line, preceded + * by a target banner if this target isn't the same as the + * one for which we last printed something. + * The rest of the data in the buffer are then shifted down + * to the start of the buffer and curPos is set accordingly. + */ + job->outBuf[i] = '\0'; + if (i >= job->curPos) { + char *cp; + + cp = JobOutput(job->outBuf, &job->outBuf[i]); + + /* + * There's still more in that thar 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 (!opts.beSilent) + SwitchOutputTo(job->node); +#ifdef USE_META + if (useMeta) { + meta_job_output(job, cp, + gotNL ? "\n" : ""); + } +#endif + (void)fprintf(stdout, "%s%s", cp, + gotNL ? "\n" : ""); + (void)fflush(stdout); + } + } + /* + * max is the last offset still in the buffer. Move any + * remaining characters to the start of the buffer and + * update the end marker curPos. + */ + if (i < max) { + (void)memmove(job->outBuf, &job->outBuf[i + 1], + max - (i + 1)); + job->curPos = max - (i + 1); + } else { + assert(i == max); + job->curPos = 0; + } + } + if (finish) { + /* + * If the finish flag is true, we must loop until we hit + * end-of-file on the pipe. This is guaranteed to happen + * eventually since the other end of the pipe is now closed + * (we closed it explicitly and the child has exited). When + * we do get an EOF, finish will be set FALSE and we'll fall + * through and out. + */ + goto again; + } } static void JobRun(GNode *targ) { -#ifdef notyet - /* - * Unfortunately it is too complicated to run .BEGIN, .END, - * and .INTERRUPT job in the parallel job module. This has - * the nice side effect that it avoids a lot of other problems. - */ - Lst lst = Lst_Init(FALSE); - Lst_AtEnd(lst, targ); - (void)Make_Run(lst); - Lst_Destroy(lst, NULL); - JobStart(targ, JOB_SPECIAL); - while (jobTokensRunning) { - Job_CatchOutput(); - } +#if 0 + /* + * Unfortunately it is too complicated to run .BEGIN, .END, and + * .INTERRUPT job in the parallel job module. As of 2020-09-25, + * unit-tests/deptgt-end-jobs.mk hangs in an endless loop. + * + * Running these jobs in compat mode also guarantees that these + * jobs do not overlap with other unrelated jobs. + */ + GNodeList lst = LST_INIT; + Lst_Append(&lst, targ); + (void)Make_Run(&lst); + Lst_Done(&lst); + JobStart(targ, TRUE); + while (jobTokensRunning != 0) { + Job_CatchOutput(); + } #else - Compat_Make(targ, targ); - if (targ->made == ERROR) { - PrintOnError(targ, "\n\nStop."); - exit(1); - } + Compat_Make(targ, targ); + /* XXX: Replace with GNode_IsError(gn) */ + if (targ->made == ERROR) { + PrintOnError(targ, "\n\nStop."); + exit(1); + } #endif } -/*- - *----------------------------------------------------------------------- - * Job_CatchChildren -- - * Handle the exit of a child. Called from Make_Make. +/* + * Handle the exit of a child. Called from Make_Make. * - * Input: - * block TRUE if should block on the wait - * - * Results: - * none. - * - * Side Effects: - * The job descriptor is removed from the list of children. + * The job descriptor is removed from the list of children. * * Notes: * We do waits, blocking or not, according to the wisdom of our * caller, until there are no more children to report. For each * job, call JobFinish to finish things off. - * - *----------------------------------------------------------------------- */ - void Job_CatchChildren(void) { - int pid; /* pid of dead child */ - int status; /* Exit/termination status */ + int pid; /* pid of dead child */ + int status; /* Exit/termination status */ - /* - * Don't even bother if we know there's no one around. - */ - if (jobTokensRunning == 0) - return; + /* Don't even bother if we know there's no one around. */ + if (jobTokensRunning == 0) + return; - while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid, - status); + /* Have we received SIGCHLD since last call? */ + if (caught_sigchld == 0) + return; + caught_sigchld = 0; + + while ((pid = waitpid((pid_t)-1, &status, WNOHANG | WUNTRACED)) > 0) { + DEBUG2(JOB, "Process %d exited/stopped status %x.\n", + pid, status); + JobReapChild(pid, status, TRUE); } - JobReapChild(pid, status, TRUE); - } } /* @@ -1980,373 +1978,349 @@ Job_CatchChildren(void) void JobReapChild(pid_t pid, int status, Boolean isJobs) { - Job *job; /* job descriptor for dead child */ + Job *job; /* job descriptor for dead child */ - /* - * Don't even bother if we know there's no one around. - */ - if (jobTokensRunning == 0) - return; + /* Don't even bother if we know there's no one around. */ + if (jobTokensRunning == 0) + return; - job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); - if (job == NULL) { - if (isJobs) { - if (!lurking_children) - Error("Child (%d) status %x not in table?", pid, status); + job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); + if (job == NULL) { + if (isJobs) { + if (!lurking_children) + Error("Child (%d) status %x not in table?", + pid, status); + } + return; /* not ours */ } - return; /* not ours */ - } - if (WIFSTOPPED(status)) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Process %d (%s) stopped.\n", - job->pid, job->node->name); + if (WIFSTOPPED(status)) { + DEBUG2(JOB, "Process %d (%s) stopped.\n", + job->pid, job->node->name); + if (!make_suspended) { + switch (WSTOPSIG(status)) { + case SIGTSTP: + (void)printf("*** [%s] Suspended\n", + job->node->name); + break; + case SIGSTOP: + (void)printf("*** [%s] Stopped\n", + job->node->name); + break; + default: + (void)printf("*** [%s] Stopped -- signal %d\n", + job->node->name, WSTOPSIG(status)); + } + job->suspended = TRUE; + } + (void)fflush(stdout); + return; } - if (!make_suspended) { - switch (WSTOPSIG(status)) { - case SIGTSTP: - (void)printf("*** [%s] Suspended\n", job->node->name); - break; - case SIGSTOP: - (void)printf("*** [%s] Stopped\n", job->node->name); - break; - default: - (void)printf("*** [%s] Stopped -- signal %d\n", - job->node->name, WSTOPSIG(status)); - } - job->job_suspended = 1; - } - (void)fflush(stdout); - return; - } - job->job_state = JOB_ST_FINISHED; - job->exit_status = status; + job->status = JOB_ST_FINISHED; + job->exit_status = status; - JobFinish(job, status); + JobFinish(job, status); } -/*- - *----------------------------------------------------------------------- - * Job_CatchOutput -- - * Catch the output from our children, if we're using - * pipes do so. Otherwise just block time until we get a - * signal(most likely a SIGCHLD) since there's no point in - * just spinning when there's nothing to do and the reaping - * of a child can wait for a while. - * - * Results: - * None - * - * Side Effects: - * Output is read from pipes if we're piping. - * ----------------------------------------------------------------------- +/* + * Catch the output from our children, if we're using pipes do so. Otherwise + * just block time until we get a signal(most likely a SIGCHLD) since there's + * no point in just spinning when there's nothing to do and the reaping of a + * child can wait for a while. */ void Job_CatchOutput(void) { - int nready; - Job *job; - int i; + int nready; + Job *job; + unsigned int i; - (void)fflush(stdout); + (void)fflush(stdout); - /* The first fd in the list is the job token pipe */ - do { - nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC); - } while (nready < 0 && errno == EINTR); - - if (nready < 0) - Punt("poll: %s", strerror(errno)); - - if (nready > 0 && readyfd(&childExitJob)) { - char token = 0; - ssize_t count; -#if defined(__minix) - /* Workaround: While the pipe is deemed ready to be read, it can still - * return EAGAIN in the read below. */ + /* The first fd in the list is the job token pipe */ do { -#endif /* defined(__minix) */ - count = read(childExitJob.inPipe, &token, 1); + nready = poll(fds + 1 - wantToken, fdsLen - 1 + wantToken, + POLL_MSEC); + } while (nready < 0 && errno == EINTR); + + if (nready < 0) + Punt("poll: %s", strerror(errno)); + + if (nready > 0 && readyfd(&childExitJob)) { + char token = 0; + ssize_t count; #if defined(__minix) - } while(-1 == count && EAGAIN == errno); + /* Workaround: While the pipe is deemed ready to be read, it can still + * return EAGAIN in the read below. */ + do { #endif /* defined(__minix) */ - switch (count) { - case 0: - Punt("unexpected eof on token pipe"); - case -1: - Punt("token pipe read: %s", strerror(errno)); - case 1: - if (token == DO_JOB_RESUME[0]) - /* Complete relay requested from our SIGCONT handler */ - JobRestartJobs(); - break; - default: - abort(); + count = read(childExitJob.inPipe, &token, 1); +#if defined(__minix) + } while(-1 == count && EAGAIN == errno); +#endif /* defined(__minix) */ + if (count == 1) { + if (token == DO_JOB_RESUME[0]) + /* + * Complete relay requested from our SIGCONT + * handler + */ + JobRestartJobs(); + } else if (count == 0) + Punt("unexpected eof on token pipe"); + else + Punt("token pipe read: %s", strerror(errno)); + nready--; } - --nready; - } - Job_CatchChildren(); - if (nready == 0) - return; - - for (i = 2; i < nfds; i++) { - if (!fds[i].revents) - continue; - job = jobfds[i]; - if (job->job_state == JOB_ST_RUNNING) - JobDoOutput(job, FALSE); - if (--nready == 0) + Job_CatchChildren(); + if (nready == 0) return; - } + + for (i = npseudojobs * nfds_per_job(); i < fdsLen; i++) { + if (fds[i].revents == 0) + continue; + job = jobByFdIndex[i]; + if (job->status == JOB_ST_RUNNING) + JobDoOutput(job, FALSE); +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + /* + * With meta mode, we may have activity on the job's filemon + * descriptor too, which at the moment is any pollfd other + * than job->inPollfd. + */ + if (useMeta && job->inPollfd != &fds[i]) { + if (meta_job_event(job) <= 0) { + fds[i].events = 0; /* never mind */ + } + } +#endif + if (--nready == 0) + return; + } } -/*- - *----------------------------------------------------------------------- - * Job_Make -- - * Start the creation of a target. Basically a front-end for - * JobStart used by the Make module. - * - * Results: - * None. - * - * Side Effects: - * Another job is started. - * - *----------------------------------------------------------------------- +/* + * Start the creation of a target. Basically a front-end for JobStart used by + * the Make module. */ void Job_Make(GNode *gn) { - (void)JobStart(gn, 0); + (void)JobStart(gn, FALSE); +} + +static void +InitShellNameAndPath(void) +{ + shellName = shell->name; + +#ifdef DEFSHELL_CUSTOM + if (shellName[0] == '/') { + shellPath = shellName; + shellName = str_basename(shellPath); + return; + } +#endif + + shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); } void Shell_Init(void) { - if (shellPath == NULL) { - /* - * We are using the default shell, which may be an absolute - * path if DEFSHELL_CUSTOM is defined. - */ - shellName = commandShell->name; -#ifdef DEFSHELL_CUSTOM - if (*shellName == '/') { - shellPath = shellName; - shellName = strrchr(shellPath, '/'); - shellName++; - } else -#endif - shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH); - } - if (commandShell->exit == NULL) { - commandShell->exit = ""; - } - if (commandShell->echo == NULL) { - commandShell->echo = ""; - } - if (commandShell->hasErrCtl && *commandShell->exit) { - if (shellErrFlag && - strcmp(commandShell->exit, &shellErrFlag[1]) != 0) { - free(shellErrFlag); - shellErrFlag = NULL; - } - if (!shellErrFlag) { - int n = strlen(commandShell->exit) + 2; + if (shellPath == NULL) + InitShellNameAndPath(); - shellErrFlag = bmake_malloc(n); - if (shellErrFlag) { - snprintf(shellErrFlag, n, "-%s", commandShell->exit); - } + Var_SetWithFlags(SCOPE_CMDLINE, ".SHELL", shellPath, VAR_SET_READONLY); + if (shell->errFlag == NULL) + shell->errFlag = ""; + if (shell->echoFlag == NULL) + shell->echoFlag = ""; + if (shell->hasErrCtl && shell->errFlag[0] != '\0') { + if (shellErrFlag != NULL && + strcmp(shell->errFlag, &shellErrFlag[1]) != 0) { + free(shellErrFlag); + shellErrFlag = NULL; + } + if (shellErrFlag == NULL) { + size_t n = strlen(shell->errFlag) + 2; + + shellErrFlag = bmake_malloc(n); + if (shellErrFlag != NULL) + snprintf(shellErrFlag, n, "-%s", + shell->errFlag); + } + } else if (shellErrFlag != NULL) { + free(shellErrFlag); + shellErrFlag = NULL; } - } else if (shellErrFlag) { - free(shellErrFlag); - shellErrFlag = NULL; - } } -/*- - * Returns the string literal that is used in the current command shell +/* + * Return the string literal that is used in the current command shell * to produce a newline character. */ const char * Shell_GetNewline(void) { - - return commandShell->newline; + return shell->newline; } void Job_SetPrefix(void) { - - if (targPrefix) { - free(targPrefix); - } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) { - Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0); - } + if (targPrefix != NULL) { + free(targPrefix); + } else if (!Var_Exists(SCOPE_GLOBAL, MAKE_JOB_PREFIX)) { + Global_Set(MAKE_JOB_PREFIX, "---"); + } - targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}", VAR_GLOBAL, 0); + (void)Var_Subst("${" MAKE_JOB_PREFIX "}", + SCOPE_GLOBAL, VARE_WANTRES, &targPrefix); + /* TODO: handle errors */ } -/*- - *----------------------------------------------------------------------- - * Job_Init -- - * Initialize the process module - * - * Input: - * - * Results: - * none - * - * Side Effects: - * lists and counters are initialized - *----------------------------------------------------------------------- - */ +static void +AddSig(int sig, SignalProc handler) +{ + if (bmake_signal(sig, SIG_IGN) != SIG_IGN) { + sigaddset(&caught_signals, sig); + (void)bmake_signal(sig, handler); + } +} + +/* Initialize the process module. */ void Job_Init(void) { - Job_SetPrefix(); - /* Allocate space for all the job info */ - job_table = bmake_malloc(maxJobs * sizeof *job_table); - memset(job_table, 0, maxJobs * sizeof *job_table); - job_table_end = job_table + maxJobs; - wantToken = 0; + Job_SetPrefix(); + /* Allocate space for all the job info */ + job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table); + memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table); + job_table_end = job_table + opts.maxJobs; + wantToken = 0; + caught_sigchld = 0; - aborting = 0; - errors = 0; + aborting = ABORT_NONE; + job_errors = 0; - lastNode = NULL; + /* + * There is a non-zero chance that we already have children. + * eg after 'make -f- < 0) + continue; + if (rval == 0) + lurking_children = TRUE; + break; + } - /* - * There is a non-zero chance that we already have children. - * eg after 'make -f- < 0) - continue; - if (rval == 0) - lurking_children = 1; - break; - } + Shell_Init(); - Shell_Init(); + JobCreatePipe(&childExitJob, 3); - JobCreatePipe(&childExitJob, 3); + { + /* Preallocate enough for the maximum number of jobs. */ + size_t nfds = (npseudojobs + (size_t)opts.maxJobs) * + nfds_per_job(); + fds = bmake_malloc(sizeof *fds * nfds); + jobByFdIndex = bmake_malloc(sizeof *jobByFdIndex * nfds); + } - /* We can only need to wait for tokens, children and output from each job */ - fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs)); - jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs)); + /* These are permanent entries and take slots 0 and 1 */ + watchfd(&tokenWaitJob); + watchfd(&childExitJob); - /* These are permanent entries and take slots 0 and 1 */ - watchfd(&tokenWaitJob); - watchfd(&childExitJob); + sigemptyset(&caught_signals); + /* + * Install a SIGCHLD handler. + */ + (void)bmake_signal(SIGCHLD, JobChildSig); + sigaddset(&caught_signals, SIGCHLD); - sigemptyset(&caught_signals); - /* - * Install a SIGCHLD handler. - */ - (void)bmake_signal(SIGCHLD, JobChildSig); - sigaddset(&caught_signals, SIGCHLD); + /* + * Catch the four signals that POSIX specifies if they aren't ignored. + * JobPassSig will take care of calling JobInterrupt if appropriate. + */ + AddSig(SIGINT, JobPassSig_int); + AddSig(SIGHUP, JobPassSig_term); + AddSig(SIGTERM, JobPassSig_term); + AddSig(SIGQUIT, JobPassSig_term); -#define ADDSIG(s,h) \ - if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \ - sigaddset(&caught_signals, s); \ - (void)bmake_signal(s, h); \ - } + /* + * There are additional signals that need to be caught and passed if + * either the export system wants to be told directly of signals or if + * we're giving each job its own process group (since then it won't get + * signals from the terminal driver as we own the terminal) + */ + AddSig(SIGTSTP, JobPassSig_suspend); + AddSig(SIGTTOU, JobPassSig_suspend); + AddSig(SIGTTIN, JobPassSig_suspend); + AddSig(SIGWINCH, JobCondPassSig); + AddSig(SIGCONT, JobContinueSig); - /* - * Catch the four signals that POSIX specifies if they aren't ignored. - * JobPassSig will take care of calling JobInterrupt if appropriate. - */ - ADDSIG(SIGINT, JobPassSig_int) - ADDSIG(SIGHUP, JobPassSig_term) - ADDSIG(SIGTERM, JobPassSig_term) - ADDSIG(SIGQUIT, JobPassSig_term) - - /* - * There are additional signals that need to be caught and passed if - * either the export system wants to be told directly of signals or if - * we're giving each job its own process group (since then it won't get - * signals from the terminal driver as we own the terminal) - */ - ADDSIG(SIGTSTP, JobPassSig_suspend) - ADDSIG(SIGTTOU, JobPassSig_suspend) - ADDSIG(SIGTTIN, JobPassSig_suspend) - ADDSIG(SIGWINCH, JobCondPassSig) - ADDSIG(SIGCONT, JobContinueSig) -#undef ADDSIG - - (void)Job_RunTarget(".BEGIN", NULL); - postCommands = Targ_FindNode(".END", TARG_CREATE); + (void)Job_RunTarget(".BEGIN", NULL); + /* Create the .END node now, even though no code in the unit tests + * depends on it. See also Targ_GetEndNode in Compat_Run. */ + (void)Targ_GetEndNode(); } -static void JobSigReset(void) +static void +DelSig(int sig) { -#define DELSIG(s) \ - if (sigismember(&caught_signals, s)) { \ - (void)bmake_signal(s, SIG_DFL); \ - } - - DELSIG(SIGINT) - DELSIG(SIGHUP) - DELSIG(SIGQUIT) - DELSIG(SIGTERM) - DELSIG(SIGTSTP) - DELSIG(SIGTTOU) - DELSIG(SIGTTIN) - DELSIG(SIGWINCH) - DELSIG(SIGCONT) -#undef DELSIG - (void)bmake_signal(SIGCHLD, SIG_DFL); + if (sigismember(&caught_signals, sig) != 0) + (void)bmake_signal(sig, SIG_DFL); } -/*- - *----------------------------------------------------------------------- - * JobMatchShell -- - * Find a shell in 'shells' given its name. - * - * Results: - * A pointer to the Shell structure. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ +static void +JobSigReset(void) +{ + DelSig(SIGINT); + DelSig(SIGHUP); + DelSig(SIGQUIT); + DelSig(SIGTERM); + DelSig(SIGTSTP); + DelSig(SIGTTOU); + DelSig(SIGTTIN); + DelSig(SIGWINCH); + DelSig(SIGCONT); + (void)bmake_signal(SIGCHLD, SIG_DFL); +} + +/* Find a shell in 'shells' given its name, or return NULL. */ static Shell * -JobMatchShell(const char *name) +FindShellByName(const char *name) { - Shell *sh; + Shell *sh = shells; + const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0]; - for (sh = shells; sh->name != NULL; sh++) { - if (strcmp(name, sh->name) == 0) - return (sh); - } - return NULL; + for (sh = shells; sh < shellsEnd; sh++) { + if (strcmp(name, sh->name) == 0) + return sh; + } + return NULL; } -/*- - *----------------------------------------------------------------------- - * Job_ParseShell -- - * Parse a shell specification and set up commandShell, shellPath - * and shellName appropriately. +/* + * Parse a shell specification and set up 'shell', shellPath and + * shellName appropriately. * * Input: * line The shell spec * * Results: - * FAILURE if the specification was incorrect. + * FALSE if the specification was incorrect. * * Side Effects: - * commandShell points to a Shell structure (either predefined or + * 'shell' points to a Shell structure (either predefined or * created from the shell spec), shellPath is the full path of the - * shell described by commandShell, while shellName is just the + * shell described by 'shell', while shellName is just the * final component of shellPath. * * Notes: @@ -2357,690 +2331,645 @@ JobMatchShell(const char *name) * provides the functionality it does in C. Each word consists of * keyword and value separated by an equal sign. There should be no * unnecessary spaces in the word. The keywords are as follows: - * name Name of shell. - * path Location of shell. - * quiet Command to turn off echoing. - * echo Command to turn echoing on - * filter Result of turning off echoing that shouldn't be - * printed. - * echoFlag Flag to turn echoing on at the start - * errFlag Flag to turn error checking on at the start - * hasErrCtl True if shell has error checking control - * newline String literal to represent a newline char - * check Command to turn on error checking if hasErrCtl - * is TRUE or template of command to echo a command - * for which error checking is off if hasErrCtl is - * FALSE. - * ignore Command to turn off error checking if hasErrCtl - * is TRUE or template of command to execute a - * command so as to ignore any errors it returns if - * hasErrCtl is FALSE. - * - *----------------------------------------------------------------------- + * name Name of shell. + * path Location of shell. + * quiet Command to turn off echoing. + * echo Command to turn echoing on + * filter Result of turning off echoing that shouldn't be + * printed. + * echoFlag Flag to turn echoing on at the start + * errFlag Flag to turn error checking on at the start + * hasErrCtl True if shell has error checking control + * newline String literal to represent a newline char + * check Command to turn on error checking if hasErrCtl + * is TRUE or template of command to echo a command + * for which error checking is off if hasErrCtl is + * FALSE. + * ignore Command to turn off error checking if hasErrCtl + * is TRUE or template of command to execute a + * command so as to ignore any errors it returns if + * hasErrCtl is FALSE. */ -ReturnStatus +Boolean Job_ParseShell(char *line) { - char **words; - char **argv; - int argc; - char *path; - Shell newShell; - Boolean fullSpec = FALSE; - Shell *sh; + Words wordsList; + char **words; + char **argv; + size_t argc; + char *path; + Shell newShell; + Boolean fullSpec = FALSE; + Shell *sh; - while (isspace((unsigned char)*line)) { - line++; - } + /* XXX: don't use line as an iterator variable */ + pp_skip_whitespace(&line); - if (shellArgv) - free(UNCONST(shellArgv)); + free(shell_freeIt); - memset(&newShell, 0, sizeof(newShell)); + memset(&newShell, 0, sizeof newShell); - /* - * Parse the specification by keyword - */ - words = brk_string(line, &argc, TRUE, &path); - if (words == NULL) { - Error("Unterminated quoted string [%s]", line); - return FAILURE; - } - shellArgv = path; + /* + * Parse the specification by keyword + */ + wordsList = Str_Words(line, TRUE); + words = wordsList.words; + argc = wordsList.len; + path = wordsList.freeIt; + if (words == NULL) { + Error("Unterminated quoted string [%s]", line); + return FALSE; + } + shell_freeIt = path; - for (path = NULL, argv = words; argc != 0; argc--, argv++) { - if (strncmp(*argv, "path=", 5) == 0) { - path = &argv[0][5]; - } else if (strncmp(*argv, "name=", 5) == 0) { - newShell.name = &argv[0][5]; - } else { - if (strncmp(*argv, "quiet=", 6) == 0) { - newShell.echoOff = &argv[0][6]; - } else if (strncmp(*argv, "echo=", 5) == 0) { - newShell.echoOn = &argv[0][5]; - } else if (strncmp(*argv, "filter=", 7) == 0) { - newShell.noPrint = &argv[0][7]; - newShell.noPLen = strlen(newShell.noPrint); - } else if (strncmp(*argv, "echoFlag=", 9) == 0) { - newShell.echo = &argv[0][9]; - } else if (strncmp(*argv, "errFlag=", 8) == 0) { - newShell.exit = &argv[0][8]; - } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) { - char c = argv[0][10]; - newShell.hasErrCtl = !((c != 'Y') && (c != 'y') && - (c != 'T') && (c != 't')); - } else if (strncmp(*argv, "newline=", 8) == 0) { - newShell.newline = &argv[0][8]; - } else if (strncmp(*argv, "check=", 6) == 0) { - newShell.errCheck = &argv[0][6]; - } else if (strncmp(*argv, "ignore=", 7) == 0) { - newShell.ignErr = &argv[0][7]; - } else if (strncmp(*argv, "errout=", 7) == 0) { - newShell.errOut = &argv[0][7]; - } else if (strncmp(*argv, "comment=", 8) == 0) { - newShell.commentChar = argv[0][8]; + for (path = NULL, argv = words; argc != 0; argc--, argv++) { + char *arg = *argv; + if (strncmp(arg, "path=", 5) == 0) { + path = arg + 5; + } else if (strncmp(arg, "name=", 5) == 0) { + newShell.name = arg + 5; } else { - Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", - *argv); - free(words); - return(FAILURE); + if (strncmp(arg, "quiet=", 6) == 0) { + newShell.echoOff = arg + 6; + } else if (strncmp(arg, "echo=", 5) == 0) { + newShell.echoOn = arg + 5; + } else if (strncmp(arg, "filter=", 7) == 0) { + newShell.noPrint = arg + 7; + newShell.noPrintLen = strlen(newShell.noPrint); + } else if (strncmp(arg, "echoFlag=", 9) == 0) { + newShell.echoFlag = arg + 9; + } else if (strncmp(arg, "errFlag=", 8) == 0) { + newShell.errFlag = arg + 8; + } else if (strncmp(arg, "hasErrCtl=", 10) == 0) { + char c = arg[10]; + newShell.hasErrCtl = c == 'Y' || c == 'y' || + c == 'T' || c == 't'; + } else if (strncmp(arg, "newline=", 8) == 0) { + newShell.newline = arg + 8; + } else if (strncmp(arg, "check=", 6) == 0) { + /* Before 2020-12-10, these two variables + * had been a single variable. */ + newShell.errOn = arg + 6; + newShell.echoTmpl = arg + 6; + } else if (strncmp(arg, "ignore=", 7) == 0) { + /* Before 2020-12-10, these two variables + * had been a single variable. */ + newShell.errOff = arg + 7; + newShell.runIgnTmpl = arg + 7; + } else if (strncmp(arg, "errout=", 7) == 0) { + newShell.runChkTmpl = arg + 7; + } else if (strncmp(arg, "comment=", 8) == 0) { + newShell.commentChar = arg[8]; + } else { + Parse_Error(PARSE_FATAL, + "Unknown keyword \"%s\"", arg); + free(words); + return FALSE; + } + fullSpec = TRUE; } - fullSpec = TRUE; - } - } - - if (path == NULL) { - /* - * If no path was given, the user wants one of the pre-defined shells, - * yes? So we find the one s/he wants with the help of JobMatchShell - * and set things up the right way. shellPath will be set up by - * Shell_Init. - */ - if (newShell.name == NULL) { - Parse_Error(PARSE_FATAL, "Neither path nor name specified"); - free(words); - return(FAILURE); - } else { - if ((sh = JobMatchShell(newShell.name)) == NULL) { - Parse_Error(PARSE_WARNING, "%s: No matching shell", - newShell.name); - free(words); - return(FAILURE); - } - commandShell = sh; - shellName = newShell.name; - if (shellPath) { - /* Shell_Init has already been called! Do it again. */ - free(UNCONST(shellPath)); - shellPath = NULL; - Shell_Init(); - } } - } 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); + /* + * If no path was given, the user wants one of the + * pre-defined shells, yes? So we find the one s/he wants + * with the help of FindShellByName and set things up the + * right way. shellPath will be set up by Shell_Init. + */ + if (newShell.name == NULL) { + Parse_Error(PARSE_FATAL, + "Neither path nor name specified"); + free(words); + return FALSE; + } else { + if ((sh = FindShellByName(newShell.name)) == NULL) { + Parse_Error(PARSE_WARNING, + "%s: No matching shell", newShell.name); + free(words); + return FALSE; + } + shell = sh; + shellName = newShell.name; + if (shellPath != NULL) { + /* + * Shell_Init has already been called! + * Do it again. + */ + free(UNCONST(shellPath)); + shellPath = NULL; + Shell_Init(); + } + } } else { - path += 1; + /* + * 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; + } + if (!fullSpec) { + if ((sh = FindShellByName(shellName)) == NULL) { + Parse_Error(PARSE_WARNING, + "%s: No matching shell", shellName); + free(words); + return FALSE; + } + shell = sh; + } else { + shell = bmake_malloc(sizeof *shell); + *shell = newShell; + } + /* this will take care of shellErrFlag */ + Shell_Init(); } - if (newShell.name != NULL) { - shellName = newShell.name; - } else { - shellName = path; - } - if (!fullSpec) { - if ((sh = JobMatchShell(shellName)) == NULL) { - Parse_Error(PARSE_WARNING, "%s: No matching shell", - shellName); - free(words); - return(FAILURE); - } - commandShell = sh; - } else { - commandShell = bmake_malloc(sizeof(Shell)); - *commandShell = newShell; - } - /* this will take care of shellErrFlag */ - Shell_Init(); - } - if (commandShell->echoOn && commandShell->echoOff) { - commandShell->hasEchoCtl = TRUE; - } + if (shell->echoOn != NULL && shell->echoOff != NULL) + shell->hasEchoCtl = TRUE; - if (!commandShell->hasErrCtl) { - if (commandShell->errCheck == NULL) { - commandShell->errCheck = ""; + if (!shell->hasErrCtl) { + if (shell->echoTmpl == NULL) + shell->echoTmpl = ""; + if (shell->runIgnTmpl == NULL) + shell->runIgnTmpl = "%s\n"; } - if (commandShell->ignErr == NULL) { - commandShell->ignErr = "%s\n"; - } - } - /* - * Do not free up the words themselves, since they might be in use by the - * shell specification. - */ - free(words); - return SUCCESS; + /* + * Do not free up the words themselves, since they might be in use + * by the shell specification. + */ + free(words); + return TRUE; } -/*- - *----------------------------------------------------------------------- - * JobInterrupt -- - * Handle the receipt of an interrupt. +/* + * Handle the receipt of an interrupt. + * + * All children are killed. Another job will be started if the .INTERRUPT + * target is defined. * * Input: * runINTERRUPT Non-zero if commands for the .INTERRUPT target * should be executed * signo signal received - * - * Results: - * None - * - * Side Effects: - * All children are killed. Another job will be started if the - * .INTERRUPT target was given. - *----------------------------------------------------------------------- */ static void -JobInterrupt(int runINTERRUPT, int signo) +JobInterrupt(Boolean runINTERRUPT, int signo) { - Job *job; /* job descriptor in that element */ - GNode *interrupt; /* the node describing the .INTERRUPT target */ - sigset_t mask; - GNode *gn; + Job *job; /* job descriptor in that element */ + GNode *interrupt; /* the node describing the .INTERRUPT target */ + sigset_t mask; + GNode *gn; - aborting = ABORT_INTERRUPT; + aborting = ABORT_INTERRUPT; - JobSigLock(&mask); + JobSigLock(&mask); - for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) - continue; + for (job = job_table; job < job_table_end; job++) { + if (job->status != JOB_ST_RUNNING) + continue; - gn = job->node; + gn = job->node; - if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) { - char *file = (gn->path == NULL ? gn->name : gn->path); - if (!noExecute && eunlink(file) != -1) { - Error("*** %s removed", file); - } + JobDeleteTarget(gn); + if (job->pid != 0) { + DEBUG2(JOB, + "JobInterrupt passing signal %d to child %d.\n", + signo, job->pid); + KILLPG(job->pid, signo); + } } - if (job->pid) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, - "JobInterrupt passing signal %d to child %d.\n", - signo, job->pid); - } - KILLPG(job->pid, signo); - } - } - JobSigUnlock(&mask); + JobSigUnlock(&mask); - if (runINTERRUPT && !touchFlag) { - interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); - if (interrupt != NULL) { - ignoreErrors = FALSE; - JobRun(interrupt); + if (runINTERRUPT && !opts.touchFlag) { + interrupt = Targ_FindNode(".INTERRUPT"); + if (interrupt != NULL) { + opts.ignoreErrors = FALSE; + JobRun(interrupt); + } } - } - Trace_Log(MAKEINTR, 0); - exit(signo); + Trace_Log(MAKEINTR, NULL); + exit(signo); /* XXX: why signo? */ } /* - *----------------------------------------------------------------------- - * Job_Finish -- - * Do final processing such as the running of the commands - * attached to the .END target. + * Do the final processing, i.e. run the commands attached to the .END target. * - * Results: - * Number of errors reported. - * - * Side Effects: - * None. - *----------------------------------------------------------------------- + * Return the number of errors reported. */ int Job_Finish(void) { - if (postCommands != NULL && - (!Lst_IsEmpty(postCommands->commands) || - !Lst_IsEmpty(postCommands->children))) { - if (errors) { - Error("Errors reported so .END ignored"); - } else { - JobRun(postCommands); + GNode *endNode = Targ_GetEndNode(); + if (!Lst_IsEmpty(&endNode->commands) || + !Lst_IsEmpty(&endNode->children)) { + if (job_errors != 0) { + Error("Errors reported so .END ignored"); + } else { + JobRun(endNode); + } } - } - return(errors); + return job_errors; } -/*- - *----------------------------------------------------------------------- - * Job_End -- - * Cleanup any memory used by the jobs module - * - * Results: - * None. - * - * Side Effects: - * Memory is freed - *----------------------------------------------------------------------- - */ +/* Clean up any memory used by the jobs module. */ void Job_End(void) { #ifdef CLEANUP - if (shellArgv) - free(shellArgv); + free(shell_freeIt); #endif } -/*- - *----------------------------------------------------------------------- - * Job_Wait -- - * Waits for all running jobs to finish and returns. Sets 'aborting' - * to ABORT_WAIT to prevent other jobs from starting. - * - * Results: - * None. - * - * Side Effects: - * Currently running jobs finish. - * - *----------------------------------------------------------------------- +/* + * Waits for all running jobs to finish and returns. + * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. */ void Job_Wait(void) { - aborting = ABORT_WAIT; - while (jobTokensRunning != 0) { - Job_CatchOutput(); - } - aborting = 0; + aborting = ABORT_WAIT; + while (jobTokensRunning != 0) { + Job_CatchOutput(); + } + aborting = ABORT_NONE; } -/*- - *----------------------------------------------------------------------- - * Job_AbortAll -- - * Abort all currently running jobs without handling output or anything. - * This function is to be called only in the event of a major - * error. Most definitely NOT to be called from JobInterrupt. +/* + * Abort all currently running jobs without handling output or anything. + * This function is to be called only in the event of a major error. + * Most definitely NOT to be called from JobInterrupt. * - * Results: - * None - * - * Side Effects: - * All children are killed, not just the firstborn - *----------------------------------------------------------------------- + * All children are killed, not just the firstborn. */ void Job_AbortAll(void) { - Job *job; /* the job descriptor in that element */ - int foo; + Job *job; /* the job descriptor in that element */ + int foo; - aborting = ABORT_ERROR; + aborting = ABORT_ERROR; - if (jobTokensRunning) { - for (job = job_table; job < job_table_end; job++) { - if (job->job_state != JOB_ST_RUNNING) - continue; - /* - * kill the child process with increasingly drastic signals to make - * darn sure it's dead. - */ - KILLPG(job->pid, SIGINT); - KILLPG(job->pid, SIGKILL); + if (jobTokensRunning != 0) { + for (job = job_table; job < job_table_end; job++) { + if (job->status != JOB_ST_RUNNING) + continue; + /* + * kill the child process with increasingly drastic + * signals to make darn sure it's dead. + */ + KILLPG(job->pid, SIGINT); + KILLPG(job->pid, SIGKILL); + } } - } - /* - * Catch as many children as want to report in at first, then give up - */ - while (waitpid((pid_t) -1, &foo, WNOHANG) > 0) - continue; + /* + * Catch as many children as want to report in at first, then give up + */ + while (waitpid((pid_t)-1, &foo, WNOHANG) > 0) + continue; } - -/*- - *----------------------------------------------------------------------- - * JobRestartJobs -- - * Tries to restart stopped jobs if there are slots available. - * Called in process context in response to a SIGCONT. - * - * Results: - * None. - * - * Side Effects: - * Resumes jobs. - * - *----------------------------------------------------------------------- +/* + * Tries to restart stopped jobs if there are slots available. + * Called in process context in response to a SIGCONT. */ static void JobRestartJobs(void) { - Job *job; + Job *job; - for (job = job_table; job < job_table_end; job++) { - if (job->job_state == JOB_ST_RUNNING && - (make_suspended || job->job_suspended)) { - if (DEBUG(JOB)) { - (void)fprintf(debug_file, "Restarting stopped job pid %d.\n", - job->pid); - } - if (job->job_suspended) { - (void)printf("*** [%s] Continued\n", job->node->name); - (void)fflush(stdout); - } - job->job_suspended = 0; - if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { - fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid); - } + for (job = job_table; job < job_table_end; job++) { + if (job->status == JOB_ST_RUNNING && + (make_suspended || job->suspended)) { + DEBUG1(JOB, "Restarting stopped job pid %d.\n", + job->pid); + if (job->suspended) { + (void)printf("*** [%s] Continued\n", + job->node->name); + (void)fflush(stdout); + } + job->suspended = FALSE; + if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { + debug_printf("Failed to send SIGCONT to %d\n", + job->pid); + } + } + if (job->status == JOB_ST_FINISHED) { + /* + * Job exit deferred after calling waitpid() in a + * signal handler + */ + JobFinish(job, job->exit_status); + } } - if (job->job_state == JOB_ST_FINISHED) - /* Job exit deferred after calling waitpid() in a signal handler */ - JobFinish(job, job->exit_status); - } - make_suspended = 0; + make_suspended = FALSE; } static void watchfd(Job *job) { - if (job->inPollfd != NULL) - Punt("Watching watched job"); + if (job->inPollfd != NULL) + Punt("Watching watched job"); - fds[nfds].fd = job->inPipe; - fds[nfds].events = POLLIN; - jobfds[nfds] = job; - job->inPollfd = &fds[nfds]; - nfds++; + fds[fdsLen].fd = job->inPipe; + fds[fdsLen].events = POLLIN; + jobByFdIndex[fdsLen] = job; + job->inPollfd = &fds[fdsLen]; + fdsLen++; +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + if (useMeta) { + fds[fdsLen].fd = meta_job_fd(job); + fds[fdsLen].events = fds[fdsLen].fd == -1 ? 0 : POLLIN; + jobByFdIndex[fdsLen] = job; + fdsLen++; + } +#endif } static void clearfd(Job *job) { - int i; - if (job->inPollfd == NULL) - Punt("Unwatching unwatched job"); - i = job->inPollfd - fds; - nfds--; - /* - * Move last job in table into hole made by dead job. - */ - if (nfds != i) { - fds[i] = fds[nfds]; - jobfds[i] = jobfds[nfds]; - jobfds[i]->inPollfd = &fds[i]; - } - job->inPollfd = NULL; + size_t i; + if (job->inPollfd == NULL) + Punt("Unwatching unwatched job"); + i = (size_t)(job->inPollfd - fds); + fdsLen--; +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + if (useMeta) { + /* + * Sanity check: there should be two fds per job, so the job's + * pollfd number should be even. + */ + assert(nfds_per_job() == 2); + if (i % 2 != 0) + Punt("odd-numbered fd with meta"); + fdsLen--; + } +#endif + /* + * Move last job in table into hole made by dead job. + */ + if (fdsLen != i) { + fds[i] = fds[fdsLen]; + jobByFdIndex[i] = jobByFdIndex[fdsLen]; + jobByFdIndex[i]->inPollfd = &fds[i]; +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + if (useMeta) { + fds[i + 1] = fds[fdsLen + 1]; + jobByFdIndex[i + 1] = jobByFdIndex[fdsLen + 1]; + } +#endif + } + job->inPollfd = NULL; } -static int +static Boolean readyfd(Job *job) { - if (job->inPollfd == NULL) - Punt("Polling unwatched job"); - return (job->inPollfd->revents & POLLIN) != 0; + if (job->inPollfd == NULL) + Punt("Polling unwatched job"); + return (job->inPollfd->revents & POLLIN) != 0; } -/*- - *----------------------------------------------------------------------- - * JobTokenAdd -- - * Put a token into the job pipe so that some make process can start - * another job. - * - * Side Effects: - * Allows more build jobs to be spawned somewhere. - * - *----------------------------------------------------------------------- +/* + * Put a token (back) into the job pipe. + * This allows a make process to start a build job. */ - static void JobTokenAdd(void) { - char tok = JOB_TOKENS[aborting], tok1; + char tok = JOB_TOKENS[aborting], tok1; - /* If we are depositing an error token flush everything else */ - while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) - continue; + /* If we are depositing an error token flush everything else */ + while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) + continue; - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) aborting %d, deposit token %c\n", + DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n", getpid(), aborting, JOB_TOKENS[aborting]); - while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) - continue; + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) + continue; } -/*- - *----------------------------------------------------------------------- - * Job_ServerStartTokenAdd -- - * Prep the job token pipe in the root make process. - * - *----------------------------------------------------------------------- - */ +/* Get a temp file */ +int +Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz) +{ + int fd; + sigset_t mask; + JobSigLock(&mask); + fd = mkTempFile(pattern, tfile, tfile_sz); + if (tfile != NULL && !DEBUG(SCRIPT)) + unlink(tfile); + JobSigUnlock(&mask); + + return fd; +} + +/* Prep the job token pipe in the root make process. */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { - int i; - char jobarg[64]; - - if (jp_0 >= 0 && jp_1 >= 0) { - /* Pipe passed in from parent */ - tokenWaitJob.inPipe = jp_0; - tokenWaitJob.outPipe = jp_1; - (void)fcntl(jp_0, F_SETFD, 1); - (void)fcntl(jp_1, F_SETFD, 1); - return; - } + int i; + char jobarg[64]; - JobCreatePipe(&tokenWaitJob, 15); + if (jp_0 >= 0 && jp_1 >= 0) { + /* Pipe passed in from parent */ + tokenWaitJob.inPipe = jp_0; + tokenWaitJob.outPipe = jp_1; + (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC); + (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC); + return; + } - snprintf(jobarg, sizeof(jobarg), "%d,%d", + JobCreatePipe(&tokenWaitJob, 15); + + snprintf(jobarg, sizeof jobarg, "%d,%d", tokenWaitJob.inPipe, tokenWaitJob.outPipe); - Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); - Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL); + Global_Append(MAKEFLAGS, "-J"); + Global_Append(MAKEFLAGS, jobarg); - /* - * Preload the job pipe with one token per job, save the one - * "extra" token for the primary job. - * - * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is - * larger than the write buffer size of the pipe, we will - * deadlock here. - */ - for (i = 1; i < max_tokens; i++) - JobTokenAdd(); + /* + * Preload the job pipe with one token per job, save the one + * "extra" token for the primary job. + * + * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is + * larger than the write buffer size of the pipe, we will + * deadlock here. + */ + for (i = 1; i < max_tokens; i++) + JobTokenAdd(); } -/*- - *----------------------------------------------------------------------- - * Job_TokenReturn -- - * Return a withdrawn token to the pool. - * - *----------------------------------------------------------------------- - */ - +/* Return a withdrawn token to the pool. */ void Job_TokenReturn(void) { - jobTokensRunning--; - if (jobTokensRunning < 0) - Punt("token botch"); - if (jobTokensRunning || JOB_TOKENS[aborting] != '+') - JobTokenAdd(); + jobTokensRunning--; + if (jobTokensRunning < 0) + Punt("token botch"); + if (jobTokensRunning != 0 || JOB_TOKENS[aborting] != '+') + JobTokenAdd(); } -/*- - *----------------------------------------------------------------------- - * Job_TokenWithdraw -- - * Attempt to withdraw a token from the pool. +/* + * Attempt to withdraw a token from the pool. * - * Results: - * Returns TRUE if a token was withdrawn, and FALSE if the pool - * is currently empty. + * If pool is empty, set wantToken so that we wake up when a token is + * released. * - * Side Effects: - * If pool is empty, set wantToken so that we wake up - * when a token is released. - * - *----------------------------------------------------------------------- + * Returns TRUE if a token was withdrawn, and FALSE if the pool is currently + * empty. */ - - Boolean Job_TokenWithdraw(void) { - char tok, tok1; - int count; + char tok, tok1; + ssize_t count; - wantToken = 0; - if (DEBUG(JOB)) - fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n", - getpid(), aborting, jobTokensRunning); + wantToken = 0; + DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n", + getpid(), aborting, jobTokensRunning); - if (aborting || (jobTokensRunning >= maxJobs)) - return FALSE; + if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs)) + return FALSE; - count = read(tokenWaitJob.inPipe, &tok, 1); - if (count == 0) - Fatal("eof on job pipe!"); - if (count < 0 && jobTokensRunning != 0) { - if (errno != EAGAIN) { - Fatal("job pipe read: %s", strerror(errno)); + count = read(tokenWaitJob.inPipe, &tok, 1); + if (count == 0) + Fatal("eof on job pipe!"); + if (count < 0 && jobTokensRunning != 0) { + if (errno != EAGAIN) { + Fatal("job pipe read: %s", strerror(errno)); + } + DEBUG1(JOB, "(%d) blocked for token\n", getpid()); + wantToken = 1; + return FALSE; } - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) blocked for token\n", getpid()); - wantToken = 1; - return FALSE; - } - if (count == 1 && tok != '+') { - /* make being abvorted - remove any other job tokens */ - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok); - while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) - continue; - /* And put the stopper back */ - while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) - continue; - Fatal("A failure has been detected in another branch of the parallel make"); - } + if (count == 1 && tok != '+') { + /* make being aborted - remove any other job tokens */ + DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok); + while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) + continue; + /* And put the stopper back */ + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && + errno == EAGAIN) + continue; + if (shouldDieQuietly(NULL, 1)) + exit(6); /* we aborted */ + Fatal("A failure has been detected " + "in another branch of the parallel make"); + } - if (count == 1 && jobTokensRunning == 0) - /* We didn't want the token really */ - while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) - continue; + if (count == 1 && jobTokensRunning == 0) + /* We didn't want the token really */ + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && + errno == EAGAIN) + continue; - jobTokensRunning++; - if (DEBUG(JOB)) - fprintf(debug_file, "(%d) withdrew token\n", getpid()); - return TRUE; + jobTokensRunning++; + DEBUG1(JOB, "(%d) withdrew token\n", getpid()); + return TRUE; } -/*- - *----------------------------------------------------------------------- - * Job_RunTarget -- - * Run the named target if found. If a filename is specified, then - * set that to the sources. +/* + * Run the named target if found. If a filename is specified, then set that + * to the sources. * - * Results: - * None - * - * Side Effects: - * exits if the target fails. - * - *----------------------------------------------------------------------- + * Exits if the target fails. */ Boolean -Job_RunTarget(const char *target, const char *fname) { - GNode *gn = Targ_FindNode(target, TARG_NOCREATE); +Job_RunTarget(const char *target, const char *fname) +{ + GNode *gn = Targ_FindNode(target); + if (gn == NULL) + return FALSE; - if (gn == NULL) - return FALSE; + if (fname != NULL) + Var_Set(gn, ALLSRC, fname); - if (fname) - Var_Set(ALLSRC, fname, gn, 0); - - JobRun(gn); - if (gn->made == ERROR) { - PrintOnError(gn, "\n\nStop."); - exit(1); - } - return TRUE; + JobRun(gn); + /* XXX: Replace with GNode_IsError(gn) */ + if (gn->made == ERROR) { + PrintOnError(gn, "\n\nStop."); + exit(1); + } + return TRUE; } #ifdef USE_SELECT int emul_poll(struct pollfd *fd, int nfd, int timeout) { - fd_set rfds, wfds; - int i, maxfd, nselect, npoll; - struct timeval tv, *tvp; - long usecs; + fd_set rfds, wfds; + int i, maxfd, nselect, npoll; + struct timeval tv, *tvp; + long usecs; - FD_ZERO(&rfds); - FD_ZERO(&wfds); + FD_ZERO(&rfds); + FD_ZERO(&wfds); - maxfd = -1; - for (i = 0; i < nfd; i++) { - fd[i].revents = 0; + maxfd = -1; + for (i = 0; i < nfd; i++) { + fd[i].revents = 0; - if (fd[i].events & POLLIN) - FD_SET(fd[i].fd, &rfds); + if (fd[i].events & POLLIN) + FD_SET(fd[i].fd, &rfds); - if (fd[i].events & POLLOUT) - FD_SET(fd[i].fd, &wfds); + if (fd[i].events & POLLOUT) + FD_SET(fd[i].fd, &wfds); - if (fd[i].fd > maxfd) - maxfd = fd[i].fd; - } - - if (maxfd >= FD_SETSIZE) { - Punt("Ran out of fd_set slots; " - "recompile with a larger FD_SETSIZE."); - } + if (fd[i].fd > maxfd) + maxfd = fd[i].fd; + } - if (timeout < 0) { - tvp = NULL; - } else { - usecs = timeout * 1000; - tv.tv_sec = usecs / 1000000; - tv.tv_usec = usecs % 1000000; - tvp = &tv; - } + if (maxfd >= FD_SETSIZE) { + Punt("Ran out of fd_set slots; " + "recompile with a larger FD_SETSIZE."); + } - nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp); + if (timeout < 0) { + tvp = NULL; + } else { + usecs = timeout * 1000; + tv.tv_sec = usecs / 1000000; + tv.tv_usec = usecs % 1000000; + tvp = &tv; + } - if (nselect <= 0) - return nselect; + nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp); - npoll = 0; - for (i = 0; i < nfd; i++) { - if (FD_ISSET(fd[i].fd, &rfds)) - fd[i].revents |= POLLIN; + if (nselect <= 0) + return nselect; - if (FD_ISSET(fd[i].fd, &wfds)) - fd[i].revents |= POLLOUT; + npoll = 0; + for (i = 0; i < nfd; i++) { + if (FD_ISSET(fd[i].fd, &rfds)) + fd[i].revents |= POLLIN; - if (fd[i].revents) - npoll++; - } + if (FD_ISSET(fd[i].fd, &wfds)) + fd[i].revents |= POLLOUT; - return npoll; + if (fd[i].revents) + npoll++; + } + + return npoll; } #endif /* USE_SELECT */ diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h index 91e2c8782..0d7d7313b 100644 --- a/usr.bin/make/job.h +++ b/usr.bin/make/job.h @@ -1,4 +1,4 @@ -/* $NetBSD: job.h,v 1.42 2013/07/05 22:14:56 sjg Exp $ */ +/* $NetBSD: job.h,v 1.72 2021/02/05 19:19:17 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -73,12 +73,12 @@ * from: @(#)job.h 8.1 (Berkeley) 6/6/93 */ -/*- - * job.h -- - * Definitions pertaining to the running of jobs in parallel mode. +/* + * Running of jobs in parallel mode. */ -#ifndef _JOB_H_ -#define _JOB_H_ + +#ifndef MAKE_JOB_H +#define MAKE_JOB_H #define TMPPAT "makeXXXXXX" /* relative to tmpdir */ @@ -97,8 +97,8 @@ struct emul_pollfd { short revents; }; -#define POLLIN 0x0001 -#define POLLOUT 0x0004 +#define POLLIN 0x0001 +#define POLLOUT 0x0004 int emul_poll(struct pollfd *fd, int nfd, int timeout); @@ -106,33 +106,10 @@ emul_poll(struct pollfd *fd, int nfd, int timeout); /* * The POLL_MSEC constant determines the maximum number of milliseconds spent - * in poll before coming out to see if a child has finished. + * in poll before coming out to see if a child has finished. */ #define POLL_MSEC 5000 - -/*- - * Job Table definitions. - * - * Each job has several things associated with it: - * 1) The process id of the child shell - * 2) The graph node describing the target being made by this job - * 3) A LstNode for the first command to be saved after the job - * completes. This is NULL if there was no "..." in the job's - * commands. - * 4) An FILE* for writing out the commands. This is only - * used before the job is actually started. - * 5) The output is being caught via a pipe and - * the descriptors of our pipe, an array in which output is line - * buffered and the current position in that buffer are all - * maintained for each job. - * 6) A word of flags which determine how the module handles errors, - * echoing, etc. for the job - * - * When a job is finished, the Make_Update function is called on each of the - * parents of the node which was just remade. This takes care of the upward - * traversal of the dependency graph. - */ struct pollfd; @@ -140,135 +117,94 @@ struct pollfd; # include "meta.h" #endif -#define JOB_BUFSIZE 1024 -typedef struct Job { - int pid; /* The child's process ID */ - GNode *node; /* The target the child is making */ - LstNode tailCmds; /* The node of the first command to be - * saved when the job has been run */ - FILE *cmdFILE; /* When creating the shell script, this is - * where the commands go */ - int exit_status; /* from wait4() in signal handler */ - char job_state; /* status of the job entry */ -#define JOB_ST_FREE 0 /* Job is available */ -#define JOB_ST_SETUP 1 /* Job is allocated but otherwise invalid */ -#define JOB_ST_RUNNING 3 /* Job is running, pid valid */ -#define JOB_ST_FINISHED 4 /* Job is done (ie after SIGCHILD) */ - char job_suspended; - short flags; /* Flags to control treatment of job */ -#define JOB_IGNERR 0x001 /* Ignore non-zero exits */ -#define JOB_SILENT 0x002 /* no output */ -#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally - * if we can't export it and maxLocal is 0 */ -#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing - * commands */ -#define JOB_TRACED 0x400 /* we've sent 'set -x' */ +typedef enum JobStatus { + JOB_ST_FREE = 0, /* Job is available */ + JOB_ST_SET_UP = 1, /* Job is allocated but otherwise invalid */ + /* XXX: What about the 2? */ + JOB_ST_RUNNING = 3, /* Job is running, pid valid */ + JOB_ST_FINISHED = 4 /* Job is done (ie after SIGCHILD) */ +} JobStatus; - int jobPipe[2]; /* Pipe for readind output from job */ - struct pollfd *inPollfd; /* pollfd associated with inPipe */ - char outBuf[JOB_BUFSIZE + 1]; - /* Buffer for storing the output of the - * job, line by line */ - int curPos; /* Current position in op_outBuf */ +/* + * A Job manages the shell commands that are run to create a single target. + * Each job is run in a separate subprocess by a shell. Several jobs can run + * in parallel. + * + * The shell commands for the target are written to a temporary file, + * then the shell is run with the temporary file as stdin, and the output + * of that shell is captured via a pipe. + * + * When a job is finished, Make_Update updates all parents of the node + * that was just remade, marking them as ready to be made next if all + * other dependencies are finished as well. + */ +typedef struct Job { + /* The process ID of the shell running the commands */ + int pid; + + /* The target the child is making */ + GNode *node; + + /* If one of the shell commands is "...", all following commands are + * delayed until the .END node is made. This list node points to the + * first of these commands, if any. */ + StringListNode *tailCmds; + + /* This is where the shell commands go. */ + FILE *cmdFILE; + + int exit_status; /* from wait4() in signal handler */ + + JobStatus status; + + Boolean suspended; + + /* Ignore non-zero exits */ + Boolean ignerr; + /* Output the command before or instead of running it. */ + Boolean echo; + /* Target is a special one. */ + Boolean special; + + int inPipe; /* Pipe for reading output from job */ + int outPipe; /* Pipe for writing control commands */ + struct pollfd *inPollfd; /* pollfd associated with inPipe */ + +#define JOB_BUFSIZE 1024 + /* Buffer for storing the output of the job, line by line. */ + char outBuf[JOB_BUFSIZE + 1]; + size_t curPos; /* Current position in outBuf. */ #ifdef USE_META - struct BuildMon bm; + struct BuildMon bm; #endif } Job; -#define inPipe jobPipe[0] -#define outPipe jobPipe[1] - - -/*- - * Shell Specifications: - * Each shell type has associated with it the following information: - * 1) The string which must match the last character of the shell name - * for the shell to be considered of this type. The longest match - * wins. - * 2) A command to issue to turn off echoing of command lines - * 3) A command to issue to turn echoing back on again - * 4) What the shell prints, and its length, when given the echo-off - * command. This line will not be printed when received from the shell - * 5) A boolean to tell if the shell has the ability to control - * error checking for individual commands. - * 6) The string to turn this checking on. - * 7) The string to turn it off. - * 8) The command-flag to give to cause the shell to start echoing - * commands right away. - * 9) The command-flag to cause the shell to Lib_Exit when an error is - * detected in one of the commands. - * - * Some special stuff goes on if a shell doesn't have error control. In such - * a case, errCheck becomes a printf template for echoing the command, - * should echoing be on and ignErr becomes another printf template for - * executing the command while ignoring the return status. Finally errOut - * is a printf template for running the command and causing the shell to - * exit on error. If any of these strings are empty when hasErrCtl is FALSE, - * the command will be executed anyway as is and if it causes an error, so be - * it. Any templates setup to echo the command will escape any '$ ` \ "'i - * characters in the command string to avoid common problems with - * echo "%s\n" as a template. - */ -typedef struct Shell { - const char *name; /* the name of the shell. For Bourne and C - * shells, this is used only to find the - * shell description when used as the single - * source of a .SHELL target. For user-defined - * shells, this is the full path of the shell. - */ - Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ - const char *echoOff; /* command to turn off echo */ - const char *echoOn; /* command to turn it back on again */ - const char *noPrint; /* command to skip when printing output from - * shell. This is usually the command which - * was executed to turn off echoing */ - int noPLen; /* length of noPrint command */ - Boolean hasErrCtl; /* set if can control error checking for - * individual commands */ - const char *errCheck; /* string to turn error checking on */ - const char *ignErr; /* string to turn off error checking */ - const char *errOut; /* string to use for testing exit code */ - const char *newline; /* string literal that results in a newline - * character when it appears outside of any - * 'quote' or "quote" characters */ - char commentChar; /* character used by shell for comment lines */ - - /* - * command-line flags - */ - const char *echo; /* echo commands */ - const char *exit; /* exit on error */ -} Shell; - extern const char *shellPath; extern const char *shellName; extern char *shellErrFlag; -extern int jobTokensRunning; /* tokens currently "out" */ -extern int maxJobs; /* Max jobs we can run */ +extern int jobTokensRunning; /* tokens currently "out" */ void Shell_Init(void); const char *Shell_GetNewline(void); void Job_Touch(GNode *, Boolean); -Boolean Job_CheckCommands(GNode *, void (*abortProc )(const char *, ...)); -#define CATCH_BLOCK 1 +Boolean Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); void Job_CatchChildren(void); void Job_CatchOutput(void); void Job_Make(GNode *); void Job_Init(void); -Boolean Job_Full(void); -Boolean Job_Empty(void); -ReturnStatus Job_ParseShell(char *); +Boolean Job_ParseShell(char *); int Job_Finish(void); void Job_End(void); void Job_Wait(void); void Job_AbortAll(void); -void JobFlagForMigration(int); void Job_TokenReturn(void); Boolean Job_TokenWithdraw(void); void Job_ServerStart(int, int, int); void Job_SetPrefix(void); Boolean Job_RunTarget(const char *, const char *); +void Job_FlagsToString(const Job *, char *, size_t); +int Job_TempFile(const char *, char *, size_t); -#endif /* _JOB_H_ */ +#endif /* MAKE_JOB_H */ diff --git a/usr.bin/make/lst.c b/usr.bin/make/lst.c new file mode 100644 index 000000000..372973112 --- /dev/null +++ b/usr.bin/make/lst.c @@ -0,0 +1,292 @@ +/* $NetBSD: lst.c,v 1.105 2021/03/15 16:45:30 rillig Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "make.h" + +MAKE_RCSID("$NetBSD: lst.c,v 1.105 2021/03/15 16:45:30 rillig Exp $"); + +static ListNode * +LstNodeNew(ListNode *prev, ListNode *next, void *datum) +{ + ListNode *ln = bmake_malloc(sizeof *ln); + + ln->prev = prev; + ln->next = next; + ln->datum = 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) +{ + ListNode *ln, *next; + + for (ln = list->first; ln != NULL; ln = next) { + next = ln->next; + free(ln); + } +} + +void +Lst_DoneCall(List *list, LstFreeProc freeProc) +{ + ListNode *ln, *next; + + for (ln = list->first; ln != NULL; ln = next) { + next = ln->next; + freeProc(ln->datum); + free(ln); + } +} + +/* 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) +{ + ListNode *newNode; + + assert(datum != NULL); + + newNode = LstNodeNew(ln->prev, ln, datum); + + if (ln->prev != NULL) + ln->prev->next = newNode; + ln->prev = newNode; + + if (ln == list->first) + list->first = newNode; +} + +/* Add a piece of data at the start of the given list. */ +void +Lst_Prepend(List *list, void *datum) +{ + ListNode *ln; + + assert(datum != NULL); + + ln = LstNodeNew(NULL, list->first, datum); + + if (list->first == NULL) { + list->first = ln; + list->last = ln; + } else { + list->first->prev = ln; + list->first = ln; + } +} + +/* Add a piece of data at the end of the given list. */ +void +Lst_Append(List *list, void *datum) +{ + ListNode *ln; + + assert(datum != NULL); + + ln = LstNodeNew(list->last, NULL, datum); + + if (list->last == NULL) { + list->first = ln; + list->last = ln; + } else { + list->last->next = ln; + list->last = ln; + } +} + +/* + * Remove the given node from the given list. + * The datum stored in the node must be freed by the caller, if necessary. + */ +void +Lst_Remove(List *list, ListNode *ln) +{ + /* unlink it from its neighbors */ + if (ln->next != NULL) + ln->next->prev = ln->prev; + if (ln->prev != NULL) + ln->prev->next = ln->next; + + /* unlink it from the list */ + if (list->first == ln) + list->first = ln->next; + if (list->last == ln) + list->last = ln->prev; +} + +/* Replace the datum in the given node with the new datum. */ +void +LstNode_Set(ListNode *ln, void *datum) +{ + assert(datum != NULL); + + ln->datum = datum; +} + +/* + * Replace the datum in the given node with NULL. + * Having NULL values in a list is unusual though. + */ +void +LstNode_SetNull(ListNode *ln) +{ + ln->datum = NULL; +} + +/* + * Return the first node that contains the given datum, or NULL. + * + * Time complexity: O(length(list)) + */ +ListNode * +Lst_FindDatum(List *list, const void *datum) +{ + ListNode *ln; + + assert(datum != NULL); + + for (ln = list->first; ln != NULL; ln = ln->next) + if (ln->datum == datum) + return ln; + + return NULL; +} + +/* + * Move all nodes from src to the end of dst. + * The source list becomes indeterminate. + */ +void +Lst_MoveAll(List *dst, List *src) +{ + if (src->first != NULL) { + src->first->prev = dst->last; + if (dst->last != NULL) + dst->last->next = src->first; + else + dst->first = src->first; + + dst->last = src->last; + } +#ifdef CLEANUP + src->first = NULL; + src->last = NULL; +#endif +} + +/* Copy the element data from src to the start of dst. */ +void +Lst_PrependAll(List *dst, List *src) +{ + ListNode *ln; + + for (ln = src->last; ln != NULL; ln = ln->prev) + Lst_Prepend(dst, ln->datum); +} + +/* Copy the element data from src to the end of dst. */ +void +Lst_AppendAll(List *dst, List *src) +{ + ListNode *ln; + + for (ln = src->first; ln != NULL; ln = ln->next) + Lst_Append(dst, ln->datum); +} + +/* Remove and return the datum at the head of the given list. */ +void * +Lst_Dequeue(List *list) +{ + void *datum = list->first->datum; + Lst_Remove(list, list->first); + assert(datum != NULL); /* since NULL would mean end of the list */ + return datum; +} + +void +Vector_Init(Vector *v, size_t itemSize) +{ + v->len = 0; + v->cap = 10; + v->itemSize = itemSize; + v->items = bmake_malloc(v->cap * v->itemSize); +} + +/* + * Add space for a new item to the vector and return a pointer to that space. + * The returned data is valid until the next modifying operation. + */ +void * +Vector_Push(Vector *v) +{ + if (v->len >= v->cap) { + v->cap *= 2; + v->items = bmake_realloc(v->items, v->cap * v->itemSize); + } + v->len++; + return Vector_Get(v, v->len - 1); +} + +/* + * Remove the last item from the vector, return the pointer to it. + * The returned data is valid until the next modifying operation. + */ +void * +Vector_Pop(Vector *v) +{ + assert(v->len > 0); + v->len--; + return Vector_Get(v, v->len); +} diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h index e207bc808..e8ba65b6d 100644 --- a/usr.bin/make/lst.h +++ b/usr.bin/make/lst.h @@ -1,4 +1,4 @@ -/* $NetBSD: lst.h,v 1.20 2014/09/07 20:55:34 joerg Exp $ */ +/* $NetBSD: lst.h,v 1.97 2021/03/15 16:06:05 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -73,117 +73,128 @@ * from: @(#)lst.h 8.1 (Berkeley) 6/6/93 */ -/*- - * lst.h -- - * Header for using the list library - */ -#ifndef _LST_H_ -#define _LST_H_ +/* Doubly-linked lists of arbitrary pointers. */ -#include -#include +#ifndef MAKE_LST_H +#define MAKE_LST_H -#include "sprite.h" +#include +#include +#include + +/* A doubly-linked list of pointers. */ +typedef struct List List; +/* A single node in the doubly-linked list. */ +typedef struct ListNode ListNode; + +struct ListNode { + ListNode *prev; /* previous node in list, or NULL */ + ListNode *next; /* next node in list, or NULL */ + void *datum; /* datum associated with this element */ +}; + +struct List { + ListNode *first; + ListNode *last; +}; + +/* 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); +/* 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 } + +/* Initialize a list, without memory allocation. */ +MAKE_INLINE void +Lst_Init(List *list) +{ + list->first = NULL; + list->last = NULL; +} + +/* Get information about a list */ + +MAKE_INLINE Boolean +Lst_IsEmpty(List *list) +{ + return list->first == NULL; +} + +/* Find the first node that contains the given datum, or NULL. */ +ListNode *Lst_FindDatum(List *, const void *); + +/* Modify a list */ + +/* Insert a datum before the given node. */ +void Lst_InsertBefore(List *, ListNode *, void *); +/* Place a datum at the front of the list. */ +void Lst_Prepend(List *, void *); +/* Place a datum at the end of the list. */ +void Lst_Append(List *, void *); +/* Remove the node from the list. */ +void Lst_Remove(List *, ListNode *); +void Lst_PrependAll(List *, List *); +void Lst_AppendAll(List *, List *); +void Lst_MoveAll(List *, List *); + +/* Node-specific functions */ + +/* Replace the value of the node. */ +void LstNode_Set(ListNode *, void *); +/* Set the value of the node to NULL. Having NULL in a list is unusual. */ +void LstNode_SetNull(ListNode *); + +/* Using the list as a queue */ + +/* Add a datum at the tail of the queue. */ +MAKE_INLINE void +Lst_Enqueue(List *list, void *datum) { + Lst_Append(list, datum); +} + +/* Remove the head node of the queue and return its datum. */ +void *Lst_Dequeue(List *); /* - * basic typedef. This is what the Lst_ functions handle + * A vector is an ordered collection of items, allowing for fast indexed + * access. */ +typedef struct Vector { + void *items; /* memory holding the items */ + size_t itemSize; /* size of a single item */ + size_t len; /* number of actually usable elements */ + size_t cap; /* capacity */ +} Vector; -typedef struct List *Lst; -typedef struct ListNode *LstNode; - -typedef void *DuplicateProc(void *); -typedef void FreeProc(void *); - -#define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */ -#define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */ +void Vector_Init(Vector *, size_t); /* - * Creation/destruction functions + * Return the pointer to the given item in the vector. + * The returned data is valid until the next modifying operation. */ -/* Create a new list */ -Lst Lst_Init(Boolean); -/* Duplicate an existing list */ -Lst Lst_Duplicate(Lst, DuplicateProc *); -/* Destroy an old one */ -void Lst_Destroy(Lst, FreeProc *); -/* True if list is empty */ -Boolean Lst_IsEmpty(Lst); +MAKE_INLINE void * +Vector_Get(Vector *v, size_t i) +{ + unsigned char *items = v->items; + return items + i * v->itemSize; +} -/* - * Functions to modify a list - */ -/* Insert an element before another */ -ReturnStatus Lst_InsertBefore(Lst, LstNode, void *); -/* Insert an element after another */ -ReturnStatus Lst_InsertAfter(Lst, LstNode, void *); -/* Place an element at the front of a lst. */ -ReturnStatus Lst_AtFront(Lst, void *); -/* Place an element at the end of a lst. */ -ReturnStatus Lst_AtEnd(Lst, void *); -/* Remove an element */ -ReturnStatus Lst_Remove(Lst, LstNode); -/* Replace a node with a new value */ -ReturnStatus Lst_Replace(LstNode, void *); -/* Concatenate two lists */ -ReturnStatus Lst_Concat(Lst, Lst, int); +void *Vector_Push(Vector *); +void *Vector_Pop(Vector *); -/* - * Node-specific functions - */ -/* Return first element in list */ -LstNode Lst_First(Lst); -/* Return last element in list */ -LstNode Lst_Last(Lst); -/* Return successor to given element */ -LstNode Lst_Succ(LstNode); -/* Return predecessor to given element */ -LstNode Lst_Prev(LstNode); -/* Get datum from LstNode */ -void *Lst_Datum(LstNode); +MAKE_INLINE void +Vector_Done(Vector *v) { + free(v->items); +} -/* - * Functions for entire lists - */ -/* Find an element in a list */ -LstNode Lst_Find(Lst, const void *, int (*)(const void *, const void *)); -/* Find an element starting from somewhere */ -LstNode Lst_FindFrom(Lst, LstNode, const void *, - int (*cProc)(const void *, const void *)); -/* - * See if the given datum is on the list. Returns the LstNode containing - * the datum - */ -LstNode Lst_Member(Lst, void *); -/* Apply a function to all elements of a lst */ -int Lst_ForEach(Lst, int (*)(void *, void *), void *); -/* - * Apply a function to all elements of a lst starting from a certain point. - * If the list is circular, the application will wrap around to the - * beginning of the list again. - */ -int Lst_ForEachFrom(Lst, LstNode, int (*)(void *, void *), - void *); -/* - * these functions are for dealing with a list as a table, of sorts. - * An idea of the "current element" is kept and used by all the functions - * between Lst_Open() and Lst_Close(). - */ -/* Open the list */ -ReturnStatus Lst_Open(Lst); -/* Next element please */ -LstNode Lst_Next(Lst); -/* Done yet? */ -Boolean Lst_IsAtEnd(Lst); -/* Finish table access */ -void Lst_Close(Lst); - -/* - * for using the list as a queue - */ -/* Place an element at tail of queue */ -ReturnStatus Lst_EnQueue(Lst, void *); -/* Remove an element from head of queue */ -void *Lst_DeQueue(Lst); - -#endif /* _LST_H_ */ +#endif /* MAKE_LST_H */ diff --git a/usr.bin/make/lst.lib/Makefile b/usr.bin/make/lst.lib/Makefile deleted file mode 100644 index 5b33a5062..000000000 --- a/usr.bin/make/lst.lib/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# $NetBSD: Makefile,v 1.6 2006/11/11 21:23:36 dsl Exp $ - -OBJ=lstAppend.o lstDupl.o lstInit.o lstOpen.o lstAtEnd.o lstEnQueue.o \ - lstInsert.o lstAtFront.o lstIsAtEnd.o lstClose.o lstFind.o lstIsEmpty.o \ - lstRemove.o lstConcat.o lstFindFrom.o lstLast.o lstReplace.o lstFirst.o \ - lstDatum.o lstForEach.o lstMember.o lstSucc.o lstDeQueue.o \ - lstForEachFrom.o lstDestroy.o lstNext.o lstPrev.o - -CPPFLAGS=-I${.CURDIR}/.. -all: ${OBJ} diff --git a/usr.bin/make/lst.lib/lstAppend.c b/usr.bin/make/lst.lib/lstAppend.c deleted file mode 100644 index 4dafe8314..000000000 --- a/usr.bin/make/lst.lib/lstAppend.c +++ /dev/null @@ -1,122 +0,0 @@ -/* $NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstAppend.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstAppend.c -- - * Add a new node with a new datum after an existing node - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_InsertAfter -- - * Create a new node and add it to the given list after the given node. - * - * Input: - * l affected list - * ln node after which to append the datum - * d said datum - * - * Results: - * SUCCESS if all went well. - * - * Side Effects: - * A new ListNode is created and linked in to the List. The lastPtr - * field of the List will be altered if ln is the last node in the - * list. lastPtr and firstPtr will alter if the list was empty and - * ln was NULL. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_InsertAfter(Lst l, LstNode ln, void *d) -{ - List list; - ListNode lNode; - ListNode nLNode; - - if (LstValid (l) && (ln == NULL && LstIsEmpty (l))) { - goto ok; - } - - if (!LstValid (l) || LstIsEmpty (l) || ! LstNodeValid (ln, l)) { - return (FAILURE); - } - ok: - - list = l; - lNode = ln; - - PAlloc (nLNode, ListNode); - nLNode->datum = d; - nLNode->useCount = nLNode->flags = 0; - - if (lNode == NULL) { - if (list->isCirc) { - nLNode->nextPtr = nLNode->prevPtr = nLNode; - } else { - nLNode->nextPtr = nLNode->prevPtr = NULL; - } - list->firstPtr = list->lastPtr = nLNode; - } else { - nLNode->prevPtr = lNode; - nLNode->nextPtr = lNode->nextPtr; - - lNode->nextPtr = nLNode; - if (nLNode->nextPtr != NULL) { - nLNode->nextPtr->prevPtr = nLNode; - } - - if (lNode == list->lastPtr) { - list->lastPtr = nLNode; - } - } - - return (SUCCESS); -} - diff --git a/usr.bin/make/lst.lib/lstAtEnd.c b/usr.bin/make/lst.lib/lstAtEnd.c deleted file mode 100644 index 10f191a20..000000000 --- a/usr.bin/make/lst.lib/lstAtEnd.c +++ /dev/null @@ -1,79 +0,0 @@ -/* $NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstAtEnd.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstAtEnd.c -- - * Add a node at the end of the list - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_AtEnd -- - * Add a node to the end of the given list - * - * Input: - * l List to which to add the datum - * d Datum to add - * - * Results: - * SUCCESS if life is good. - * - * Side Effects: - * A new ListNode is created and added to the list. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_AtEnd(Lst l, void *d) -{ - LstNode end; - - end = Lst_Last(l); - return (Lst_InsertAfter(l, end, d)); -} diff --git a/usr.bin/make/lst.lib/lstAtFront.c b/usr.bin/make/lst.lib/lstAtFront.c deleted file mode 100644 index d8be16647..000000000 --- a/usr.bin/make/lst.lib/lstAtFront.c +++ /dev/null @@ -1,76 +0,0 @@ -/* $NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstAtFront.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstAtFront.c -- - * Add a node at the front of the list - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_AtFront -- - * Place a piece of data at the front of a list - * - * Results: - * SUCCESS or FAILURE - * - * Side Effects: - * A new ListNode is created and stuck at the front of the list. - * hence, firstPtr (and possible lastPtr) in the list are altered. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_AtFront(Lst l, void *d) -{ - LstNode front; - - front = Lst_First(l); - return (Lst_InsertBefore(l, front, d)); -} diff --git a/usr.bin/make/lst.lib/lstClose.c b/usr.bin/make/lst.lib/lstClose.c deleted file mode 100644 index 06b68c5c0..000000000 --- a/usr.bin/make/lst.lib/lstClose.c +++ /dev/null @@ -1,86 +0,0 @@ -/* $NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstClose.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstClose.c -- - * Close a list for sequential access. - * The sequential functions access the list in a slightly different way. - * CurPtr points to their idea of the current node in the list and they - * access the list based on it. Because the list is circular, Lst_Next - * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be - * used to determine when to stop. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Close -- - * Close a list which was opened for sequential access. - * - * Input: - * l The list to close - * - * Results: - * None. - * - * Side Effects: - * The list is closed. - * - *----------------------------------------------------------------------- - */ -void -Lst_Close(Lst l) -{ - List list = l; - - if (LstValid(l) == TRUE) { - list->isOpen = FALSE; - list->atEnd = Unknown; - } -} - diff --git a/usr.bin/make/lst.lib/lstConcat.c b/usr.bin/make/lst.lib/lstConcat.c deleted file mode 100644 index 534d34e45..000000000 --- a/usr.bin/make/lst.lib/lstConcat.c +++ /dev/null @@ -1,185 +0,0 @@ -/* $NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstConcat.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * listConcat.c -- - * Function to concatentate two lists. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Concat -- - * Concatenate two lists. New elements are created to hold the data - * elements, if specified, but the elements themselves are not copied. - * If the elements should be duplicated to avoid confusion with another - * list, the Lst_Duplicate function should be called first. - * If LST_CONCLINK is specified, the second list is destroyed since - * its pointers have been corrupted and the list is no longer useable. - * - * Input: - * l1 The list to which l2 is to be appended - * l2 The list to append to l1 - * flags LST_CONCNEW if LstNode's should be duplicated - * LST_CONCLINK if should just be relinked - * - * Results: - * SUCCESS if all went well. FAILURE otherwise. - * - * Side Effects: - * New elements are created and appended the first list. - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_Concat(Lst l1, Lst l2, int flags) -{ - ListNode ln; /* original LstNode */ - ListNode nln; /* new LstNode */ - ListNode last; /* the last element in the list. Keeps - * bookkeeping until the end */ - List list1 = l1; - List list2 = l2; - - if (!LstValid (l1) || !LstValid (l2)) { - return (FAILURE); - } - - if (flags == LST_CONCLINK) { - if (list2->firstPtr != NULL) { - /* - * We set the nextPtr of the - * last element of list two to be NIL to make the loop easier and - * so we don't need an extra case should the first list turn - * out to be non-circular -- the final element will already point - * to NIL space and the first element will be untouched if it - * existed before and will also point to NIL space if it didn't. - */ - list2->lastPtr->nextPtr = NULL; - /* - * So long as the second list isn't empty, we just link the - * first element of the second list to the last element of the - * first list. If the first list isn't empty, we then link the - * last element of the list to the first element of the second list - * The last element of the second list, if it exists, then becomes - * the last element of the first list. - */ - list2->firstPtr->prevPtr = list1->lastPtr; - if (list1->lastPtr != NULL) { - list1->lastPtr->nextPtr = list2->firstPtr; - } else { - list1->firstPtr = list2->firstPtr; - } - list1->lastPtr = list2->lastPtr; - } - if (list1->isCirc && list1->firstPtr != NULL) { - /* - * If the first list is supposed to be circular and it is (now) - * non-empty, we must make sure it's circular by linking the - * first element to the last and vice versa - */ - list1->firstPtr->prevPtr = list1->lastPtr; - list1->lastPtr->nextPtr = list1->firstPtr; - } - free(l2); - } else if (list2->firstPtr != NULL) { - /* - * We set the nextPtr of the last element of list 2 to be nil to make - * the loop less difficult. The loop simply goes through the entire - * second list creating new LstNodes and filling in the nextPtr, and - * prevPtr to fit into l1 and its datum field from the - * datum field of the corresponding element in l2. The 'last' node - * follows the last of the new nodes along until the entire l2 has - * been appended. Only then does the bookkeeping catch up with the - * changes. During the first iteration of the loop, if 'last' is nil, - * the first list must have been empty so the newly-created node is - * made the first node of the list. - */ - list2->lastPtr->nextPtr = NULL; - for (last = list1->lastPtr, ln = list2->firstPtr; - ln != NULL; - ln = ln->nextPtr) - { - PAlloc (nln, ListNode); - nln->datum = ln->datum; - if (last != NULL) { - last->nextPtr = nln; - } else { - list1->firstPtr = nln; - } - nln->prevPtr = last; - nln->flags = nln->useCount = 0; - last = nln; - } - - /* - * Finish bookkeeping. The last new element becomes the last element - * of list one. - */ - list1->lastPtr = last; - - /* - * The circularity of both list one and list two must be corrected - * for -- list one because of the new nodes added to it; list two - * because of the alteration of list2->lastPtr's nextPtr to ease the - * above for loop. - */ - if (list1->isCirc) { - list1->lastPtr->nextPtr = list1->firstPtr; - list1->firstPtr->prevPtr = list1->lastPtr; - } else { - last->nextPtr = NULL; - } - - if (list2->isCirc) { - list2->lastPtr->nextPtr = list2->firstPtr; - } - } - - return (SUCCESS); -} - diff --git a/usr.bin/make/lst.lib/lstDatum.c b/usr.bin/make/lst.lib/lstDatum.c deleted file mode 100644 index 6e2d9ad0e..000000000 --- a/usr.bin/make/lst.lib/lstDatum.c +++ /dev/null @@ -1,77 +0,0 @@ -/* $NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstDatum.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstDatum.c -- - * Return the datum associated with a list node. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Datum -- - * Return the datum stored in the given node. - * - * Results: - * The datum or NULL if the node is invalid. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -void * -Lst_Datum(LstNode ln) -{ - if (ln != NULL) { - return ((ln)->datum); - } else { - return NULL; - } -} - diff --git a/usr.bin/make/lst.lib/lstDeQueue.c b/usr.bin/make/lst.lib/lstDeQueue.c deleted file mode 100644 index bdb05cc11..000000000 --- a/usr.bin/make/lst.lib/lstDeQueue.c +++ /dev/null @@ -1,87 +0,0 @@ -/* $NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstDeQueue.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstDeQueue.c -- - * Remove the node and return its datum from the head of the list - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_DeQueue -- - * Remove and return the datum at the head of the given list. - * - * Results: - * The datum in the node at the head or NULL if the list - * is empty. - * - * Side Effects: - * The head node is removed from the list. - * - *----------------------------------------------------------------------- - */ -void * -Lst_DeQueue(Lst l) -{ - void *rd; - ListNode tln; - - tln = Lst_First(l); - if (tln == NULL) { - return NULL; - } - - rd = tln->datum; - if (Lst_Remove(l, tln) == FAILURE) { - return NULL; - } else { - return (rd); - } -} - diff --git a/usr.bin/make/lst.lib/lstDestroy.c b/usr.bin/make/lst.lib/lstDestroy.c deleted file mode 100644 index 92c5b2b20..000000000 --- a/usr.bin/make/lst.lib/lstDestroy.c +++ /dev/null @@ -1,101 +0,0 @@ -/* $NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstDestroy.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstDestroy.c -- - * Nuke a list and all its resources - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Destroy -- - * Destroy a list and free all its resources. If the freeProc is - * given, it is called with the datum from each node in turn before - * the node is freed. - * - * Results: - * None. - * - * Side Effects: - * The given list is freed in its entirety. - * - *----------------------------------------------------------------------- - */ -void -Lst_Destroy(Lst list, FreeProc *freeProc) -{ - ListNode ln; - ListNode tln = NULL; - - if (list == NULL) - return; - - /* To ease scanning */ - if (list->lastPtr != NULL) - list->lastPtr->nextPtr = NULL; - else { - free(list); - return; - } - - if (freeProc) { - for (ln = list->firstPtr; ln != NULL; ln = tln) { - tln = ln->nextPtr; - freeProc(ln->datum); - free(ln); - } - } else { - for (ln = list->firstPtr; ln != NULL; ln = tln) { - tln = ln->nextPtr; - free(ln); - } - } - - free(list); -} diff --git a/usr.bin/make/lst.lib/lstDupl.c b/usr.bin/make/lst.lib/lstDupl.c deleted file mode 100644 index 2174ff782..000000000 --- a/usr.bin/make/lst.lib/lstDupl.c +++ /dev/null @@ -1,107 +0,0 @@ -/* $NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstDupl.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * listDupl.c -- - * Duplicate a list. This includes duplicating the individual - * elements. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Duplicate -- - * Duplicate an entire list. If a function to copy a void *is - * given, the individual client elements will be duplicated as well. - * - * Input: - * l the list to duplicate - * copyProc A function to duplicate each void * - * - * Results: - * The new Lst structure or NULL if failure. - * - * Side Effects: - * A new list is created. - *----------------------------------------------------------------------- - */ -Lst -Lst_Duplicate(Lst l, DuplicateProc *copyProc) -{ - Lst nl; - ListNode ln; - List list = l; - - if (!LstValid (l)) { - return NULL; - } - - nl = Lst_Init(list->isCirc); - if (nl == NULL) { - return NULL; - } - - ln = list->firstPtr; - while (ln != NULL) { - if (copyProc != NULL) { - if (Lst_AtEnd(nl, copyProc(ln->datum)) == FAILURE) { - return NULL; - } - } else if (Lst_AtEnd(nl, ln->datum) == FAILURE) { - return NULL; - } - - if (list->isCirc && ln == list->lastPtr) { - ln = NULL; - } else { - ln = ln->nextPtr; - } - } - - return (nl); -} diff --git a/usr.bin/make/lst.lib/lstEnQueue.c b/usr.bin/make/lst.lib/lstEnQueue.c deleted file mode 100644 index be386c91a..000000000 --- a/usr.bin/make/lst.lib/lstEnQueue.c +++ /dev/null @@ -1,78 +0,0 @@ -/* $NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstEnQueue.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstEnQueue.c-- - * Treat the list as a queue and place a datum at its end - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_EnQueue -- - * Add the datum to the tail of the given list. - * - * Results: - * SUCCESS or FAILURE as returned by Lst_InsertAfter. - * - * Side Effects: - * the lastPtr field is altered all the time and the firstPtr field - * will be altered if the list used to be empty. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_EnQueue(Lst l, void *d) -{ - if (LstValid (l) == FALSE) { - return (FAILURE); - } - - return (Lst_InsertAfter(l, Lst_Last(l), d)); -} - diff --git a/usr.bin/make/lst.lib/lstFind.c b/usr.bin/make/lst.lib/lstFind.c deleted file mode 100644 index d07dbe7f9..000000000 --- a/usr.bin/make/lst.lib/lstFind.c +++ /dev/null @@ -1,74 +0,0 @@ -/* $NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstFind.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstFind.c -- - * Find a node on a list. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Find -- - * Find a node on the given list using the given comparison function - * and the given datum. - * - * Results: - * The found node or NULL if none matches. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_Find(Lst l, const void *d, int (*cProc)(const void *, const void *)) -{ - return (Lst_FindFrom(l, Lst_First(l), d, cProc)); -} - diff --git a/usr.bin/make/lst.lib/lstFindFrom.c b/usr.bin/make/lst.lib/lstFindFrom.c deleted file mode 100644 index e2beab632..000000000 --- a/usr.bin/make/lst.lib/lstFindFrom.c +++ /dev/null @@ -1,90 +0,0 @@ -/* $NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstFindFrom.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstFindFrom.c -- - * Find a node on a list from a given starting point. Used by Lst_Find. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_FindFrom -- - * Search for a node starting and ending with the given one on the - * given list using the passed datum and comparison function to - * determine when it has been found. - * - * Results: - * The found node or NULL - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_FindFrom(Lst l, LstNode ln, const void *d, - int (*cProc)(const void *, const void *)) -{ - ListNode tln; - - if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) { - return NULL; - } - - tln = ln; - - do { - if ((*cProc)(tln->datum, d) == 0) - return (tln); - tln = tln->nextPtr; - } while (tln != ln && tln != NULL); - - return NULL; -} - diff --git a/usr.bin/make/lst.lib/lstFirst.c b/usr.bin/make/lst.lib/lstFirst.c deleted file mode 100644 index 4e8334f8b..000000000 --- a/usr.bin/make/lst.lib/lstFirst.c +++ /dev/null @@ -1,77 +0,0 @@ -/* $NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstFirst.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstFirst.c -- - * Return the first node of a list - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_First -- - * Return the first node on the given list. - * - * Results: - * The first node or NULL if the list is empty. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_First(Lst l) -{ - if (!LstValid (l) || LstIsEmpty (l)) { - return NULL; - } else { - return (l->firstPtr); - } -} - diff --git a/usr.bin/make/lst.lib/lstForEach.c b/usr.bin/make/lst.lib/lstForEach.c deleted file mode 100644 index 917e4ea80..000000000 --- a/usr.bin/make/lst.lib/lstForEach.c +++ /dev/null @@ -1,76 +0,0 @@ -/* $NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstForEach.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstForeach.c -- - * Perform a given function on all elements of a list. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_ForEach -- - * Apply the given function to each element of the given list. The - * function should return 0 if Lst_ForEach should continue and non- - * zero if it should abort. - * - * Results: - * None. - * - * Side Effects: - * Only those created by the passed-in function. - * - *----------------------------------------------------------------------- - */ -/*VARARGS2*/ -int -Lst_ForEach(Lst l, int (*proc)(void *, void *), void *d) -{ - return Lst_ForEachFrom(l, Lst_First(l), proc, d); -} - diff --git a/usr.bin/make/lst.lib/lstForEachFrom.c b/usr.bin/make/lst.lib/lstForEachFrom.c deleted file mode 100644 index c7f44adc4..000000000 --- a/usr.bin/make/lst.lib/lstForEachFrom.c +++ /dev/null @@ -1,125 +0,0 @@ -/* $NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstForEachFrom.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * lstForEachFrom.c -- - * Perform a given function on all elements of a list starting from - * a given point. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_ForEachFrom -- - * Apply the given function to each element of the given list. The - * function should return 0 if traversal should continue and non- - * zero if it should abort. - * - * Results: - * None. - * - * Side Effects: - * Only those created by the passed-in function. - * - *----------------------------------------------------------------------- - */ -/*VARARGS2*/ -int -Lst_ForEachFrom(Lst l, LstNode ln, int (*proc)(void *, void *), - void *d) -{ - ListNode tln = ln; - List list = l; - ListNode next; - Boolean done; - int result; - - if (!LstValid (list) || LstIsEmpty (list)) { - return 0; - } - - do { - /* - * Take care of having the current element deleted out from under - * us. - */ - - next = tln->nextPtr; - - /* - * We're done with the traversal if - * - the next node to examine is the first in the queue or - * doesn't exist and - * - nothing's been added after the current node (check this - * after proc() has been called). - */ - done = (next == NULL || next == list->firstPtr); - - (void) tln->useCount++; - result = (*proc) (tln->datum, d); - (void) tln->useCount--; - - /* - * Now check whether a node has been added. - * Note: this doesn't work if this node was deleted before - * the new node was added. - */ - if (next != tln->nextPtr) { - next = tln->nextPtr; - done = 0; - } - - if (tln->flags & LN_DELETED) { - free((char *)tln); - } - tln = next; - } while (!result && !LstIsEmpty(list) && !done); - - return result; -} - diff --git a/usr.bin/make/lst.lib/lstInit.c b/usr.bin/make/lst.lib/lstInit.c deleted file mode 100644 index f98ac42b0..000000000 --- a/usr.bin/make/lst.lib/lstInit.c +++ /dev/null @@ -1,85 +0,0 @@ -/* $NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstInit.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * init.c -- - * Initialize a new linked list. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Init -- - * Create and initialize a new list. - * - * Input: - * circ TRUE if the list should be made circular - * - * Results: - * The created list. - * - * Side Effects: - * A list is created, what else? - * - *----------------------------------------------------------------------- - */ -Lst -Lst_Init(Boolean circ) -{ - List nList; - - PAlloc (nList, List); - - nList->firstPtr = NULL; - nList->lastPtr = NULL; - nList->isOpen = FALSE; - nList->isCirc = circ; - nList->atEnd = Unknown; - - return (nList); -} diff --git a/usr.bin/make/lst.lib/lstInsert.c b/usr.bin/make/lst.lib/lstInsert.c deleted file mode 100644 index 77187bb32..000000000 --- a/usr.bin/make/lst.lib/lstInsert.c +++ /dev/null @@ -1,122 +0,0 @@ -/* $NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstInsert.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstInsert.c -- - * Insert a new datum before an old one - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_InsertBefore -- - * Insert a new node with the given piece of data before the given - * node in the given list. - * - * Input: - * l list to manipulate - * ln node before which to insert d - * d datum to be inserted - * - * Results: - * SUCCESS or FAILURE. - * - * Side Effects: - * the firstPtr field will be changed if ln is the first node in the - * list. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_InsertBefore(Lst l, LstNode ln, void *d) -{ - ListNode nLNode; /* new lnode for d */ - ListNode lNode = ln; - List list = l; - - - /* - * check validity of arguments - */ - if (LstValid (l) && (LstIsEmpty (l) && ln == NULL)) - goto ok; - - if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) { - return (FAILURE); - } - - ok: - PAlloc (nLNode, ListNode); - - nLNode->datum = d; - nLNode->useCount = nLNode->flags = 0; - - if (ln == NULL) { - if (list->isCirc) { - nLNode->prevPtr = nLNode->nextPtr = nLNode; - } else { - nLNode->prevPtr = nLNode->nextPtr = NULL; - } - list->firstPtr = list->lastPtr = nLNode; - } else { - nLNode->prevPtr = lNode->prevPtr; - nLNode->nextPtr = lNode; - - if (nLNode->prevPtr != NULL) { - nLNode->prevPtr->nextPtr = nLNode; - } - lNode->prevPtr = nLNode; - - if (lNode == list->firstPtr) { - list->firstPtr = nLNode; - } - } - - return (SUCCESS); -} - diff --git a/usr.bin/make/lst.lib/lstInt.h b/usr.bin/make/lst.lib/lstInt.h deleted file mode 100644 index ac53dcb67..000000000 --- a/usr.bin/make/lst.lib/lstInt.h +++ /dev/null @@ -1,105 +0,0 @@ -/* $NetBSD: lstInt.h,v 1.22 2014/09/07 20:55:34 joerg Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * from: @(#)lstInt.h 8.1 (Berkeley) 6/6/93 - */ - -/*- - * lstInt.h -- - * Internals for the list library - */ -#ifndef _LSTINT_H_ -#define _LSTINT_H_ - -#include "../lst.h" -#include "../make_malloc.h" - -typedef struct ListNode { - struct ListNode *prevPtr; /* previous element in list */ - struct ListNode *nextPtr; /* next in list */ - unsigned int useCount:8, /* Count of functions using the node. - * node may not be deleted until count - * goes to 0 */ - flags:8; /* Node status flags */ - void *datum; /* datum associated with this element */ -} *ListNode; -/* - * Flags required for synchronization - */ -#define LN_DELETED 0x0001 /* List node should be removed when done */ - -typedef enum { - Head, Middle, Tail, Unknown -} Where; - -typedef struct List { - ListNode firstPtr; /* first node in list */ - ListNode lastPtr; /* last node in list */ - Boolean isCirc; /* true if the list should be considered - * circular */ -/* - * fields for sequential access - */ - Where atEnd; /* Where in the list the last access was */ - Boolean isOpen; /* true if list has been Lst_Open'ed */ - ListNode curPtr; /* current node, if open. NULL if - * *just* opened */ - ListNode prevPtr; /* Previous node, if open. Used by - * Lst_Remove */ -} *List; - -/* - * PAlloc (var, ptype) -- - * Allocate a pointer-typedef structure 'ptype' into the variable 'var' - */ -#define PAlloc(var,ptype) var = (ptype) bmake_malloc(sizeof *(var)) - -/* - * LstValid (l) -- - * Return TRUE if the list l is valid - */ -#define LstValid(l) ((Lst)(l) != NULL) - -/* - * LstNodeValid (ln, l) -- - * Return TRUE if the LstNode ln is valid with respect to l - */ -#define LstNodeValid(ln, l) ((ln) != NULL) - -/* - * LstIsEmpty (l) -- - * TRUE if the list l is empty. - */ -#define LstIsEmpty(l) (((List)(l))->firstPtr == NULL) - -#endif /* _LSTINT_H_ */ diff --git a/usr.bin/make/lst.lib/lstIsAtEnd.c b/usr.bin/make/lst.lib/lstIsAtEnd.c deleted file mode 100644 index 70270d295..000000000 --- a/usr.bin/make/lst.lib/lstIsAtEnd.c +++ /dev/null @@ -1,87 +0,0 @@ -/* $NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstIsAtEnd.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstIsAtEnd.c -- - * Tell if the current node is at the end of the list. - * The sequential functions access the list in a slightly different way. - * CurPtr points to their idea of the current node in the list and they - * access the list based on it. Because the list is circular, Lst_Next - * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be - * used to determine when to stop. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_IsAtEnd -- - * Return true if have reached the end of the given list. - * - * Results: - * TRUE if at the end of the list (this includes the list not being - * open or being invalid) or FALSE if not. We return TRUE if the list - * is invalid or unopend so as to cause the caller to exit its loop - * asap, the assumption being that the loop is of the form - * while (!Lst_IsAtEnd (l)) { - * ... - * } - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -Boolean -Lst_IsAtEnd(Lst l) -{ - List list = l; - - return (!LstValid (l) || !list->isOpen || - (list->atEnd == Head) || (list->atEnd == Tail)); -} - diff --git a/usr.bin/make/lst.lib/lstIsEmpty.c b/usr.bin/make/lst.lib/lstIsEmpty.c deleted file mode 100644 index 8b1d6ed0d..000000000 --- a/usr.bin/make/lst.lib/lstIsEmpty.c +++ /dev/null @@ -1,75 +0,0 @@ -/* $NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstIsEmpty.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstIsEmpty.c -- - * A single function to decide if a list is empty - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_IsEmpty -- - * Return TRUE if the given list is empty. - * - * Results: - * TRUE if the list is empty, FALSE otherwise. - * - * Side Effects: - * None. - * - * A list is considered empty if its firstPtr == NULL (or if - * the list itself is NULL). - *----------------------------------------------------------------------- - */ -Boolean -Lst_IsEmpty(Lst l) -{ - return ( ! LstValid (l) || LstIsEmpty(l)); -} - diff --git a/usr.bin/make/lst.lib/lstLast.c b/usr.bin/make/lst.lib/lstLast.c deleted file mode 100644 index 096ca24d1..000000000 --- a/usr.bin/make/lst.lib/lstLast.c +++ /dev/null @@ -1,77 +0,0 @@ -/* $NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstLast.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstLast.c -- - * Return the last element of a list - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Last -- - * Return the last node on the list l. - * - * Results: - * The requested node or NULL if the list is empty. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_Last(Lst l) -{ - if (!LstValid(l) || LstIsEmpty (l)) { - return NULL; - } else { - return (l->lastPtr); - } -} - diff --git a/usr.bin/make/lst.lib/lstMember.c b/usr.bin/make/lst.lib/lstMember.c deleted file mode 100644 index e9046aca1..000000000 --- a/usr.bin/make/lst.lib/lstMember.c +++ /dev/null @@ -1,77 +0,0 @@ -/* $NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstMember.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * lstMember.c -- - * See if a given datum is on a given list. - */ - -#include "lstInt.h" - -LstNode -Lst_Member(Lst l, void *d) -{ - List list = l; - ListNode lNode; - - if (list == NULL) { - return NULL; - } - lNode = list->firstPtr; - if (lNode == NULL) { - return NULL; - } - - do { - if (lNode->datum == d) { - return lNode; - } - lNode = lNode->nextPtr; - } while (lNode != NULL && lNode != list->firstPtr); - - return NULL; -} diff --git a/usr.bin/make/lst.lib/lstNext.c b/usr.bin/make/lst.lib/lstNext.c deleted file mode 100644 index 5c2e0eece..000000000 --- a/usr.bin/make/lst.lib/lstNext.c +++ /dev/null @@ -1,120 +0,0 @@ -/* $NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstNext.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstNext.c -- - * Return the next node for a list. - * The sequential functions access the list in a slightly different way. - * CurPtr points to their idea of the current node in the list and they - * access the list based on it. Because the list is circular, Lst_Next - * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be - * used to determine when to stop. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Next -- - * Return the next node for the given list. - * - * Results: - * The next node or NULL if the list has yet to be opened. Also - * if the list is non-circular and the end has been reached, NULL - * is returned. - * - * Side Effects: - * the curPtr field is updated. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_Next(Lst l) -{ - ListNode tln; - List list = l; - - if ((LstValid (l) == FALSE) || - (list->isOpen == FALSE)) { - return NULL; - } - - list->prevPtr = list->curPtr; - - if (list->curPtr == NULL) { - if (list->atEnd == Unknown) { - /* - * If we're just starting out, atEnd will be Unknown. - * Then we want to start this thing off in the right - * direction -- at the start with atEnd being Middle. - */ - list->curPtr = tln = list->firstPtr; - list->atEnd = Middle; - } else { - tln = NULL; - list->atEnd = Tail; - } - } else { - tln = list->curPtr->nextPtr; - list->curPtr = tln; - - if (tln == list->firstPtr || tln == NULL) { - /* - * If back at the front, then we've hit the end... - */ - list->atEnd = Tail; - } else { - /* - * Reset to Middle if gone past first. - */ - list->atEnd = Middle; - } - } - - return (tln); -} - diff --git a/usr.bin/make/lst.lib/lstOpen.c b/usr.bin/make/lst.lib/lstOpen.c deleted file mode 100644 index 941293e7a..000000000 --- a/usr.bin/make/lst.lib/lstOpen.c +++ /dev/null @@ -1,87 +0,0 @@ -/* $NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstOpen.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstOpen.c -- - * Open a list for sequential access. The sequential functions access the - * list in a slightly different way. CurPtr points to their idea of the - * current node in the list and they access the list based on it. - * If the list is circular, Lst_Next and Lst_Prev will go around - * the list forever. Lst_IsAtEnd must be used to determine when to stop. - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Open -- - * Open a list for sequential access. A list can still be searched, - * etc., without confusing these functions. - * - * Results: - * SUCCESS or FAILURE. - * - * Side Effects: - * isOpen is set TRUE and curPtr is set to NULL so the - * other sequential functions no it was just opened and can choose - * the first element accessed based on this. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_Open(Lst l) -{ - if (LstValid (l) == FALSE) { - return (FAILURE); - } - (l)->isOpen = TRUE; - (l)->atEnd = LstIsEmpty (l) ? Head : Unknown; - (l)->curPtr = NULL; - - return (SUCCESS); -} - diff --git a/usr.bin/make/lst.lib/lstPrev.c b/usr.bin/make/lst.lib/lstPrev.c deleted file mode 100644 index 0ec865d51..000000000 --- a/usr.bin/make/lst.lib/lstPrev.c +++ /dev/null @@ -1,79 +0,0 @@ -/* $NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstPrev.c -- - * return the predecessor to a given node - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Prev -- - * Return the predecessor to the given node on its list. - * - * Results: - * The predecessor of the node, if it exists (note that on a circular - * list, if the node is the only one in the list, it is its own - * predecessor). - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_Prev(LstNode ln) -{ - if (ln == NULL) { - return NULL; - } else { - return (ln->prevPtr); - } -} - diff --git a/usr.bin/make/lst.lib/lstRemove.c b/usr.bin/make/lst.lib/lstRemove.c deleted file mode 100644 index 7480d30ba..000000000 --- a/usr.bin/make/lst.lib/lstRemove.c +++ /dev/null @@ -1,136 +0,0 @@ -/* $NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstRemove.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstRemove.c -- - * Remove an element from a list - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Remove -- - * Remove the given node from the given list. - * - * Results: - * SUCCESS or FAILURE. - * - * Side Effects: - * The list's firstPtr will be set to NULL if ln is the last - * node on the list. firsPtr and lastPtr will be altered if ln is - * either the first or last node, respectively, on the list. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_Remove(Lst l, LstNode ln) -{ - List list = l; - ListNode lNode = ln; - - if (!LstValid (l) || - !LstNodeValid (ln, l)) { - return (FAILURE); - } - - /* - * unlink it from the list - */ - if (lNode->nextPtr != NULL) { - lNode->nextPtr->prevPtr = lNode->prevPtr; - } - if (lNode->prevPtr != NULL) { - lNode->prevPtr->nextPtr = lNode->nextPtr; - } - - /* - * if either the firstPtr or lastPtr of the list point to this node, - * adjust them accordingly - */ - if (list->firstPtr == lNode) { - list->firstPtr = lNode->nextPtr; - } - if (list->lastPtr == lNode) { - list->lastPtr = lNode->prevPtr; - } - - /* - * Sequential access stuff. If the node we're removing is the current - * node in the list, reset the current node to the previous one. If the - * previous one was non-existent (prevPtr == NULL), we set the - * end to be Unknown, since it is. - */ - if (list->isOpen && (list->curPtr == lNode)) { - list->curPtr = list->prevPtr; - if (list->curPtr == NULL) { - list->atEnd = Unknown; - } - } - - /* - * the only way firstPtr can still point to ln is if ln is the last - * node on the list (the list is circular, so lNode->nextptr == lNode in - * this case). The list is, therefore, empty and is marked as such - */ - if (list->firstPtr == lNode) { - list->firstPtr = NULL; - } - - /* - * note that the datum is unmolested. The caller must free it as - * necessary and as expected. - */ - if (lNode->useCount == 0) { - free(ln); - } else { - lNode->flags |= LN_DELETED; - } - - return (SUCCESS); -} - diff --git a/usr.bin/make/lst.lib/lstReplace.c b/usr.bin/make/lst.lib/lstReplace.c deleted file mode 100644 index 090e91a72..000000000 --- a/usr.bin/make/lst.lib/lstReplace.c +++ /dev/null @@ -1,78 +0,0 @@ -/* $NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstReplace.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstReplace.c -- - * Replace the datum in a node with a new datum - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Replace -- - * Replace the datum in the given node with the new datum - * - * Results: - * SUCCESS or FAILURE. - * - * Side Effects: - * The datum field fo the node is altered. - * - *----------------------------------------------------------------------- - */ -ReturnStatus -Lst_Replace(LstNode ln, void *d) -{ - if (ln == NULL) { - return (FAILURE); - } else { - (ln)->datum = d; - return (SUCCESS); - } -} - diff --git a/usr.bin/make/lst.lib/lstSucc.c b/usr.bin/make/lst.lib/lstSucc.c deleted file mode 100644 index 3f13aa5e7..000000000 --- a/usr.bin/make/lst.lib/lstSucc.c +++ /dev/null @@ -1,79 +0,0 @@ -/* $NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * LstSucc.c -- - * return the successor to a given node - */ - -#include "lstInt.h" - -/*- - *----------------------------------------------------------------------- - * Lst_Succ -- - * Return the successor to the given node on its list. - * - * Results: - * The successor of the node, if it exists (note that on a circular - * list, if the node is the only one in the list, it is its own - * successor). - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -LstNode -Lst_Succ(LstNode ln) -{ - if (ln == NULL) { - return NULL; - } else { - return (ln->nextPtr); - } -} - diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c index 73f4aaa9c..cd9321a61 100644 --- a/usr.bin/make/main.c +++ b/usr.bin/make/main.c @@ -1,4 +1,4 @@ -/* $NetBSD: main.c,v 1.233 2015/09/10 17:15:11 sjg Exp $ */ +/* $NetBSD: main.c,v 1.533 2021/02/05 19:19:17 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,49 +68,24 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: main.c,v 1.233 2015/09/10 17:15:11 sjg Exp $"; -#else -#include -#ifndef lint -__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\ - The Regents of the University of California. All rights reserved."); -#endif /* not lint */ - -#ifndef lint -#if 0 -static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: main.c,v 1.233 2015/09/10 17:15:11 sjg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * main.c -- - * The main file for this entire program. Exit routines etc - * reside here. +/* + * The main file for this entire program. Exit routines etc. reside here. * * Utility functions defined in this file: - * Main_ParseArgLine Takes a line of arguments, breaks them and - * treats them as if they were given when first - * invoked. Used by the parse module to implement - * the .MFLAGS target. * - * Error Print a tagged error message. The global - * MAKE variable must have been defined. This - * takes a format string and optional arguments - * for it. + * Main_ParseArgLine + * Parse and process command line arguments from a + * single string. Used to implement the special targets + * .MFLAGS and .MAKEFLAGS. * - * Fatal Print an error message and exit. Also takes - * a format string and arguments for it. + * Error Print a tagged error message. * - * Punt Aborts all jobs and exits with a message. Also - * takes a format string and arguments for it. + * Fatal Print an error message and exit. * - * Finish Finish things up by printing the number of - * errors which occurred, as passed to it, and - * exiting. + * Punt Abort all jobs and exit with a message. + * + * Finish Finish things up by printing the number of errors + * that occurred, and exit. */ #include @@ -125,121 +100,157 @@ __RCSID("$NetBSD: main.c,v 1.233 2015/09/10 17:15:11 sjg Exp $"); #include #include -#include #include #include -#include -#include #include -#include #include "make.h" -#include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #include "trace.h" -#ifdef USE_IOVEC -#include +/* "@(#)main.c 8.3 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: main.c,v 1.533 2021/02/05 19:19:17 sjg Exp $"); +#if defined(MAKE_NATIVE) && !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993 " + "The Regents of the University of California. " + "All rights reserved."); #endif -#ifndef DEFMAXLOCAL -#define DEFMAXLOCAL DEFMAXJOBS -#endif /* DEFMAXLOCAL */ +CmdOpts opts; +time_t now; /* Time at start of make */ +GNode *defaultNode; /* .DEFAULT node */ +Boolean allPrecious; /* .PRECIOUS given on line by itself */ +Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ -Lst create; /* Targets to be made */ -time_t now; /* Time at start of make */ -GNode *DEFAULT; /* .DEFAULT node */ -Boolean allPrecious; /* .PRECIOUS given on line by itself */ +static int maxJobTokens; /* -j argument */ +Boolean enterFlagObj; /* -w and objdir != srcdir */ -static Boolean noBuiltins; /* -r flag */ -static Lst makefiles; /* ordered list of makefiles to read */ -static Boolean printVars; /* print value of one or more vars */ -static Lst variables; /* list of variables to print */ -int maxJobs; /* -j argument */ -static int maxJobTokens; /* -j argument */ -Boolean compatMake; /* -B argument */ -int debug; /* -d argument */ -Boolean debugVflag; /* -dV */ -Boolean noExecute; /* -n flag */ -Boolean noRecursiveExecute; /* -N flag */ -Boolean keepgoing; /* -k flag */ -Boolean queryFlag; /* -q flag */ -Boolean touchFlag; /* -t flag */ -Boolean enterFlag; /* -w flag */ -Boolean enterFlagObj; /* -w and objdir != srcdir */ -Boolean ignoreErrors; /* -i flag */ -Boolean beSilent; /* -s flag */ -Boolean oldVars; /* variable substitution style */ -Boolean checkEnvFirst; /* -e flag */ -Boolean parseWarnFatal; /* -W flag */ -Boolean jobServer; /* -J flag */ -static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ -Boolean varNoExportEnv; /* -X flag */ -Boolean doing_depend; /* Set while reading .depend */ -static Boolean jobsRunning; /* TRUE if the jobs might be running */ -static const char * tracefile; -static void MainParseArgs(int, char **); -static int ReadMakefile(const void *, const void *); -static void usage(void) MAKE_ATTR_DEAD; +static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ +Boolean doing_depend; /* Set while reading .depend */ +static Boolean jobsRunning; /* TRUE if the jobs might be running */ +static const char *tracefile; +static int ReadMakefile(const char *); +static void purge_relative_cached_realpaths(void); -static Boolean ignorePWD; /* if we use -C, PWD is meaningless */ -static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ -char curdir[MAXPATHLEN + 1]; /* Startup directory */ -char *progname; /* the program name */ +static Boolean ignorePWD; /* if we use -C, PWD is meaningless */ +static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ +char curdir[MAXPATHLEN + 1]; /* Startup directory */ +const char *progname; char *makeDependfile; pid_t myPid; int makelevel; Boolean forceJobs = FALSE; - -extern Lst parseIncPath; +static int main_errors = 0; +static HashTable cached_realpaths; /* * For compatibility with the POSIX version of MAKEFLAGS that includes - * all the options with out -, 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) { - size_t len; - char *nf, *st; - const char *f; + size_t len; + char *nf, *st; + const char *f; - if (flags == NULL) - return NULL; + if (flags == NULL) + return NULL; - for (f = flags; *f; f++) - if (!isalpha((unsigned char)*f)) - break; + for (f = flags; *f != '\0'; f++) + if (!ch_isalpha(*f)) + break; - if (*f) - return bmake_strdup(flags); + if (*f != '\0') + return bmake_strdup(flags); - len = strlen(flags); - st = nf = bmake_malloc(len * 3 + 1); - while (*flags) { - *nf++ = '-'; - *nf++ = *flags++; - *nf++ = ' '; - } - *nf = '\0'; - return st; + len = strlen(flags); + st = nf = bmake_malloc(len * 3 + 1); + while (*flags != '\0') { + *nf++ = '-'; + *nf++ = *flags++; + *nf++ = ' '; + } + *nf = '\0'; + return st; } - + +/* + * usage -- + * exit with usage message + */ +MAKE_ATTR_DEAD static void +usage(void) +{ + size_t prognameLen = strcspn(progname, "["); + + (void)fprintf(stderr, +"usage: %.*s [-BeikNnqrSstWwX]\n" +" [-C directory] [-D variable] [-d flags] [-f makefile]\n" +" [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n" +" [-V variable] [-v variable] [variable=value] [target ...]\n", + (int)prognameLen, progname); + exit(2); +} + static void -parse_debug_options(const char *argvalue) +MainParseArgDebugFile(const char *arg) +{ + const char *mode; + size_t len; + char *fname; + + if (opts.debug_file != stdout && opts.debug_file != stderr) + fclose(opts.debug_file); + + if (*arg == '+') { + arg++; + mode = "a"; + } else + mode = "w"; + + if (strcmp(arg, "stdout") == 0) { + opts.debug_file = stdout; + return; + } + if (strcmp(arg, "stderr") == 0) { + opts.debug_file = stderr; + return; + } + + len = strlen(arg); + fname = bmake_malloc(len + 20); + memcpy(fname, arg, len + 1); + + /* Let the filename be modified by the pid */ + if (strcmp(fname + len - 3, ".%d") == 0) + snprintf(fname + len - 2, 20, "%d", getpid()); + + opts.debug_file = fopen(fname, mode); + if (opts.debug_file == NULL) { + fprintf(stderr, "Cannot open debug file %s\n", + fname); + usage(); + } + free(fname); +} + +static void +MainParseArgDebug(const char *argvalue) { const char *modules; - const char *mode; - char *fname; - int len; + DebugFlags debug = opts.debug; - for (modules = argvalue; *modules; ++modules) { + for (modules = argvalue; *modules != '\0'; modules++) { switch (*modules) { + case '0': /* undocumented, only intended for tests */ + debug = DEBUG_NONE; + break; case 'A': - debug = ~0; + debug = DEBUG_ALL; break; case 'a': debug |= DEBUG_ARCH; @@ -262,20 +273,24 @@ parse_debug_options(const char *argvalue) case 'g': if (modules[1] == '1') { debug |= DEBUG_GRAPH1; - ++modules; - } - else if (modules[1] == '2') { + modules++; + } else if (modules[1] == '2') { debug |= DEBUG_GRAPH2; - ++modules; - } - else if (modules[1] == '3') { + modules++; + } else if (modules[1] == '3') { debug |= DEBUG_GRAPH3; - ++modules; + modules++; } break; + case 'h': + debug |= DEBUG_HASH; + break; case 'j': debug |= DEBUG_JOB; break; + case 'L': + opts.strict = TRUE; + break; case 'l': debug |= DEBUG_LOUD; break; @@ -298,7 +313,7 @@ parse_debug_options(const char *argvalue) debug |= DEBUG_TARG; break; case 'V': - debugVflag = TRUE; + opts.debugVflag = TRUE; break; case 'v': debug |= DEBUG_VAR; @@ -307,34 +322,7 @@ parse_debug_options(const char *argvalue) debug |= DEBUG_SHELL; break; case 'F': - if (debug_file != stdout && debug_file != stderr) - fclose(debug_file); - if (*++modules == '+') { - modules++; - mode = "a"; - } else - mode = "w"; - if (strcmp(modules, "stdout") == 0) { - debug_file = stdout; - goto debug_setbuf; - } - if (strcmp(modules, "stderr") == 0) { - debug_file = stderr; - goto debug_setbuf; - } - len = strlen(modules); - fname = malloc(len + 20); - memcpy(fname, modules, len + 1); - /* Let the filename be modified by the pid */ - if (strcmp(fname + len - 3, ".%d") == 0) - snprintf(fname + len - 2, 20, "%d", getpid()); - debug_file = fopen(fname, mode); - if (!debug_file) { - fprintf(stderr, "Cannot open debug file %s\n", - fname); - usage(); - } - free(fname); + MainParseArgDebugFile(modules + 1); goto debug_setbuf; default: (void)fprintf(stderr, @@ -343,60 +331,281 @@ parse_debug_options(const char *argvalue) usage(); } } + debug_setbuf: + opts.debug = debug; + /* * Make the debug_file unbuffered, and make * stdout line buffered (unless debugfile == stdout). */ - setvbuf(debug_file, NULL, _IONBF, 0); - if (debug_file != stdout) { + setvbuf(opts.debug_file, NULL, _IONBF, 0); + if (opts.debug_file != stdout) { setvbuf(stdout, NULL, _IOLBF, 0); } } -/*- - * MainParseArgs -- - * Parse a given argument vector. Called from main() and from - * Main_ParseArgLine() when the .MAKEFLAGS target is used. +/* Is path relative, or does it contain any relative component "." or ".."? */ +static Boolean +IsRelativePath(const char *path) +{ + const char *cp; + + if (path[0] != '/') + return TRUE; + cp = path; + while ((cp = strstr(cp, "/.")) != NULL) { + cp += 2; + if (*cp == '.') + cp++; + if (cp[0] == '/' || cp[0] == '\0') + return TRUE; + } + return FALSE; +} + +static void +MainParseArgChdir(const char *argvalue) +{ + struct stat sa, sb; + + if (chdir(argvalue) == -1) { + (void)fprintf(stderr, "%s: chdir %s: %s\n", + progname, argvalue, strerror(errno)); + exit(2); /* Not 1 so -q can distinguish error */ + } + if (getcwd(curdir, MAXPATHLEN) == NULL) { + (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); + exit(2); + } + if (!IsRelativePath(argvalue) && + stat(argvalue, &sa) != -1 && + stat(curdir, &sb) != -1 && + sa.st_ino == sb.st_ino && + sa.st_dev == sb.st_dev) + strncpy(curdir, argvalue, MAXPATHLEN); + ignorePWD = TRUE; +} + +static void +MainParseArgJobsInternal(const char *argvalue) +{ + char end; + if (sscanf(argvalue, "%d,%d%c", &jp_0, &jp_1, &end) != 2) { + (void)fprintf(stderr, + "%s: internal error -- J option malformed (%s)\n", + progname, argvalue); + usage(); + } + if ((fcntl(jp_0, F_GETFD, 0) < 0) || + (fcntl(jp_1, F_GETFD, 0) < 0)) { +#if 0 + (void)fprintf(stderr, + "%s: ###### warning -- J descriptors were closed!\n", + progname); + exit(2); +#endif + jp_0 = -1; + jp_1 = -1; + opts.compatMake = TRUE; + } else { + Global_Append(MAKEFLAGS, "-J"); + Global_Append(MAKEFLAGS, argvalue); + } +} + +static void +MainParseArgJobs(const char *argvalue) +{ + char *p; + + forceJobs = TRUE; + opts.maxJobs = (int)strtol(argvalue, &p, 0); + if (*p != '\0' || opts.maxJobs < 1) { + (void)fprintf(stderr, + "%s: illegal argument to -j -- must be positive integer!\n", + progname); + exit(2); /* Not 1 so -q can distinguish error */ + } + Global_Append(MAKEFLAGS, "-j"); + Global_Append(MAKEFLAGS, argvalue); + Global_Set(".MAKE.JOBS", argvalue); + maxJobTokens = opts.maxJobs; +} + +static void +MainParseArgSysInc(const char *argvalue) +{ + /* look for magic parent directory search string */ + if (strncmp(".../", argvalue, 4) == 0) { + char *found_path = Dir_FindHereOrAbove(curdir, argvalue + 4); + if (found_path == NULL) + return; + (void)SearchPath_Add(sysIncPath, found_path); + free(found_path); + } else { + (void)SearchPath_Add(sysIncPath, argvalue); + } + Global_Append(MAKEFLAGS, "-m"); + Global_Append(MAKEFLAGS, argvalue); +} + +static Boolean +MainParseArg(char c, const char *argvalue) +{ + switch (c) { + case '\0': + break; + case 'B': + opts.compatMake = TRUE; + Global_Append(MAKEFLAGS, "-B"); + Global_Set(MAKE_MODE, "compat"); + break; + case 'C': + MainParseArgChdir(argvalue); + break; + case 'D': + if (argvalue[0] == '\0') return FALSE; + Global_SetExpand(argvalue, "1"); + Global_Append(MAKEFLAGS, "-D"); + Global_Append(MAKEFLAGS, argvalue); + break; + case 'I': + Parse_AddIncludeDir(argvalue); + Global_Append(MAKEFLAGS, "-I"); + Global_Append(MAKEFLAGS, argvalue); + break; + case 'J': + MainParseArgJobsInternal(argvalue); + break; + case 'N': + opts.noExecute = TRUE; + opts.noRecursiveExecute = TRUE; + Global_Append(MAKEFLAGS, "-N"); + break; + case 'S': + opts.keepgoing = FALSE; + Global_Append(MAKEFLAGS, "-S"); + break; + case 'T': + tracefile = bmake_strdup(argvalue); + Global_Append(MAKEFLAGS, "-T"); + Global_Append(MAKEFLAGS, argvalue); + break; + case 'V': + case 'v': + opts.printVars = c == 'v' ? PVM_EXPANDED : PVM_UNEXPANDED; + Lst_Append(&opts.variables, bmake_strdup(argvalue)); + /* XXX: Why always -V? */ + Global_Append(MAKEFLAGS, "-V"); + Global_Append(MAKEFLAGS, argvalue); + break; + case 'W': + opts.parseWarnFatal = TRUE; + /* XXX: why no Var_Append? */ + break; + case 'X': + opts.varNoExportEnv = TRUE; + Global_Append(MAKEFLAGS, "-X"); + break; + case 'd': + /* If '-d-opts' don't pass to children */ + if (argvalue[0] == '-') + argvalue++; + else { + Global_Append(MAKEFLAGS, "-d"); + Global_Append(MAKEFLAGS, argvalue); + } + MainParseArgDebug(argvalue); + break; + case 'e': + opts.checkEnvFirst = TRUE; + Global_Append(MAKEFLAGS, "-e"); + break; + case 'f': + Lst_Append(&opts.makefiles, bmake_strdup(argvalue)); + break; + case 'i': + opts.ignoreErrors = TRUE; + Global_Append(MAKEFLAGS, "-i"); + break; + case 'j': + MainParseArgJobs(argvalue); + break; + case 'k': + opts.keepgoing = TRUE; + Global_Append(MAKEFLAGS, "-k"); + break; + case 'm': + MainParseArgSysInc(argvalue); + /* XXX: why no Var_Append? */ + break; + case 'n': + opts.noExecute = TRUE; + Global_Append(MAKEFLAGS, "-n"); + break; + case 'q': + opts.queryFlag = TRUE; + /* Kind of nonsensical, wot? */ + Global_Append(MAKEFLAGS, "-q"); + break; + case 'r': + opts.noBuiltins = TRUE; + Global_Append(MAKEFLAGS, "-r"); + break; + case 's': + opts.beSilent = TRUE; + Global_Append(MAKEFLAGS, "-s"); + break; + case 't': + opts.touchFlag = TRUE; + Global_Append(MAKEFLAGS, "-t"); + break; + case 'w': + opts.enterFlag = TRUE; + Global_Append(MAKEFLAGS, "-w"); + break; + default: + case '?': + usage(); + } + return TRUE; +} + +/* + * Parse the given arguments. Called from main() and from + * Main_ParseArgLine() when the .MAKEFLAGS target is used. * - * XXX: Deal with command line overriding .MAKEFLAGS in makefile + * The arguments must be treated as read-only and will be freed after the + * call. * - * Results: - * None - * - * Side Effects: - * Various global and local flags will be set depending on the flags - * given + * XXX: Deal with command line overriding .MAKEFLAGS in makefile */ static void MainParseArgs(int argc, char **argv) { - char *p; - int c = '?'; + char c; int arginc; char *argvalue; - const char *getopt_def; char *optscan; Boolean inOption, dashDash = FALSE; - char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ -#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstw" + const char *optspecs = "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w"; /* Can't actually use getopt(3) because rescanning is not portable */ - getopt_def = OPTFLAGS; -rearg: +rearg: inOption = FALSE; optscan = NULL; - while(argc > 1) { - char *getopt_spec; - if(!inOption) + while (argc > 1) { + const char *optspec; + if (!inOption) optscan = argv[1]; c = *optscan++; arginc = 0; - if(inOption) { - if(c == '\0') { - ++argv; - --argc; + if (inOption) { + if (c == '\0') { + argv++; + argc--; inOption = FALSE; continue; } @@ -407,219 +616,54 @@ rearg: c = *optscan++; } /* '-' found at some earlier point */ - getopt_spec = strchr(getopt_def, c); - if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') { + optspec = strchr(optspecs, c); + if (c != '\0' && optspec != NULL && optspec[1] == ':') { /* - found, and should have an arg */ inOption = FALSE; arginc = 1; argvalue = optscan; - if(*argvalue == '\0') { + if (*argvalue == '\0') { if (argc < 3) goto noarg; argvalue = argv[2]; arginc = 2; } } else { - argvalue = NULL; + argvalue = NULL; } - switch(c) { + switch (c) { case '\0': arginc = 1; inOption = FALSE; break; - case 'B': - compatMake = TRUE; - Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL); - Var_Set(MAKE_MODE, "compat", VAR_GLOBAL, 0); - break; - case 'C': - if (chdir(argvalue) == -1) { - (void)fprintf(stderr, - "%s: chdir %s: %s\n", - progname, argvalue, - strerror(errno)); - exit(1); - } - if (getcwd(curdir, MAXPATHLEN) == NULL) { - (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); - exit(2); - } - ignorePWD = TRUE; - break; - case 'D': - if (argvalue == NULL || argvalue[0] == 0) goto noarg; - Var_Set(argvalue, "1", VAR_GLOBAL, 0); - Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'I': - if (argvalue == NULL) goto noarg; - Parse_AddIncludeDir(argvalue); - Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'J': - if (argvalue == NULL) goto noarg; - if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { - (void)fprintf(stderr, - "%s: internal error -- J option malformed (%s)\n", - progname, argvalue); - usage(); - } - if ((fcntl(jp_0, F_GETFD, 0) < 0) || - (fcntl(jp_1, F_GETFD, 0) < 0)) { -#if 0 - (void)fprintf(stderr, - "%s: ###### warning -- J descriptors were closed!\n", - progname); - exit(2); -#endif - jp_0 = -1; - jp_1 = -1; - compatMake = TRUE; - } else { - Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - jobServer = TRUE; - } - break; - case 'N': - noExecute = TRUE; - noRecursiveExecute = TRUE; - Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL); - break; - case 'S': - keepgoing = FALSE; - Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); - break; - case 'T': - if (argvalue == NULL) goto noarg; - tracefile = bmake_strdup(argvalue); - Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'V': - if (argvalue == NULL) goto noarg; - printVars = TRUE; - (void)Lst_AtEnd(variables, argvalue); - Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'W': - parseWarnFatal = TRUE; - break; - case 'X': - varNoExportEnv = TRUE; - Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL); - break; - case 'd': - if (argvalue == NULL) goto noarg; - /* If '-d-opts' don't pass to children */ - if (argvalue[0] == '-') - argvalue++; - else { - Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - } - parse_debug_options(argvalue); - break; - case 'e': - checkEnvFirst = TRUE; - Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); - break; - case 'f': - if (argvalue == NULL) goto noarg; - (void)Lst_AtEnd(makefiles, argvalue); - break; - case 'i': - ignoreErrors = TRUE; - Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); - break; - case 'j': - if (argvalue == NULL) goto noarg; - forceJobs = TRUE; - maxJobs = strtol(argvalue, &p, 0); - if (*p != '\0' || maxJobs < 1) { - (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n", - progname); - exit(1); - } - Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL, 0); - maxJobTokens = maxJobs; - break; - case 'k': - keepgoing = TRUE; - Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); - break; - case 'm': - if (argvalue == NULL) goto noarg; - /* look for magic parent directory search string */ - if (strncmp(".../", argvalue, 4) == 0) { - if (!Dir_FindHereOrAbove(curdir, argvalue+4, - found_path, sizeof(found_path))) - break; /* nothing doing */ - (void)Dir_AddDir(sysIncPath, found_path); - } else { - (void)Dir_AddDir(sysIncPath, argvalue); - } - Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL); - Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); - break; - case 'n': - noExecute = TRUE; - Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); - break; - case 'q': - queryFlag = TRUE; - /* Kind of nonsensical, wot? */ - Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); - break; - case 'r': - noBuiltins = TRUE; - Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); - break; - case 's': - beSilent = TRUE; - Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); - break; - case 't': - touchFlag = TRUE; - Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); - break; - case 'w': - enterFlag = TRUE; - Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL); - break; case '-': dashDash = TRUE; break; default: - case '?': - usage(); + if (!MainParseArg(c, argvalue)) + goto noarg; } argv += arginc; argc -= arginc; } - oldVars = TRUE; - /* * See if the rest of the arguments are variable assignments and * perform them if so. Else take them to be targets and stuff them * on the end of the "create" list. */ - for (; argc > 1; ++argv, --argc) - if (Parse_IsVar(argv[1])) { - Parse_DoVar(argv[1], VAR_CMD); + for (; argc > 1; argv++, argc--) { + VarAssign var; + if (Parse_IsVar(argv[1], &var)) { + Parse_DoVar(&var, SCOPE_CMDLINE); } else { - if (!*argv[1]) + if (argv[1][0] == '\0') Punt("illegal (null) argument."); - if (*argv[1] == '-' && !dashDash) + if (argv[1][0] == '-' && !dashDash) goto rearg; - (void)Lst_AtEnd(create, bmake_strdup(argv[1])); + Lst_Append(&opts.create, bmake_strdup(argv[1])); } + } return; noarg: @@ -628,126 +672,128 @@ noarg: usage(); } -/*- - * Main_ParseArgLine -- - * Used by the parse module when a .MFLAGS or .MAKEFLAGS target - * is encountered and by main() when reading the .MAKEFLAGS envariable. - * Takes a line of arguments and breaks it into its - * component words and passes those words and the number of them to the - * MainParseArgs function. - * The line should have all its leading whitespace removed. +/* + * Break a line of arguments into words and parse them. * - * Input: - * line Line to fracture - * - * Results: - * None - * - * Side Effects: - * Only those that come from the various arguments. + * Used when a .MFLAGS or .MAKEFLAGS target is encountered during parsing and + * by main() when reading the MAKEFLAGS environment variable. */ void Main_ParseArgLine(const char *line) { - char **argv; /* Manufactured argument vector */ - int argc; /* Number of arguments in argv */ - char *args; /* Space used by the args */ - char *buf, *p1; - char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1); - size_t len; + Words words; + char *buf; if (line == NULL) return; - for (; *line == ' '; ++line) + /* XXX: don't use line as an iterator variable */ + for (; *line == ' '; line++) continue; - if (!*line) + if (line[0] == '\0') return; - buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2); - (void)snprintf(buf, len, "%s %s", argv0, line); - if (p1) - free(p1); + { + FStr argv0 = Var_Value(SCOPE_GLOBAL, ".MAKE"); + buf = str_concat3(argv0.str, " ", line); + FStr_Done(&argv0); + } - argv = brk_string(buf, &argc, TRUE, &args); - if (argv == NULL) { + words = Str_Words(buf, TRUE); + if (words.words == NULL) { Error("Unterminated quoted string [%s]", buf); free(buf); return; } free(buf); - MainParseArgs(argc, argv); + MainParseArgs((int)words.len, words.words); - free(args); - free(argv); + Words_Free(words); } Boolean -Main_SetObjdir(const char *path) +Main_SetObjdir(Boolean writable, const char *fmt, ...) { struct stat sb; - char *p = NULL; + char *path; char buf[MAXPATHLEN + 1]; + char buf2[MAXPATHLEN + 1]; Boolean rc = FALSE; + va_list ap; - /* expand variable substitutions */ - if (strchr(path, '$') != 0) { - snprintf(buf, MAXPATHLEN, "%s", path); - path = p = Var_Subst(NULL, buf, VAR_GLOBAL, 0); - } + va_start(ap, fmt); + vsnprintf(path = buf, MAXPATHLEN, fmt, ap); + va_end(ap); if (path[0] != '/') { - snprintf(buf, MAXPATHLEN, "%s/%s", curdir, path); - path = buf; + snprintf(buf2, MAXPATHLEN, "%s/%s", curdir, path); + path = buf2; } /* look for the directory and try to chdir there */ if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { - if (chdir(path)) { - (void)fprintf(stderr, "make warning: %s: %s.\n", - path, strerror(errno)); + if ((writable && access(path, W_OK) != 0) || + (chdir(path) != 0)) { + (void)fprintf(stderr, "%s warning: %s: %s.\n", + progname, path, strerror(errno)); } else { - strncpy(objdir, path, MAXPATHLEN); - Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0); + snprintf(objdir, sizeof objdir, "%s", path); + Global_Set(".OBJDIR", objdir); setenv("PWD", objdir, 1); Dir_InitDot(); + purge_relative_cached_realpaths(); rc = TRUE; - if (enterFlag && strcmp(objdir, curdir) != 0) + if (opts.enterFlag && strcmp(objdir, curdir) != 0) enterFlagObj = TRUE; } } - if (p) - free(p); return rc; } -/*- - * ReadAllMakefiles -- - * wrapper around ReadMakefile() to read all. - * - * Results: - * TRUE if ok, FALSE on error - */ -static int -ReadAllMakefiles(const void *p, const void *q) +static Boolean +SetVarObjdir(Boolean writable, const char *var, const char *suffix) { - return (ReadMakefile(p, q) == 0); + FStr path = Var_Value(SCOPE_CMDLINE, var); + FStr xpath; + + if (path.str == NULL || path.str[0] == '\0') { + FStr_Done(&path); + return FALSE; + } + + /* expand variable substitutions */ + xpath = FStr_InitRefer(path.str); + if (strchr(path.str, '$') != 0) { + char *expanded; + (void)Var_Subst(path.str, SCOPE_GLOBAL, VARE_WANTRES, &expanded); + /* TODO: handle errors */ + xpath = FStr_InitOwn(expanded); + } + + (void)Main_SetObjdir(writable, "%s%s", xpath.str, suffix); + + FStr_Done(&xpath); + FStr_Done(&path); + return TRUE; } +/* + * Splits str into words, adding them to the list. + * The string must be kept alive as long as the list. + */ int -str2Lst_Append(Lst lp, char *str, const char *sep) +str2Lst_Append(StringList *lp, char *str) { - char *cp; - int n; + char *cp; + int n; - if (!sep) - sep = " \t"; + const char *sep = " \t"; - for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { - (void)Lst_AtEnd(lp, cp); - n++; - } - return (n); + for (n = 0, cp = strtok(str, sep); cp != NULL; cp = strtok(NULL, sep)) { + Lst_Append(lp, cp); + n++; + } + return n; } #ifdef SIGINFO @@ -758,110 +804,565 @@ siginfo(int signo MAKE_ATTR_UNUSED) char dir[MAXPATHLEN]; char str[2 * MAXPATHLEN]; int len; - if (getcwd(dir, sizeof(dir)) == NULL) + if (getcwd(dir, sizeof dir) == NULL) return; - len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir); + len = snprintf(str, sizeof str, "%s: Working in: %s\n", progname, dir); if (len > 0) (void)write(STDERR_FILENO, str, (size_t)len); } #endif -/* - * Allow makefiles some control over the mode we run in. - */ -void -MakeMode(const char *mode) +/* Allow makefiles some control over the mode we run in. */ +static void +MakeMode(void) { - char *mp = NULL; + char *mode; - if (!mode) - mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}", VAR_GLOBAL, 0); + (void)Var_Subst("${" MAKE_MODE ":tl}", SCOPE_GLOBAL, VARE_WANTRES, &mode); + /* TODO: handle errors */ - if (mode && *mode) { - if (strstr(mode, "compat")) { - compatMake = TRUE; - forceJobs = FALSE; - } + if (mode[0] != '\0') { + if (strstr(mode, "compat") != NULL) { + opts.compatMake = TRUE; + forceJobs = FALSE; + } #if USE_META - if (strstr(mode, "meta")) - meta_mode_init(mode); + if (strstr(mode, "meta") != NULL) + meta_mode_init(mode); #endif - } - if (mp) - free(mp); + } + + free(mode); } -/*- - * main -- - * The main function, for obvious reasons. Initializes variables - * and a few modules, then parses the arguments give it in the - * environment and on the command line. Reads the system makefile - * followed by either Makefile, makefile or the file given by the - * -f argument. Sets the .MAKEFLAGS PMake variable based on all the - * flags it has received by then uses either the Make or the Compat - * module to create the initial list of targets. - * - * Results: - * If -q was given, exits -1 if anything was out-of-date. Else it exits - * 0. - * - * Side Effects: - * The program exits when done. Targets are created. etc. etc. etc. - */ -int -main(int argc, char **argv) +static void +PrintVar(const char *varname, Boolean expandVars) +{ + if (strchr(varname, '$') != NULL) { + char *evalue; + (void)Var_Subst(varname, SCOPE_GLOBAL, VARE_WANTRES, &evalue); + /* TODO: handle errors */ + printf("%s\n", evalue); + bmake_free(evalue); + + } else if (expandVars) { + char *expr = str_concat3("${", varname, "}"); + char *evalue; + (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &evalue); + /* TODO: handle errors */ + free(expr); + printf("%s\n", evalue); + bmake_free(evalue); + + } else { + FStr value = Var_Value(SCOPE_GLOBAL, varname); + printf("%s\n", value.str != NULL ? value.str : ""); + FStr_Done(&value); + } +} + +/* + * Return a Boolean based on a variable. + * + * If the knob is not set, return the fallback. + * If set, anything that looks or smells like "No", "False", "Off", "0", etc. + * is FALSE, otherwise TRUE. + */ +static Boolean +GetBooleanVar(const char *varname, Boolean fallback) +{ + char *expr = str_concat3("${", varname, ":U}"); + char *value; + Boolean res; + + (void)Var_Subst(expr, SCOPE_GLOBAL, VARE_WANTRES, &value); + /* TODO: handle errors */ + res = ParseBoolean(value, fallback); + free(value); + free(expr); + return res; +} + +static void +doPrintVars(void) +{ + StringListNode *ln; + Boolean expandVars; + + if (opts.printVars == PVM_EXPANDED) + expandVars = TRUE; + else if (opts.debugVflag) + expandVars = FALSE; + else + expandVars = GetBooleanVar(".MAKE.EXPAND_VARIABLES", FALSE); + + for (ln = opts.variables.first; ln != NULL; ln = ln->next) { + const char *varname = ln->datum; + PrintVar(varname, expandVars); + } +} + +static Boolean +runTargets(void) +{ + GNodeList targs = LST_INIT; /* target nodes to create */ + Boolean outOfDate; /* FALSE if all targets up to date */ + + /* + * Have now read the entire graph and need to make a list of + * targets to create. If none was given on the command line, + * we consult the parsing module to find the main target(s) + * to create. + */ + if (Lst_IsEmpty(&opts.create)) + Parse_MainName(&targs); + else + Targ_FindList(&targs, &opts.create); + + if (!opts.compatMake) { + /* + * Initialize job module before traversing the graph + * now that any .BEGIN and .END targets have been read. + * This is done only if the -q flag wasn't given + * (to prevent the .BEGIN from being executed should + * it exist). + */ + if (!opts.queryFlag) { + Job_Init(); + jobsRunning = TRUE; + } + + /* Traverse the graph, checking on all the targets */ + outOfDate = Make_Run(&targs); + } else { + /* + * Compat_Init will take care of creating all the + * targets as well as initializing the module. + */ + Compat_Run(&targs); + outOfDate = FALSE; + } + Lst_Done(&targs); /* Don't free the targets themselves. */ + return outOfDate; +} + +/* + * Set up the .TARGETS variable to contain the list of targets to be + * created. If none specified, make the variable empty -- the parser + * will fill the thing in with the default or .MAIN target. + */ +static void +InitVarTargets(void) +{ + StringListNode *ln; + + if (Lst_IsEmpty(&opts.create)) { + Global_Set(".TARGETS", ""); + return; + } + + for (ln = opts.create.first; ln != NULL; ln = ln->next) { + const char *name = ln->datum; + Global_Append(".TARGETS", name); + } +} + +static void +InitRandom(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + srandom((unsigned int)(tv.tv_sec + tv.tv_usec)); +} + +static const char * +InitVarMachine(const struct utsname *utsname MAKE_ATTR_UNUSED) +{ + const char *machine = getenv("MACHINE"); + if (machine != NULL) + return machine; + +#if defined(MAKE_NATIVE) + return utsname->machine; +#elif defined(MAKE_MACHINE) + return MAKE_MACHINE; +#else + return "unknown"; +#endif +} + +static const char * +InitVarMachineArch(void) +{ + const char *env = getenv("MACHINE_ARCH"); + if (env != NULL) + return env; + +#ifdef MAKE_NATIVE + { + struct utsname utsname; + static char machine_arch_buf[sizeof utsname.machine]; + const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; + size_t len = sizeof machine_arch_buf; + + if (sysctl(mib, (unsigned int)__arraycount(mib), + machine_arch_buf, &len, NULL, 0) < 0) { + (void)fprintf(stderr, "%s: sysctl failed (%s).\n", + progname, strerror(errno)); + exit(2); + } + + return machine_arch_buf; + } +#elif defined(MACHINE_ARCH) + return MACHINE_ARCH; +#elif defined(MAKE_MACHINE_ARCH) + return MAKE_MACHINE_ARCH; +#else + return "unknown"; +#endif +} + +#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 + * not provide it. + * + * So, to stop it breaking this case only, we ignore PWD if + * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a variable expression. + */ +static void +HandlePWD(const struct stat *curdir_st) +{ + char *pwd; + FStr prefix, makeobjdir; + struct stat pwd_st; + + if (ignorePWD || (pwd = getenv("PWD")) == NULL) + return; + + prefix = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIRPREFIX"); + if (prefix.str != NULL) { + FStr_Done(&prefix); + return; + } + + makeobjdir = Var_Value(SCOPE_CMDLINE, "MAKEOBJDIR"); + if (makeobjdir.str != NULL && strchr(makeobjdir.str, '$') != NULL) + goto ignore_pwd; + + if (stat(pwd, &pwd_st) == 0 && + curdir_st->st_ino == pwd_st.st_ino && + curdir_st->st_dev == pwd_st.st_dev) + (void)strncpy(curdir, pwd, MAXPATHLEN); + +ignore_pwd: + FStr_Done(&makeobjdir); +} +#endif + +/* + * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, + * MAKEOBJDIR is set in the environment, try only that value + * and fall back to .CURDIR if it does not exist. + * + * Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE, + * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none + * of these paths exist, just use .CURDIR. + */ +static void +InitObjdir(const char *machine, const char *machine_arch) +{ + Boolean writable; + + Dir_InitCur(curdir); + writable = GetBooleanVar("MAKE_OBJDIR_CHECK_WRITABLE", TRUE); + (void)Main_SetObjdir(FALSE, "%s", curdir); + + if (!SetVarObjdir(writable, "MAKEOBJDIRPREFIX", curdir) && + !SetVarObjdir(writable, "MAKEOBJDIR", "") && + !Main_SetObjdir(writable, "%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && + !Main_SetObjdir(writable, "%s.%s", _PATH_OBJDIR, machine) && + !Main_SetObjdir(writable, "%s", _PATH_OBJDIR)) + (void)Main_SetObjdir(writable, "%s%s", _PATH_OBJDIRPREFIX, curdir); +} + +/* get rid of resource limit on file descriptors */ +static void +UnlimitFiles(void) +{ +#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && + rl.rlim_cur != rl.rlim_max) { + rl.rlim_cur = rl.rlim_max; + (void)setrlimit(RLIMIT_NOFILE, &rl); + } +#endif +} + +static void +CmdOpts_Init(void) +{ + opts.compatMake = FALSE; + opts.debug = DEBUG_NONE; + /* opts.debug_file has already been initialized earlier */ + opts.strict = FALSE; + opts.debugVflag = FALSE; + opts.checkEnvFirst = FALSE; + Lst_Init(&opts.makefiles); + opts.ignoreErrors = FALSE; /* Pay attention to non-zero returns */ + opts.maxJobs = 1; + opts.keepgoing = FALSE; /* Stop on error */ + opts.noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ + opts.noExecute = FALSE; /* Execute all commands */ + opts.queryFlag = FALSE; + opts.noBuiltins = FALSE; /* Read the built-in rules */ + opts.beSilent = FALSE; /* Print commands as executed */ + opts.touchFlag = FALSE; + opts.printVars = PVM_NONE; + Lst_Init(&opts.variables); + opts.parseWarnFatal = FALSE; + opts.enterFlag = FALSE; + opts.varNoExportEnv = FALSE; + Lst_Init(&opts.create); +} + +/* + * Initialize MAKE and .MAKE to the path of the executable, so that it can be + * found by execvp(3) and the shells, even after a chdir. + * + * If it's a relative path and contains a '/', resolve it to an absolute path. + * Otherwise keep it as is, assuming it will be found in the PATH. + */ +static void +InitVarMake(const char *argv0) +{ + const char *make = argv0; + + if (argv0[0] != '/' && strchr(argv0, '/') != NULL) { + char pathbuf[MAXPATHLEN]; + const char *abspath = cached_realpath(argv0, pathbuf); + struct stat st; + if (abspath != NULL && abspath[0] == '/' && + stat(make, &st) == 0) + make = abspath; + } + + Global_Set("MAKE", make); + Global_Set(".MAKE", make); +} + +/* + * Add the directories from the colon-separated syspath to defSysIncPath. + * After returning, the contents of syspath is unspecified. + */ +static void +InitDefSysIncPath(char *syspath) { - Lst targs; /* target nodes to create -- passed to Make_Init */ - Boolean outOfDate = FALSE; /* FALSE if all targets up to date */ - struct stat sb, sa; - char *p1, *path; - char mdpath[MAXPATHLEN]; - const char *machine = getenv("MACHINE"); - const char *machine_arch = getenv("MACHINE_ARCH"); - char *syspath = getenv("MAKESYSPATH"); - Lst sysMkPath; /* Path of sys.mk */ - char *cp = NULL, *start; - /* avoid faults on read-only strings */ static char defsyspath[] = _PATH_DEFSYSPATH; - char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ - struct timeval rightnow; /* to initialize random seed */ + char *start, *cp; + + /* + * If no user-supplied system path was given (through the -m option) + * add the directories from the DEFSYSPATH (more than one may be given + * as dir1:...:dirn) to the system include path. + */ + if (syspath == NULL || syspath[0] == '\0') + syspath = defsyspath; + else + syspath = bmake_strdup(syspath); + + for (start = syspath; *start != '\0'; start = cp) { + for (cp = start; *cp != '\0' && *cp != ':'; cp++) + continue; + if (*cp == ':') + *cp++ = '\0'; + + /* look for magic parent directory search string */ + if (strncmp(start, ".../", 4) == 0) { + char *dir = Dir_FindHereOrAbove(curdir, start + 4); + if (dir != NULL) { + (void)SearchPath_Add(defSysIncPath, dir); + free(dir); + } + } else { + (void)SearchPath_Add(defSysIncPath, start); + } + } + + if (syspath != defsyspath) + free(syspath); +} + +static void +ReadBuiltinRules(void) +{ + StringListNode *ln; + StringList sysMkFiles = LST_INIT; + + SearchPath_Expand( + Lst_IsEmpty(&sysIncPath->dirs) ? defSysIncPath : sysIncPath, + _PATH_DEFSYSMK, + &sysMkFiles); + if (Lst_IsEmpty(&sysMkFiles)) + Fatal("%s: no system rules (%s).", progname, _PATH_DEFSYSMK); + + for (ln = sysMkFiles.first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum) == 0) + break; + + if (ln == NULL) + Fatal("%s: cannot open %s.", + progname, (const char *)sysMkFiles.first->datum); + + /* Free the list nodes but not the actual filenames since these may + * still be used in GNodes. */ + Lst_Done(&sysMkFiles); +} + +static void +InitMaxJobs(void) +{ + char *value; + int n; + + if (forceJobs || opts.compatMake || + !Var_Exists(SCOPE_GLOBAL, ".MAKE.JOBS")) + return; + + (void)Var_Subst("${.MAKE.JOBS}", SCOPE_GLOBAL, VARE_WANTRES, &value); + /* TODO: handle errors */ + n = (int)strtol(value, NULL, 0); + if (n < 1) { + (void)fprintf(stderr, + "%s: illegal value for .MAKE.JOBS " + "-- must be positive integer!\n", + progname); + exit(2); /* Not 1 so -q can distinguish error */ + } + + if (n != opts.maxJobs) { + Global_Append(MAKEFLAGS, "-j"); + Global_Append(MAKEFLAGS, value); + } + + opts.maxJobs = n; + maxJobTokens = opts.maxJobs; + forceJobs = TRUE; + free(value); +} + +/* + * For compatibility, look at the directories in the VPATH variable + * and add them to the search path, if the variable is defined. The + * variable's value is in the same format as the PATH environment + * variable, i.e. ::... + */ +static void +InitVpath(void) +{ + char *vpath, savec, *path; + if (!Var_Exists(SCOPE_CMDLINE, "VPATH")) + return; + + (void)Var_Subst("${VPATH}", SCOPE_CMDLINE, VARE_WANTRES, &vpath); + /* TODO: handle errors */ + path = vpath; + do { + char *cp; + /* skip to end of directory */ + for (cp = path; *cp != ':' && *cp != '\0'; cp++) + continue; + /* Save terminator character so know when to stop */ + savec = *cp; + *cp = '\0'; + /* Add directory to search path */ + (void)SearchPath_Add(&dirSearchPath, path); + *cp = savec; + path = cp + 1; + } while (savec == ':'); + free(vpath); +} + +static void +ReadAllMakefiles(StringList *makefiles) +{ + StringListNode *ln; + + for (ln = makefiles->first; ln != NULL; ln = ln->next) { + const char *fname = ln->datum; + if (ReadMakefile(fname) != 0) + Fatal("%s: cannot open %s.", progname, fname); + } +} + +static void +ReadFirstDefaultMakefile(void) +{ + StringListNode *ln; + char *prefs; + + (void)Var_Subst("${" MAKE_MAKEFILE_PREFERENCE "}", + SCOPE_CMDLINE, VARE_WANTRES, &prefs); + /* TODO: handle errors */ + + /* XXX: This should use a local list instead of opts.makefiles + * since these makefiles do not come from the command line. They + * also have different semantics in that only the first file that + * is found is processed. See ReadAllMakefiles. */ + (void)str2Lst_Append(&opts.makefiles, prefs); + + for (ln = opts.makefiles.first; ln != NULL; ln = ln->next) + if (ReadMakefile(ln->datum) == 0) + break; + + free(prefs); +} + +/* + * Initialize variables such as MAKE, MACHINE, .MAKEFLAGS. + * Initialize a few modules. + * Parse the arguments from MAKEFLAGS and the command line. + */ +static void +main_Init(int argc, char **argv) +{ + struct stat sa; + const char *machine; + const char *machine_arch; + char *syspath = getenv("MAKESYSPATH"); struct utsname utsname; /* default to writing debug to stderr */ - debug_file = stderr; + opts.debug_file = stderr; + + HashTable_Init(&cached_realpaths); #ifdef SIGINFO (void)bmake_signal(SIGINFO, siginfo); #endif - /* - * Set the seed to produce a different random sequence - * on each program execution. - */ - gettimeofday(&rightnow, NULL); - srandom(rightnow.tv_sec + rightnow.tv_usec); - - if ((progname = strrchr(argv[0], '/')) != NULL) - progname++; - else - progname = argv[0]; -#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) - /* - * get rid of resource limit on file descriptors - */ - { - struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && - rl.rlim_cur != rl.rlim_max) { - rl.rlim_cur = rl.rlim_max; - (void)setrlimit(RLIMIT_NOFILE, &rl); - } - } -#endif + + InitRandom(); + + progname = str_basename(argv[0]); + + UnlimitFiles(); if (uname(&utsname) == -1) { - (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, - strerror(errno)); - exit(2); + (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, + strerror(errno)); + exit(2); } /* @@ -872,89 +1373,38 @@ main(int argc, char **argv) * Note that both MACHINE and MACHINE_ARCH are decided at * run-time. */ - if (!machine) { -#ifdef MAKE_NATIVE - machine = utsname.machine; -#else -#ifdef MAKE_MACHINE - machine = MAKE_MACHINE; -#else - machine = "unknown"; -#endif -#endif - } + machine = InitVarMachine(&utsname); + machine_arch = InitVarMachineArch(); - if (!machine_arch) { -#if defined(MAKE_NATIVE) - static char machine_arch_buf[sizeof(utsname.machine)]; - const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; - size_t len = sizeof(machine_arch_buf); - - if (sysctl(mib, __arraycount(mib), machine_arch_buf, - &len, NULL, 0) < 0) { - (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, - strerror(errno)); - exit(2); - } - - machine_arch = machine_arch_buf; -#else -#ifndef MACHINE_ARCH -#ifdef MAKE_MACHINE_ARCH - machine_arch = MAKE_MACHINE_ARCH; -#else - machine_arch = "unknown"; -#endif -#else - machine_arch = MACHINE_ARCH; -#endif -#endif - } - - myPid = getpid(); /* remember this for vFork() */ + myPid = getpid(); /* remember this for vFork() */ /* * Just in case MAKEOBJDIR wants us to do something tricky. */ - Var_Init(); /* Initialize the lists of variables for - * parsing arguments */ - Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0); - Var_Set("MACHINE", machine, VAR_GLOBAL, 0); - Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0); + Targ_Init(); + Var_Init(); + Global_Set(".MAKE.OS", utsname.sysname); + Global_Set("MACHINE", machine); + Global_Set("MACHINE_ARCH", machine_arch); #ifdef MAKE_VERSION - Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0); + Global_Set("MAKE_VERSION", MAKE_VERSION); #endif - Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */ + Global_Set(".newline", "\n"); /* handy for :@ loops */ /* * This is the traditional preference for makefiles. */ #ifndef MAKEFILE_PREFERENCE_LIST # define MAKEFILE_PREFERENCE_LIST "makefile Makefile" #endif - Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, - VAR_GLOBAL, 0); - Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0); + Global_Set(MAKE_MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST); + Global_Set(MAKE_DEPENDFILE, ".depend"); - create = Lst_Init(FALSE); - makefiles = Lst_Init(FALSE); - printVars = FALSE; - debugVflag = FALSE; - variables = Lst_Init(FALSE); - beSilent = FALSE; /* Print commands as executed */ - ignoreErrors = FALSE; /* Pay attention to non-zero returns */ - noExecute = FALSE; /* Execute all commands */ - noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ - keepgoing = FALSE; /* Stop on error */ - allPrecious = FALSE; /* Remove targets when interrupted */ - queryFlag = FALSE; /* This is not just a check-run */ - noBuiltins = FALSE; /* Read the built-in rules */ - touchFlag = FALSE; /* Actually update targets */ - debug = 0; /* No debug verbosity, please. */ + CmdOpts_Init(); + allPrecious = FALSE; /* Remove targets when interrupted */ + deleteOnError = FALSE; /* Historical default behavior */ jobsRunning = FALSE; - maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ - maxJobTokens = maxJobs; - compatMake = FALSE; /* No compat mode */ + maxJobTokens = opts.maxJobs; ignorePWD = FALSE; /* @@ -970,65 +1420,54 @@ main(int argc, char **argv) * MFLAGS also gets initialized empty, for compatibility. */ Parse_Init(); - if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) { - /* - * Leave alone if it is an absolute path, or if it does - * not contain a '/' in which case we need to find it in - * the path, like execvp(3) and the shells do. - */ - p1 = argv[0]; - } else { - /* - * A relative path, canonicalize it. - */ - p1 = realpath(argv[0], mdpath); - if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) { - p1 = argv[0]; /* realpath failed */ - } - } - Var_Set("MAKE", p1, VAR_GLOBAL, 0); - Var_Set(".MAKE", p1, VAR_GLOBAL, 0); - Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0); - Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0); - Var_Set("MFLAGS", "", VAR_GLOBAL, 0); - Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0); + InitVarMake(argv[0]); + Global_Set(MAKEFLAGS, ""); + Global_Set(MAKEOVERRIDES, ""); + Global_Set("MFLAGS", ""); + Global_Set(".ALLTARGETS", ""); /* some makefiles need to know this */ - Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0); + Var_Set(SCOPE_CMDLINE, MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV); - /* - * Set some other useful macros - */ + /* Set some other useful variables. */ { - char tmp[64], *ep; + char tmp[64], *ep = getenv(MAKE_LEVEL_ENV); - makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0; - if (makelevel < 0) - makelevel = 0; - snprintf(tmp, sizeof(tmp), "%d", makelevel); - Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0); - snprintf(tmp, sizeof(tmp), "%u", myPid); - Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0); - snprintf(tmp, sizeof(tmp), "%u", getppid()); - Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0); + makelevel = ep != NULL && ep[0] != '\0' ? atoi(ep) : 0; + if (makelevel < 0) + makelevel = 0; + snprintf(tmp, sizeof tmp, "%d", makelevel); + Global_Set(MAKE_LEVEL, tmp); + snprintf(tmp, sizeof tmp, "%u", myPid); + Global_Set(".MAKE.PID", tmp); + snprintf(tmp, sizeof tmp, "%u", getppid()); + Global_Set(".MAKE.PPID", tmp); + snprintf(tmp, sizeof tmp, "%u", getuid()); + Global_Set(".MAKE.UID", tmp); + snprintf(tmp, sizeof tmp, "%u", getgid()); + Global_Set(".MAKE.GID", tmp); } if (makelevel > 0) { char pn[1024]; - snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel); + snprintf(pn, sizeof pn, "%s[%d]", progname, makelevel); progname = bmake_strdup(pn); } #ifdef USE_META meta_init(); #endif + Dir_Init(); + /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ #ifdef POSIX - p1 = explode(getenv("MAKEFLAGS")); - Main_ParseArgLine(p1); - free(p1); + { + char *p1 = explode(getenv("MAKEFLAGS")); + Main_ParseArgLine(p1); + free(p1); + } #else Main_ParseArgLine(getenv("MAKE")); #endif @@ -1045,244 +1484,105 @@ main(int argc, char **argv) MainParseArgs(argc, argv); - if (enterFlag) + if (opts.enterFlag) printf("%s: Entering directory `%s'\n", progname, curdir); /* * Verify that cwd is sane. */ if (stat(curdir, &sa) == -1) { - (void)fprintf(stderr, "%s: %s: %s.\n", - progname, curdir, strerror(errno)); - exit(2); + (void)fprintf(stderr, "%s: %s: %s.\n", + progname, curdir, strerror(errno)); + exit(2); } - /* - * All this code is so that we know where we are when we start up - * on a different machine with pmake. - * 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 - * not provide it. - * So, to stop it breaking this case only, we ignore PWD if - * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform. - */ #ifndef NO_PWD_OVERRIDE - if (!ignorePWD) { - char *pwd, *ptmp1 = NULL, *ptmp2 = NULL; - - if ((pwd = getenv("PWD")) != NULL && - Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) { - const char *makeobjdir = Var_Value("MAKEOBJDIR", - VAR_CMD, &ptmp2); - - if (makeobjdir == NULL || !strchr(makeobjdir, '$')) { - if (stat(pwd, &sb) == 0 && - sa.st_ino == sb.st_ino && - sa.st_dev == sb.st_dev) - (void)strncpy(curdir, pwd, MAXPATHLEN); - } - } - free(ptmp1); - free(ptmp2); - } + HandlePWD(&sa); #endif - Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0); + Global_Set(".CURDIR", curdir); - /* - * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, - * MAKEOBJDIR is set in the environment, try only that value - * and fall back to .CURDIR if it does not exist. - * - * Otherwise, try _PATH_OBJDIR.MACHINE, _PATH_OBJDIR, and - * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none - * of these paths exist, just use .CURDIR. - */ - Dir_Init(curdir); - (void)Main_SetObjdir(curdir); + InitObjdir(machine, machine_arch); - if ((path = Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &p1)) != NULL) { - (void)snprintf(mdpath, MAXPATHLEN, "%s%s", path, curdir); - (void)Main_SetObjdir(mdpath); - free(p1); - } else if ((path = Var_Value("MAKEOBJDIR", VAR_CMD, &p1)) != NULL) { - (void)Main_SetObjdir(path); - free(p1); - } else { - (void)snprintf(mdpath, MAXPATHLEN, "%s.%s", _PATH_OBJDIR, machine); - if (!Main_SetObjdir(mdpath) && !Main_SetObjdir(_PATH_OBJDIR)) { - (void)snprintf(mdpath, MAXPATHLEN, "%s%s", - _PATH_OBJDIRPREFIX, curdir); - (void)Main_SetObjdir(mdpath); - } - } - - /* - * Be compatible if user did not specify -j and did not explicitly - * turned compatibility on - */ - if (!compatMake && !forceJobs) { - compatMake = TRUE; - } - /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); - Targ_Init(); Suff_Init(); Trace_Init(tracefile); - DEFAULT = NULL; + defaultNode = NULL; (void)time(&now); Trace_Log(MAKESTART, NULL); - - /* - * Set up the .TARGETS variable to contain the list of targets to be - * created. If none specified, make the variable empty -- the parser - * will fill the thing in with the default or .MAIN target. - */ - if (!Lst_IsEmpty(create)) { - LstNode ln; - for (ln = Lst_First(create); ln != NULL; - ln = Lst_Succ(ln)) { - char *name = (char *)Lst_Datum(ln); + InitVarTargets(); - Var_Append(".TARGETS", name, VAR_GLOBAL); - } - } else - Var_Set(".TARGETS", "", VAR_GLOBAL, 0); + InitDefSysIncPath(syspath); +} +/* + * Read the system makefile followed by either makefile, Makefile or the + * files given by the -f option. Exit on parse errors. + */ +static void +main_ReadFiles(void) +{ - /* - * If no user-supplied system path was given (through the -m option) - * add the directories from the DEFSYSPATH (more than one may be given - * as dir1:...:dirn) to the system include path. - */ - if (syspath == NULL || *syspath == '\0') - syspath = defsyspath; + if (!opts.noBuiltins) + ReadBuiltinRules(); + + if (!Lst_IsEmpty(&opts.makefiles)) + ReadAllMakefiles(&opts.makefiles); else - syspath = bmake_strdup(syspath); - - for (start = syspath; *start != '\0'; start = cp) { - for (cp = start; *cp != '\0' && *cp != ':'; cp++) - continue; - if (*cp == ':') { - *cp++ = '\0'; - } - /* look for magic parent directory search string */ - if (strncmp(".../", start, 4) != 0) { - (void)Dir_AddDir(defIncPath, start); - } else { - if (Dir_FindHereOrAbove(curdir, start+4, - found_path, sizeof(found_path))) { - (void)Dir_AddDir(defIncPath, found_path); - } - } - } - if (syspath != defsyspath) - free(syspath); - - /* - * Read in the built-in rules first, followed by the specified - * makefile, if it was (makefile != NULL), or the default - * makefile and Makefile, in that order, if it wasn't. - */ - if (!noBuiltins) { - LstNode ln; - - sysMkPath = Lst_Init(FALSE); - Dir_Expand(_PATH_DEFSYSMK, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath, - sysMkPath); - if (Lst_IsEmpty(sysMkPath)) - Fatal("%s: no system rules (%s).", progname, - _PATH_DEFSYSMK); - ln = Lst_Find(sysMkPath, NULL, ReadMakefile); - if (ln == NULL) - Fatal("%s: cannot open %s.", progname, - (char *)Lst_Datum(ln)); - } - - if (!Lst_IsEmpty(makefiles)) { - LstNode ln; - - ln = Lst_Find(makefiles, NULL, ReadAllMakefiles); - if (ln != NULL) - Fatal("%s: cannot open %s.", progname, - (char *)Lst_Datum(ln)); - } else { - p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}", - VAR_CMD, 0); - if (p1) { - (void)str2Lst_Append(makefiles, p1, NULL); - (void)Lst_Find(makefiles, NULL, ReadMakefile); - free(p1); - } - } + ReadFirstDefaultMakefile(); +} +/* Compute the dependency graph. */ +static void +main_PrepareMaking(void) +{ /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ - if (!noBuiltins || !printVars) { - makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}", - VAR_CMD, 0); - doing_depend = TRUE; - (void)ReadMakefile(makeDependfile, NULL); - doing_depend = FALSE; + if (!opts.noBuiltins || opts.printVars == PVM_NONE) { + (void)Var_Subst("${.MAKE.DEPENDFILE}", + SCOPE_CMDLINE, VARE_WANTRES, &makeDependfile); + if (makeDependfile[0] != '\0') { + /* TODO: handle errors */ + doing_depend = TRUE; + (void)ReadMakefile(makeDependfile); + doing_depend = FALSE; + } } if (enterFlagObj) printf("%s: Entering directory `%s'\n", progname, objdir); - - MakeMode(NULL); - Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL); - if (p1) - free(p1); + MakeMode(); - if (!compatMake) - Job_ServerStart(maxJobTokens, jp_0, jp_1); - if (DEBUG(JOB)) - fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", - jp_0, jp_1, maxJobs, maxJobTokens, compatMake); + { + FStr makeflags = Var_Value(SCOPE_GLOBAL, MAKEFLAGS); + Global_Append("MFLAGS", makeflags.str); + FStr_Done(&makeflags); + } - Main_ExportMAKEFLAGS(TRUE); /* initial export */ - + InitMaxJobs(); /* - * For compatibility, look at the directories in the VPATH variable - * and add them to the search path, if the variable is defined. The - * variable's value is in the same format as the PATH envariable, i.e. - * ::... + * Be compatible if the user did not specify -j and did not explicitly + * turn compatibility on. */ - if (Var_Exists("VPATH", VAR_CMD)) { - char *vpath, savec; - /* - * GCC stores string constants in read-only memory, but - * Var_Subst will want to write this thing, so store it - * in an array - */ - static char VPATH[] = "${VPATH}"; + if (!opts.compatMake && !forceJobs) + opts.compatMake = TRUE; - vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE); - path = vpath; - do { - /* skip to end of directory */ - for (cp = path; *cp != ':' && *cp != '\0'; cp++) - continue; - /* Save terminator character so know when to stop */ - savec = *cp; - *cp = '\0'; - /* Add directory to search path */ - (void)Dir_AddDir(dirSearchPath, path); - *cp = savec; - path = cp + 1; - } while (savec == ':'); - free(vpath); - } + if (!opts.compatMake) + Job_ServerStart(maxJobTokens, jp_0, jp_1); + DEBUG5(JOB, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", + jp_0, jp_1, opts.maxJobs, maxJobTokens, opts.compatMake ? 1 : 0); + + if (opts.printVars == PVM_NONE) + Main_ExportMAKEFLAGS(TRUE); /* initial export */ + + InitVpath(); /* * Now that all search paths have been read for suffixes et al, it's @@ -1298,143 +1598,113 @@ main(int argc, char **argv) /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); +} - /* print the values of any variables requested by the user */ - if (printVars) { - LstNode ln; - Boolean expandVars; - - if (debugVflag) - expandVars = FALSE; - else - expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); - for (ln = Lst_First(variables); ln != NULL; - ln = Lst_Succ(ln)) { - char *var = (char *)Lst_Datum(ln); - char *value; - - if (strchr(var, '$')) { - value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, 0); - } else if (expandVars) { - char tmp[128]; - - if (snprintf(tmp, sizeof(tmp), "${%s}", var) >= (int)(sizeof(tmp))) - Fatal("%s: variable name too big: %s", - progname, var); - value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); - } else { - value = Var_Value(var, VAR_GLOBAL, &p1); - } - printf("%s\n", value ? value : ""); - if (p1) - free(p1); - } +/* + * Make the targets. + * If the -v or -V options are given, print variables instead. + * Return whether any of the targets is out-of-date. + */ +static Boolean +main_Run(void) +{ + if (opts.printVars != PVM_NONE) { + /* print the values of any variables requested by the user */ + doPrintVars(); + return FALSE; } else { - /* - * Have now read the entire graph and need to make a list of - * targets to create. If none was given on the command line, - * we consult the parsing module to find the main target(s) - * to create. - */ - if (Lst_IsEmpty(create)) - targs = Parse_MainName(); - else - targs = Targ_FindList(create, TARG_CREATE); - - if (!compatMake) { - /* - * Initialize job module before traversing the graph - * now that any .BEGIN and .END targets have been read. - * This is done only if the -q flag wasn't given - * (to prevent the .BEGIN from being executed should - * it exist). - */ - if (!queryFlag) { - Job_Init(); - jobsRunning = TRUE; - } - - /* Traverse the graph, checking on all the targets */ - outOfDate = Make_Run(targs); - } else { - /* - * Compat_Init will take care of creating all the - * targets as well as initializing the module. - */ - Compat_Run(targs); - } + return runTargets(); } +} +/* Clean up after making the targets. */ +static void +main_CleanUp(void) +{ #ifdef CLEANUP - Lst_Destroy(targs, NULL); - Lst_Destroy(variables, NULL); - Lst_Destroy(makefiles, NULL); - Lst_Destroy(create, (FreeProc *)free); + Lst_DoneCall(&opts.variables, free); + /* + * Don't free the actual strings from opts.makefiles, they may be + * used in GNodes. + */ + Lst_Done(&opts.makefiles); + Lst_DoneCall(&opts.create, free); #endif /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); - Trace_Log(MAKEEND, 0); + Trace_Log(MAKEEND, NULL); if (enterFlagObj) printf("%s: Leaving directory `%s'\n", progname, objdir); - if (enterFlag) + if (opts.enterFlag) printf("%s: Leaving directory `%s'\n", progname, curdir); +#ifdef USE_META + meta_finish(); +#endif Suff_End(); - Targ_End(); + Targ_End(); Arch_End(); Var_End(); Parse_End(); Dir_End(); Job_End(); Trace_End(); +} +/* Determine the exit code. */ +static int +main_Exit(Boolean outOfDate) +{ + if (opts.strict && (main_errors > 0 || Parse_GetFatals() > 0)) + return 2; /* Not 1 so -q can distinguish error */ return outOfDate ? 1 : 0; } -/*- - * ReadMakefile -- - * Open and parse the given makefile. +int +main(int argc, char **argv) +{ + Boolean outOfDate; + + main_Init(argc, argv); + main_ReadFiles(); + main_PrepareMaking(); + outOfDate = main_Run(); + main_CleanUp(); + return main_Exit(outOfDate); +} + +/* + * Open and parse the given makefile, with all its side effects. * * Results: * 0 if ok. -1 if couldn't open file. - * - * Side Effects: - * lots */ static int -ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED) +ReadMakefile(const char *fname) { - const char *fname = p; /* makefile to read */ int fd; - size_t len = MAXPATHLEN; - char *name, *path = bmake_malloc(len); + char *name, *path = NULL; - if (!strcmp(fname, "-")) { + if (strcmp(fname, "-") == 0) { Parse_File(NULL /*stdin*/, -1); - Var_Set("MAKEFILE", "", VAR_INTERNAL, 0); + Var_Set(SCOPE_INTERNAL, "MAKEFILE", ""); } else { /* if we've chdir'd, rebuild the path name */ - if (strcmp(curdir, objdir) && *fname != '/') { - size_t plen = strlen(curdir) + strlen(fname) + 2; - if (len < plen) - path = bmake_realloc(path, len = 2 * plen); - - (void)snprintf(path, len, "%s/%s", curdir, fname); + if (strcmp(curdir, objdir) != 0 && *fname != '/') { + path = str_concat3(curdir, "/", fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; goto found; } - + free(path); + /* If curdir failed, try objdir (ala .depend) */ - plen = strlen(objdir) + strlen(fname) + 2; - if (len < plen) - path = bmake_realloc(path, len = 2 * plen); - (void)snprintf(path, len, "%s/%s", objdir, fname); + path = str_concat3(objdir, "/", fname); fd = open(path, O_RDONLY); if (fd != -1) { fname = path; @@ -1447,14 +1717,15 @@ ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED) } /* look in -I and system include directories. */ name = Dir_FindFile(fname, parseIncPath); - if (!name) - name = Dir_FindFile(fname, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); - if (!name || (fd = open(name, O_RDONLY)) == -1) { - if (name) - free(name); + if (name == NULL) { + SearchPath *sysInc = Lst_IsEmpty(&sysIncPath->dirs) + ? defSysIncPath : sysIncPath; + name = Dir_FindFile(fname, sysInc); + } + if (name == NULL || (fd = open(name, O_RDONLY)) == -1) { + free(name); free(path); - return(-1); + return -1; } fname = name; /* @@ -1464,178 +1735,145 @@ ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED) */ found: if (!doing_depend) - Var_Set("MAKEFILE", fname, VAR_INTERNAL, 0); + Var_Set(SCOPE_INTERNAL, "MAKEFILE", fname); Parse_File(fname, fd); } free(path); - return(0); + return 0; } - - -/*- +/* * Cmd_Exec -- * Execute the command in cmd, and return the output of that command - * in a string. + * in a string. In the output, newlines are replaced with spaces. * * Results: - * A string containing the output of the command, or the empty string - * If errnum is not NULL, it contains the reason for the command failure + * A string containing the output of the command, or the empty string. + * *errfmt returns a format string describing the command failure, + * if any, using a single %s conversion specification. * * Side Effects: * The string must be freed by the caller. */ char * -Cmd_Exec(const char *cmd, const char **errnum) +Cmd_Exec(const char *cmd, const char **errfmt) { - const char *args[4]; /* Args for invoking the shell */ - int fds[2]; /* Pipe streams */ - int cpid; /* Child PID */ - int pid; /* PID from wait() */ - char *res; /* result */ - int status; /* command exit status */ - Buffer buf; /* buffer to store the result */ - char *cp; - int cc; /* bytes read, or -1 */ - int savederr; /* saved errno */ + const char *args[4]; /* Args for invoking the shell */ + int pipefds[2]; + int cpid; /* Child PID */ + int pid; /* PID from wait() */ + int status; /* command exit status */ + Buffer buf; /* buffer to store the result */ + ssize_t bytes_read; + char *res; /* result */ + size_t res_len; + char *cp; + int savederr; /* saved errno */ + *errfmt = NULL; - *errnum = NULL; - - if (!shellName) - Shell_Init(); - /* - * Set up arguments for shell - */ - args[0] = shellName; - args[1] = "-c"; - args[2] = cmd; - args[3] = NULL; - - /* - * Open a pipe for fetching its output - */ - if (pipe(fds) == -1) { - *errnum = "Couldn't create pipe for \"%s\""; - goto bad; - } - - /* - * Fork - */ - switch (cpid = vFork()) { - case 0: + if (shellName == NULL) + Shell_Init(); /* - * Close input side of pipe + * Set up arguments for shell */ - (void)close(fds[0]); + args[0] = shellName; + args[1] = "-c"; + args[2] = cmd; + args[3] = NULL; /* - * Duplicate the output stream to the shell's output, then - * shut the extra thing down. Note we don't fetch the error - * stream...why not? Why? + * Open a pipe for fetching its output */ - (void)dup2(fds[1], 1); - (void)close(fds[1]); - - Var_ExportVars(); - - (void)execv(shellPath, UNCONST(args)); - _exit(1); - /*NOTREACHED*/ - - case -1: - *errnum = "Couldn't exec \"%s\""; - goto bad; - - default: - /* - * No need for the writing half - */ - (void)close(fds[1]); - - savederr = 0; - Buf_Init(&buf, 0); - - do { - char result[BUFSIZ]; - cc = read(fds[0], result, sizeof(result)); - if (cc > 0) - Buf_AddBytes(&buf, cc, result); + if (pipe(pipefds) == -1) { + *errfmt = "Couldn't create pipe for \"%s\""; + goto bad; } - while (cc > 0 || (cc == -1 && errno == EINTR)); - if (cc == -1) - savederr = errno; + + Var_ReexportVars(); /* - * Close the input side of the pipe. + * Fork */ - (void)close(fds[0]); + switch (cpid = vfork()) { + case 0: + (void)close(pipefds[0]); /* Close input side of pipe */ - /* - * Wait for the process to exit. - */ - while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) { - JobReapChild(pid, status, FALSE); - continue; + /* + * Duplicate the output stream to the shell's output, then + * shut the extra thing down. Note we don't fetch the error + * stream...why not? Why? + */ + (void)dup2(pipefds[1], 1); + (void)close(pipefds[1]); + + (void)execv(shellPath, UNCONST(args)); + _exit(1); + /*NOTREACHED*/ + + case -1: + *errfmt = "Couldn't exec \"%s\""; + goto bad; + + default: + (void)close(pipefds[1]); /* No need for the writing half */ + + savederr = 0; + Buf_Init(&buf); + + do { + char result[BUFSIZ]; + bytes_read = read(pipefds[0], result, sizeof result); + if (bytes_read > 0) + Buf_AddBytes(&buf, result, (size_t)bytes_read); + } while (bytes_read > 0 || + (bytes_read == -1 && errno == EINTR)); + if (bytes_read == -1) + savederr = errno; + + (void)close(pipefds[0]); /* Close the input side of the pipe. */ + + /* Wait for the process to exit. */ + while ((pid = waitpid(cpid, &status, 0)) != cpid && pid >= 0) + JobReapChild(pid, status, FALSE); + + res_len = buf.len; + res = Buf_DoneData(&buf); + + if (savederr != 0) + *errfmt = "Couldn't read shell's output for \"%s\""; + + if (WIFSIGNALED(status)) + *errfmt = "\"%s\" exited on a signal"; + else if (WEXITSTATUS(status) != 0) + *errfmt = "\"%s\" returned non-zero status"; + + /* Convert newlines to spaces. A final newline is just stripped */ + if (res_len > 0 && res[res_len - 1] == '\n') + res[res_len - 1] = '\0'; + for (cp = res; *cp != '\0'; cp++) + if (*cp == '\n') + *cp = ' '; + break; } - cc = Buf_Size(&buf); - res = Buf_Destroy(&buf, FALSE); - - if (savederr != 0) - *errnum = "Couldn't read shell's output for \"%s\""; - - if (WIFSIGNALED(status)) - *errnum = "\"%s\" exited on a signal"; - else if (WEXITSTATUS(status) != 0) - *errnum = "\"%s\" returned non-zero status"; - - /* - * Null-terminate the result, convert newlines to spaces and - * install it in the variable. - */ - res[cc] = '\0'; - cp = &res[cc]; - - if (cc > 0 && *--cp == '\n') { - /* - * A final newline is just stripped - */ - *cp-- = '\0'; - } - while (cp >= res) { - if (*cp == '\n') { - *cp = ' '; - } - cp--; - } - break; - } - return res; + return res; bad: - res = bmake_malloc(1); - *res = '\0'; - return res; + return bmake_strdup(""); } -/*- - * Error -- - * Print an error message given its format. +/* + * Print a printf-style error message. * - * Results: - * None. - * - * Side Effects: - * The message is printed. + * In default mode, this error message has no consequences, in particular it + * does not affect the exit status. Only in lint mode (-dL) it does. */ -/* VARARGS */ void Error(const char *fmt, ...) { va_list ap; FILE *err_file; - err_file = debug_file; + err_file = opts.debug_file; if (err_file == stdout) err_file = stderr; (void)fflush(stdout); @@ -1650,30 +1888,26 @@ Error(const char *fmt, ...) break; err_file = stderr; } + main_errors++; } -/*- - * Fatal -- - * Produce a Fatal error message. If jobs are running, waits for them - * to finish. +/* + * Wait for any running jobs to finish, then produce an error message, + * finally exit immediately. * - * Results: - * None - * - * Side Effects: - * The program exits + * Exiting immediately differs from Parse_Error, which exits only after the + * current top-level makefile has been parsed completely. */ -/* VARARGS */ void Fatal(const char *fmt, ...) { va_list ap; - va_start(ap, fmt); if (jobsRunning) Job_Wait(); (void)fflush(stdout); + va_start(ap, fmt); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); @@ -1683,22 +1917,14 @@ Fatal(const char *fmt, ...) if (DEBUG(GRAPH2) || DEBUG(GRAPH3)) Targ_PrintGraph(2); - Trace_Log(MAKEERROR, 0); + Trace_Log(MAKEERROR, NULL); exit(2); /* Not 1 so -q can distinguish error */ } /* - * Punt -- - * Major exception once jobs are being created. Kills all jobs, prints - * a message and exits. - * - * Results: - * None - * - * Side Effects: - * All children are killed indiscriminately and the program Lib_Exits + * Major exception once jobs are being created. + * Kills all jobs, prints a message and exits. */ -/* VARARGS */ void Punt(const char *fmt, ...) { @@ -1717,16 +1943,7 @@ Punt(const char *fmt, ...) DieHorribly(); } -/*- - * DieHorribly -- - * Exit without giving a message. - * - * Results: - * None - * - * Side Effects: - * A big one... - */ +/* Exit without giving a message. */ void DieHorribly(void) { @@ -1734,26 +1951,21 @@ DieHorribly(void) Job_AbortAll(); if (DEBUG(GRAPH2)) Targ_PrintGraph(2); - Trace_Log(MAKEERROR, 0); - exit(2); /* Not 1, so -q can distinguish error */ + Trace_Log(MAKEERROR, NULL); + exit(2); /* Not 1 so -q can distinguish error */ } /* - * Finish -- - * Called when aborting due to errors in child shell to signal - * abnormal exit. - * - * Results: - * None - * - * Side Effects: - * The program exits + * Called when aborting due to errors in child shell to signal abnormal exit. + * The program exits. + * Errors is the number of errors encountered in Make_Make. */ void -Finish(int errors) - /* number of errors encountered in Make_Make */ +Finish(int errs) { - Fatal("%d error%s", errors, errors == 1 ? "" : "s"); + if (shouldDieQuietly(NULL, -1)) + exit(2); + Fatal("%d error%s", errs, errs == 1 ? "" : "s"); } /* @@ -1775,223 +1987,276 @@ eunlink(const char *file) return unlink(file); } +static void +write_all(int fd, const void *data, size_t n) +{ + const char *mem = data; + + while (n > 0) { + ssize_t written = write(fd, mem, n); + if (written == -1 && errno == EAGAIN) + continue; + if (written == -1) + break; + mem += written; + n -= (size_t)written; + } +} + /* - * execError -- + * execDie -- * Print why exec failed, avoiding stdio. */ -void -execError(const char *af, const char *av) +void MAKE_ATTR_DEAD +execDie(const char *af, const char *av) { -#ifdef USE_IOVEC - int i = 0; - struct iovec iov[8]; -#define IOADD(s) \ - (void)(iov[i].iov_base = UNCONST(s), \ - iov[i].iov_len = strlen(iov[i].iov_base), \ - i++) -#else -#define IOADD(void)write(2, s, strlen(s)) -#endif + Buffer buf; - IOADD(progname); - IOADD(": "); - IOADD(af); - IOADD("("); - IOADD(av); - IOADD(") failed ("); - IOADD(strerror(errno)); - IOADD(")\n"); + Buf_Init(&buf); + Buf_AddStr(&buf, progname); + Buf_AddStr(&buf, ": "); + Buf_AddStr(&buf, af); + Buf_AddStr(&buf, "("); + Buf_AddStr(&buf, av); + Buf_AddStr(&buf, ") failed ("); + Buf_AddStr(&buf, strerror(errno)); + Buf_AddStr(&buf, ")\n"); -#ifdef USE_IOVEC - while (writev(2, iov, 8) == -1 && errno == EAGAIN) - continue; -#endif + write_all(STDERR_FILENO, buf.data, buf.len); + + Buf_Done(&buf); + _exit(1); +} + +/* purge any relative paths */ +static void +purge_relative_cached_realpaths(void) +{ + HashEntry *he, *nhe; + HashIter hi; + + HashIter_Init(&hi, &cached_realpaths); + he = HashIter_Next(&hi); + while (he != NULL) { + nhe = HashIter_Next(&hi); + if (he->key[0] != '/') { + DEBUG1(DIR, "cached_realpath: purging %s\n", he->key); + HashTable_DeleteEntry(&cached_realpaths, he); + /* XXX: What about the allocated he->value? Either + * free them or document why they cannot be freed. */ + } + he = nhe; + } +} + +char * +cached_realpath(const char *pathname, char *resolved) +{ + const char *rp; + + if (pathname == NULL || pathname[0] == '\0') + return NULL; + + rp = HashTable_FindValue(&cached_realpaths, pathname); + if (rp != NULL) { + /* a hit */ + strncpy(resolved, rp, MAXPATHLEN); + resolved[MAXPATHLEN - 1] = '\0'; + return resolved; + } + + rp = realpath(pathname, resolved); + if (rp != NULL) { + HashTable_Set(&cached_realpaths, pathname, bmake_strdup(rp)); + DEBUG2(DIR, "cached_realpath: %s -> %s\n", pathname, rp); + return resolved; + } + + /* should we negative-cache? */ + return NULL; } /* - * usage -- - * exit with usage message + * Return true if we should die without noise. + * For example our failing child was a sub-make or failure happened elsewhere. */ +Boolean +shouldDieQuietly(GNode *gn, int bf) +{ + static int quietly = -1; + + if (quietly < 0) { + if (DEBUG(JOB) || !GetBooleanVar(".MAKE.DIE_QUIETLY", TRUE)) + quietly = 0; + else if (bf >= 0) + quietly = bf; + else + quietly = (gn != NULL && (gn->type & OP_MAKE)) ? 1 : 0; + } + return quietly != 0; +} + static void -usage(void) +SetErrorVars(GNode *gn) { - char *p; - if ((p = strchr(progname, '[')) != NULL) - *p = '\0'; + StringListNode *ln; - (void)fprintf(stderr, -"usage: %s [-BeikNnqrstWwX] \n\ - [-C directory] [-D variable] [-d flags] [-f makefile]\n\ - [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\ - [-V variable] [variable=value] [target ...]\n", progname); - exit(2); -} - - -int -PrintAddr(void *a, void *b) -{ - printf("%lx ", (unsigned long) a); - return b ? 0 : 0; -} - - - -void -PrintOnError(GNode *gn, const char *s) -{ - static GNode *en = NULL; - char tmp[64]; - char *cp; - - if (s) - printf("%s", s); - - printf("\n%s: stopped in %s\n", progname, curdir); - - if (en) - return; /* we've been here! */ - if (gn) { /* * We can print this even if there is no .ERROR target. */ - Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL, 0); - } - strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", - sizeof(tmp) - 1); - cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); - if (cp) { - if (*cp) - printf("%s", cp); - free(cp); - } - /* - * Finally, see if there is a .ERROR target, and run it if so. - */ - en = Targ_FindNode(".ERROR", TARG_NOCREATE); - if (en) { - en->type |= OP_SPECIAL; - Compat_Make(en, en); - } + Global_Set(".ERROR_TARGET", gn->name); + Global_Delete(".ERROR_CMD"); + + for (ln = gn->commands.first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + + if (cmd == NULL) + break; + Global_Append(".ERROR_CMD", cmd); + } +} + +/* + * Print some helpful information in case of an error. + * The caller should exit soon after calling this function. + */ +void +PrintOnError(GNode *gn, const char *msg) +{ + static GNode *errorNode = NULL; + + if (DEBUG(HASH)) { + Targ_Stats(); + Var_Stats(); + } + + if (errorNode != NULL) + return; /* we've been here! */ + + if (msg != NULL) + printf("%s", msg); + printf("\n%s: stopped in %s\n", progname, curdir); + + /* we generally want to keep quiet if a sub-make died */ + if (shouldDieQuietly(gn, -1)) + return; + + if (gn != NULL) + SetErrorVars(gn); + + { + char *errorVarsValues; + (void)Var_Subst("${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", + SCOPE_GLOBAL, VARE_WANTRES, &errorVarsValues); + /* TODO: handle errors */ + printf("%s", errorVarsValues); + free(errorVarsValues); + } + + fflush(stdout); + + /* + * Finally, see if there is a .ERROR target, and run it if so. + */ + errorNode = Targ_FindNode(".ERROR"); + if (errorNode != NULL) { + errorNode->type |= OP_SPECIAL; + Compat_Make(errorNode, errorNode); + } } void Main_ExportMAKEFLAGS(Boolean first) { - static int once = 1; - char tmp[64]; - char *s; + static Boolean once = TRUE; + const char *expr; + char *s; - if (once != first) - return; - once = 0; - - strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", - sizeof(tmp)); - s = Var_Subst(NULL, tmp, VAR_CMD, 0); - if (s && *s) { + if (once != first) + return; + once = FALSE; + + expr = "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}"; + (void)Var_Subst(expr, SCOPE_CMDLINE, VARE_WANTRES, &s); + /* TODO: handle errors */ + if (s[0] != '\0') { #ifdef POSIX - setenv("MAKEFLAGS", s, 1); + setenv("MAKEFLAGS", s, 1); #else - setenv("MAKE", s, 1); + setenv("MAKE", s, 1); #endif - } + } } char * getTmpdir(void) { - static char *tmpdir = NULL; - - if (!tmpdir) { + static char *tmpdir = NULL; struct stat st; - /* - * Honor $TMPDIR but only if it is valid. - * Ensure it ends with /. - */ - tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, 0); + if (tmpdir != NULL) + return tmpdir; + + /* Honor $TMPDIR but only if it is valid. Ensure it ends with '/'. */ + (void)Var_Subst("${TMPDIR:tA:U" _PATH_TMP "}/", + SCOPE_GLOBAL, VARE_WANTRES, &tmpdir); + /* TODO: handle errors */ + if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { - free(tmpdir); - tmpdir = bmake_strdup(_PATH_TMP); + free(tmpdir); + tmpdir = bmake_strdup(_PATH_TMP); } - } - return tmpdir; + return tmpdir; } /* * Create and open a temp file using "pattern". - * If "fnamep" is provided set it to a copy of the filename created. + * If out_fname is provided, set it to a copy of the filename created. * Otherwise unlink the file once open. */ int -mkTempFile(const char *pattern, char **fnamep) +mkTempFile(const char *pattern, char *tfile, size_t tfile_sz) { - static char *tmpdir = NULL; - char tfile[MAXPATHLEN]; - int fd; - - if (!pattern) - pattern = TMPPAT; - if (!tmpdir) - tmpdir = getTmpdir(); - if (pattern[0] == '/') { - snprintf(tfile, sizeof(tfile), "%s", pattern); - } else { - snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern); - } - if ((fd = mkstemp(tfile)) < 0) - Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); - if (fnamep) { - *fnamep = bmake_strdup(tfile); - } else { - unlink(tfile); /* we just want the descriptor */ - } - return fd; -} + static char *tmpdir = NULL; + char tbuf[MAXPATHLEN]; + int fd; + if (pattern == NULL) + pattern = TMPPAT; + if (tmpdir == NULL) + tmpdir = getTmpdir(); + if (tfile == NULL) { + tfile = tbuf; + tfile_sz = sizeof tbuf; + } + if (pattern[0] == '/') { + snprintf(tfile, tfile_sz, "%s", pattern); + } else { + snprintf(tfile, tfile_sz, "%s%s", tmpdir, pattern); + } + if ((fd = mkstemp(tfile)) < 0) + Punt("Could not create temporary file %s: %s", tfile, + strerror(errno)); + if (tfile == tbuf) { + unlink(tfile); /* we just want the descriptor */ + } + return fd; +} /* - * Return a Boolean based on setting of a knob. - * - * If the knob is not set, the supplied default is the return value. - * If set, anything that looks or smells like "No", "False", "Off", "0" etc, - * is FALSE, otherwise TRUE. + * Convert a string representation of a boolean into a boolean value. + * Anything that looks like "No", "False", "Off", "0" etc. is FALSE, + * the empty string is the fallback, everything else is TRUE. */ Boolean -getBoolean(const char *name, Boolean bf) +ParseBoolean(const char *s, Boolean fallback) { - char tmp[64]; - char *cp; - - if (snprintf(tmp, sizeof(tmp), "${%s:tl}", name) < (int)(sizeof(tmp))) { - cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0); - - if (cp) { - switch(*cp) { - case '\0': /* not set - the default wins */ - break; - case '0': - case 'f': - case 'n': - bf = FALSE; - break; - case 'o': - switch (cp[1]) { - case 'f': - bf = FALSE; - break; - default: - bf = TRUE; - break; - } - break; - default: - bf = TRUE; - break; - } - free(cp); - } - } - return (bf); + char ch = ch_tolower(s[0]); + if (ch == '\0') + return fallback; + if (ch == '0' || ch == 'f' || ch == 'n') + return FALSE; + if (ch == 'o') + return ch_tolower(s[1]) != 'f'; + return TRUE; } diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1 index 14725498a..c1bbcbeda 100644 --- a/usr.bin/make/make.1 +++ b/usr.bin/make/make.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: make.1,v 1.249 2015/06/05 07:33:40 wiz Exp $ +.\" $NetBSD: make.1,v 1.296 2021/02/04 21:42:46 rillig 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 June 4, 2015 +.Dd December 22, 2020 .Dt MAKE 1 .Os .Sh NAME @@ -37,7 +37,7 @@ .Nd maintain program dependencies .Sh SYNOPSIS .Nm -.Op Fl BeikNnqrstWwX +.Op Fl BeikNnqrSstWwX .Op Fl C Ar directory .Op Fl D Ar variable .Op Fl d Ar flags @@ -48,6 +48,7 @@ .Op Fl m Ar directory .Op Fl T Ar file .Op Fl V Ar variable +.Op Fl v Ar variable .Op Ar variable=value .Op Ar target ... .Sh DESCRIPTION @@ -98,7 +99,7 @@ is equivalent to .It Fl D Ar variable Define .Ar variable -to be 1, in the global context. +to be 1, in the global scope. .It Fl d Ar [-]flags Turn on debugging, and specify which portions of .Nm @@ -165,8 +166,15 @@ Print the input graph after making everything, or before exiting on error. .It Ar "g3" Print the input graph before exiting on error. +.It Ar h +Print debugging information about hash table operations. .It Ar j Print debugging information about running multiple shells. +.It Ar L +Turn on lint checks. +This will throw errors for variable assignments that do not parse +correctly, at the time of assignment so the file and line number +are available. .It Ar l Print commands in Makefiles regardless of whether or not they are prefixed by .Ql @ @@ -206,7 +214,9 @@ Print debugging information about target list maintenance. .It Ar V Force the .Fl V -option to print raw values of variables. +option to print raw values of variables, overriding the default behavior +set via +.Va .MAKE.EXPAND_VARIABLES . .It Ar v Print debugging information about variable assignment. .It Ar x @@ -269,7 +279,7 @@ that do not depend on the target whose creation caused the error. .It Fl m Ar directory Specify a directory in which to search for sys.mk and makefiles included via the -.Ao Ar file Ac Ns -style +.Li \&< Ns Ar file Ns Li \&> Ns -style include statement. The .Fl m @@ -277,7 +287,7 @@ option can be used multiple times to form a search path. This path will override the default system include path: /usr/share/mk. Furthermore the system include path will be appended to the search path used for -.Qo Ar file Qc Ns -style +.Li \*q Ns Ar file Ns Li \*q Ns -style include statements (see the .Fl I option). @@ -293,7 +303,7 @@ then will search for the specified file or directory named in the remaining part of the argument string. The search starts with the current directory of -the Makefile and then works upward towards the root of the filesystem. +the Makefile and then works upward towards the root of the file system. If the search is successful, then the resulting directory replaces the .Qq \&.../ specification in the @@ -308,7 +318,8 @@ as an argument). .It Fl n Display the commands that would have been executed, but do not actually execute them unless the target depends on the .MAKE special -source (see below). +source (see below) or the command is prefixed with +.Ql Ic + . .It Fl N Display the commands which would have been executed, but do not actually execute any of them; useful for debugging top-level makefiles @@ -318,6 +329,10 @@ Do not execute any commands, but exit 0 if the specified targets are up-to-date and 1, otherwise. .It Fl r Do not use the built-in rules specified in the system makefile. +.It Fl S +Stop processing if an error is encountered. +This is the default behavior and the opposite of +.Fl k . .It Fl s Do not echo any commands as they are executed. Equivalent to specifying @@ -334,20 +349,39 @@ for each job started and completed. Rather than re-building a target as specified in the makefile, create it or update its modification time to make it appear up-to-date. .It Fl V Ar variable -Print -.Nm Ns 's -idea of the value of -.Ar variable , -in the global context. +Print the value of +.Ar variable . Do not build any targets. Multiple instances of this option may be specified; the variables will be printed one per line, with a blank line for each null or undefined variable. +The value printed is extracted from the global scope after all +makefiles have been read. +By default, the raw variable contents (which may +include additional unexpanded variable references) are shown. If .Ar variable contains a .Ql \&$ -then the value will be expanded before printing. +then the value will be recursively expanded to its complete resultant +text before printing. +The expanded value will also be printed if +.Va .MAKE.EXPAND_VARIABLES +is set to true and +the +.Fl dV +option has not been used to override it. +Note that loop-local and target-local variables, as well as values +taken temporarily by global variables during makefile processing, are +not accessible via this option. +The +.Fl dv +debug mode can be used to see these at the cost of generating +substantial extraneous output. +.It Fl v Ar variable +Like +.Fl V +but the variable is always expanded to its complete value. .It Fl W Treat any warnings during makefile parsing as errors. .It Fl w @@ -390,37 +424,44 @@ or more sources. This creates a relationship where the targets .Dq depend on the sources -and are usually created from them. -The exact relationship between the target and the source is determined -by the operator that separates them. -The three operators are as follows: +and are customarily created from them. +A target is considered out-of-date if it does not exist, or if its +modification time is less than that of any of its sources. +An out-of-date target will be re-created, but not until all sources +have been examined and themselves re-created as needed. +Three operators may be used: .Bl -tag -width flag .It Ic \&: -A target is considered out-of-date if its modification time is less than -those of any of its sources. -Sources for a target accumulate over dependency lines when this operator -is used. -The target is removed if +Many dependency lines may name this target but only one may have +attached shell commands. +All sources named in all dependency lines are considered together, +and if needed the attached shell commands are run to create or +re-create the target. +If .Nm -is interrupted. +is interrupted, the target is removed. .It Ic \&! -Targets are always re-created, but not until all sources have been -examined and re-created as necessary. -Sources for a target accumulate over dependency lines when this operator -is used. -The target is removed if -.Nm -is interrupted. +The same, but the target is always re-created whether or not it is out +of date. .It Ic \&:: -If no sources are specified, the target is always re-created. -Otherwise, a target is considered out-of-date if any of its sources has -been modified more recently than the target. -Sources for a target do not accumulate over dependency lines when this -operator is used. -The target will not be removed if +Any dependency line may have attached shell commands, but each one +is handled independently: its sources are considered and the attached +shell commands are run if the target is out of date with respect to +(only) those sources. +Thus, different groups of the attached shell commands may be run +depending on the circumstances. +Furthermore, unlike +.Ic \&:, +for dependency lines with no sources, the attached shell +commands are always run. +Also unlike +.Ic \&:, +the target will not be removed if .Nm is interrupted. .El +All dependency lines mentioning a particular target must use the same +operator. .Pp Targets and sources may contain the shell wildcard values .Ql \&? , @@ -586,7 +627,7 @@ This shorter form is not recommended. .Pp If the variable name contains a dollar, then the name itself is expanded first. This allows almost arbitrary variable names, however names containing dollar, -braces, parenthesis, or whitespace are really best avoided! +braces, parentheses, or whitespace are really best avoided! .Pp If the result of expanding a variable contains a dollar sign .Pq Ql \&$ @@ -657,7 +698,7 @@ The seven local variables are as follows: .Bl -tag -width ".ARCHIVE" -offset indent .It Va .ALLSRC The list of all sources for this target; also known as -.Ql Va \&\*[Gt] . +.Ql Va \&> . .It Va .ARCHIVE The name of the archive file; also known as .Ql Va \&! . @@ -666,7 +707,7 @@ In suffix-transformation rules, the name/path of the source from which the target is to be transformed (the .Dq implied source); also known as -.Ql Va \&\*[Lt] . +.Ql Va \&< . It is not defined in explicit rules. .It Va .MEMBER The name of the archive member; also known as @@ -685,12 +726,15 @@ or it will not be recognized. .It Va .TARGET The name of the target; also known as .Ql Va @ . +For compatibility with other makes this is an alias for +.Ic .ARCHIVE +in archive member rules. .El .Pp The shorter forms -.Ql ( Va \*[Gt] , +.Ql ( Va > , .Ql Va \&! , -.Ql Va \*[Lt] , +.Ql Va < , .Ql Va % , .Ql Va \&? , .Ql Va * , @@ -773,6 +817,10 @@ from which generated dependencies are read. A boolean that controls the default behavior of the .Fl V option. +If true, variable values printed with +.Fl V +are fully expanded; if false, the raw variable contents (which may +include additional unexpanded variable references) are shown. .It Va .MAKE.EXPORTED The list of variables exported by .Nm . @@ -843,7 +891,7 @@ Can affect the mode that .Nm runs in. It can contain a number of keywords: -.Bl -hang -width ignore-cmd +.Bl -hang -width missing-filemon=bf. .It Pa compat Like .Fl B , @@ -867,8 +915,19 @@ will not create .meta files in This can be overridden by setting .Va bf to a value which represents True. +.It Pa missing-meta= Ar bf +If +.Va bf +is True, then a missing .meta file makes the target out-of-date. +.It Pa missing-filemon= Ar bf +If +.Va bf +is True, then missing filemon data makes the target out-of-date. +.It Pa nofilemon +Do not use +.Xr filemon 4 . .It Pa env -For debugging, it can be useful to inlcude the environment +For debugging, it can be useful to include the environment in the .meta file. .It Pa verbose If in "meta" mode, print a clue about the target being built. @@ -910,6 +969,12 @@ Provides a list of path prefixes that should be ignored; because the contents are expected to change over time. The default list includes: .Ql Pa /dev /etc /proc /tmp /var/run /var/tmp +.It Va .MAKE.META.IGNORE_PATTERNS +Provides a list of patterns to match against pathnames. +Ignore any that match. +.It Va .MAKE.META.IGNORE_FILTER +Provides a list of variable modifiers to apply to each pathname. +Ignore if the expansion is an empty string. .It Va .MAKE.META.PREFIX Defines the message printed for each meta file updated in "meta verbose" mode. The default value is: @@ -918,7 +983,7 @@ The default value is: This variable is used to record the names of variables assigned to on the command line, so that they may be exported as part of .Ql Ev MAKEFLAGS . -This behaviour can be disabled by assigning an empty value to +This behavior can be disabled by assigning an empty value to .Ql Va .MAKEOVERRIDES within a makefile. Extra variables can be exported from a makefile @@ -941,10 +1006,40 @@ The process-id of .It Va .MAKE.PPID The parent process-id of .Nm . +.It Va .MAKE.SAVE_DOLLARS +value should be a boolean that controls whether +.Ql $$ +are preserved when doing +.Ql := +assignments. +The default is true, for compatibility with other makes. +If set to false, +.Ql $$ +becomes +.Ql $ +per normal evaluation rules. +.It Va .MAKE.UID +The user-id running +.Nm . +.It Va .MAKE.GID +The group-id running +.Nm . .It Va MAKE_PRINT_VAR_ON_ERROR When .Nm -stops due to an error, it prints its name and the value of +stops due to an error, it sets +.Ql Va .ERROR_TARGET +to the name of the target that failed, +.Ql Va .ERROR_CMD +to the commands of the failed target, +and in "meta" mode, it also sets +.Ql Va .ERROR_CWD +to the +.Xr getcwd 3 , +and +.Ql Va .ERROR_META_FILE +to the path of the meta file (if any) describing the failed target. +It then prints its name and the value of .Ql Va .CURDIR as well as the value of any variables named in .Ql Va MAKE_PRINT_VAR_ON_ERROR . @@ -1004,6 +1099,15 @@ to the specified directory if it exists, and set and .Ql Ev PWD to that directory before executing any targets. +.Pp +Except in the case of an explicit +.Ql Ic .OBJDIR +target, +.Nm +will check that the specified directory is writable and ignore it if not. +This check can be skipped by setting the environment variable +.Ql Ev MAKE_OBJDIR_CHECK_WRITABLE +to "no". . .It Va .PARSEDIR A path to the directory of the current @@ -1044,7 +1148,7 @@ sets to the value of .Ql Ev PWD instead. -This behaviour is disabled if +This behavior is disabled if .Ql Ev MAKEOBJDIRPREFIX is set or .Ql Ev MAKEOBJDIR @@ -1055,6 +1159,9 @@ is set to the value of for all programs which .Nm executes. +.It Ev .SHELL +The pathname of the shell used to run target scripts. +It is read-only. .It Ev .TARGETS The list of targets explicitly specified on the command line, if any. .It Ev VPATH @@ -1100,7 +1207,7 @@ Replaces each word in the variable with its suffix. .It Cm \&:H Replaces each word in the variable with everything but the last component. .It Cm \&:M Ns Ar pattern -Select only those words that match +Selects only those words that match .Ar pattern . The standard shell wildcard characters .Pf ( Ql * , @@ -1114,7 +1221,7 @@ The wildcard characters may be escaped with a backslash As a consequence of the way values are split into words, matched, and then joined, a construct like .Dl ${VAR:M*} -will normalise the inter-word spacing, removing all leading and +will normalize the inter-word spacing, removing all leading and trailing space, and converting multiple consecutive spaces to single spaces. . @@ -1124,17 +1231,15 @@ This is identical to but selects all words which do not match .Ar pattern . .It Cm \&:O -Order every word in variable alphabetically. -To sort words in -reverse order use the -.Ql Cm \&:O:[-1..1] -combination of modifiers. +Orders every word in variable alphabetically. +.It Cm \&:Or +Orders every word in variable in reverse alphabetical order. .It Cm \&:Ox -Randomize words in variable. +Shuffles the words in variable. The results will be different each time you are referring to the modified variable; use the assignment with expansion .Pq Ql Cm \&:= -to prevent such behaviour. +to prevent such behavior. For example, .Bd -literal -offset indent LIST= uno due tre quattro @@ -1156,24 +1261,41 @@ due uno quattro tre .Ed .It Cm \&:Q Quotes every shell meta-character in the variable, so that it can be passed +safely to the shell. +.It Cm \&:q +Quotes every shell meta-character in the variable, and also doubles +.Sq $ +characters so that it can be passed safely through recursive invocations of .Nm . +This is equivalent to: +.Sq \&:S/\e\&$/&&/g:Q . .It Cm \&:R Replaces each word in the variable with everything but its suffix. -.It Cm \&:gmtime +.It Cm \&:range[=count] +The value is an integer sequence representing the words of the original +value, or the supplied +.Va count . +.It Cm \&:gmtime[=utc] The value is a format string for .Xr strftime 3 , -using the current +using .Xr gmtime 3 . +If a +.Va utc +value is not provided or is 0, the current time is used. .It Cm \&:hash -Compute a 32bit hash of the value and encode it as hex digits. -.It Cm \&:localtime +Computes a 32-bit hash of the value and encode it as hex digits. +.It Cm \&:localtime[=utc] The value is a format string for .Xr strftime 3 , -using the current +using .Xr localtime 3 . +If a +.Va utc +value is not provided or is 0, the current time is used. .It Cm \&:tA -Attempt to convert variable to an absolute path using +Attempts to convert variable to an absolute path using .Xr realpath 3 , if that fails, the value is unchanged. .It Cm \&:tl @@ -1185,7 +1307,7 @@ This modifier sets the separator to the character If .Ar c is omitted, then no separator is used. -The common escapes (including octal numeric codes), work as expected. +The common escapes (including octal numeric codes) work as expected. .It Cm \&:tu Converts variable to upper-case letters. .It Cm \&:tW @@ -1201,21 +1323,21 @@ See also .Sm off .It Cm \&:S No \&/ Ar old_string No \&/ Ar new_string No \&/ Op Cm 1gW .Sm on -Modify the first occurrence of +Modifies the first occurrence of .Ar old_string -in the variable's value, replacing it with +in each word of the variable's value, replacing it with .Ar new_string . If a .Ql g -is appended to the last slash of the pattern, all occurrences +is appended to the last delimiter of the pattern, all occurrences in each word are replaced. If a .Ql 1 -is appended to the last slash of the pattern, only the first word +is appended to the last delimiter of the pattern, only the first occurrence is affected. If a .Ql W -is appended to the last slash of the pattern, +is appended to the last delimiter of the pattern, then the value is treated as a single word (possibly containing embedded white space). If @@ -1232,7 +1354,7 @@ it is anchored at the end of each word. Inside .Ar new_string , an ampersand -.Pq Ql \*[Am] +.Pq Ql & is replaced by .Ar old_string (without any @@ -1284,13 +1406,6 @@ as occur in the word or words it is found in; the .Ql W modifier causes the value to be treated as a single word (possibly containing embedded white space). -Note that -.Ql 1 -and -.Ql g -are orthogonal; the former specifies whether multiple words are -potentially affected, the latter whether multiple substitutions can -potentially occur within each affected word. .Pp As for the .Cm \&:S @@ -1301,9 +1416,9 @@ and are subjected to variable expansion before being parsed as regular expressions. .It Cm \&:T -Replaces each word in the variable with its last component. +Replaces each word in the variable with its last path component. .It Cm \&:u -Remove adjacent duplicate words (like +Removes adjacent duplicate words (like .Xr uniq 1 ) . .Sm off .It Cm \&:\&? Ar true_string Cm \&: Ar false_string @@ -1319,7 +1434,7 @@ usually contain variable expansions. A common error is trying to use expressions like .Dl ${NUMBERS:M42:?match:no} which actually tests defined(NUMBERS), -to determine is any words match "42" you need to use something like: +to determine if any words match "42" you need to use something like: .Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} . .It Ar :old_string=new_string This is the @@ -1341,6 +1456,29 @@ is the substring of .Ar old_string to be replaced in .Ar new_string . +If only +.Ar old_string +contains the pattern matching character +.Ar % , +and +.Ar old_string +matches, then the result is the +.Ar new_string . +If only the +.Ar new_string +contains the pattern matching character +.Ar % , +then it is not treated specially and it is printed as a literal +.Ar % +on match. +If there is more than one pattern matching character +.Ar ( % ) +in either the +.Ar new_string +or +.Ar old_string , +only the first instance is treated specially (as the pattern character); +all subsequent instances are treated as regular characters. .Pp Variable expansion occurs in the normal fashion inside both .Ar old_string @@ -1357,11 +1495,10 @@ This is the loop expansion mechanism from the OSF Development Environment (ODE) make. Unlike .Cm \&.for -loops expansion occurs at the time of -reference. -Assign +loops, expansion occurs at the time of reference. +Assigns .Ar temp -to each word in the variable and evaluate +to each word in the variable and evaluates .Ar string . The ODE convention is that .Ar temp @@ -1371,8 +1508,29 @@ For example. .Pp However a single character variable is often more readable: .Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} +.It Cm \&:_[=var] +Saves the current variable value in +.Ql $_ +or the named +.Va var +for later reference. +Example usage: +.Bd -literal -offset indent +M_cmpv.units = 1 1000 1000000 +M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\ +\\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh + +.Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} + +.Ed +Here +.Ql $_ +is used to save the result of the +.Ql :S +modifier which is later referenced using the index values from +.Ql :range . .It Cm \&:U Ns Ar newval -If the variable is undefined +If the variable is undefined, .Ar newval is the value. If the variable is defined, the existing value is returned. @@ -1382,7 +1540,7 @@ It is handy for setting per-target CFLAGS for instance: If a value is only required if the variable is undefined, use: .Dl ${VAR:D:Unewval} .It Cm \&:D Ns Ar newval -If the variable is defined +If the variable is defined, .Ar newval is the value. .It Cm \&:L @@ -1444,7 +1602,7 @@ value is divided into words. .Pp Ordinarily, a value is treated as a sequence of words delimited by white space. -Some modifiers suppress this behaviour, +Some modifiers suppress this behavior, causing a value to be treated as a single word (possibly containing embedded white space). An empty value, or a value that consists entirely of white-space, @@ -1482,6 +1640,11 @@ then the words are output in reverse order. For example, .Ql Cm \&:[-1..1] selects all the words from last to first. +If the list is already ordered, then this effectively reverses +the list, but it is more efficient to use +.Ql Cm \&:Or +instead of +.Ql Cm \&:O:[-1..1] . .\" :[*] .It Cm \&* Causes subsequent modifiers to treat the value as a single word @@ -1506,7 +1669,7 @@ Returns the number of words in the value. .El \" :[range] .El .Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS -Makefile inclusion, conditional structures and for loops reminiscent +Makefile inclusion, conditional structures and for loops reminiscent of the C programming language are provided in .Nm . All such structures are identified by a line beginning with a single @@ -1514,9 +1677,9 @@ dot .Pq Ql \&. character. Files are included with either -.Cm \&.include Aq Ar file +.Cm \&.include \&< Ns Ar file Ns Cm \&> or -.Cm \&.include Pf \*q Ar file Ns \*q . +.Cm \&.include \&\*q Ns Ar file Ns Cm \&\*q . Variables between the angle brackets or double quotes are expanded to form the file name. If angle brackets are used, the included makefile is expected to be in @@ -1530,12 +1693,20 @@ For compatibility with other versions of .Nm .Ql include file ... is also accepted. +.Pp If the include statement is written as .Cm .-include or as .Cm .sinclude then errors locating and/or opening include files are ignored. .Pp +If the include statement is written as +.Cm .dinclude +not only are errors locating and/or opening include files ignored, +but stale dependencies within the included file will be ignored +just like +.Va .MAKE.DEPENDFILE . +.Pp Conditional expressions are also preceded by a single dot as the first character of a line. The possible conditionals are as follows: @@ -1544,7 +1715,7 @@ The possible conditionals are as follows: The message is printed along with the name of the makefile and line number, then .Nm -will exit. +will exit immediately. .It Ic .export Ar variable ... Export the specified global variable. If no variable list is provided, all globals are exported @@ -1571,11 +1742,15 @@ This allows exporting a value to the environment which is different from that used by .Nm internally. +.It Ic .export-literal Ar variable ... +The same as +.Ql .export-env , +except that variables in the value are not expanded. .It Ic .info Ar message The message is printed along with the name of the makefile and line number. -.It Ic .undef Ar variable -Un-define the specified global variable. -Only global variables may be un-defined. +.It Ic .undef Ar variable ... +Un-define the specified global variables. +Only global variables can be un-defined. .It Ic .unexport Ar variable ... The opposite of .Ql .export . @@ -1663,7 +1838,7 @@ may be any one of the following: .Bl -tag -width "Cm XX" .It Cm \&|\&| Logical OR. -.It Cm \&\*[Am]\*[Am] +.It Cm \&&& Logical .Tn AND ; of higher precedence than @@ -1680,7 +1855,7 @@ The boolean operator may be used to logically negate an entire conditional. It is of higher precedence than -.Ql Ic \&\*[Am]\*[Am] . +.Ql Ic \&&& . .Pp The value of .Ar expression @@ -1715,7 +1890,7 @@ has been defined and has commands associated with it. .Ar Expression may also be an arithmetic or string comparison. Variable expansion is -performed on both sides of the comparison, after which the integral +performed on both sides of the comparison, after which the numerical values are compared. A value is interpreted as hexadecimal if it is preceded by 0x, otherwise it is decimal; octal numbers are not supported. @@ -1725,11 +1900,11 @@ variable expansion, either the left or right hand side of a .Ql Ic == or .Ql Ic "!=" -operator is not an integral value, then +operator is not a numerical value, then string comparison is performed between the expanded variables. If no relational operator is given, it is assumed that the expanded -variable is being compared against 0 or an empty string in the case +variable is being compared against 0, or an empty string in the case of a string comparison. .Pp When @@ -1751,7 +1926,8 @@ expression is applied. Similarly, if the form is .Ql Ic .ifmake or -.Ql Ic .ifnmake , the +.Ql Ic .ifnmake , +the .Dq make expression is applied. .Pp @@ -1769,7 +1945,7 @@ The syntax of a for loop is: .Pp .Bl -tag -compact -width Ds .It Ic \&.for Ar variable Oo Ar variable ... Oc Ic in Ar expression -.It Aq make-rules +.It Aq make-lines .It Ic \&.endfor .El .Pp @@ -1781,7 +1957,7 @@ On each iteration of the loop, one word is taken and assigned to each in order, and these .Ic variables are substituted into the -.Ic make-rules +.Ic make-lines inside the body of the for loop. The number of words must come out even; that is, if there are three iteration variables, the number of words provided must be a multiple @@ -1954,6 +2130,14 @@ variable of a target that inherits .Ic .DEFAULT Ns 's commands is set to the target's own name. +.It Ic .DELETE_ON_ERROR +If this target is present in the makefile, it globally causes make to +delete targets whose commands fail. +(By default, only targets whose commands are interrupted during +execution are deleted. +This is the historical behavior.) +This setting can be used to help prevent half-finished or malformed +targets from being left around and corrupting future rebuilds. .It Ic .END Any command lines attached to this target are executed after everything else is done. @@ -2068,7 +2252,7 @@ The sources are a set of pairs. .Bl -tag -width hasErrCtls .It Ar name -This is the minimal specification, used to select one of the builtin +This is the minimal specification, used to select one of the built-in shell specs; .Ar sh , .Ar ksh , @@ -2274,9 +2458,9 @@ file). .Sh BUGS The .Nm -syntax is difficult to parse without actually acting of the data. -For instance finding the end of a variable use should involve scanning each -the modifiers using the correct terminator for each field. +syntax is difficult to parse without actually acting on the data. +For instance, finding the end of a variable's use should involve scanning +each of the modifiers, using the correct terminator for each field. In many places .Nm just counts {} and () in order to find the end of a variable expansion. diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c index fde185065..1cd7299a7 100644 --- a/usr.bin/make/make.c +++ b/usr.bin/make/make.c @@ -1,4 +1,4 @@ -/* $NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $ */ +/* $NetBSD: make.c,v 1.242 2021/02/05 05:15:12 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,1418 +68,1297 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93"; -#else -__RCSID("$NetBSD: make.c,v 1.91 2014/10/18 08:33:30 snj Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * make.c -- - * The functions which perform the examination of targets and - * their suitability for creation +/* + * Examination of targets and their suitability for creation. * * Interface: - * Make_Run Initialize things for the module and recreate - * whatever needs recreating. Returns TRUE if - * work was (or would have been) done and FALSE - * otherwise. + * Make_Run Initialize things for the module. Returns TRUE if + * work was (or would have been) done. * - * Make_Update Update all parents of a given child. Performs - * various bookkeeping chores like the updating - * of the cmgn field of the parent, filling - * of the IMPSRC context variable, etc. It will - * place the parent on the toBeMade queue if it - * should be. + * Make_Update After a target is made, update all its parents. + * Perform various bookkeeping chores like the updating + * of the youngestChild field of the parent, filling + * of the IMPSRC variable, etc. Place the parent on the + * toBeMade queue if it should be. * - * Make_TimeStamp Function to set the parent's cmgn field - * based on a child's modification time. + * GNode_UpdateYoungestChild + * Update the node's youngestChild field based on the + * child's modification time. * - * Make_DoAllVar Set up the various local variables for a - * target, including the .ALLSRC variable, making - * sure that any variable that needs to exist - * at the very least has the empty value. + * Make_DoAllVar Set up the various local variables for a + * target, including the .ALLSRC variable, making + * sure that any variable that needs to exist + * at the very least has the empty value. * - * Make_OODate Determine if a target is out-of-date. + * GNode_IsOODate Determine if a target is out-of-date. * - * Make_HandleUse See if a child is a .USE node for a parent - * and perform the .USE actions if so. + * Make_HandleUse See if a child is a .USE node for a parent + * and perform the .USE actions if so. * - * Make_ExpandUse Expand .USE nodes + * Make_ExpandUse Expand .USE nodes */ -#include "make.h" -#include "hash.h" -#include "dir.h" -#include "job.h" +#include "make.h" +#include "dir.h" +#include "job.h" -static unsigned int checked = 1;/* Sequence # to detect recursion */ -static Lst toBeMade; /* The current fringe of the graph. These - * are nodes which await examination by - * MakeOODate. It is added to by - * Make_Update and subtracted from by - * MakeStartJobs */ +/* "@(#)make.c 8.1 (Berkeley) 6/6/93" */ +MAKE_RCSID("$NetBSD: make.c,v 1.242 2021/02/05 05:15:12 rillig Exp $"); -static int MakeAddChild(void *, void *); -static int MakeFindChild(void *, void *); -static int MakeUnmark(void *, void *); -static int MakeAddAllSrc(void *, void *); -static int MakeTimeStamp(void *, void *); -static int MakeHandleUse(void *, void *); -static Boolean MakeStartJobs(void); -static int MakePrintStatus(void *, void *); -static int MakeCheckOrder(void *, void *); -static int MakeBuildChild(void *, void *); -static int MakeBuildParent(void *, void *); +/* Sequence # to detect recursion. */ +static unsigned int checked_seqno = 1; -MAKE_ATTR_DEAD static void -make_abort(GNode *gn, int line) +/* + * The current fringe of the graph. + * These are nodes which await examination by MakeOODate. + * It is added to by Make_Update and subtracted from by MakeStartJobs + */ +static GNodeList toBeMade = LST_INIT; + + +void +debug_printf(const char *fmt, ...) { - static int two = 2; + va_list args; - fprintf(debug_file, "make_abort from line %d\n", line); - Targ_PrintNode(gn, &two); - Lst_ForEach(toBeMade, Targ_PrintNode, &two); - Targ_PrintGraph(3); - abort(); + va_start(args, fmt); + vfprintf(opts.debug_file, fmt, args); + va_end(args); } -/*- - *----------------------------------------------------------------------- - * Make_TimeStamp -- - * Set the cmgn field of a parent node based on the mtime stamp in its - * child. Called from MakeOODate via Lst_ForEach. - * - * Input: - * pgn the current parent - * cgn the child we've just examined - * - * Results: - * Always returns 0. - * - * Side Effects: - * The cmgn of the parent node will be changed if the mtime - * field of the child is greater than it. - *----------------------------------------------------------------------- - */ -int -Make_TimeStamp(GNode *pgn, GNode *cgn) +MAKE_ATTR_DEAD static void +make_abort(GNode *gn, int lineno) { - if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) { - pgn->cmgn = cgn; - } - return (0); + + debug_printf("make_abort from line %d\n", lineno); + Targ_PrintNode(gn, 2); + Targ_PrintNodes(&toBeMade, 2); + Targ_PrintGraph(3); + abort(); +} + +ENUM_FLAGS_RTTI_31(GNodeType, + OP_DEPENDS, OP_FORCE, OP_DOUBLEDEP, +/* OP_OPMASK is omitted since it combines other flags */ + OP_OPTIONAL, OP_USE, OP_EXEC, OP_IGNORE, + OP_PRECIOUS, OP_SILENT, OP_MAKE, OP_JOIN, + OP_MADE, OP_SPECIAL, OP_USEBEFORE, OP_INVISIBLE, + OP_NOTMAIN, OP_PHONY, OP_NOPATH, OP_WAIT, + OP_NOMETA, OP_META, OP_NOMETA_CMP, OP_SUBMAKE, + OP_TRANSFORM, OP_MEMBER, OP_LIB, OP_ARCHV, + OP_HAS_COMMANDS, OP_SAVE_CMDS, OP_DEPS_FOUND, OP_MARK); + +ENUM_FLAGS_RTTI_9(GNodeFlags, + REMAKE, CHILDMADE, FORCE, DONE_WAIT, + DONE_ORDER, FROM_DEPEND, DONE_ALLSRC, CYCLE, + DONECYCLE); + +void +GNode_FprintDetails(FILE *f, const char *prefix, const GNode *gn, + const char *suffix) +{ + char type_buf[GNodeType_ToStringSize]; + char flags_buf[GNodeFlags_ToStringSize]; + + fprintf(f, "%s%s, type %s, flags %s%s", + prefix, + GNodeMade_Name(gn->made), + GNodeType_ToString(type_buf, gn->type), + GNodeFlags_ToString(flags_buf, gn->flags), + suffix); +} + +Boolean +GNode_ShouldExecute(GNode *gn) +{ + return !((gn->type & OP_MAKE) + ? opts.noRecursiveExecute + : opts.noExecute); +} + +/* Update the youngest child of the node, according to the given child. */ +void +GNode_UpdateYoungestChild(GNode *gn, GNode *cgn) +{ + if (gn->youngestChild == NULL || cgn->mtime > gn->youngestChild->mtime) + gn->youngestChild = cgn; +} + +static Boolean +IsOODateRegular(GNode *gn) +{ + /* These rules are inherited from the original Make. */ + + if (gn->youngestChild != NULL) { + if (gn->mtime < gn->youngestChild->mtime) { + DEBUG1(MAKE, "modified before source \"%s\"...", + GNode_Path(gn->youngestChild)); + return TRUE; + } + return FALSE; + } + + if (gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) { + DEBUG0(MAKE, "nonexistent and no sources..."); + return TRUE; + } + + if (gn->type & OP_DOUBLEDEP) { + DEBUG0(MAKE, ":: operator and no sources..."); + return TRUE; + } + + return FALSE; } /* - * Input: - * pgn the current parent - * cgn the child we've just examined + * See if the node is out of date with respect to its sources. * - */ -static int -MakeTimeStamp(void *pgn, void *cgn) -{ - return Make_TimeStamp((GNode *)pgn, (GNode *)cgn); -} - -/*- - *----------------------------------------------------------------------- - * Make_OODate -- - * See if a given node is out of date with respect to its sources. - * Used by Make_Run when deciding which nodes to place on the - * toBeMade queue initially and by Make_Update to screen out USE and - * EXEC nodes. In the latter case, however, any other sort of node - * must be considered out-of-date since at least one of its children - * will have been recreated. + * Used by Make_Run when deciding which nodes to place on the + * toBeMade queue initially and by Make_Update to screen out .USE and + * .EXEC nodes. In the latter case, however, any other sort of node + * must be considered out-of-date since at least one of its children + * will have been recreated. * - * Input: - * gn the node to check - * - * Results: - * TRUE if the node is out of date. FALSE otherwise. - * - * Side Effects: - * The mtime field of the node and the cmgn field of its parents - * will/may be changed. - *----------------------------------------------------------------------- + * The mtime field of the node and the youngestChild field of its parents + * may be changed. */ Boolean -Make_OODate(GNode *gn) +GNode_IsOODate(GNode *gn) { - Boolean oodate; + Boolean oodate; - /* - * Certain types of targets needn't even be sought as their datedness - * doesn't depend on their modification time... - */ - if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) { - (void)Dir_MTime(gn, 1); - if (DEBUG(MAKE)) { - if (gn->mtime != 0) { - fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime)); - } else { - fprintf(debug_file, "non-existent..."); - } - } - } - - /* - * A target is remade in one of the following circumstances: - * its modification time is smaller than that of its youngest child - * and it would actually be run (has commands or type OP_NOP) - * it's the object of a force operator - * it has no children, was on the lhs of an operator and doesn't exist - * already. - * - * Libraries are only considered out-of-date if the archive module says - * they are. - * - * These weird rules are brought to you by Backward-Compatibility and - * the strange people who wrote 'Make'. - */ - if (gn->type & (OP_USE|OP_USEBEFORE)) { /* - * If the node is a USE node it is *never* out of date - * no matter *what*. + * Certain types of targets needn't even be sought as their datedness + * doesn't depend on their modification time... */ - if (DEBUG(MAKE)) { - fprintf(debug_file, ".USE node..."); - } - oodate = FALSE; - } else if ((gn->type & OP_LIB) && - ((gn->mtime==0) || Arch_IsLib(gn))) { - if (DEBUG(MAKE)) { - fprintf(debug_file, "library..."); + if (!(gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC))) { + Dir_UpdateMTime(gn, TRUE); + if (DEBUG(MAKE)) { + if (gn->mtime != 0) + debug_printf("modified %s...", + Targ_FmtTime(gn->mtime)); + else + debug_printf("nonexistent..."); + } } /* - * always out of date if no children and :: target - * or non-existent. + * A target is remade in one of the following circumstances: + * + * its modification time is smaller than that of its youngest + * child and it would actually be run (has commands or is not + * GNode_IsTarget) + * + * it's the object of a force operator + * + * it has no children, was on the lhs of an operator and doesn't + * exist already. + * + * Libraries are only considered out-of-date if the archive module + * says they are. + * + * These weird rules are brought to you by Backward-Compatibility + * and the strange people who wrote 'Make'. */ - oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || - (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP))); - } else if (gn->type & OP_JOIN) { - /* - * A target with the .JOIN attribute is only considered - * out-of-date if any of its children was out-of-date. - */ - if (DEBUG(MAKE)) { - fprintf(debug_file, ".JOIN node..."); + if (gn->type & (OP_USE | OP_USEBEFORE)) { + /* + * If the node is a USE node it is *never* out of date + * no matter *what*. + */ + DEBUG0(MAKE, ".USE node..."); + oodate = FALSE; + } else if ((gn->type & OP_LIB) && (gn->mtime == 0 || Arch_IsLib(gn))) { + DEBUG0(MAKE, "library..."); + + /* + * always out of date if no children and :: target + * or nonexistent. + */ + oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || + (gn->youngestChild == NULL && + (gn->type & OP_DOUBLEDEP))); + } else if (gn->type & OP_JOIN) { + /* + * A target with the .JOIN attribute is only considered + * out-of-date if any of its children was out-of-date. + */ + DEBUG0(MAKE, ".JOIN node..."); + DEBUG1(MAKE, "source %smade...", + gn->flags & CHILDMADE ? "" : "not "); + oodate = (gn->flags & CHILDMADE) != 0; + } else if (gn->type & (OP_FORCE | OP_EXEC | OP_PHONY)) { + /* + * A node which is the object of the force (!) operator or + * which has the .EXEC attribute is always considered + * out-of-date. + */ + if (DEBUG(MAKE)) { + if (gn->type & OP_FORCE) { + debug_printf("! operator..."); + } else if (gn->type & OP_PHONY) { + debug_printf(".PHONY node..."); + } else { + debug_printf(".EXEC node..."); + } + } + oodate = TRUE; + } else if (IsOODateRegular(gn)) { + oodate = TRUE; + } else { + /* + * When a nonexistent child with no sources + * (such as a typically used FORCE source) has been made and + * the target of the child (usually a directory) has the same + * timestamp as the timestamp just given to the nonexistent + * child after it was considered made. + */ + if (DEBUG(MAKE)) { + if (gn->flags & FORCE) + debug_printf("non existing child..."); + } + oodate = (gn->flags & FORCE) != 0; } - if (DEBUG(MAKE)) { - fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); - } - oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; - } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { - /* - * A node which is the object of the force (!) operator or which has - * the .EXEC attribute is always considered out-of-date. - */ - if (DEBUG(MAKE)) { - if (gn->type & OP_FORCE) { - fprintf(debug_file, "! operator..."); - } else if (gn->type & OP_PHONY) { - fprintf(debug_file, ".PHONY node..."); - } else { - fprintf(debug_file, ".EXEC node..."); - } - } - oodate = TRUE; - } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) || - (gn->cmgn == NULL && - ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) - || gn->type & OP_DOUBLEDEP))) - { - /* - * A node whose modification time is less than that of its - * youngest child or that has no children (cmgn == NULL) and - * either doesn't exist (mtime == 0) and it isn't optional - * or was the object of a * :: operator is out-of-date. - * Why? Because that's the way Make does it. - */ - if (DEBUG(MAKE)) { - if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) { - fprintf(debug_file, "modified before source %s...", - gn->cmgn->path); - } else if (gn->mtime == 0) { - fprintf(debug_file, "non-existent and no sources..."); - } else { - fprintf(debug_file, ":: operator and no sources..."); - } - } - oodate = TRUE; - } else { - /* - * When a non-existing child with no sources - * (such as a typically used FORCE source) has been made and - * the target of the child (usually a directory) has the same - * timestamp as the timestamp just given to the non-existing child - * after it was considered made. - */ - if (DEBUG(MAKE)) { - if (gn->flags & FORCE) - fprintf(debug_file, "non existing child..."); - } - oodate = (gn->flags & FORCE) ? TRUE : FALSE; - } #ifdef USE_META - if (useMeta) { - oodate = meta_oodate(gn, oodate); - } + if (useMeta) { + oodate = meta_oodate(gn, oodate); + } #endif - /* - * If the target isn't out-of-date, the parents need to know its - * modification time. Note that targets that appear to be out-of-date - * but aren't, because they have no commands and aren't of type OP_NOP, - * have their mtime stay below their children's mtime to keep parents from - * thinking they're out-of-date. - */ - if (!oodate) { - Lst_ForEach(gn->parents, MakeTimeStamp, gn); - } + /* + * If the target isn't out-of-date, the parents need to know its + * modification time. Note that targets that appear to be out-of-date + * but aren't, because they have no commands and are GNode_IsTarget, + * have their mtime stay below their children's mtime to keep parents + * from thinking they're out-of-date. + */ + if (!oodate) { + GNodeListNode *ln; + for (ln = gn->parents.first; ln != NULL; ln = ln->next) + GNode_UpdateYoungestChild(ln->datum, gn); + } - return (oodate); + return oodate; } - -/*- - *----------------------------------------------------------------------- - * MakeAddChild -- - * Function used by Make_Run to add a child to the list l. - * It will only add the child if its make field is FALSE. - * - * Input: - * gnp the node to add - * lp the list to which to add it - * - * Results: - * Always returns 0 - * - * Side Effects: - * The given list is extended - *----------------------------------------------------------------------- - */ -static int -MakeAddChild(void *gnp, void *lp) + +static void +PretendAllChildrenAreMade(GNode *pgn) { - GNode *gn = (GNode *)gnp; - Lst l = (Lst) lp; + GNodeListNode *ln; - if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeAddChild: need to examine %s%s\n", - gn->name, gn->cohort_num); - (void)Lst_EnQueue(l, gn); - } - return (0); + for (ln = pgn->children.first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; + + /* This may also update cgn->path. */ + Dir_UpdateMTime(cgn, FALSE); + GNode_UpdateYoungestChild(pgn, cgn); + pgn->unmade--; + } } - -/*- - *----------------------------------------------------------------------- - * MakeFindChild -- - * Function used by Make_Run to find the pathname of a child - * that was already made. + +/* + * Called by Make_Run and SuffApplyTransform on the downward pass to handle + * .USE and transformation nodes, by copying the child node's commands, type + * flags and children to the parent node. + * + * A .USE node is much like an explicit transformation rule, except its + * commands are always added to the target node, even if the target already + * has commands. * * Input: - * gnp the node to find - * - * Results: - * Always returns 0 - * - * Side Effects: - * The path and mtime of the node and the cmgn of the parent are - * updated; the unmade children count of the parent is decremented. - *----------------------------------------------------------------------- - */ -static int -MakeFindChild(void *gnp, void *pgnp) -{ - GNode *gn = (GNode *)gnp; - GNode *pgn = (GNode *)pgnp; - - (void)Dir_MTime(gn, 0); - Make_TimeStamp(pgn, gn); - pgn->unmade--; - - return (0); -} - -/*- - *----------------------------------------------------------------------- - * Make_HandleUse -- - * Function called by Make_Run and SuffApplyTransform on the downward - * pass to handle .USE and transformation nodes. It implements the - * .USE and transformation functionality by copying the node's commands, - * type flags and children to the parent node. - * - * A .USE node is much like an explicit transformation rule, except - * its commands are always added to the target node, even if the - * target already has commands. - * - * Input: - * cgn The .USE node - * pgn The target of the .USE node - * - * Results: - * none - * - * Side Effects: - * Children and commands may be added to the parent and the parent's - * type may be changed. - * - *----------------------------------------------------------------------- + * cgn The source node, which is either a .USE/.USEBEFORE + * node or a transformation node (OP_TRANSFORM). + * pgn The target node */ void Make_HandleUse(GNode *cgn, GNode *pgn) { - LstNode ln; /* An element in the children list */ + GNodeListNode *ln; /* An element in the children list */ #ifdef DEBUG_SRC - if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { - fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name); - return; - } + if (!(cgn->type & (OP_USE | OP_USEBEFORE | OP_TRANSFORM))) { + debug_printf("Make_HandleUse: called for plain node %s\n", + cgn->name); + /* XXX: debug mode should not affect control flow */ + return; + } #endif - if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) { - if (cgn->type & OP_USEBEFORE) { - /* - * .USEBEFORE -- - * prepend the child's commands to the parent. - */ - Lst cmds = pgn->commands; - pgn->commands = Lst_Duplicate(cgn->commands, NULL); - (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW); - Lst_Destroy(cmds, NULL); - } else { - /* - * .USE or target has no commands -- - * append the child's commands to the parent. - */ - (void)Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW); - } - } - - if (Lst_Open(cgn->children) == SUCCESS) { - while ((ln = Lst_Next(cgn->children)) != NULL) { - GNode *tgn, *gn = (GNode *)Lst_Datum(ln); - - /* - * Expand variables in the .USE node's name - * and save the unexpanded form. - * We don't need to do this for commands. - * They get expanded properly when we execute. - */ - if (gn->uname == NULL) { - gn->uname = gn->name; - } else { - if (gn->name) - free(gn->name); - } - gn->name = Var_Subst(NULL, gn->uname, pgn, FALSE); - if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) { - /* See if we have a target for this node. */ - tgn = Targ_FindNode(gn->name, TARG_NOCREATE); - if (tgn != NULL) - gn = tgn; - } - - (void)Lst_AtEnd(pgn->children, gn); - (void)Lst_AtEnd(gn->parents, pgn); - pgn->unmade += 1; + if ((cgn->type & (OP_USE | OP_USEBEFORE)) || + Lst_IsEmpty(&pgn->commands)) { + if (cgn->type & OP_USEBEFORE) { + /* .USEBEFORE */ + Lst_PrependAll(&pgn->commands, &cgn->commands); + } else { + /* .USE, or target has no commands */ + Lst_AppendAll(&pgn->commands, &cgn->commands); + } } - Lst_Close(cgn->children); - } - pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM); + for (ln = cgn->children.first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + + /* + * Expand variables in the .USE node's name + * and save the unexpanded form. + * We don't need to do this for commands. + * They get expanded properly when we execute. + */ + if (gn->uname == NULL) { + gn->uname = gn->name; + } else { + free(gn->name); + } + (void)Var_Subst(gn->uname, pgn, VARE_WANTRES, &gn->name); + /* TODO: handle errors */ + if (gn->uname != NULL && strcmp(gn->name, gn->uname) != 0) { + /* See if we have a target for this node. */ + GNode *tgn = Targ_FindNode(gn->name); + if (tgn != NULL) + gn = tgn; + } + + Lst_Append(&pgn->children, gn); + Lst_Append(&gn->parents, pgn); + pgn->unmade++; + } + + pgn->type |= + cgn->type & ~(OP_OPMASK | OP_USE | OP_USEBEFORE | OP_TRANSFORM); } -/*- - *----------------------------------------------------------------------- - * MakeHandleUse -- - * Callback function for Lst_ForEach, used by Make_Run on the downward - * pass to handle .USE nodes. Should be called before the children - * are enqueued to be looked at by MakeAddChild. - * This function calls Make_HandleUse to copy the .USE node's commands, - * type flags and children to the parent node. +/* + * Used by Make_Run on the downward pass to handle .USE nodes. Should be + * called before the children are enqueued to be looked at by MakeAddChild. + * + * For a .USE child, the commands, type flags and children are copied to the + * parent node, and since the relation to the .USE node is then no longer + * needed, that relation is removed. * * Input: - * cgnp the child we've just examined - * pgnp the current parent - * - * Results: - * returns 0. - * - * Side Effects: - * After expansion, .USE child nodes are removed from the parent - * - *----------------------------------------------------------------------- + * cgn the child, which may be a .USE node + * pgn the current parent */ -static int -MakeHandleUse(void *cgnp, void *pgnp) +static void +MakeHandleUse(GNode *cgn, GNode *pgn, GNodeListNode *ln) { - GNode *cgn = (GNode *)cgnp; - GNode *pgn = (GNode *)pgnp; - LstNode ln; /* An element in the children list */ - int unmarked; + Boolean unmarked; - unmarked = ((cgn->type & OP_MARK) == 0); - cgn->type |= OP_MARK; + unmarked = !(cgn->type & OP_MARK); + cgn->type |= OP_MARK; - if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) - return (0); + if (!(cgn->type & (OP_USE | OP_USEBEFORE))) + return; - if (unmarked) - Make_HandleUse(cgn, pgn); + if (unmarked) + Make_HandleUse(cgn, pgn); - /* - * This child node is now "made", so we decrement the count of - * unmade children in the parent... We also remove the child - * from the parent's list to accurately reflect the number of decent - * children the parent has. This is used by Make_Run to decide - * whether to queue the parent or examine its children... - */ - if ((ln = Lst_Member(pgn->children, cgn)) != NULL) { - Lst_Remove(pgn->children, ln); + /* + * This child node is now "made", so we decrement the count of + * unmade children in the parent... We also remove the child + * from the parent's list to accurately reflect the number of decent + * children the parent has. This is used by Make_Run to decide + * whether to queue the parent or examine its children... + */ + Lst_Remove(&pgn->children, ln); pgn->unmade--; - } - return (0); +} + +static void +HandleUseNodes(GNode *gn) +{ + GNodeListNode *ln, *nln; + for (ln = gn->children.first; ln != NULL; ln = nln) { + nln = ln->next; + MakeHandleUse(ln->datum, gn, ln); + } } -/*- - *----------------------------------------------------------------------- - * Make_Recheck -- - * Check the modification time of a gnode, and update it as described - * in the comments below. - * - * Results: - * returns 0 if the gnode does not exist, or its filesystem - * time if it does. - * - * Side Effects: - * the gnode's modification time and path name are affected. - * - *----------------------------------------------------------------------- +/* + * Check the modification time of a gnode, and update it if necessary. + * Return 0 if the gnode does not exist, or its filesystem time if it does. */ time_t Make_Recheck(GNode *gn) { - time_t mtime = Dir_MTime(gn, 1); + time_t mtime; + + Dir_UpdateMTime(gn, TRUE); + mtime = gn->mtime; #ifndef RECHECK - /* - * We can't re-stat the thing, but we can at least take care of rules - * where a target depends on a source that actually creates the - * target, but only if it has changed, e.g. - * - * parse.h : parse.o - * - * parse.o : parse.y - * yacc -d parse.y - * cc -c y.tab.c - * mv y.tab.o parse.o - * cmp -s y.tab.h parse.h || mv y.tab.h parse.h - * - * In this case, if the definitions produced by yacc haven't changed - * from before, parse.h won't have been updated and gn->mtime will - * reflect the current modification time for parse.h. This is - * something of a kludge, I admit, but it's a useful one.. - * XXX: People like to use a rule like - * - * FRC: - * - * To force things that 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)) { - gn->mtime = now; - } + /* + * We can't re-stat the thing, but we can at least take care of rules + * where a target depends on a source that actually creates the + * target, but only if it has changed, e.g. + * + * parse.h : parse.o + * + * parse.o : parse.y + * yacc -d parse.y + * cc -c y.tab.c + * mv y.tab.o parse.o + * cmp -s y.tab.h parse.h || mv y.tab.h parse.h + * + * In this case, if the definitions produced by yacc haven't changed + * from before, parse.h won't have been updated and gn->mtime will + * reflect the current modification time for parse.h. This is + * something of a kludge, I admit, but it's a useful one. + * + * XXX: People like to use a rule like "FRC:" to force things that + * 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)) { + gn->mtime = now; + } #else - /* - * This is what Make does and it's actually a good thing, as it - * allows rules like - * - * cmp -s y.tab.h parse.h || cp y.tab.h parse.h - * - * to function as intended. Unfortunately, thanks to the stateless - * nature of NFS (by which I mean the loose coupling of two clients - * using the same file from a common server), there are times - * when the modification time of a file created on a remote - * machine will not be modified before the local stat() implied by - * the Dir_MTime occurs, thus leading us to believe that the file - * is unchanged, wreaking havoc with files that depend on this one. - * - * I have decided it is better to make too much than to make too - * little, so this stuff is commented out unless you're sure it's ok. - * -- ardeb 1/12/88 - */ - /* - * Christos, 4/9/92: If we are saving commands pretend that - * the target is made now. Otherwise archives with ... rules - * don't work! - */ - if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) || + /* + * This is what Make does and it's actually a good thing, as it + * allows rules like + * + * cmp -s y.tab.h parse.h || cp y.tab.h parse.h + * + * to function as intended. Unfortunately, thanks to the stateless + * nature of NFS (by which I mean the loose coupling of two clients + * using the same file from a common server), there are times when + * the modification time of a file created on a remote machine + * will not be modified before the local stat() implied by the + * Dir_UpdateMTime occurs, thus leading us to believe that the file + * is unchanged, wreaking havoc with files that depend on this one. + * + * I have decided it is better to make too much than to make too + * little, so this stuff is commented out unless you're sure it's ok. + * -- ardeb 1/12/88 + */ + /* + * Christos, 4/9/92: If we are saving commands, pretend that + * the target is made now. Otherwise archives with '...' rules + * don't work! + */ + if (!GNode_ShouldExecute(gn) || (gn->type & OP_SAVE_CMDS) || (mtime == 0 && !(gn->type & OP_WAIT))) { - if (DEBUG(MAKE)) { - fprintf(debug_file, " recheck(%s): update time from %s to now\n", - gn->name, Targ_FmtTime(gn->mtime)); + DEBUG2(MAKE, " recheck(%s): update time from %s to now\n", + gn->name, + gn->mtime == 0 ? "nonexistent" : Targ_FmtTime(gn->mtime)); + gn->mtime = now; + } else { + DEBUG2(MAKE, " recheck(%s): current update time: %s\n", + gn->name, Targ_FmtTime(gn->mtime)); } - gn->mtime = now; - } - else { - if (DEBUG(MAKE)) { - fprintf(debug_file, " recheck(%s): current update time: %s\n", - gn->name, Targ_FmtTime(gn->mtime)); - } - } #endif - return mtime; + + /* XXX: The returned mtime may differ from gn->mtime. + * Intentionally? */ + return mtime; } -/*- - *----------------------------------------------------------------------- - * Make_Update -- - * Perform update on the parents of a node. Used by JobFinish once - * a node has been dealt with and by MakeStartJobs if it finds an - * up-to-date node. +/* + * Set the .PREFIX and .IMPSRC variables for all the implied parents + * of this node. + */ +static void +UpdateImplicitParentsVars(GNode *cgn, const char *cname) +{ + GNodeListNode *ln; + const char *cpref = GNode_VarPrefix(cgn); + + for (ln = cgn->implicitParents.first; ln != NULL; ln = ln->next) { + GNode *pgn = ln->datum; + if (pgn->flags & REMAKE) { + Var_Set(pgn, IMPSRC, cname); + if (cpref != NULL) + Var_Set(pgn, PREFIX, cpref); + } + } +} + +/* See if a .ORDER rule stops us from building this node. */ +static Boolean +IsWaitingForOrder(GNode *gn) +{ + GNodeListNode *ln; + + for (ln = gn->order_pred.first; ln != NULL; ln = ln->next) { + GNode *ogn = ln->datum; + + if (GNode_IsDone(ogn) || !(ogn->flags & REMAKE)) + continue; + + DEBUG2(MAKE, + "IsWaitingForOrder: Waiting for .ORDER node \"%s%s\"\n", + ogn->name, ogn->cohort_num); + return TRUE; + } + return FALSE; +} + +static void MakeBuildParent(GNode *, GNodeListNode *); + +static void +ScheduleOrderSuccessors(GNode *gn) +{ + GNodeListNode *toBeMadeNext = toBeMade.first; + GNodeListNode *ln; + + for (ln = gn->order_succ.first; ln != NULL; ln = ln->next) + MakeBuildParent(ln->datum, toBeMadeNext); +} + +/* + * Perform update on the parents of a node. Used by JobFinish once + * a node has been dealt with and by MakeStartJobs if it finds an + * up-to-date node. * - * Input: - * cgn the child node + * The unmade field of pgn is decremented and pgn may be placed on + * the toBeMade queue if this field becomes 0. * - * Results: - * Always returns 0 + * If the child was made, the parent's flag CHILDMADE field will be + * set true. * - * Side Effects: - * The unmade field of pgn is decremented and pgn may be placed on - * the toBeMade queue if this field becomes 0. + * If the child is not up-to-date and still does not exist, + * set the FORCE flag on the parents. * - * If the child was made, the parent's flag CHILDMADE field will be - * set true. + * If the child wasn't made, the youngestChild field of the parent will be + * altered if the child's mtime is big enough. * - * If the child is not up-to-date and still does not exist, - * set the FORCE flag on the parents. - * - * If the child wasn't made, the cmgn field of the parent will be - * altered if the child's mtime is big enough. - * - * Finally, if the child is the implied source for the parent, the - * parent's IMPSRC variable is set appropriately. - * - *----------------------------------------------------------------------- + * Finally, if the child is the implied source for the parent, the + * parent's IMPSRC variable is set appropriately. */ void Make_Update(GNode *cgn) { - GNode *pgn; /* the parent node */ - char *cname; /* the child's name */ - LstNode ln; /* Element in parents and iParents lists */ - time_t mtime = -1; - char *p1; - Lst parents; - GNode *centurion; + const char *cname; /* the child's name */ + time_t mtime = -1; + GNodeList *parents; + GNodeListNode *ln; + GNode *centurion; - /* It is save to re-examine any nodes again */ - checked++; + /* It is save to re-examine any nodes again */ + checked_seqno++; - cname = Var_Value(TARGET, cgn, &p1); - if (p1) - free(p1); + cname = GNode_VarTarget(cgn); - if (DEBUG(MAKE)) - fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num); + DEBUG2(MAKE, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num); - /* - * If the child was actually made, see what its modification time is - * now -- some rules won't actually update the file. If the file still - * doesn't exist, make its mtime now. - */ - if (cgn->made != UPTODATE) { - mtime = Make_Recheck(cgn); - } + /* + * If the child was actually made, see what its modification time is + * now -- some rules won't actually update the file. If the file + * still doesn't exist, make its mtime now. + */ + if (cgn->made != UPTODATE) { + mtime = Make_Recheck(cgn); + } - /* - * If this is a `::' node, we must consult its first instance - * which is where all parents are linked. - */ - if ((centurion = cgn->centurion) != NULL) { - if (!Lst_IsEmpty(cgn->parents)) - Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num); - centurion->unmade_cohorts -= 1; - if (centurion->unmade_cohorts < 0) - Error("Graph cycles through centurion %s", centurion->name); - } else { - centurion = cgn; - } - parents = centurion->parents; + /* + * If this is a `::' node, we must consult its first instance + * which is where all parents are linked. + */ + if ((centurion = cgn->centurion) != NULL) { + if (!Lst_IsEmpty(&cgn->parents)) + Punt("%s%s: cohort has parents", cgn->name, + cgn->cohort_num); + centurion->unmade_cohorts--; + if (centurion->unmade_cohorts < 0) + Error("Graph cycles through centurion %s", + centurion->name); + } else { + centurion = cgn; + } + parents = ¢urion->parents; - /* If this was a .ORDER node, schedule the RHS */ - Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade)); + /* If this was a .ORDER node, schedule the RHS */ + ScheduleOrderSuccessors(centurion); - /* Now mark all the parents as having one less unmade child */ - if (Lst_Open(parents) == SUCCESS) { - while ((ln = Lst_Next(parents)) != NULL) { - pgn = (GNode *)Lst_Datum(ln); - if (DEBUG(MAKE)) - fprintf(debug_file, "inspect parent %s%s: flags %x, " - "type %x, made %d, unmade %d ", - pgn->name, pgn->cohort_num, pgn->flags, - pgn->type, pgn->made, pgn->unmade-1); + /* Now mark all the parents as having one less unmade child */ + for (ln = parents->first; ln != NULL; ln = ln->next) { + GNode *pgn = ln->datum; - if (!(pgn->flags & REMAKE)) { - /* This parent isn't needed */ - if (DEBUG(MAKE)) - fprintf(debug_file, "- not needed\n"); - continue; - } - if (mtime == 0 && !(cgn->type & OP_WAIT)) - pgn->flags |= FORCE; + if (DEBUG(MAKE)) { + debug_printf("inspect parent %s%s: ", pgn->name, + pgn->cohort_num); + GNode_FprintDetails(opts.debug_file, "", pgn, ""); + debug_printf(", unmade %d ", pgn->unmade - 1); + } - /* - * If the parent has the .MADE attribute, its timestamp got - * updated to that of its newest child, and its unmake - * child count got set to zero in Make_ExpandUse(). - * However other things might cause us to build one of its - * children - and so we mustn't do any processing here when - * the child build finishes. - */ - if (pgn->type & OP_MADE) { - if (DEBUG(MAKE)) - fprintf(debug_file, "- .MADE\n"); - continue; - } + if (!(pgn->flags & REMAKE)) { + /* This parent isn't needed */ + DEBUG0(MAKE, "- not needed\n"); + continue; + } + if (mtime == 0 && !(cgn->type & OP_WAIT)) + pgn->flags |= FORCE; - if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) { - if (cgn->made == MADE) - pgn->flags |= CHILDMADE; - (void)Make_TimeStamp(pgn, cgn); - } + /* + * If the parent has the .MADE attribute, its timestamp got + * updated to that of its newest child, and its unmade + * child count got set to zero in Make_ExpandUse(). + * However other things might cause us to build one of its + * children - and so we mustn't do any processing here when + * the child build finishes. + */ + if (pgn->type & OP_MADE) { + DEBUG0(MAKE, "- .MADE\n"); + continue; + } - /* - * A parent must wait for the completion of all instances - * of a `::' dependency. - */ - if (centurion->unmade_cohorts != 0 || centurion->made < MADE) { - if (DEBUG(MAKE)) - fprintf(debug_file, + if (!(cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE))) { + if (cgn->made == MADE) + pgn->flags |= CHILDMADE; + GNode_UpdateYoungestChild(pgn, cgn); + } + + /* + * A parent must wait for the completion of all instances + * of a `::' dependency. + */ + if (centurion->unmade_cohorts != 0 || + !GNode_IsDone(centurion)) { + DEBUG2(MAKE, "- centurion made %d, %d unmade cohorts\n", centurion->made, centurion->unmade_cohorts); - continue; - } - - /* One more child of this parent is now made */ - pgn->unmade -= 1; - if (pgn->unmade < 0) { - if (DEBUG(MAKE)) { - fprintf(debug_file, "Graph cycles through %s%s\n", - pgn->name, pgn->cohort_num); - Targ_PrintGraph(2); + continue; + } + + /* One more child of this parent is now made */ + pgn->unmade--; + if (pgn->unmade < 0) { + if (DEBUG(MAKE)) { + debug_printf("Graph cycles through %s%s\n", + pgn->name, pgn->cohort_num); + Targ_PrintGraph(2); + } + Error("Graph cycles through %s%s", pgn->name, + pgn->cohort_num); } - Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num); - } - /* We must always rescan the parents of .WAIT and .ORDER nodes. */ - if (pgn->unmade != 0 && !(centurion->type & OP_WAIT) - && !(centurion->flags & DONE_ORDER)) { - if (DEBUG(MAKE)) - fprintf(debug_file, "- unmade children\n"); - continue; - } - if (pgn->made != DEFERRED) { /* - * Either this parent is on a different branch of the tree, - * or it on the RHS of a .WAIT directive - * or it is already on the toBeMade list. + * We must always rescan the parents of .WAIT and .ORDER + * nodes. */ - if (DEBUG(MAKE)) - fprintf(debug_file, "- not deferred\n"); - continue; - } - if (pgn->order_pred - && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) { - /* A .ORDER rule stops us building this */ - continue; - } - if (DEBUG(MAKE)) { - static int two = 2; - fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n", - cgn->name, cgn->cohort_num, - pgn->name, pgn->cohort_num, pgn->made); - Targ_PrintNode(pgn, &two); - } - /* Ok, we can schedule the parent again */ - pgn->made = REQUESTED; - (void)Lst_EnQueue(toBeMade, pgn); - } - Lst_Close(parents); - } + if (pgn->unmade != 0 && !(centurion->type & OP_WAIT) + && !(centurion->flags & DONE_ORDER)) { + DEBUG0(MAKE, "- unmade children\n"); + continue; + } + if (pgn->made != DEFERRED) { + /* + * Either this parent is on a different branch of + * the tree, or it on the RHS of a .WAIT directive + * or it is already on the toBeMade list. + */ + DEBUG0(MAKE, "- not deferred\n"); + continue; + } - /* - * Set the .PREFIX and .IMPSRC variables for all the implied parents - * of this node. - */ - if (Lst_Open(cgn->iParents) == SUCCESS) { - char *cpref = Var_Value(PREFIX, cgn, &p1); + if (IsWaitingForOrder(pgn)) + continue; - while ((ln = Lst_Next(cgn->iParents)) != NULL) { - pgn = (GNode *)Lst_Datum(ln); - if (pgn->flags & REMAKE) { - Var_Set(IMPSRC, cname, pgn, 0); - if (cpref != NULL) - Var_Set(PREFIX, cpref, pgn, 0); - } + if (DEBUG(MAKE)) { + debug_printf("- %s%s made, schedule %s%s (made %d)\n", + cgn->name, cgn->cohort_num, + pgn->name, pgn->cohort_num, pgn->made); + Targ_PrintNode(pgn, 2); + } + /* Ok, we can schedule the parent again */ + pgn->made = REQUESTED; + Lst_Enqueue(&toBeMade, pgn); } - if (p1) - free(p1); - Lst_Close(cgn->iParents); - } + + UpdateImplicitParentsVars(cgn, cname); } - -/*- - *----------------------------------------------------------------------- - * MakeAddAllSrc -- - * Add a child's name to the ALLSRC and OODATE variables of the given - * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only - * if it has not been given the .EXEC, .USE or .INVISIBLE attributes. - * .EXEC and .USE children are very rarely going to be files, so... - * If the child is a .JOIN node, its ALLSRC is propagated to the parent. - * - * A child is added to the OODATE variable if its modification time is - * later than that of its parent, as defined by Make, except if the - * parent is a .JOIN node. In that case, it is only added to the OODATE - * variable if it was actually made (since .JOIN nodes don't have - * modification times, the comparison is rather unfair...).. - * - * Results: - * Always returns 0 - * - * Side Effects: - * The ALLSRC variable for the given node is extended. - *----------------------------------------------------------------------- - */ -static int -MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED) -{ - GNode *cgn = (GNode *)cgnp; - cgn->type &= ~OP_MARK; - return (0); +static void +UnmarkChildren(GNode *gn) +{ + GNodeListNode *ln; + + for (ln = gn->children.first; ln != NULL; ln = ln->next) { + GNode *child = ln->datum; + child->type &= ~OP_MARK; + } } /* - * Input: - * cgnp The child to add - * pgnp The parent to whose ALLSRC variable it should - * be added + * Add a child's name to the ALLSRC and OODATE variables of the given + * node, but only if it has not been given the .EXEC, .USE or .INVISIBLE + * attributes. .EXEC and .USE children are very rarely going to be files, + * so... * + * If the child is a .JOIN node, its ALLSRC is propagated to the parent. + * + * A child is added to the OODATE variable if its modification time is + * later than that of its parent, as defined by Make, except if the + * parent is a .JOIN node. In that case, it is only added to the OODATE + * variable if it was actually made (since .JOIN nodes don't have + * modification times, the comparison is rather unfair...).. + * + * Input: + * cgn The child to add + * pgn The parent to whose ALLSRC variable it should + * be added */ -static int -MakeAddAllSrc(void *cgnp, void *pgnp) +static void +MakeAddAllSrc(GNode *cgn, GNode *pgn) { - GNode *cgn = (GNode *)cgnp; - GNode *pgn = (GNode *)pgnp; + const char *child, *allsrc; - if (cgn->type & OP_MARK) - return (0); - cgn->type |= OP_MARK; + if (cgn->type & OP_MARK) + return; + cgn->type |= OP_MARK; - if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { - char *child, *allsrc; - char *p1 = NULL, *p2 = NULL; + if (cgn->type & (OP_EXEC | OP_USE | OP_USEBEFORE | OP_INVISIBLE)) + return; if (cgn->type & OP_ARCHV) - child = Var_Value(MEMBER, cgn, &p1); + child = GNode_VarMember(cgn); else - child = cgn->path ? cgn->path : cgn->name; - if (cgn->type & OP_JOIN) { - allsrc = Var_Value(ALLSRC, cgn, &p2); - } else { - allsrc = child; - } + child = GNode_Path(cgn); + + if (cgn->type & OP_JOIN) + allsrc = GNode_VarAllsrc(cgn); + else + allsrc = child; + if (allsrc != NULL) - Var_Append(ALLSRC, allsrc, pgn); - if (p2) - free(p2); + Var_Append(pgn, ALLSRC, allsrc); + if (pgn->type & OP_JOIN) { - if (cgn->made == MADE) { - Var_Append(OODATE, child, pgn); - } + if (cgn->made == MADE) + Var_Append(pgn, OODATE, child); + } else if ((pgn->mtime < cgn->mtime) || - (cgn->mtime >= now && cgn->made == MADE)) - { - /* - * It goes in the OODATE variable if the parent is younger than the - * child or if the child has been modified more recently than - * the start of the make. This is to keep pmake from getting - * confused if something else updates the parent after the - * make starts (shouldn't happen, I know, but sometimes it - * does). In such a case, if we've updated the kid, the parent - * is likely to have a modification time later than that of - * the kid and anything that relies on the OODATE variable will - * be hosed. - * - * XXX: This will cause all made children to go in the OODATE - * variable, even if they're not touched, if RECHECK isn't defined, - * since cgn->mtime is set to now in Make_Update. According to - * some people, this is good... - */ - Var_Append(OODATE, child, pgn); + (cgn->mtime >= now && cgn->made == MADE)) { + /* + * It goes in the OODATE variable if the parent is + * younger than the child or if the child has been + * modified more recently than the start of the make. + * This is to keep pmake from getting confused if + * something else updates the parent after the make + * starts (shouldn't happen, I know, but sometimes it + * does). In such a case, if we've updated the child, + * the parent is likely to have a modification time + * later than that of the child and anything that + * relies on the OODATE variable will be hosed. + * + * XXX: This will cause all made children to go in + * the OODATE variable, even if they're not touched, + * if RECHECK isn't defined, since cgn->mtime is set + * to now in Make_Update. According to some people, + * this is good... + */ + Var_Append(pgn, OODATE, child); } - if (p1) - free(p1); - } - return (0); } - -/*- - *----------------------------------------------------------------------- - * Make_DoAllVar -- - * Set up the ALLSRC and OODATE variables. Sad to say, it must be - * done separately, rather than while traversing the graph. This is - * because Make defined OODATE to contain all sources whose modification - * times were later than that of the target, *not* those sources that - * were out-of-date. Since in both compatibility and native modes, - * the modification time of the parent isn't found until the child - * has been dealt with, we have to wait until now to fill in the - * variable. As for ALLSRC, the ordering is important and not - * guaranteed when in native mode, so it must be set here, too. + +/* + * Set up the ALLSRC and OODATE variables. Sad to say, it must be + * done separately, rather than while traversing the graph. This is + * because Make defined OODATE to contain all sources whose modification + * times were later than that of the target, *not* those sources that + * were out-of-date. Since in both compatibility and native modes, + * the modification time of the parent isn't found until the child + * has been dealt with, we have to wait until now to fill in the + * variable. As for ALLSRC, the ordering is important and not + * guaranteed when in native mode, so it must be set here, too. * - * Results: - * None - * - * Side Effects: - * The ALLSRC and OODATE variables of the given node is filled in. - * If the node is a .JOIN node, its TARGET variable will be set to - * match its ALLSRC variable. - *----------------------------------------------------------------------- + * If the node is a .JOIN node, its TARGET variable will be set to + * match its ALLSRC variable. */ void Make_DoAllVar(GNode *gn) { - if (gn->flags & DONE_ALLSRC) - return; - - Lst_ForEach(gn->children, MakeUnmark, gn); - Lst_ForEach(gn->children, MakeAddAllSrc, gn); + GNodeListNode *ln; - if (!Var_Exists (OODATE, gn)) { - Var_Set(OODATE, "", gn, 0); - } - if (!Var_Exists (ALLSRC, gn)) { - Var_Set(ALLSRC, "", gn, 0); - } + if (gn->flags & DONE_ALLSRC) + return; - if (gn->type & OP_JOIN) { - char *p1; - Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0); - if (p1) - free(p1); - } - gn->flags |= DONE_ALLSRC; -} - -/*- - *----------------------------------------------------------------------- - * MakeStartJobs -- - * Start as many jobs as possible. - * - * Results: - * If the query flag was given to pmake, no job will be started, - * but as soon as an out-of-date target is found, this function - * returns TRUE. At all other times, this function returns FALSE. - * - * Side Effects: - * Nodes are removed from the toBeMade queue and job table slots - * are filled. - * - *----------------------------------------------------------------------- - */ + UnmarkChildren(gn); + for (ln = gn->children.first; ln != NULL; ln = ln->next) + MakeAddAllSrc(ln->datum, gn); -static int -MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) -{ - GNode *bn = v_bn; + if (!Var_Exists(gn, OODATE)) + Var_Set(gn, OODATE, ""); + if (!Var_Exists(gn, ALLSRC)) + Var_Set(gn, ALLSRC, ""); - if (bn->made >= MADE || !(bn->flags & REMAKE)) - return 0; - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n", - bn->name, bn->cohort_num); - return 1; -} - -static int -MakeBuildChild(void *v_cn, void *toBeMade_next) -{ - GNode *cn = v_cn; - - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n", - cn->name, cn->cohort_num, cn->made, cn->type); - if (cn->made > DEFERRED) - return 0; - - /* If this node is on the RHS of a .ORDER, check LHSs. */ - if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) { - /* Can't build this (or anything else in this child list) yet */ - cn->made = DEFERRED; - return 0; /* but keep looking */ - } - - if (DEBUG(MAKE)) - fprintf(debug_file, "MakeBuildChild: schedule %s%s\n", - cn->name, cn->cohort_num); - - cn->made = REQUESTED; - if (toBeMade_next == NULL) - Lst_AtEnd(toBeMade, cn); - else - Lst_InsertBefore(toBeMade, toBeMade_next, cn); - - if (cn->unmade_cohorts != 0) - Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next); - - /* - * If this node is a .WAIT node with unmade chlidren - * then don't add the next sibling. - */ - return cn->type & OP_WAIT && cn->unmade > 0; -} - -/* When a .ORDER LHS node completes we do this on each RHS */ -static int -MakeBuildParent(void *v_pn, void *toBeMade_next) -{ - GNode *pn = v_pn; - - if (pn->made != DEFERRED) - return 0; - - if (MakeBuildChild(pn, toBeMade_next) == 0) { - /* Mark so that when this node is built we reschedule its parents */ - pn->flags |= DONE_ORDER; - } - - return 0; + if (gn->type & OP_JOIN) + Var_Set(gn, TARGET, GNode_VarAllsrc(gn)); + gn->flags |= DONE_ALLSRC; } static Boolean -MakeStartJobs(void) +MakeBuildChild(GNode *cn, GNodeListNode *toBeMadeNext) { - GNode *gn; - int have_token = 0; - while (!Lst_IsEmpty (toBeMade)) { - /* Get token now to avoid cycling job-list when we only have 1 token */ - if (!have_token && !Job_TokenWithdraw()) - break; - have_token = 1; + if (DEBUG(MAKE)) { + debug_printf("MakeBuildChild: inspect %s%s, ", + cn->name, cn->cohort_num); + GNode_FprintDetails(opts.debug_file, "", cn, "\n"); + } + if (GNode_IsReady(cn)) + return FALSE; - gn = (GNode *)Lst_DeQueue(toBeMade); - if (DEBUG(MAKE)) - fprintf(debug_file, "Examining %s%s...\n", - gn->name, gn->cohort_num); - - if (gn->made != REQUESTED) { - if (DEBUG(MAKE)) - fprintf(debug_file, "state %d\n", gn->made); - - make_abort(gn, __LINE__); + /* If this node is on the RHS of a .ORDER, check LHSs. */ + if (IsWaitingForOrder(cn)) { + /* Can't build this (or anything else in this child list) yet */ + cn->made = DEFERRED; + return FALSE; /* but keep looking */ } - if (gn->checked == checked) { - /* We've already looked at this node since a job finished... */ - if (DEBUG(MAKE)) - fprintf(debug_file, "already checked %s%s\n", - gn->name, gn->cohort_num); - gn->made = DEFERRED; - continue; - } - gn->checked = checked; + DEBUG2(MAKE, "MakeBuildChild: schedule %s%s\n", + cn->name, cn->cohort_num); - if (gn->unmade != 0) { - /* - * We can't build this yet, add all unmade children to toBeMade, - * just before the current first element. - */ - gn->made = DEFERRED; - Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade)); - /* and drop this node on the floor */ - if (DEBUG(MAKE)) - fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num); - continue; - } + cn->made = REQUESTED; + if (toBeMadeNext == NULL) + Lst_Append(&toBeMade, cn); + else + Lst_InsertBefore(&toBeMade, toBeMadeNext, cn); - gn->made = BEINGMADE; - if (Make_OODate(gn)) { - if (DEBUG(MAKE)) { - fprintf(debug_file, "out-of-date\n"); - } - if (queryFlag) { - return (TRUE); - } - Make_DoAllVar(gn); - Job_Make(gn); - have_token = 0; - } else { - if (DEBUG(MAKE)) { - fprintf(debug_file, "up-to-date\n"); - } - gn->made = UPTODATE; - if (gn->type & OP_JOIN) { - /* - * Even for an up-to-date .JOIN node, we need it to have its - * context variables so references to it get the correct - * value for .TARGET when building up the context variables - * of its parent(s)... - */ - Make_DoAllVar(gn); - } - Make_Update(gn); - } - } + if (cn->unmade_cohorts != 0) { + ListNode *ln; - if (have_token) - Job_TokenReturn(); - - return (FALSE); -} - -/*- - *----------------------------------------------------------------------- - * MakePrintStatus -- - * Print the status of a top-level node, viz. it being up-to-date - * already or not created due to an error in a lower level. - * Callback function for Make_Run via Lst_ForEach. - * - * Input: - * gnp Node to examine - * cyclep True if gn->unmade being non-zero implies a - * cycle in the graph, not an error in an - * inferior. - * - * Results: - * Always returns 0. - * - * Side Effects: - * A message may be printed. - * - *----------------------------------------------------------------------- - */ -static int -MakePrintStatusOrder(void *ognp, void *gnp) -{ - GNode *ogn = ognp; - GNode *gn = gnp; - - if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED) - /* not waiting for this one */ - return 0; - - printf(" `%s%s' has .ORDER dependency against %s%s " - "(made %d, flags %x, type %x)\n", - gn->name, gn->cohort_num, - ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type); - if (DEBUG(MAKE) && debug_file != stdout) - fprintf(debug_file, " `%s%s' has .ORDER dependency against %s%s " - "(made %d, flags %x, type %x)\n", - gn->name, gn->cohort_num, - ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type); - return 0; -} - -static int -MakePrintStatus(void *gnp, void *v_errors) -{ - GNode *gn = (GNode *)gnp; - int *errors = v_errors; - - if (gn->flags & DONECYCLE) - /* We've completely processed this node before, don't do it again. */ - return 0; - - if (gn->unmade == 0) { - gn->flags |= DONECYCLE; - switch (gn->made) { - case UPTODATE: - printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num); - break; - case MADE: - break; - case UNMADE: - case DEFERRED: - case REQUESTED: - case BEINGMADE: - (*errors)++; - printf("`%s%s' was not built (made %d, flags %x, type %x)!\n", - gn->name, gn->cohort_num, gn->made, gn->flags, gn->type); - if (DEBUG(MAKE) && debug_file != stdout) - fprintf(debug_file, - "`%s%s' was not built (made %d, flags %x, type %x)!\n", - gn->name, gn->cohort_num, gn->made, gn->flags, gn->type); - /* Most likely problem is actually caused by .ORDER */ - Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn); - break; - default: - /* Errors - already counted */ - printf("`%s%s' not remade because of errors.\n", - gn->name, gn->cohort_num); - if (DEBUG(MAKE) && debug_file != stdout) - fprintf(debug_file, "`%s%s' not remade because of errors.\n", - gn->name, gn->cohort_num); - break; - } - return 0; - } - - if (DEBUG(MAKE)) - fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n", - gn->name, gn->cohort_num, gn->unmade); - /* - * If printing cycles and came to one that has unmade children, - * print out the cycle by recursing on its children. - */ - if (!(gn->flags & CYCLE)) { - /* Fist time we've seen this node, check all children */ - gn->flags |= CYCLE; - Lst_ForEach(gn->children, MakePrintStatus, errors); - /* Mark that this node needn't be processed again */ - gn->flags |= DONECYCLE; - return 0; - } - - /* Only output the error once per node */ - gn->flags |= DONECYCLE; - Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num); - if ((*errors)++ > 100) - /* Abandon the whole error report */ - return 1; - - /* Reporting for our children will give the rest of the loop */ - Lst_ForEach(gn->children, MakePrintStatus, errors); - return 0; -} - - -/*- - *----------------------------------------------------------------------- - * Make_ExpandUse -- - * Expand .USE nodes and create a new targets list - * - * Input: - * targs the initial list of targets - * - * Side Effects: - *----------------------------------------------------------------------- - */ -void -Make_ExpandUse(Lst targs) -{ - GNode *gn; /* a temporary pointer */ - Lst examine; /* List of targets to examine */ - - examine = Lst_Duplicate(targs, NULL); - - /* - * Make an initial downward pass over the graph, marking nodes to be made - * as we go down. We call Suff_FindDeps to find where a node is and - * to get some children for it if it has none and also has no commands. - * If the node is a leaf, we stick it on the toBeMade queue to - * be looked at in a minute, otherwise we add its children to our queue - * and go on about our business. - */ - while (!Lst_IsEmpty (examine)) { - gn = (GNode *)Lst_DeQueue(examine); - - if (gn->flags & REMAKE) - /* We've looked at this one already */ - continue; - gn->flags |= REMAKE; - if (DEBUG(MAKE)) - fprintf(debug_file, "Make_ExpandUse: examine %s%s\n", - gn->name, gn->cohort_num); - - if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) { - /* Append all the 'cohorts' to the list of things to examine */ - Lst new; - new = Lst_Duplicate(gn->cohorts, NULL); - Lst_Concat(new, examine, LST_CONCLINK); - examine = new; + for (ln = cn->cohorts.first; ln != NULL; ln = ln->next) + if (MakeBuildChild(ln->datum, toBeMadeNext)) + break; } /* - * Apply any .USE rules before looking for implicit dependencies - * to make sure everything has commands that should... - * Make sure that the TARGET is set, so that we can make - * expansions. + * If this node is a .WAIT node with unmade children + * then don't add the next sibling. */ - if (gn->type & OP_ARCHV) { - char *eoa, *eon; - eoa = strchr(gn->name, '('); - eon = strchr(gn->name, ')'); - if (eoa == NULL || eon == NULL) - continue; - *eoa = '\0'; - *eon = '\0'; - Var_Set(MEMBER, eoa + 1, gn, 0); - Var_Set(ARCHIVE, gn->name, gn, 0); - *eoa = '('; - *eon = ')'; - } - - (void)Dir_MTime(gn, 0); - Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); - Lst_ForEach(gn->children, MakeUnmark, gn); - Lst_ForEach(gn->children, MakeHandleUse, gn); - - if ((gn->type & OP_MADE) == 0) - Suff_FindDeps(gn); - else { - /* Pretend we made all this node's children */ - Lst_ForEach(gn->children, MakeFindChild, gn); - if (gn->unmade != 0) - printf("Warning: %s%s still has %d unmade children\n", - gn->name, gn->cohort_num, gn->unmade); - } - - if (gn->unmade != 0) - Lst_ForEach(gn->children, MakeAddChild, examine); - } - - Lst_Destroy(examine, NULL); + return cn->type & OP_WAIT && cn->unmade > 0; } -/*- - *----------------------------------------------------------------------- - * Make_ProcessWait -- - * Convert .WAIT nodes into dependencies - * - * Input: - * targs the initial list of targets - * - *----------------------------------------------------------------------- - */ - -static int -link_parent(void *cnp, void *pnp) +/* When a .ORDER LHS node completes, we do this on each RHS. */ +static void +MakeBuildParent(GNode *pn, GNodeListNode *toBeMadeNext) { - GNode *cn = cnp; - GNode *pn = pnp; + if (pn->made != DEFERRED) + return; - Lst_AtEnd(pn->children, cn); - Lst_AtEnd(cn->parents, pn); - pn->unmade++; - return 0; -} - -static int -add_wait_dep(void *v_cn, void *v_wn) -{ - GNode *cn = v_cn; - GNode *wn = v_wn; - - if (cn == wn) - return 1; - - if (cn == NULL || wn == NULL) { - printf("bad wait dep %p %p\n", cn, wn); - exit(4); - } - if (DEBUG(MAKE)) - fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n", - cn->name, cn->cohort_num, wn->name); - - Lst_AtEnd(wn->children, cn); - wn->unmade++; - Lst_AtEnd(cn->parents, wn); - return 0; + if (!MakeBuildChild(pn, toBeMadeNext)) { + /* When this node is built, reschedule its parents. */ + pn->flags |= DONE_ORDER; + } } static void -Make_ProcessWait(Lst targs) +MakeChildren(GNode *gn) { - GNode *pgn; /* 'parent' node we are examining */ - GNode *cgn; /* Each child in turn */ - LstNode owln; /* Previous .WAIT node */ - Lst examine; /* List of targets to examine */ - LstNode ln; + GNodeListNode *toBeMadeNext = toBeMade.first; + GNodeListNode *ln; - /* - * We need all the nodes to have a common parent in order for the - * .WAIT and .ORDER scheduling to work. - * Perhaps this should be done earlier... - */ - - pgn = Targ_NewGN(".MAIN"); - pgn->flags = REMAKE; - pgn->type = OP_PHONY | OP_DEPENDS; - /* Get it displayed in the diag dumps */ - Lst_AtFront(Targ_List(), pgn); - - Lst_ForEach(targs, link_parent, pgn); - - /* Start building with the 'dummy' .MAIN' node */ - MakeBuildChild(pgn, NULL); - - examine = Lst_Init(FALSE); - Lst_AtEnd(examine, pgn); - - while (!Lst_IsEmpty (examine)) { - pgn = Lst_DeQueue(examine); - - /* We only want to process each child-list once */ - if (pgn->flags & DONE_WAIT) - continue; - pgn->flags |= DONE_WAIT; - if (DEBUG(MAKE)) - fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name); - - if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) { - /* Append all the 'cohorts' to the list of things to examine */ - Lst new; - new = Lst_Duplicate(pgn->cohorts, NULL); - Lst_Concat(new, examine, LST_CONCLINK); - examine = new; - } - - owln = Lst_First(pgn->children); - Lst_Open(pgn->children); - for (; (ln = Lst_Next(pgn->children)) != NULL; ) { - cgn = Lst_Datum(ln); - if (cgn->type & OP_WAIT) { - /* Make the .WAIT node depend on the previous children */ - Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn); - owln = ln; - } else { - Lst_AtEnd(examine, cgn); - } - } - Lst_Close(pgn->children); - } - - Lst_Destroy(examine, NULL); + for (ln = gn->children.first; ln != NULL; ln = ln->next) + if (MakeBuildChild(ln->datum, toBeMadeNext)) + break; } -/*- - *----------------------------------------------------------------------- - * Make_Run -- - * Initialize the nodes to remake and the list of nodes which are - * ready to be made by doing a breadth-first traversal of the graph - * starting from the nodes in the given list. Once this traversal - * is finished, all the 'leaves' of the graph are in the toBeMade - * queue. - * Using this queue and the Job module, work back up the graph, - * calling on MakeStartJobs to keep the job table as full as - * possible. +/* + * Start as many jobs as possible, taking them from the toBeMade queue. + * + * If the -q option was given, no job will be started, + * but as soon as an out-of-date target is found, this function + * returns TRUE. In all other cases, this function returns FALSE. + */ +static Boolean +MakeStartJobs(void) +{ + GNode *gn; + Boolean have_token = FALSE; + + while (!Lst_IsEmpty(&toBeMade)) { + /* + * Get token now to avoid cycling job-list when we only + * have 1 token + */ + if (!have_token && !Job_TokenWithdraw()) + break; + have_token = TRUE; + + gn = Lst_Dequeue(&toBeMade); + DEBUG2(MAKE, "Examining %s%s...\n", gn->name, gn->cohort_num); + + if (gn->made != REQUESTED) { + /* + * XXX: Replace %d with string representation; + * see made_name. + */ + DEBUG1(MAKE, "state %d\n", gn->made); + + make_abort(gn, __LINE__); + } + + if (gn->checked_seqno == checked_seqno) { + /* + * We've already looked at this node since a job + * finished... + */ + DEBUG2(MAKE, "already checked %s%s\n", gn->name, + gn->cohort_num); + gn->made = DEFERRED; + continue; + } + gn->checked_seqno = checked_seqno; + + if (gn->unmade != 0) { + /* + * We can't build this yet, add all unmade children + * to toBeMade, just before the current first element. + */ + gn->made = DEFERRED; + + MakeChildren(gn); + + /* and drop this node on the floor */ + DEBUG2(MAKE, "dropped %s%s\n", gn->name, + gn->cohort_num); + continue; + } + + gn->made = BEINGMADE; + if (GNode_IsOODate(gn)) { + DEBUG0(MAKE, "out-of-date\n"); + if (opts.queryFlag) + return TRUE; + Make_DoAllVar(gn); + Job_Make(gn); + have_token = FALSE; + } else { + DEBUG0(MAKE, "up-to-date\n"); + gn->made = UPTODATE; + if (gn->type & OP_JOIN) { + /* + * Even for an up-to-date .JOIN node, we + * need it to have its local variables so + * references to it get the correct value + * for .TARGET when building up the local + * variables of its parent(s)... + */ + Make_DoAllVar(gn); + } + Make_Update(gn); + } + } + + if (have_token) + Job_TokenReturn(); + + return FALSE; +} + +/* Print the status of a .ORDER node. */ +static void +MakePrintStatusOrderNode(GNode *ogn, GNode *gn) +{ + if (!GNode_IsWaitingFor(ogn)) + return; + + printf(" `%s%s' has .ORDER dependency against %s%s ", + gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); + GNode_FprintDetails(stdout, "(", ogn, ")\n"); + + if (DEBUG(MAKE) && opts.debug_file != stdout) { + debug_printf(" `%s%s' has .ORDER dependency against %s%s ", + gn->name, gn->cohort_num, ogn->name, ogn->cohort_num); + GNode_FprintDetails(opts.debug_file, "(", ogn, ")\n"); + } +} + +static void +MakePrintStatusOrder(GNode *gn) +{ + GNodeListNode *ln; + for (ln = gn->order_pred.first; ln != NULL; ln = ln->next) + MakePrintStatusOrderNode(ln->datum, gn); +} + +static void MakePrintStatusList(GNodeList *, int *); + +/* + * Print the status of a top-level node, viz. it being up-to-date already + * or not created due to an error in a lower level. + */ +static Boolean +MakePrintStatus(GNode *gn, int *errors) +{ + if (gn->flags & DONECYCLE) { + /* + * We've completely processed this node before, don't do + * it again. + */ + return FALSE; + } + + if (gn->unmade == 0) { + gn->flags |= DONECYCLE; + switch (gn->made) { + case UPTODATE: + printf("`%s%s' is up to date.\n", gn->name, + gn->cohort_num); + break; + case MADE: + break; + case UNMADE: + case DEFERRED: + case REQUESTED: + case BEINGMADE: + (*errors)++; + printf("`%s%s' was not built", gn->name, + gn->cohort_num); + GNode_FprintDetails(stdout, " (", gn, ")!\n"); + if (DEBUG(MAKE) && opts.debug_file != stdout) { + debug_printf("`%s%s' was not built", gn->name, + gn->cohort_num); + GNode_FprintDetails(opts.debug_file, " (", gn, + ")!\n"); + } + /* Most likely problem is actually caused by .ORDER */ + MakePrintStatusOrder(gn); + break; + default: + /* Errors - already counted */ + printf("`%s%s' not remade because of errors.\n", + gn->name, gn->cohort_num); + if (DEBUG(MAKE) && opts.debug_file != stdout) + debug_printf( + "`%s%s' not remade because of errors.\n", + gn->name, gn->cohort_num); + break; + } + return FALSE; + } + + DEBUG3(MAKE, "MakePrintStatus: %s%s has %d unmade children\n", + gn->name, gn->cohort_num, gn->unmade); + /* + * If printing cycles and came to one that has unmade children, + * print out the cycle by recursing on its children. + */ + if (!(gn->flags & CYCLE)) { + /* First time we've seen this node, check all children */ + gn->flags |= CYCLE; + MakePrintStatusList(&gn->children, errors); + /* Mark that this node needn't be processed again */ + gn->flags |= DONECYCLE; + return FALSE; + } + + /* Only output the error once per node */ + gn->flags |= DONECYCLE; + Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num); + if ((*errors)++ > 100) + /* Abandon the whole error report */ + return TRUE; + + /* Reporting for our children will give the rest of the loop */ + MakePrintStatusList(&gn->children, errors); + return FALSE; +} + +static void +MakePrintStatusList(GNodeList *gnodes, int *errors) +{ + GNodeListNode *ln; + + for (ln = gnodes->first; ln != NULL; ln = ln->next) + if (MakePrintStatus(ln->datum, errors)) + break; +} + +static void +ExamineLater(GNodeList *examine, GNodeList *toBeExamined) +{ + ListNode *ln; + + for (ln = toBeExamined->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + + if (gn->flags & REMAKE) + continue; + if (gn->type & (OP_USE | OP_USEBEFORE)) + continue; + + DEBUG2(MAKE, "ExamineLater: need to examine \"%s%s\"\n", + gn->name, gn->cohort_num); + Lst_Enqueue(examine, gn); + } +} + +/* + * Expand .USE nodes and create a new targets list. + * + * Input: + * targs the initial list of targets + */ +void +Make_ExpandUse(GNodeList *targs) +{ + GNodeList examine = LST_INIT; /* Queue of targets to examine */ + Lst_AppendAll(&examine, targs); + + /* + * Make an initial downward pass over the graph, marking nodes to + * be made as we go down. + * + * We call Suff_FindDeps to find where a node is and to get some + * children for it if it has none and also has no commands. If the + * node is a leaf, we stick it on the toBeMade queue to be looked + * at in a minute, otherwise we add its children to our queue and + * go on about our business. + */ + while (!Lst_IsEmpty(&examine)) { + GNode *gn = Lst_Dequeue(&examine); + + if (gn->flags & REMAKE) + /* We've looked at this one already */ + continue; + gn->flags |= REMAKE; + DEBUG2(MAKE, "Make_ExpandUse: examine %s%s\n", + gn->name, gn->cohort_num); + + if (gn->type & OP_DOUBLEDEP) + Lst_PrependAll(&examine, &gn->cohorts); + + /* + * Apply any .USE rules before looking for implicit + * dependencies to make sure everything has commands that + * should. + * + * Make sure that the TARGET is set, so that we can make + * expansions. + */ + if (gn->type & OP_ARCHV) { + char *eoa = strchr(gn->name, '('); + char *eon = strchr(gn->name, ')'); + if (eoa == NULL || eon == NULL) + continue; + *eoa = '\0'; + *eon = '\0'; + Var_Set(gn, MEMBER, eoa + 1); + Var_Set(gn, ARCHIVE, gn->name); + *eoa = '('; + *eon = ')'; + } + + Dir_UpdateMTime(gn, FALSE); + Var_Set(gn, TARGET, GNode_Path(gn)); + UnmarkChildren(gn); + HandleUseNodes(gn); + + if (!(gn->type & OP_MADE)) + Suff_FindDeps(gn); + else { + PretendAllChildrenAreMade(gn); + if (gn->unmade != 0) { + printf( + "Warning: " + "%s%s still has %d unmade children\n", + gn->name, gn->cohort_num, gn->unmade); + } + } + + if (gn->unmade != 0) + ExamineLater(&examine, &gn->children); + } + + Lst_Done(&examine); +} + +/* Make the .WAIT node depend on the previous children */ +static void +add_wait_dependency(GNodeListNode *owln, GNode *wn) +{ + GNodeListNode *cln; + GNode *cn; + + for (cln = owln; (cn = cln->datum) != wn; cln = cln->next) { + DEBUG3(MAKE, ".WAIT: add dependency %s%s -> %s\n", + cn->name, cn->cohort_num, wn->name); + + /* XXX: This pattern should be factored out, it repeats often */ + Lst_Append(&wn->children, cn); + wn->unmade++; + Lst_Append(&cn->parents, wn); + } +} + +/* Convert .WAIT nodes into dependencies. */ +static void +Make_ProcessWait(GNodeList *targs) +{ + GNode *pgn; /* 'parent' node we are examining */ + GNodeListNode *owln; /* Previous .WAIT node */ + GNodeList examine; /* List of targets to examine */ + + /* + * We need all the nodes to have a common parent in order for the + * .WAIT and .ORDER scheduling to work. + * Perhaps this should be done earlier... + */ + + pgn = GNode_New(".MAIN"); + pgn->flags = REMAKE; + pgn->type = OP_PHONY | OP_DEPENDS; + /* Get it displayed in the diag dumps */ + Lst_Prepend(Targ_List(), pgn); + + { + GNodeListNode *ln; + for (ln = targs->first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; + + Lst_Append(&pgn->children, cgn); + Lst_Append(&cgn->parents, pgn); + pgn->unmade++; + } + } + + /* Start building with the 'dummy' .MAIN' node */ + MakeBuildChild(pgn, NULL); + + Lst_Init(&examine); + Lst_Append(&examine, pgn); + + while (!Lst_IsEmpty(&examine)) { + GNodeListNode *ln; + + pgn = Lst_Dequeue(&examine); + + /* We only want to process each child-list once */ + if (pgn->flags & DONE_WAIT) + continue; + pgn->flags |= DONE_WAIT; + DEBUG1(MAKE, "Make_ProcessWait: examine %s\n", pgn->name); + + if (pgn->type & OP_DOUBLEDEP) + Lst_PrependAll(&examine, &pgn->cohorts); + + owln = pgn->children.first; + for (ln = pgn->children.first; ln != NULL; ln = ln->next) { + GNode *cgn = ln->datum; + if (cgn->type & OP_WAIT) { + add_wait_dependency(owln, cgn); + owln = ln; + } else { + Lst_Append(&examine, cgn); + } + } + } + + Lst_Done(&examine); +} + +/* + * Initialize the nodes to remake and the list of nodes which are ready to + * be made by doing a breadth-first traversal of the graph starting from the + * nodes in the given list. Once this traversal is finished, all the 'leaves' + * of the graph are in the toBeMade queue. + * + * Using this queue and the Job module, work back up the graph, calling on + * MakeStartJobs to keep the job table as full as possible. * * Input: * targs the initial list of targets @@ -1491,71 +1370,70 @@ Make_ProcessWait(Lst targs) * The make field of all nodes involved in the creation of the given * targets is set to 1. The toBeMade list is set to contain all the * 'leaves' of these subgraphs. - *----------------------------------------------------------------------- */ Boolean -Make_Run(Lst targs) +Make_Run(GNodeList *targs) { - int errors; /* Number of errors the Job module reports */ + int errors; /* Number of errors the Job module reports */ - /* Start trying to make the current targets... */ - toBeMade = Lst_Init(FALSE); + /* Start trying to make the current targets... */ + Lst_Init(&toBeMade); - Make_ExpandUse(targs); - Make_ProcessWait(targs); + Make_ExpandUse(targs); + Make_ProcessWait(targs); - if (DEBUG(MAKE)) { - fprintf(debug_file, "#***# full graph\n"); - Targ_PrintGraph(1); - } - - if (queryFlag) { - /* - * We wouldn't do any work unless we could start some jobs in the - * next loop... (we won't actually start any, of course, this is just - * to see if any of the targets was out of date) - */ - return (MakeStartJobs()); - } - /* - * Initialization. At the moment, no jobs are running and until some - * get started, nothing will happen since the remaining upward - * traversal of the graph is performed by the routines in job.c upon - * the finishing of a job. So we fill the Job table as much as we can - * before going into our loop. - */ - (void)MakeStartJobs(); - - /* - * Main Loop: The idea here is that the ending of jobs will take - * care of the maintenance of data structures and the waiting for output - * will cause us to be idle most of the time while our children run as - * much as possible. Because the job table is kept as full as possible, - * the only time when it will be empty is when all the jobs which need - * running have been run, so that is the end condition of this loop. - * Note that the Job module will exit if there were any errors unless the - * keepgoing flag was given. - */ - while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) { - Job_CatchOutput(); - (void)MakeStartJobs(); - } - - errors = Job_Finish(); - - /* - * Print the final status of each target. E.g. if it wasn't made - * because some inferior reported an error. - */ - if (DEBUG(MAKE)) - fprintf(debug_file, "done: errors %d\n", errors); - if (errors == 0) { - Lst_ForEach(targs, MakePrintStatus, &errors); if (DEBUG(MAKE)) { - fprintf(debug_file, "done: errors %d\n", errors); - if (errors) - Targ_PrintGraph(4); + debug_printf("#***# full graph\n"); + Targ_PrintGraph(1); } - } - return errors != 0; + + if (opts.queryFlag) { + /* + * We wouldn't do any work unless we could start some jobs + * in the next loop... (we won't actually start any, of + * course, this is just to see if any of the targets was out + * of date) + */ + return MakeStartJobs(); + } + /* + * Initialization. At the moment, no jobs are running and until some + * get started, nothing will happen since the remaining upward + * traversal of the graph is performed by the routines in job.c upon + * the finishing of a job. So we fill the Job table as much as we can + * before going into our loop. + */ + (void)MakeStartJobs(); + + /* + * Main Loop: The idea here is that the ending of jobs will take + * care of the maintenance of data structures and the waiting for + * output will cause us to be idle most of the time while our + * children run as much as possible. Because the job table is kept + * as full as possible, the only time when it will be empty is when + * all the jobs which need running have been run, so that is the end + * condition of this loop. Note that the Job module will exit if + * there were any errors unless the keepgoing flag was given. + */ + while (!Lst_IsEmpty(&toBeMade) || jobTokensRunning > 0) { + Job_CatchOutput(); + (void)MakeStartJobs(); + } + + errors = Job_Finish(); + + /* + * Print the final status of each target. E.g. if it wasn't made + * because some inferior reported an error. + */ + DEBUG1(MAKE, "done: errors %d\n", errors); + if (errors == 0) { + MakePrintStatusList(targs, &errors); + if (DEBUG(MAKE)) { + debug_printf("done: errors %d\n", errors); + if (errors > 0) + Targ_PrintGraph(4); + } + } + return errors > 0; } diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h index f162ef670..646673778 100644 --- a/usr.bin/make/make.h +++ b/usr.bin/make/make.h @@ -1,4 +1,4 @@ -/* $NetBSD: make.h,v 1.96 2015/09/21 21:50:16 pooka Exp $ */ +/* $NetBSD: make.h,v 1.256 2021/02/05 19:19:17 sjg Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -72,18 +72,22 @@ * from: @(#)make.h 8.3 (Berkeley) 6/13/95 */ -/*- +/* * make.h -- * The global definitions for pmake */ -#ifndef _MAKE_H_ -#define _MAKE_H_ +#ifndef MAKE_MAKE_H +#define MAKE_MAKE_H #include #include +#include +#include #include +#include +#include #include #include #include @@ -93,26 +97,30 @@ # include #endif +#ifndef FD_CLOEXEC +#define FD_CLOEXEC 1 +#endif + #if defined(__GNUC__) -#define MAKE_GNUC_PREREQ(x, y) \ +#define MAKE_GNUC_PREREQ(x, y) \ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \ (__GNUC__ > (x))) #else /* defined(__GNUC__) */ -#define MAKE_GNUC_PREREQ(x, y) 0 +#define MAKE_GNUC_PREREQ(x, y) 0 #endif /* defined(__GNUC__) */ #if MAKE_GNUC_PREREQ(2, 7) -#define MAKE_ATTR_UNUSED __attribute__((__unused__)) +#define MAKE_ATTR_UNUSED __attribute__((__unused__)) #else -#define MAKE_ATTR_UNUSED /* delete */ +#define MAKE_ATTR_UNUSED /* delete */ #endif #if MAKE_GNUC_PREREQ(2, 5) -#define MAKE_ATTR_DEAD __attribute__((__noreturn__)) +#define MAKE_ATTR_DEAD __attribute__((__noreturn__)) #elif defined(__GNUC__) -#define MAKE_ATTR_DEAD __volatile +#define MAKE_ATTR_DEAD __volatile #else -#define MAKE_ATTR_DEAD /* delete */ +#define MAKE_ATTR_DEAD /* delete */ #endif #if MAKE_GNUC_PREREQ(2, 7) @@ -122,371 +130,643 @@ #define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ #endif -#include "sprite.h" +#define MAKE_INLINE static inline MAKE_ATTR_UNUSED + +/* + * A boolean type is defined as an integer, not an enum, for historic reasons. + * The only allowed values are the constants TRUE and FALSE (1 and 0). + */ +#if defined(lint) || defined(USE_C99_BOOLEAN) +#include +typedef bool Boolean; +#define FALSE false +#define TRUE true +#elif defined(USE_DOUBLE_BOOLEAN) +/* During development, to find type mismatches in function declarations. */ +typedef double Boolean; +#define TRUE 1.0 +#define FALSE 0.0 +#elif defined(USE_UCHAR_BOOLEAN) +/* + * During development, to find code that depends on the exact value of TRUE or + * that stores other values in Boolean variables. + */ +typedef unsigned char Boolean; +#define TRUE ((unsigned char)0xFF) +#define FALSE ((unsigned char)0x00) +#elif defined(USE_CHAR_BOOLEAN) +/* + * During development, to find code that uses a boolean as array index, via + * -Wchar-subscripts. + */ +typedef char Boolean; +#define TRUE ((char)-1) +#define FALSE ((char)0x00) +#elif defined(USE_ENUM_BOOLEAN) +typedef enum Boolean { FALSE, TRUE } Boolean; +#else +typedef int Boolean; +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#endif + #include "lst.h" +#include "enum.h" #include "hash.h" #include "config.h" #include "buf.h" #include "make_malloc.h" -/*- - * The structure for an individual graph node. Each node has several - * pieces of data associated with it. - * 1) the name of the target it describes - * 2) the location of the target file in the file system. - * 3) the type of operator used to define its sources (qv. parse.c) - * 4) whether it is involved in this invocation of make - * 5) whether the target has been remade - * 6) whether any of its children has been remade - * 7) the number of its children that are, as yet, unmade - * 8) its modification time - * 9) the modification time of its youngest child (qv. make.c) - * 10) a list of nodes for which this is a source (parents) - * 11) a list of nodes on which this depends (children) - * 12) a list of nodes that depend on this, as gleaned from the - * transformation rules (iParents) - * 13) a list of ancestor nodes, which includes parents, iParents, - * and recursive parents of parents - * 14) a list of nodes of the same name created by the :: operator - * 15) a list of nodes that must be made (if they're made) before - * this node can be, but that do not enter into the datedness of - * this node. - * 16) a list of nodes that must be made (if they're made) before - * this node or any child of this node can be, but that do not - * enter into the datedness of this node. - * 17) a list of nodes that must be made (if they're made) after - * this node is, but that do not depend on this node, in the - * normal sense. - * 18) a Lst of ``local'' variables that are specific to this target - * and this target only (qv. var.c [$@ $< $?, etc.]) - * 19) a Lst of strings that are commands to be given to a shell - * to create this target. +/* + * The typical flow of states is: + * + * The direct successful path: + * UNMADE -> BEINGMADE -> MADE. + * + * The direct error path: + * UNMADE -> BEINGMADE -> ERROR. + * + * The successful path when dependencies need to be made first: + * UNMADE -> DEFERRED -> REQUESTED -> BEINGMADE -> MADE. + * + * A node that has dependencies, and one of the dependencies cannot be made: + * UNMADE -> DEFERRED -> ABORTED. + * + * A node that turns out to be up-to-date: + * UNMADE -> BEINGMADE -> UPTODATE. */ -typedef struct GNode { - char *name; /* The target's name */ - char *uname; /* The unexpanded name of a .USE node */ - char *path; /* The full pathname of the file */ - int type; /* Its type (see the OP flags, below) */ +typedef enum GNodeMade { + /* Not examined yet. */ + UNMADE, + /* The node has been examined but is not yet ready since its + * dependencies have to be made first. */ + DEFERRED, - int flags; -#define REMAKE 0x1 /* this target needs to be (re)made */ -#define CHILDMADE 0x2 /* children of this target were made */ -#define FORCE 0x4 /* children don't exist, and we pretend made */ -#define DONE_WAIT 0x8 /* Set by Make_ProcessWait() */ -#define DONE_ORDER 0x10 /* Build requested by .ORDER processing */ -#define FROM_DEPEND 0x20 /* Node created from .depend */ -#define DONE_ALLSRC 0x40 /* We do it once only */ -#define CYCLE 0x1000 /* Used by MakePrintStatus */ -#define DONECYCLE 0x2000 /* Used by MakePrintStatus */ - enum enum_made { - UNMADE, DEFERRED, REQUESTED, BEINGMADE, - MADE, UPTODATE, ERROR, ABORTED - } made; /* Set to reflect the state of processing - * on this node: - * UNMADE - Not examined yet - * DEFERRED - Examined once (building child) - * REQUESTED - on toBeMade list - * BEINGMADE - Target is already being made. - * Indicates a cycle in the graph. - * MADE - Was out-of-date and has been made - * UPTODATE - Was already up-to-date - * ERROR - An error occurred while it was being - * made (used only in compat mode) - * ABORTED - The target was aborted due to - * an error making an inferior (compat). - */ - int unmade; /* The number of unmade children */ + /* The node is on the toBeMade list. */ + REQUESTED, - time_t mtime; /* Its modification time */ - struct GNode *cmgn; /* The youngest child */ + /* The node is already being made. Trying to build a node in this + * state indicates a cycle in the graph. */ + BEINGMADE, - Lst iParents; /* Links to parents for which this is an - * implied source, if any */ - Lst cohorts; /* Other nodes for the :: operator */ - Lst parents; /* Nodes that depend on this one */ - Lst children; /* Nodes on which this one depends */ - Lst order_pred; /* .ORDER nodes we need made */ - Lst order_succ; /* .ORDER nodes who need us */ - - char cohort_num[8]; /* #n for this cohort */ - int unmade_cohorts;/* # of unmade instances on the - cohorts list */ - struct GNode *centurion; /* Pointer to the first instance of a :: - node; only set when on a cohorts list */ - unsigned int checked; /* Last time we tried to makle this node */ - - Hash_Table context; /* The local variables */ - Lst commands; /* Creation commands */ - - struct _Suff *suffix; /* Suffix for the node (determined by - * Suff_FindDeps and opaque to everyone - * but the Suff module) */ - const char *fname; /* filename where the GNode got defined */ - int lineno; /* line number where the GNode got defined */ -} GNode; + /* Was out-of-date and has been made. */ + MADE, + /* Was already up-to-date, does not need to be made. */ + UPTODATE, + /* An error occurred while it was being made. + * Used only in compat mode. */ + ERROR, + /* The target was aborted due to an error making a dependency. + * Used only in compat mode. */ + ABORTED +} GNodeMade; /* * The OP_ constants are used when parsing a dependency line as a way of * communicating to other parts of the program the way in which a target - * should be made. These constants are bitwise-OR'ed together and - * placed in the 'type' field of each node. Any node that has - * a 'type' field which satisfies the OP_NOP function was never never on - * the lefthand side of an operator, though it may have been on the - * righthand side... + * should be made. + * + * Some of the OP_ constants can be combined, others cannot. */ -#define OP_DEPENDS 0x00000001 /* Execution of commands depends on - * kids (:) */ -#define OP_FORCE 0x00000002 /* Always execute commands (!) */ -#define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids - * per line (::) */ -#define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP) +typedef enum GNodeType { + OP_NONE = 0, -#define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't - * exist and can't be created */ -#define OP_USE 0x00000010 /* Use associated commands for parents */ -#define OP_EXEC 0x00000020 /* Target is never out of date, but always - * execute commands anyway. Its time - * doesn't matter, so it has none...sort - * of */ -#define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */ -#define OP_PRECIOUS 0x00000080 /* Don't remove the target when - * interrupted */ -#define OP_SILENT 0x00000100 /* Don't echo commands when executed */ -#define OP_MAKE 0x00000200 /* Target is a recursive make so its - * commands should always be executed when - * it is out of date, regardless of the - * state of the -n or -t flags */ -#define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its - * children was out-of-date */ -#define OP_MADE 0x00000800 /* Assume the children of the node have - * been already made */ -#define OP_SPECIAL 0x00001000 /* Special .BEGIN, .END, .INTERRUPT */ -#define OP_USEBEFORE 0x00002000 /* Like .USE, only prepend commands */ -#define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents. - * I.e. it doesn't show up in the parents's - * local variables. */ -#define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main - * target' processing in parse.c */ -#define OP_PHONY 0x00010000 /* Not a file target; run always */ -#define OP_NOPATH 0x00020000 /* Don't search for file in the path */ -#define OP_WAIT 0x00040000 /* .WAIT phony node */ -#define OP_NOMETA 0x00080000 /* .NOMETA do not create a .meta file */ -#define OP_META 0x00100000 /* .META we _do_ want a .meta file */ -#define OP_NOMETA_CMP 0x00200000 /* Do not compare commands in .meta file */ -#define OP_SUBMAKE 0x00400000 /* Possibly a submake node */ -/* Attributes applied by PMake */ -#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */ -#define OP_MEMBER 0x40000000 /* Target is a member of an archive */ -#define OP_LIB 0x20000000 /* Target is a library */ -#define OP_ARCHV 0x10000000 /* Target is an archive construct */ -#define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should. - * Used when parsing to catch multiple - * commands for a target */ -#define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */ -#define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */ -#define OP_MARK 0x01000000 /* Node found while expanding .ALLSRC */ + /* The dependency operator ':' is the most common one. The commands + * of this node are executed if any child is out-of-date. */ + OP_DEPENDS = 1 << 0, + /* The dependency operator '!' always executes its commands, even if + * its children are up-to-date. */ + OP_FORCE = 1 << 1, + /* The dependency operator '::' behaves like ':', except that it + * allows multiple dependency groups to be defined. Each of these + * groups is executed on its own, independently from the others. + * Each individual dependency group is called a cohort. */ + OP_DOUBLEDEP = 1 << 2, -#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute) -/* - * OP_NOP will return TRUE if the node with the given type was not the - * object of a dependency operator - */ -#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) + /* Matches the dependency operators ':', '!' and '::'. */ + OP_OPMASK = OP_DEPENDS | OP_FORCE | OP_DOUBLEDEP, -#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM) + /* Don't care if the target doesn't exist and can't be created. */ + OP_OPTIONAL = 1 << 3, + /* Use associated commands for parents. */ + OP_USE = 1 << 4, + /* Target is never out of date, but always execute commands anyway. + * Its time doesn't matter, so it has none...sort of. */ + OP_EXEC = 1 << 5, + /* Ignore non-zero exit status from shell commands when creating the + * node. */ + OP_IGNORE = 1 << 6, + /* Don't remove the target when interrupted. */ + OP_PRECIOUS = 1 << 7, + /* Don't echo commands when executed. */ + OP_SILENT = 1 << 8, + /* Target is a recursive make so its commands should always be + * executed when it is out of date, regardless of the state of the + * -n or -t flags. */ + OP_MAKE = 1 << 9, + /* Target is out-of-date only if any of its children was out-of-date. */ + OP_JOIN = 1 << 10, + /* Assume the children of the node have been already made. */ + OP_MADE = 1 << 11, + /* Special .BEGIN, .END or .INTERRUPT. */ + OP_SPECIAL = 1 << 12, + /* Like .USE, only prepend commands. */ + OP_USEBEFORE = 1 << 13, + /* The node is invisible to its parents. I.e. it doesn't show up in + * the parents' local variables (.IMPSRC, .ALLSRC). */ + OP_INVISIBLE = 1 << 14, + /* The node does not become the main target, even if it is the first + * target in the first makefile. */ + OP_NOTMAIN = 1 << 15, + /* Not a file target; run always. */ + OP_PHONY = 1 << 16, + /* Don't search for the file in the path. */ + OP_NOPATH = 1 << 17, + /* In a dependency line "target: source1 .WAIT source2", source1 is + * made first, including its children. Once that is finished, + * source2 is made, including its children. The .WAIT keyword may + * appear more than once in a single dependency declaration. */ + OP_WAIT = 1 << 18, + /* .NOMETA do not create a .meta file */ + OP_NOMETA = 1 << 19, + /* .META we _do_ want a .meta file */ + OP_META = 1 << 20, + /* Do not compare commands in .meta file */ + OP_NOMETA_CMP = 1 << 21, + /* Possibly a submake node */ + OP_SUBMAKE = 1 << 22, + + /* Attributes applied by PMake */ + + /* The node is a transformation rule, such as ".c.o". */ + OP_TRANSFORM = 1 << 30, + /* Target is a member of an archive */ + /* XXX: How does this differ from OP_ARCHV? */ + OP_MEMBER = 1 << 29, + /* The node is a library, + * its name has the form "-l" */ + OP_LIB = 1 << 28, + /* The node is an archive member, + * its name has the form "archive(member)" */ + /* XXX: How does this differ from OP_MEMBER? */ + OP_ARCHV = 1 << 27, + /* Target has all the commands it should. Used when parsing to catch + * multiple command groups for a target. Only applies to the + * dependency operators ':' and '!', but not to '::'. */ + OP_HAS_COMMANDS = 1 << 26, + /* The special command "..." has been seen. All further commands from + * this node will be saved on the .END node instead, to be executed at + * the very end. */ + OP_SAVE_CMDS = 1 << 25, + /* Already processed by Suff_FindDeps, to find dependencies from + * suffix transformation rules. */ + OP_DEPS_FOUND = 1 << 24, + /* Node found while expanding .ALLSRC */ + OP_MARK = 1 << 23, + + OP_NOTARGET = OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM +} GNodeType; + +typedef enum GNodeFlags { + GNF_NONE = 0, + /* this target needs to be (re)made */ + REMAKE = 1 << 0, + /* children of this target were made */ + CHILDMADE = 1 << 1, + /* children don't exist, and we pretend made */ + FORCE = 1 << 2, + /* Set by Make_ProcessWait() */ + DONE_WAIT = 1 << 3, + /* Build requested by .ORDER processing */ + DONE_ORDER = 1 << 4, + /* Node created from .depend */ + FROM_DEPEND = 1 << 5, + /* We do it once only */ + DONE_ALLSRC = 1 << 6, + /* Used by MakePrintStatus */ + CYCLE = 1 << 12, + /* Used by MakePrintStatus */ + DONECYCLE = 1 << 13 +} GNodeFlags; + +typedef struct List StringList; +typedef struct ListNode StringListNode; + +typedef struct List GNodeList; +typedef struct ListNode GNodeListNode; + +typedef struct SearchPath { + List /* of CachedDir */ dirs; +} SearchPath; /* - * The TARG_ constants are used when calling the Targ_FindNode and - * Targ_FindList functions in targ.c. They simply tell the functions what to - * do if the desired node(s) is (are) not found. If the TARG_CREATE constant - * is given, a new, empty node will be created for the target, placed in the - * table of all targets and its address returned. If TARG_NOCREATE is given, - * a NULL pointer will be returned. + * A graph node represents a target that can possibly be made, including its + * relation to other targets and a lot of other details. */ -#define TARG_NOCREATE 0x00 /* don't create it */ -#define TARG_CREATE 0x01 /* create node if not found */ -#define TARG_NOHASH 0x02 /* don't look in/add to hash table */ +typedef struct GNode { + /* The target's name, such as "clean" or "make.c" */ + char *name; + /* The unexpanded name of a .USE node */ + char *uname; + /* The full pathname of the file belonging to the target. + * XXX: What about .PHONY targets? These don't have an associated + * path. */ + char *path; + + /* The type of operator used to define the sources (see the OP flags + * below). + * XXX: This looks like a wild mixture of type and flags. */ + GNodeType type; + GNodeFlags flags; + + /* The state of processing on this node */ + GNodeMade made; + /* The number of unmade children */ + int unmade; + + /* The modification time; 0 means the node does not have a + * corresponding file; see GNode_IsOODate. */ + time_t mtime; + struct GNode *youngestChild; + + /* The GNodes for which this node is an implied source. May be empty. + * For example, when there is an inference rule for .c.o, the node for + * file.c has the node for file.o in this list. */ + GNodeList implicitParents; + + /* The nodes that depend on this one, or in other words, the nodes for + * which this is a source. */ + GNodeList parents; + /* The nodes on which this one depends. */ + GNodeList children; + + /* .ORDER nodes we need made. The nodes that must be made (if they're + * made) before this node can be made, but that do not enter into the + * datedness of this node. */ + GNodeList order_pred; + /* .ORDER nodes who need us. The nodes that must be made (if they're + * made at all) after this node is made, but that do not depend on + * this node, in the normal sense. */ + GNodeList order_succ; + + /* + * Other nodes of the same name, for targets that were defined using + * the '::' dependency operator (OP_DOUBLEDEP). + */ + GNodeList cohorts; + /* The "#n" suffix for this cohort, or "" for other nodes */ + char cohort_num[8]; + /* The number of unmade instances on the cohorts list */ + int unmade_cohorts; + /* Pointer to the first instance of a '::' node; only set when on a + * cohorts list */ + struct GNode *centurion; + + /* Last time (sequence number) we tried to make this node */ + unsigned int checked_seqno; + + /* + * The "local" variables that are specific to this target and this + * target only, such as $@, $<, $?. + * + * Also used for the global variable scopes SCOPE_GLOBAL, + * SCOPE_CMDLINE, SCOPE_INTERNAL, which contain variables with + * arbitrary names. + */ + HashTable /* of Var pointer */ vars; + + /* The commands to be given to a shell to create this target. */ + StringList commands; + + /* Suffix for the node (determined by Suff_FindDeps and opaque to + * everyone but the Suff module) */ + struct Suffix *suffix; + + /* Filename where the GNode got defined */ + /* XXX: What is the lifetime of this string? */ + const char *fname; + /* Line number where the GNode got defined */ + int lineno; +} GNode; + +/* Error levels for diagnostics during parsing. */ +typedef enum ParseErrorLevel { + /* Exit when the current top-level makefile has been parsed + * completely. */ + PARSE_FATAL = 1, + /* Print "warning"; may be upgraded to fatal by the -w option. */ + PARSE_WARNING, + /* Informational, mainly used during development of makefiles. */ + PARSE_INFO +} ParseErrorLevel; /* - * These constants are all used by the Str_Concat function to decide how the - * final string should look. If STR_ADDSPACE is given, a space will be - * placed between the two strings. If STR_ADDSLASH is given, a '/' will - * be used instead of a space. If neither is given, no intervening characters - * will be placed between the two strings in the final output. If the - * STR_DOFREE bit is set, the two input strings will be freed before - * Str_Concat returns. + * Values returned by Cond_EvalLine and Cond_EvalCondition. */ -#define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ -#define STR_ADDSLASH 0x02 /* add a slash when Str_Concat'ing */ +typedef enum CondEvalResult { + COND_PARSE, /* Parse the next lines */ + COND_SKIP, /* Skip the next lines */ + COND_INVALID /* Not a conditional statement */ +} CondEvalResult; -/* - * Error levels for parsing. PARSE_FATAL means the process cannot continue - * once the makefile has been parsed. PARSE_WARNING means it can. Passed - * as the first argument to Parse_Error. - */ -#define PARSE_WARNING 2 -#define PARSE_FATAL 1 - -/* - * Values returned by Cond_Eval. - */ -#define COND_PARSE 0 /* Parse the next lines */ -#define COND_SKIP 1 /* Skip the next lines */ -#define COND_INVALID 2 /* Not a conditional statement */ - -/* - * Definitions for the "local" variables. Used only for clarity. - */ -#define TARGET "@" /* Target of dependency */ -#define OODATE "?" /* All out-of-date sources */ -#define ALLSRC ">" /* All sources */ -#define IMPSRC "<" /* Source implied by transformation */ -#define PREFIX "*" /* Common prefix */ -#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ -#define MEMBER "%" /* Member in "archive(member)" syntax */ - -#define FTARGET "@F" /* file part of TARGET */ -#define DTARGET "@D" /* directory part of TARGET */ -#define FIMPSRC "" /* All sources */ +#define IMPSRC "<" /* Source implied by transformation */ +#define PREFIX "*" /* Common prefix */ +#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ +#define MEMBER "%" /* Member in "archive(member)" syntax */ /* * Global Variables */ -extern Lst create; /* The list of target names specified on the - * command line. used to resolve #if - * make(...) statements */ -extern Lst dirSearchPath; /* The list of directories to search when - * looking for targets */ -extern Boolean compatMake; /* True if we are make compatible */ -extern Boolean ignoreErrors; /* True if should ignore all errors */ -extern Boolean beSilent; /* True if should print no commands */ -extern Boolean noExecute; /* True if should execute nothing */ -extern Boolean noRecursiveExecute; /* True if should execute nothing */ -extern Boolean allPrecious; /* True if every target is precious */ -extern Boolean keepgoing; /* True if should continue on unaffected - * portions of the graph when have an error - * in one portion */ -extern Boolean touchFlag; /* TRUE if targets should just be 'touched' - * if out of date. Set by the -t flag */ -extern Boolean queryFlag; /* TRUE if we aren't supposed to really make - * anything, just see if the targets are out- - * of-date */ -extern Boolean doing_depend; /* TRUE if processing .depend */ - -extern Boolean checkEnvFirst; /* TRUE if environment should be searched for - * variables before the global context */ -extern Boolean jobServer; /* a jobServer already exists */ - -extern Boolean parseWarnFatal; /* TRUE if makefile parsing warnings are - * treated as errors */ - -extern Boolean varNoExportEnv; /* TRUE if we should not export variables - * set on the command line to the env. */ - -extern GNode *DEFAULT; /* .DEFAULT rule */ - -extern GNode *VAR_INTERNAL; /* Variables defined internally by make - * which should not override those set by - * makefiles. - */ -extern GNode *VAR_GLOBAL; /* Variables defined in a global context, e.g - * in the Makefile itself */ -extern GNode *VAR_CMD; /* Variables defined on the command line */ -extern GNode *VAR_FOR; /* Iteration variables */ -extern char var_Error[]; /* Value returned by Var_Parse when an error - * is encountered. It actually points to - * an empty string, so naive callers needn't - * worry about it. */ - -extern time_t now; /* The time at the start of this whole - * process */ - -extern Boolean oldVars; /* Do old-style variable substitution */ - -extern Lst sysIncPath; /* The system include path. */ -extern Lst defIncPath; /* The default include path. */ - -extern char curdir[]; /* Startup directory */ -extern char *progname; /* The program name */ -extern char *makeDependfile; /* .depend */ -extern char **savedEnv; /* if we replaced environ this will be non-NULL */ +/* True if every target is precious */ +extern Boolean allPrecious; +/* True if failed targets should be deleted */ +extern Boolean deleteOnError; +/* TRUE while processing .depend */ +extern Boolean doing_depend; +/* .DEFAULT rule */ +extern GNode *defaultNode; /* - * We cannot vfork() in a child of vfork(). - * Most systems do not enforce this but some do. + * Variables defined internally by make which should not override those set + * by makefiles. */ -#define vFork() ((getpid() == myPid) ? vfork() : fork()) -extern pid_t myPid; +extern GNode *SCOPE_INTERNAL; +/* Variables defined in a global scope, e.g in the makefile itself. */ +extern GNode *SCOPE_GLOBAL; +/* Variables defined on the command line. */ +extern GNode *SCOPE_CMDLINE; -#define MAKEFLAGS ".MAKEFLAGS" -#define MAKEOVERRIDES ".MAKEOVERRIDES" -#define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */ -#define MAKE_EXPORTED ".MAKE.EXPORTED" /* variables we export */ -#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all the makefiles we read */ -#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */ -#define MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" -#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */ +/* + * Value returned by Var_Parse when an error is encountered. It actually + * points to an empty string, so naive callers needn't worry about it. + */ +extern char var_Error[]; + +/* The time at the start of this whole process */ +extern time_t now; + +/* + * The list of directories to search when looking for targets (set by the + * special target .PATH). + */ +extern SearchPath dirSearchPath; +/* Used for .include "...". */ +extern SearchPath *parseIncPath; +/* + * Used for .include <...>, for the built-in sys.mk and for makefiles from + * the command line arguments. + */ +extern SearchPath *sysIncPath; +/* The default for sysIncPath. */ +extern SearchPath *defSysIncPath; + +/* Startup directory */ +extern char curdir[]; +/* The basename of the program name, suffixed with [n] for sub-makes. */ +extern const char *progname; +extern int makelevel; +/* Name of the .depend makefile */ +extern char *makeDependfile; +/* If we replaced environ, this will be non-NULL. */ +extern char **savedEnv; + +extern pid_t myPid; + +#define MAKEFLAGS ".MAKEFLAGS" +#define MAKEOVERRIDES ".MAKEOVERRIDES" +/* prefix when printing the target of a job */ +#define MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" +#define MAKE_EXPORTED ".MAKE.EXPORTED" /* exported variables */ +#define MAKE_MAKEFILES ".MAKE.MAKEFILES" /* all loaded makefiles */ +#define MAKE_LEVEL ".MAKE.LEVEL" /* recursion level */ +#define MAKE_MAKEFILE_PREFERENCE ".MAKE.MAKEFILE_PREFERENCE" +#define MAKE_DEPENDFILE ".MAKE.DEPENDFILE" /* .depend */ #define MAKE_MODE ".MAKE.MODE" #ifndef MAKE_LEVEL_ENV # define MAKE_LEVEL_ENV "MAKELEVEL" #endif -/* - * debug control: - * There is one bit per module. It is up to the module what debug - * information to print. - */ -FILE *debug_file; /* Output written here - default stdout */ -extern int debug; -#define DEBUG_ARCH 0x00001 -#define DEBUG_COND 0x00002 -#define DEBUG_DIR 0x00004 -#define DEBUG_GRAPH1 0x00008 -#define DEBUG_GRAPH2 0x00010 -#define DEBUG_JOB 0x00020 -#define DEBUG_MAKE 0x00040 -#define DEBUG_SUFF 0x00080 -#define DEBUG_TARG 0x00100 -#define DEBUG_VAR 0x00200 -#define DEBUG_FOR 0x00400 -#define DEBUG_SHELL 0x00800 -#define DEBUG_ERROR 0x01000 -#define DEBUG_LOUD 0x02000 -#define DEBUG_META 0x04000 +typedef enum DebugFlags { + DEBUG_NONE = 0, + DEBUG_ARCH = 1 << 0, + DEBUG_COND = 1 << 1, + DEBUG_CWD = 1 << 2, + DEBUG_DIR = 1 << 3, + DEBUG_ERROR = 1 << 4, + DEBUG_FOR = 1 << 5, + DEBUG_GRAPH1 = 1 << 6, + DEBUG_GRAPH2 = 1 << 7, + DEBUG_GRAPH3 = 1 << 8, + DEBUG_HASH = 1 << 9, + DEBUG_JOB = 1 << 10, + DEBUG_LOUD = 1 << 11, + DEBUG_MAKE = 1 << 12, + DEBUG_META = 1 << 13, + DEBUG_PARSE = 1 << 14, + DEBUG_SCRIPT = 1 << 15, + DEBUG_SHELL = 1 << 16, + DEBUG_SUFF = 1 << 17, + DEBUG_TARG = 1 << 18, + DEBUG_VAR = 1 << 19, + DEBUG_ALL = (1 << 20) - 1 +} DebugFlags; -#define DEBUG_GRAPH3 0x10000 -#define DEBUG_SCRIPT 0x20000 -#define DEBUG_PARSE 0x40000 -#define DEBUG_CWD 0x80000 +#define CONCAT(a, b) a##b -#define CONCAT(a,b) a##b +#define DEBUG(module) ((opts.debug & CONCAT(DEBUG_, module)) != 0) -#define DEBUG(module) (debug & CONCAT(DEBUG_,module)) +void debug_printf(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); + +#define DEBUG_IMPL(module, args) \ + do { \ + if (DEBUG(module)) \ + debug_printf args; \ + } while (/*CONSTCOND*/FALSE) + +#define DEBUG0(module, text) \ + DEBUG_IMPL(module, ("%s", text)) +#define DEBUG1(module, fmt, arg1) \ + DEBUG_IMPL(module, (fmt, arg1)) +#define DEBUG2(module, fmt, arg1, arg2) \ + DEBUG_IMPL(module, (fmt, arg1, arg2)) +#define DEBUG3(module, fmt, arg1, arg2, arg3) \ + DEBUG_IMPL(module, (fmt, arg1, arg2, arg3)) +#define DEBUG4(module, fmt, arg1, arg2, arg3, arg4) \ + DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4)) +#define DEBUG5(module, fmt, arg1, arg2, arg3, arg4, arg5) \ + DEBUG_IMPL(module, (fmt, arg1, arg2, arg3, arg4, arg5)) + +typedef enum PrintVarsMode { + PVM_NONE, + PVM_UNEXPANDED, + PVM_EXPANDED +} PrintVarsMode; + +/* Command line options */ +typedef struct CmdOpts { + /* -B: whether we are make compatible */ + Boolean compatMake; + + /* -d: debug control: There is one bit per module. It is up to the + * module what debug information to print. */ + DebugFlags debug; + + /* -df: debug output is written here - default stderr */ + FILE *debug_file; + + /* -dL: lint mode + * + * Runs make in strict mode, with additional checks and better error + * handling. */ + Boolean strict; + + /* -dV: for the -V option, print unexpanded variable values */ + Boolean debugVflag; + + /* -e: check environment variables before global variables */ + Boolean checkEnvFirst; + + /* -f: the makefiles to read */ + StringList makefiles; + + /* -i: if true, ignore all errors from shell commands */ + Boolean ignoreErrors; + + /* -j: the maximum number of jobs that can run in parallel; + * this is coordinated with the submakes */ + int maxJobs; + + /* -k: if true and an error occurs while making a node, continue + * making nodes that do not depend on the erroneous node */ + Boolean keepgoing; + + /* -N: execute no commands from the targets */ + Boolean noRecursiveExecute; + + /* -n: execute almost no commands from the targets */ + Boolean noExecute; + + /* + * -q: if true, do not really make anything, just see if the targets + * are out-of-date + */ + Boolean queryFlag; + + /* -r: raw mode, do not load the builtin rules. */ + Boolean noBuiltins; + + /* -s: don't echo the shell commands before executing them */ + Boolean beSilent; + + /* -t: touch the targets if they are out-of-date, but don't actually + * make them */ + Boolean touchFlag; + + /* -[Vv]: print expanded or unexpanded selected variables */ + PrintVarsMode printVars; + /* -[Vv]: the variables to print */ + StringList variables; + + /* -W: if true, makefile parsing warnings are treated as errors */ + Boolean parseWarnFatal; + + /* -w: print 'Entering' and 'Leaving' for submakes */ + Boolean enterFlag; + + /* -X: if true, do not export variables set on the command line to the + * environment. */ + Boolean varNoExportEnv; + + /* The target names specified on the command line. + * Used to resolve .if make(...) statements. */ + StringList create; + +} CmdOpts; + +extern CmdOpts opts; #include "nonints.h" -int Make_TimeStamp(GNode *, GNode *); -Boolean Make_OODate(GNode *); -void Make_ExpandUse(Lst); +void GNode_UpdateYoungestChild(GNode *, GNode *); +Boolean GNode_IsOODate(GNode *); +void Make_ExpandUse(GNodeList *); time_t Make_Recheck(GNode *); void Make_HandleUse(GNode *, GNode *); void Make_Update(GNode *); void Make_DoAllVar(GNode *); -Boolean Make_Run(Lst); -char * Check_Cwd_Cmd(const char *); -void Check_Cwd(const char **); +Boolean Make_Run(GNodeList *); +Boolean shouldDieQuietly(GNode *, int); void PrintOnError(GNode *, const char *); void Main_ExportMAKEFLAGS(Boolean); -Boolean Main_SetObjdir(const char *); -int mkTempFile(const char *, char **); -int str2Lst_Append(Lst, char *, const char *); +Boolean Main_SetObjdir(Boolean, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +int mkTempFile(const char *, char *, size_t); +int str2Lst_Append(StringList *, char *); +void GNode_FprintDetails(FILE *, const char *, const GNode *, const char *); +Boolean GNode_ShouldExecute(GNode *gn); + +/* See if the node was seen on the left-hand side of a dependency operator. */ +MAKE_INLINE Boolean +GNode_IsTarget(const GNode *gn) +{ + return (gn->type & OP_OPMASK) != 0; +} + +MAKE_INLINE const char * +GNode_Path(const GNode *gn) +{ + return gn->path != NULL ? gn->path : gn->name; +} + +MAKE_INLINE Boolean +GNode_IsWaitingFor(const GNode *gn) +{ + return (gn->flags & REMAKE) && gn->made <= REQUESTED; +} + +MAKE_INLINE Boolean +GNode_IsReady(const GNode *gn) +{ + return gn->made > DEFERRED; +} + +MAKE_INLINE Boolean +GNode_IsDone(const GNode *gn) +{ + return gn->made >= MADE; +} + +MAKE_INLINE Boolean +GNode_IsError(const GNode *gn) +{ + return gn->made == ERROR || gn->made == ABORTED; +} + +MAKE_INLINE const char * +GNode_VarTarget(GNode *gn) { return GNode_ValueDirect(gn, TARGET); } +MAKE_INLINE const char * +GNode_VarOodate(GNode *gn) { return GNode_ValueDirect(gn, OODATE); } +MAKE_INLINE const char * +GNode_VarAllsrc(GNode *gn) { return GNode_ValueDirect(gn, ALLSRC); } +MAKE_INLINE const char * +GNode_VarImpsrc(GNode *gn) { return GNode_ValueDirect(gn, IMPSRC); } +MAKE_INLINE const char * +GNode_VarPrefix(GNode *gn) { return GNode_ValueDirect(gn, PREFIX); } +MAKE_INLINE const char * +GNode_VarArchive(GNode *gn) { return GNode_ValueDirect(gn, ARCHIVE); } +MAKE_INLINE const char * +GNode_VarMember(GNode *gn) { return GNode_ValueDirect(gn, MEMBER); } #ifdef __GNUC__ -#define UNCONST(ptr) ({ \ +#define UNCONST(ptr) ({ \ union __unconst { \ const void *__cp; \ void *__p; \ @@ -496,13 +776,6 @@ int str2Lst_Append(Lst, char *, const char *); #define UNCONST(ptr) (void *)(ptr) #endif -#ifndef MIN -#define MIN(a, b) ((a < b) ? a : b) -#endif -#ifndef MAX -#define MAX(a, b) ((a > b) ? a : b) -#endif - /* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */ #include #ifndef MAXPATHLEN @@ -512,4 +785,68 @@ int str2Lst_Append(Lst, char *, const char *); #define PATH_MAX MAXPATHLEN #endif -#endif /* _MAKE_H_ */ +#if defined(SYSV) +#define KILLPG(pid, sig) kill(-(pid), (sig)) +#else +#define KILLPG(pid, sig) killpg((pid), (sig)) +#endif + +MAKE_INLINE Boolean +ch_isalnum(char ch) { return isalnum((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isalpha(char ch) { return isalpha((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isdigit(char ch) { return isdigit((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isspace(char ch) { return isspace((unsigned char)ch) != 0; } +MAKE_INLINE Boolean +ch_isupper(char ch) { return isupper((unsigned char)ch) != 0; } +MAKE_INLINE char +ch_tolower(char ch) { return (char)tolower((unsigned char)ch); } +MAKE_INLINE char +ch_toupper(char ch) { return (char)toupper((unsigned char)ch); } + +MAKE_INLINE void +cpp_skip_whitespace(const char **pp) +{ + while (ch_isspace(**pp)) + (*pp)++; +} + +MAKE_INLINE void +cpp_skip_hspace(const char **pp) +{ + while (**pp == ' ' || **pp == '\t') + (*pp)++; +} + +MAKE_INLINE void +pp_skip_whitespace(char **pp) +{ + while (ch_isspace(**pp)) + (*pp)++; +} + +MAKE_INLINE void +pp_skip_hspace(char **pp) +{ + while (**pp == ' ' || **pp == '\t') + (*pp)++; +} + +#if defined(lint) +# define MAKE_RCSID(id) extern void do_not_define_rcsid(void) +#elif defined(MAKE_NATIVE) +# include +# define MAKE_RCSID(id) __RCSID(id) +#elif defined(MAKE_ALL_IN_ONE) && defined(__COUNTER__) +# define MAKE_RCSID_CONCAT(x, y) CONCAT(x, y) +# define MAKE_RCSID(id) static volatile char \ + MAKE_RCSID_CONCAT(rcsid_, __COUNTER__)[] = id +#elif defined(MAKE_ALL_IN_ONE) +# define MAKE_RCSID(id) extern void do_not_define_rcsid(void) +#else +# define MAKE_RCSID(id) static volatile char rcsid[] = id +#endif + +#endif /* MAKE_MAKE_H */ diff --git a/usr.bin/make/make_malloc.c b/usr.bin/make/make_malloc.c index b8ac23f84..42a69f437 100644 --- a/usr.bin/make/make_malloc.c +++ b/usr.bin/make/make_malloc.c @@ -1,6 +1,6 @@ -/* $NetBSD: make_malloc.c,v 1.10 2012/06/20 17:46:28 sjg Exp $ */ +/* $NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $ */ -/*- +/* * Copyright (c) 2009 The NetBSD Foundation, Inc. * All rights reserved. * @@ -26,36 +26,23 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifdef MAKE_NATIVE -#include -__RCSID("$NetBSD: make_malloc.c,v 1.10 2012/06/20 17:46:28 sjg Exp $"); -#endif - -#include -#include -#include #include #include "make.h" -#ifndef USE_EMALLOC -static void enomem(void) MAKE_ATTR_DEAD; +MAKE_RCSID("$NetBSD: make_malloc.c,v 1.25 2021/01/19 20:51:46 rillig Exp $"); -/* - * enomem -- - * die when out of memory. - */ -static void +#ifndef USE_EMALLOC + +/* die when out of memory. */ +static MAKE_ATTR_DEAD void enomem(void) { (void)fprintf(stderr, "%s: %s.\n", progname, strerror(ENOMEM)); exit(2); } -/* - * bmake_malloc -- - * malloc, but die on error. - */ +/* malloc, but die on error. */ void * bmake_malloc(size_t len) { @@ -63,13 +50,10 @@ bmake_malloc(size_t len) if ((p = malloc(len)) == NULL) enomem(); - return(p); + return p; } -/* - * bmake_strdup -- - * strdup, but die on error. - */ +/* strdup, but die on error. */ char * bmake_strdup(const char *str) { @@ -77,43 +61,33 @@ bmake_strdup(const char *str) char *p; len = strlen(str) + 1; - if ((p = malloc(len)) == NULL) - enomem(); + p = bmake_malloc(len); return memcpy(p, str, len); } -/* - * bmake_strndup -- - * strndup, but die on error. - */ +/* Allocate a string starting from str with exactly len characters. */ char * -bmake_strndup(const char *str, size_t max_len) +bmake_strldup(const char *str, size_t len) { - size_t len; - char *p; - - if (str == NULL) - return NULL; - - len = strlen(str); - if (len > max_len) - len = max_len; - p = bmake_malloc(len + 1); + char *p = bmake_malloc(len + 1); memcpy(p, str, len); p[len] = '\0'; - - return(p); + return p; } -/* - * bmake_realloc -- - * realloc, but die on error. - */ +/* realloc, but die on error. */ void * bmake_realloc(void *ptr, size_t size) { if ((ptr = realloc(ptr, size)) == NULL) enomem(); - return(ptr); + return ptr; } #endif + +/* Allocate a string from start up to but excluding end. */ +char * +bmake_strsedup(const char *start, const char *end) +{ + return bmake_strldup(start, (size_t)(end - start)); +} diff --git a/usr.bin/make/make_malloc.h b/usr.bin/make/make_malloc.h index 36d3eff3c..72f6c11fd 100644 --- a/usr.bin/make/make_malloc.h +++ b/usr.bin/make/make_malloc.h @@ -1,6 +1,6 @@ -/* $NetBSD: make_malloc.h,v 1.4 2009/01/24 14:43:29 dsl Exp $ */ +/* $NetBSD: make_malloc.h,v 1.16 2021/01/19 20:51:46 rillig Exp $ */ -/*- +/* * Copyright (c) 2009 The NetBSD Foundation, Inc. * All rights reserved. * @@ -30,12 +30,27 @@ void *bmake_malloc(size_t); void *bmake_realloc(void *, size_t); char *bmake_strdup(const char *); -char *bmake_strndup(const char *, size_t); +char *bmake_strldup(const char *, size_t); #else #include -#define bmake_malloc(x) emalloc(x) -#define bmake_realloc(x,y) erealloc(x,y) -#define bmake_strdup(x) estrdup(x) -#define bmake_strndup(x,y) estrndup(x,y) +#define bmake_malloc(n) emalloc(n) +#define bmake_realloc(p, n) erealloc(p, n) +#define bmake_strdup(s) estrdup(s) +#define bmake_strldup(s, n) estrndup(s, n) #endif +char *bmake_strsedup(const char *, const char *); + +/* + * Thin wrapper around free(3) to avoid the extra function call in case + * p is NULL, to save a few machine instructions. + * + * The case of a NULL pointer happens especially often after Var_Value, + * since only environment variables need to be freed, but not others. + */ +MAKE_INLINE void +bmake_free(void *p) +{ + if (p != NULL) + free(p); +} diff --git a/usr.bin/make/meta.c b/usr.bin/make/meta.c index 5b2a7257f..ff02fc38e 100644 --- a/usr.bin/make/meta.c +++ b/usr.bin/make/meta.c @@ -1,4 +1,4 @@ -/* $NetBSD: meta.c,v 1.39 2015/10/10 03:58:59 sjg Exp $ */ +/* $NetBSD: meta.c,v 1.179 2021/03/15 12:15:03 rillig Exp $ */ /* * Implement 'meta' mode. @@ -6,18 +6,18 @@ * --sjg */ /* - * Copyright (c) 2009-2010, Juniper Networks, Inc. + * Copyright (c) 2009-2016, Juniper Networks, Inc. * Portions Copyright (c) 2009, John Birrell. - * + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: + * modification, are permitted provided that the following conditions + * are met: * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * + * documentation and/or other materials provided with the distribution. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -28,7 +28,7 @@ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined(USE_META) @@ -36,8 +36,6 @@ # include "config.h" #endif #include -#include -#include #include #include #if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H) @@ -45,37 +43,46 @@ #endif #include "make.h" +#include "dir.h" #include "job.h" -#ifdef HAVE_FILEMON_H -# include -#endif -#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD) -# define USE_FILEMON +#ifdef USE_FILEMON +#include "filemon/filemon.h" #endif static BuildMon Mybm; /* for compat */ -static Lst metaBailiwick; /* our scope of control */ -static Lst metaIgnorePaths; /* paths we deliberately ignore */ +static StringList metaBailiwick = LST_INIT; /* our scope of control */ +static char *metaBailiwickStr; /* string storage for the list */ +static StringList metaIgnorePaths = LST_INIT; /* paths we deliberately ignore */ +static char *metaIgnorePathsStr; /* string storage for the list */ #ifndef MAKE_META_IGNORE_PATHS #define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS" #endif +#ifndef MAKE_META_IGNORE_PATTERNS +#define MAKE_META_IGNORE_PATTERNS ".MAKE.META.IGNORE_PATTERNS" +#endif +#ifndef MAKE_META_IGNORE_FILTER +#define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER" +#endif Boolean useMeta = FALSE; static Boolean useFilemon = FALSE; static Boolean writeMeta = FALSE; +static Boolean metaMissing = FALSE; /* oodate if missing */ +static Boolean filemonMissing = FALSE; /* oodate if missing */ static Boolean metaEnv = FALSE; /* don't save env unless asked */ static Boolean metaVerbose = FALSE; static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */ +static Boolean metaIgnorePatterns = FALSE; /* do we need to do pattern matches */ +static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */ static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */ static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ extern Boolean forceJobs; -extern Boolean comatMake; extern char **environ; -#define MAKE_META_PREFIX ".MAKE.META.PREFIX" +#define MAKE_META_PREFIX ".MAKE.META.PREFIX" #ifndef N2U # define N2U(n, u) (((n) + ((u) - 1)) / (u)) @@ -85,7 +92,7 @@ extern char **environ; #endif #if !defined(HAVE_STRSEP) -# define strsep(s, d) stresep((s), (d), 0) +# define strsep(s, d) stresep((s), (d), '\0') #endif /* @@ -102,34 +109,28 @@ extern char **environ; * * See meta_oodate below - we mainly care about 'E' and 'R'. * - * We can still use meta mode without filemon, but + * We can still use meta mode without filemon, but * the benefits are more limited. */ #ifdef USE_FILEMON -# ifndef _PATH_FILEMON -# define _PATH_FILEMON "/dev/filemon" -# endif /* * Open the filemon device. */ static void -filemon_open(BuildMon *pbm) +meta_open_filemon(BuildMon *pbm) { - int retry; - - pbm->mon_fd = pbm->filemon_fd = -1; - if (!useFilemon) + int dupfd; + + pbm->mon_fd = -1; + pbm->filemon = NULL; + if (!useFilemon || pbm->mfp == NULL) return; - for (retry = 5; retry >= 0; retry--) { - if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0) - break; - } - - if (pbm->filemon_fd < 0) { + pbm->filemon = filemon_open(); + if (pbm->filemon == NULL) { useFilemon = FALSE; - warn("Could not open %s", _PATH_FILEMON); + warn("Could not open filemon %s", filemon_path()); return; } @@ -139,41 +140,57 @@ filemon_open(BuildMon *pbm) * cwd causing getcwd to do a lot more work. * We only care about the descriptor. */ - pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL); - if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) { + if (!opts.compatMake) + pbm->mon_fd = Job_TempFile("filemon.XXXXXX", NULL, 0); + else + pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL, 0); + if ((dupfd = dup(pbm->mon_fd)) == -1) { + err(1, "Could not dup filemon output!"); + } + (void)fcntl(dupfd, F_SETFD, FD_CLOEXEC); + if (filemon_setfd(pbm->filemon, dupfd) == -1) { err(1, "Could not set filemon file descriptor!"); } /* we don't need these once we exec */ - (void)fcntl(pbm->mon_fd, F_SETFD, 1); - (void)fcntl(pbm->filemon_fd, F_SETFD, 1); + (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC); } /* * Read the build monitor output file and write records to the target's * metadata file. */ -static void +static int filemon_read(FILE *mfp, int fd) { char buf[BUFSIZ]; - int n; + int error; /* Check if we're not writing to a meta data file.*/ if (mfp == NULL) { if (fd >= 0) close(fd); /* not interested */ - return; + return 0; } /* rewind */ - (void)lseek(fd, (off_t)0, SEEK_SET); + if (lseek(fd, (off_t)0, SEEK_SET) < 0) { + error = errno; + warn("Could not rewind filemon"); + fprintf(mfp, "\n"); + } else { + ssize_t n; - fprintf(mfp, "\n-- filemon acquired metadata --\n"); + error = 0; + fprintf(mfp, "\n-- filemon acquired metadata --\n"); - while ((n = read(fd, buf, sizeof(buf))) > 0) { - fwrite(buf, 1, n, mfp); + while ((n = read(fd, buf, sizeof buf)) > 0) { + if ((ssize_t)fwrite(buf, 1, (size_t)n, mfp) < n) + error = EIO; + } } fflush(mfp); - close(fd); + if (close(fd) < 0) + error = errno; + return error; } #endif @@ -201,10 +218,10 @@ eat_dots(char *buf, size_t bufsz, int dots) default: return; } - + do { cp = strstr(buf, eat); - if (cp) { + if (cp != NULL) { cp2 = cp + eatlen; if (dots == 2 && cp > buf) { do { @@ -212,35 +229,26 @@ eat_dots(char *buf, size_t bufsz, int dots) } while (cp > buf && *cp != '/'); } if (*cp == '/') { - strlcpy(cp, cp2, bufsz - (cp - buf)); + strlcpy(cp, cp2, bufsz - (size_t)(cp - buf)); } else { return; /* can't happen? */ } } - } while (cp); + } while (cp != NULL); } static char * -meta_name(struct GNode *gn, char *mname, size_t mnamelen, +meta_name(char *mname, size_t mnamelen, const char *dname, - const char *tname) + const char *tname, + const char *cwd) { char buf[MAXPATHLEN]; - char cwd[MAXPATHLEN]; char *rp; char *cp; char *tp; - char *p[4]; /* >= number of possible uses */ - int i; - - i = 0; - if (!dname) - dname = Var_Value(".OBJDIR", gn, &p[i++]); - if (!tname) - tname = Var_Value(TARGET, gn, &p[i++]); - - if (realpath(dname, cwd)) - dname = cwd; + char *dtp; + size_t ldname; /* * Weed out relative paths from the target file name. @@ -249,13 +257,13 @@ meta_name(struct GNode *gn, char *mname, size_t mnamelen, * So we use realpath() just to get the dirname, and leave the * basename as given to us. */ - if ((cp = strrchr(tname, '/'))) { - if (realpath(tname, buf)) { - if ((rp = strrchr(buf, '/'))) { + if ((cp = strrchr(tname, '/')) != NULL) { + if (cached_realpath(tname, buf) != NULL) { + if ((rp = strrchr(buf, '/')) != NULL) { rp++; cp++; if (strcmp(cp, rp) != 0) - strlcpy(rp, cp, sizeof(buf) - (rp - buf)); + strlcpy(rp, cp, sizeof buf - (size_t)(rp - buf)); } tname = buf; } else { @@ -266,21 +274,26 @@ meta_name(struct GNode *gn, char *mname, size_t mnamelen, * next time through. */ if (tname[0] == '/') { - strlcpy(buf, tname, sizeof(buf)); + strlcpy(buf, tname, sizeof buf); } else { - snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); + snprintf(buf, sizeof buf, "%s/%s", cwd, tname); } - eat_dots(buf, sizeof(buf), 1); /* ./ */ - eat_dots(buf, sizeof(buf), 2); /* ../ */ + eat_dots(buf, sizeof buf, 1); /* ./ */ + eat_dots(buf, sizeof buf, 2); /* ../ */ tname = buf; } } /* on some systems dirname may modify its arg */ tp = bmake_strdup(tname); - if (strcmp(dname, dirname(tp)) == 0) + dtp = dirname(tp); + if (strcmp(dname, dtp) == 0) snprintf(mname, mnamelen, "%s.meta", tname); else { - snprintf(mname, mnamelen, "%s/%s.meta", dname, tname); + ldname = strlen(dname); + if (strncmp(dname, dtp, ldname) == 0 && dtp[ldname] == '/') + snprintf(mname, mnamelen, "%s/%s.meta", dname, &tname[ldname+1]); + else + snprintf(mname, mnamelen, "%s/%s.meta", dname, tname); /* * Replace path separators in the file name after the @@ -295,115 +308,121 @@ meta_name(struct GNode *gn, char *mname, size_t mnamelen, } } free(tp); - for (i--; i >= 0; i--) { - if (p[i]) - free(p[i]); - } - return (mname); + return mname; } /* * Return true if running ${.MAKE} * Bypassed if target is flagged .MAKE */ -static int -is_submake(void *cmdp, void *gnp) +static Boolean +is_submake(const char *cmd, GNode *gn) { - static char *p_make = NULL; - static int p_len; - char *cmd = cmdp; - GNode *gn = gnp; + static const char *p_make = NULL; + static size_t p_len; char *mp = NULL; char *cp; char *cp2; - int rc = 0; /* keep looking */ + Boolean rc = FALSE; - if (!p_make) { - p_make = Var_Value(".MAKE", gn, &cp); + if (p_make == NULL) { + p_make = Var_Value(gn, ".MAKE").str; p_len = strlen(p_make); } cp = strchr(cmd, '$'); - if ((cp)) { - mp = Var_Subst(NULL, cmd, gn, FALSE); + if (cp != NULL) { + (void)Var_Subst(cmd, gn, VARE_WANTRES, &mp); + /* TODO: handle errors */ cmd = mp; } cp2 = strstr(cmd, p_make); - if ((cp2)) { + if (cp2 != NULL) { switch (cp2[p_len]) { case '\0': case ' ': case '\t': case '\n': - rc = 1; + rc = TRUE; break; } - if (cp2 > cmd && rc > 0) { + if (cp2 > cmd && rc) { switch (cp2[-1]) { case ' ': case '\t': case '\n': break; default: - rc = 0; /* no match */ + rc = FALSE; /* no match */ break; } } } - if (mp) - free(mp); - return (rc); + free(mp); + return rc; } -typedef struct meta_file_s { - FILE *fp; - GNode *gn; -} meta_file_t; - -static int -printCMD(void *cmdp, void *mfpp) +static Boolean +any_is_submake(GNode *gn) { - meta_file_t *mfp = mfpp; - char *cmd = cmdp; - char *cp = NULL; + StringListNode *ln; - if (strchr(cmd, '$')) { - cmd = cp = Var_Subst(NULL, cmd, mfp->gn, FALSE); + for (ln = gn->commands.first; ln != NULL; ln = ln->next) + if (is_submake(ln->datum, gn)) + return TRUE; + return FALSE; +} + +static void +printCMD(const char *ucmd, FILE *fp, GNode *gn) +{ + FStr xcmd = FStr_InitRefer(ucmd); + + if (strchr(ucmd, '$') != NULL) { + char *expanded; + (void)Var_Subst(ucmd, gn, VARE_WANTRES, &expanded); + /* TODO: handle errors */ + xcmd = FStr_InitOwn(expanded); } - fprintf(mfp->fp, "CMD %s\n", cmd); - if (cp) - free(cp); - return 0; + + fprintf(fp, "CMD %s\n", xcmd.str); + FStr_Done(&xcmd); +} + +static void +printCMDs(GNode *gn, FILE *fp) +{ + StringListNode *ln; + + for (ln = gn->commands.first; ln != NULL; ln = ln->next) + printCMD(ln->datum, fp, gn); } /* * Certain node types never get a .meta file */ #define SKIP_META_TYPE(_type) do { \ - if ((gn->type & __CONCAT(OP_, _type))) { \ - if (DEBUG(META)) { \ - fprintf(debug_file, "Skipping meta for %s: .%s\n", \ - gn->name, __STRING(_type)); \ + if ((gn->type & __CONCAT(OP_, _type))) { \ + if (verbose) { \ + debug_printf("Skipping meta for %s: .%s\n", \ + gn->name, __STRING(_type)); \ } \ - return (NULL); \ + return FALSE; \ } \ -} while (0) +} while (/*CONSTCOND*/FALSE) -static FILE * -meta_create(BuildMon *pbm, GNode *gn) + +/* + * Do we need/want a .meta file ? + */ +static Boolean +meta_needed(GNode *gn, const char *dname, + char *objdir_realpath, Boolean verbose) { - meta_file_t mf; - char buf[MAXPATHLEN]; - char objdir[MAXPATHLEN]; - char **ptr; - const char *dname; - const char *tname; - char *fname; - const char *cp; - char *p[4]; /* >= possible uses */ - int i; - struct stat fs; + struct cached_stat cst; + + if (verbose) + verbose = DEBUG(META); - /* This may be a phony node which we don't want meta data for... */ /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */ /* Or it may be explicitly flagged as .NOMETA */ @@ -415,116 +434,122 @@ meta_create(BuildMon *pbm, GNode *gn) SKIP_META_TYPE(MAKE); } - mf.fp = NULL; - - i = 0; - - dname = Var_Value(".OBJDIR", gn, &p[i++]); - tname = Var_Value(TARGET, gn, &p[i++]); - - /* The object directory may not exist. Check it.. */ - if (stat(dname, &fs) != 0) { - if (DEBUG(META)) - fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n", - gn->name); - goto out; - } /* Check if there are no commands to execute. */ - if (Lst_IsEmpty(gn->commands)) { - if (DEBUG(META)) - fprintf(debug_file, "Skipping meta for %s: no commands\n", - gn->name); - goto out; + if (Lst_IsEmpty(&gn->commands)) { + if (verbose) + debug_printf("Skipping meta for %s: no commands\n", gn->name); + return FALSE; + } + if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) { + /* OP_SUBMAKE is a bit too aggressive */ + if (any_is_submake(gn)) { + DEBUG1(META, "Skipping meta for %s: .SUBMAKE\n", gn->name); + return FALSE; + } + } + + /* The object directory may not exist. Check it.. */ + if (cached_stat(dname, &cst) != 0) { + if (verbose) + debug_printf("Skipping meta for %s: no .OBJDIR\n", gn->name); + return FALSE; } /* make sure these are canonical */ - if (realpath(dname, objdir)) - dname = objdir; + if (cached_realpath(dname, objdir_realpath) != NULL) + dname = objdir_realpath; /* If we aren't in the object directory, don't create a meta file. */ if (!metaCurdirOk && strcmp(curdir, dname) == 0) { - if (DEBUG(META)) - fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n", - gn->name); + if (verbose) + debug_printf("Skipping meta for %s: .OBJDIR == .CURDIR\n", + gn->name); + return FALSE; + } + return TRUE; +} + + +static FILE * +meta_create(BuildMon *pbm, GNode *gn) +{ + FILE *fp; + char buf[MAXPATHLEN]; + char objdir_realpath[MAXPATHLEN]; + char **ptr; + FStr dname; + const char *tname; + char *fname; + const char *cp; + + fp = NULL; + + dname = Var_Value(gn, ".OBJDIR"); + tname = GNode_VarTarget(gn); + + /* if this succeeds objdir_realpath is realpath of dname */ + if (!meta_needed(gn, dname.str, objdir_realpath, TRUE)) goto out; - } - if (!(gn->type & OP_META)) { - /* We do not generate .meta files for sub-makes */ - if (Lst_ForEach(gn->commands, is_submake, gn)) { - if (DEBUG(META)) - fprintf(debug_file, "Skipping meta for %s: .MAKE\n", - gn->name); - goto out; - } - } + dname.str = objdir_realpath; if (metaVerbose) { char *mp; /* Describe the target we are building */ - mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, 0); - if (*mp) + (void)Var_Subst("${" MAKE_META_PREFIX "}", gn, VARE_WANTRES, &mp); + /* TODO: handle errors */ + if (mp[0] != '\0') fprintf(stdout, "%s\n", mp); free(mp); } /* Get the basename of the target */ - if ((cp = strrchr(tname, '/')) == NULL) { - cp = tname; - } else { - cp++; - } + cp = str_basename(tname); fflush(stdout); - if (strcmp(cp, makeDependfile) == 0) - goto out; - if (!writeMeta) /* Don't create meta data. */ goto out; - fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname), - dname, tname); + fname = meta_name(pbm->meta_fname, sizeof pbm->meta_fname, + dname.str, tname, objdir_realpath); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_create: %s\n", fname); + DEBUG1(META, "meta_create: %s\n", fname); #endif - if ((mf.fp = fopen(fname, "w")) == NULL) + if ((fp = fopen(fname, "w")) == NULL) err(1, "Could not open meta file '%s'", fname); - fprintf(mf.fp, "# Meta data file %s\n", fname); + fprintf(fp, "# Meta data file %s\n", fname); - mf.gn = gn; - - Lst_ForEach(gn->commands, printCMD, &mf); - - fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); - fprintf(mf.fp, "TARGET %s\n", tname); + printCMDs(gn, fp); + fprintf(fp, "CWD %s\n", getcwd(buf, sizeof buf)); + fprintf(fp, "TARGET %s\n", tname); + cp = GNode_VarOodate(gn); + if (cp != NULL && *cp != '\0') { + fprintf(fp, "OODATE %s\n", cp); + } if (metaEnv) { for (ptr = environ; *ptr != NULL; ptr++) - fprintf(mf.fp, "ENV %s\n", *ptr); + fprintf(fp, "ENV %s\n", *ptr); } - fprintf(mf.fp, "-- command output --\n"); - fflush(mf.fp); + fprintf(fp, "-- command output --\n"); + fflush(fp); - Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); - Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL); + Global_Append(".MAKE.META.FILES", fname); + Global_Append(".MAKE.META.CREATED", fname); gn->type |= OP_META; /* in case anyone wants to know */ if (metaSilent) { gn->type |= OP_SILENT; } out: - for (i--; i >= 0; i--) { - if (p[i]) - free(p[i]); - } + FStr_Done(&dname); - return (mf.fp); + return fp; } static Boolean @@ -549,76 +574,89 @@ meta_init(void) { #ifdef USE_FILEMON /* this allows makefiles to test if we have filemon support */ - Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0); + Global_Set(".MAKE.PATH_FILEMON", filemon_path()); #endif } +#define get_mode_bf(bf, token) \ + if ((cp = strstr(make_mode, token)) != NULL) \ + bf = boolValue(cp + sizeof (token) - 1) + /* * Initialization we need after reading makefiles. */ void meta_mode_init(const char *make_mode) { - static int once = 0; + static Boolean once = FALSE; char *cp; + FStr value; useMeta = TRUE; useFilemon = TRUE; writeMeta = TRUE; - if (make_mode) { - if (strstr(make_mode, "env")) + if (make_mode != NULL) { + if (strstr(make_mode, "env") != NULL) metaEnv = TRUE; - if (strstr(make_mode, "verb")) + if (strstr(make_mode, "verb") != NULL) metaVerbose = TRUE; - if (strstr(make_mode, "read")) + if (strstr(make_mode, "read") != NULL) writeMeta = FALSE; - if (strstr(make_mode, "nofilemon")) + if (strstr(make_mode, "nofilemon") != NULL) useFilemon = FALSE; - if ((cp = strstr(make_mode, "curdirok="))) { - metaCurdirOk = boolValue(&cp[9]); - } - if ((cp = strstr(make_mode, "silent="))) { - metaSilent = boolValue(&cp[7]); - } - if (strstr(make_mode, "ignore-cmd")) + if (strstr(make_mode, "ignore-cmd") != NULL) metaIgnoreCMDs = TRUE; - /* for backwards compatability */ - Var_Set(".MAKE.META_CREATED", "${.MAKE.META.CREATED}", VAR_GLOBAL, 0); - Var_Set(".MAKE.META_FILES", "${.MAKE.META.FILES}", VAR_GLOBAL, 0); + if (useFilemon) + get_mode_bf(filemonMissing, "missing-filemon="); + get_mode_bf(metaCurdirOk, "curdirok="); + get_mode_bf(metaMissing, "missing-meta="); + get_mode_bf(metaSilent, "silent="); } - if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) { + if (metaVerbose && !Var_Exists(SCOPE_GLOBAL, MAKE_META_PREFIX)) { /* * The default value for MAKE_META_PREFIX * prints the absolute path of the target. * This works be cause :H will generate '.' if there is no / * and :tA will resolve that to cwd. */ - Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0); + Global_Set(MAKE_META_PREFIX, + "Building ${.TARGET:H:tA}/${.TARGET:T}"); } if (once) return; - once = 1; - memset(&Mybm, 0, sizeof(Mybm)); + once = TRUE; + memset(&Mybm, 0, sizeof Mybm); /* * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} */ - metaBailiwick = Lst_Init(FALSE); - cp = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", VAR_GLOBAL, 0); - if (cp) { - str2Lst_Append(metaBailiwick, cp, NULL); - } + (void)Var_Subst("${.MAKE.META.BAILIWICK:O:u:tA}", + SCOPE_GLOBAL, VARE_WANTRES, &metaBailiwickStr); + /* TODO: handle errors */ + str2Lst_Append(&metaBailiwick, metaBailiwickStr); /* * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} */ - metaIgnorePaths = Lst_Init(FALSE); - Var_Append(MAKE_META_IGNORE_PATHS, - "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL); - cp = Var_Subst(NULL, - "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, 0); - if (cp) { - str2Lst_Append(metaIgnorePaths, cp, NULL); + Global_Append(MAKE_META_IGNORE_PATHS, + "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}"); + (void)Var_Subst("${" MAKE_META_IGNORE_PATHS ":O:u:tA}", + SCOPE_GLOBAL, VARE_WANTRES, &metaIgnorePathsStr); + /* TODO: handle errors */ + str2Lst_Append(&metaIgnorePaths, metaIgnorePathsStr); + + /* + * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} + */ + value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_PATTERNS); + if (value.str != NULL) { + metaIgnorePatterns = TRUE; + FStr_Done(&value); + } + value = Var_Value(SCOPE_GLOBAL, MAKE_META_IGNORE_FILTER); + if (value.str != NULL) { + metaIgnoreFilter = TRUE; + FStr_Done(&value); } } @@ -643,9 +681,10 @@ meta_job_start(Job *job, GNode *gn) #endif #ifdef USE_FILEMON if (pbm->mfp != NULL && useFilemon) { - filemon_open(pbm); + meta_open_filemon(pbm); } else { - pbm->mon_fd = pbm->filemon_fd = -1; + pbm->mon_fd = -1; + pbm->filemon = NULL; } #endif } @@ -667,11 +706,11 @@ meta_job_child(Job *job) } if (pbm->mfp != NULL) { close(fileno(pbm->mfp)); - if (useFilemon) { + if (useFilemon && pbm->filemon != NULL) { pid_t pid; pid = getpid(); - if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) { + if (filemon_setpid_child(pbm->filemon, pid) == -1) { err(1, "Could not set filemon pid!"); } } @@ -680,31 +719,81 @@ meta_job_child(Job *job) } void -meta_job_error(Job *job, GNode *gn, int flags, int status) +meta_job_parent(Job *job, pid_t pid) +{ +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + if (useFilemon && pbm->filemon != NULL) { + filemon_setpid_parent(pbm->filemon, pid); + } +#endif +} + +int +meta_job_fd(Job *job) +{ +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + if (useFilemon && pbm->filemon != NULL) { + return filemon_readfd(pbm->filemon); + } +#endif + return -1; +} + +int +meta_job_event(Job *job) +{ +#if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + if (useFilemon && pbm->filemon != NULL) { + return filemon_process(pbm->filemon); + } +#endif + return 0; +} + +void +meta_job_error(Job *job, GNode *gn, Boolean ignerr, int status) { char cwd[MAXPATHLEN]; BuildMon *pbm; if (job != NULL) { pbm = &job->bm; - } else { - if (!gn) + if (gn == NULL) gn = job->node; + } else { pbm = &Mybm; } if (pbm->mfp != NULL) { - fprintf(pbm->mfp, "*** Error code %d%s\n", - status, - (flags & JOB_IGNERR) ? - "(ignored)" : ""); + fprintf(pbm->mfp, "\n*** Error code %d%s\n", + status, ignerr ? "(ignored)" : ""); } - if (gn) { - Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0); - } - getcwd(cwd, sizeof(cwd)); - Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0); - if (pbm && pbm->meta_fname[0]) { - Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0); + if (gn != NULL) + Global_Set(".ERROR_TARGET", GNode_Path(gn)); + getcwd(cwd, sizeof cwd); + Global_Set(".ERROR_CWD", cwd); + if (pbm->meta_fname[0] != '\0') { + Global_Set(".ERROR_META_FILE", pbm->meta_fname); } meta_job_finish(job); } @@ -713,7 +802,7 @@ void meta_job_output(Job *job, char *cp, const char *nl) { BuildMon *pbm; - + if (job != NULL) { pbm = &job->bm; } else { @@ -722,48 +811,67 @@ meta_job_output(Job *job, char *cp, const char *nl) if (pbm->mfp != NULL) { if (metaVerbose) { static char *meta_prefix = NULL; - static int meta_prefix_len; + static size_t meta_prefix_len; - if (!meta_prefix) { + if (meta_prefix == NULL) { char *cp2; - meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", VAR_GLOBAL, 0); - if ((cp2 = strchr(meta_prefix, '$'))) - meta_prefix_len = cp2 - meta_prefix; + (void)Var_Subst("${" MAKE_META_PREFIX "}", + SCOPE_GLOBAL, VARE_WANTRES, &meta_prefix); + /* TODO: handle errors */ + if ((cp2 = strchr(meta_prefix, '$')) != NULL) + meta_prefix_len = (size_t)(cp2 - meta_prefix); else meta_prefix_len = strlen(meta_prefix); } if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) { - cp = strchr(cp+1, '\n'); - if (!cp++) + cp = strchr(cp + 1, '\n'); + if (cp == NULL) return; + cp++; } } fprintf(pbm->mfp, "%s%s", cp, nl); } } -void +int meta_cmd_finish(void *pbmp) { -#ifdef USE_FILEMON + int error = 0; BuildMon *pbm = pbmp; +#ifdef USE_FILEMON + int x; +#endif - if (!pbm) + if (pbm == NULL) pbm = &Mybm; - if (pbm->filemon_fd >= 0) { - close(pbm->filemon_fd); - filemon_read(pbm->mfp, pbm->mon_fd); - pbm->filemon_fd = pbm->mon_fd = -1; +#ifdef USE_FILEMON + if (pbm->filemon != NULL) { + while (filemon_process(pbm->filemon) > 0) + continue; + if (filemon_close(pbm->filemon) == -1) + error = errno; + x = filemon_read(pbm->mfp, pbm->mon_fd); + if (error == 0 && x != 0) + error = x; + pbm->mon_fd = -1; + pbm->filemon = NULL; + return error; } #endif + + fprintf(pbm->mfp, "\n"); /* ensure end with newline */ + return error; } -void +int meta_job_finish(Job *job) { BuildMon *pbm; + int error = 0; + int x; if (job != NULL) { pbm = &job->bm; @@ -771,18 +879,30 @@ meta_job_finish(Job *job) pbm = &Mybm; } if (pbm->mfp != NULL) { - meta_cmd_finish(pbm); - fclose(pbm->mfp); + error = meta_cmd_finish(pbm); + x = fclose(pbm->mfp); + if (error == 0 && x != 0) + error = errno; pbm->mfp = NULL; pbm->meta_fname[0] = '\0'; } + return error; +} + +void +meta_finish(void) +{ + Lst_Done(&metaBailiwick); + free(metaBailiwickStr); + Lst_Done(&metaIgnorePaths); + free(metaIgnorePathsStr); } /* * Fetch a full line from fp - growing bufp if needed * Return length in bufp. */ -static int +static int fgetLine(char **bufp, size_t *szp, int o, FILE *fp) { char *buf = *bufp; @@ -790,9 +910,9 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) struct stat fs; int x; - if (fgets(&buf[o], bufsz - o, fp) != NULL) { + if (fgets(&buf[o], (int)bufsz - o, fp) != NULL) { check_newline: - x = o + strlen(&buf[o]); + x = o + (int)strlen(&buf[o]); if (buf[x - 1] == '\n') return x; /* @@ -803,45 +923,117 @@ fgetLine(char **bufp, size_t *szp, int o, FILE *fp) size_t newsz; char *p; - newsz = ROUNDUP((fs.st_size / 2), BUFSIZ); + newsz = ROUNDUP(((size_t)fs.st_size / 2), BUFSIZ); if (newsz <= bufsz) - newsz = ROUNDUP(fs.st_size, BUFSIZ); - if (DEBUG(META)) - fprintf(debug_file, "growing buffer %zu -> %zu\n", - bufsz, newsz); + newsz = ROUNDUP((size_t)fs.st_size, BUFSIZ); + if (newsz <= bufsz) + return x; /* truncated */ + DEBUG2(META, "growing buffer %u -> %u\n", + (unsigned)bufsz, (unsigned)newsz); p = bmake_realloc(buf, newsz); - if (p) { - *bufp = buf = p; - *szp = bufsz = newsz; - /* fetch the rest */ - if (!fgets(&buf[x], bufsz - x, fp)) - return x; /* truncated! */ - goto check_newline; - } + *bufp = buf = p; + *szp = bufsz = newsz; + /* fetch the rest */ + if (fgets(&buf[x], (int)bufsz - x, fp) == NULL) + return x; /* truncated! */ + goto check_newline; } } return 0; } -static int -prefix_match(void *p, void *q) +static Boolean +prefix_match(const char *prefix, const char *path) { - const char *prefix = p; - const char *path = q; size_t n = strlen(prefix); - return (0 == strncmp(path, prefix, n)); + return strncmp(path, prefix, n) == 0; } -static int -string_match(const void *p, const void *q) +static Boolean +has_any_prefix(const char *path, StringList *prefixes) { - const char *p1 = p; - const char *p2 = q; + StringListNode *ln; - return strcmp(p1, p2); + for (ln = prefixes->first; ln != NULL; ln = ln->next) + if (prefix_match(ln->datum, path)) + return TRUE; + return FALSE; } +/* See if the path equals prefix or starts with "prefix/". */ +static Boolean +path_starts_with(const char *path, const char *prefix) +{ + size_t n = strlen(prefix); + + if (strncmp(path, prefix, n) != 0) + return FALSE; + return path[n] == '\0' || path[n] == '/'; +} + +static Boolean +meta_ignore(GNode *gn, const char *p) +{ + char fname[MAXPATHLEN]; + + if (p == NULL) + return TRUE; + + if (*p == '/') { + cached_realpath(p, fname); /* clean it up */ + if (has_any_prefix(fname, &metaIgnorePaths)) { +#ifdef DEBUG_META_MODE + DEBUG1(META, "meta_oodate: ignoring path: %s\n", p); +#endif + return TRUE; + } + } + + if (metaIgnorePatterns) { + const char *expr; + char *pm; + + /* + * XXX: This variable is set on a target GNode but is not one of + * the usual local variables. It should be deleted afterwards. + * Ideally it would not be created in the first place, just like + * in a .for loop. + */ + Var_Set(gn, ".p.", p); + expr = "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}"; + (void)Var_Subst(expr, gn, VARE_WANTRES, &pm); + /* TODO: handle errors */ + if (pm[0] != '\0') { +#ifdef DEBUG_META_MODE + DEBUG1(META, "meta_oodate: ignoring pattern: %s\n", p); +#endif + free(pm); + return TRUE; + } + free(pm); + } + + if (metaIgnoreFilter) { + char *fm; + + /* skip if filter result is empty */ + snprintf(fname, sizeof fname, + "${%s:L:${%s:ts:}}", + p, MAKE_META_IGNORE_FILTER); + (void)Var_Subst(fname, gn, VARE_WANTRES, &fm); + /* TODO: handle errors */ + if (*fm == '\0') { +#ifdef DEBUG_META_MODE + DEBUG1(META, "meta_oodate: ignoring filtered: %s\n", p); +#endif + free(fm); + return TRUE; + } + free(fm); + } + return FALSE; +} /* * When running with 'meta' functionality, a target can be out-of-date @@ -856,7 +1048,7 @@ string_match(const void *p, const void *q) * if we detect this we want to reproduce it. * Setting oodate TRUE will have that effect. */ -#define CHECK_VALID_META(p) if (!(p && *p)) { \ +#define CHECK_VALID_META(p) if (!(p != NULL && *p != '\0')) { \ warnx("%s: %d: malformed", fname, lineno); \ oodate = TRUE; \ continue; \ @@ -865,10 +1057,21 @@ string_match(const void *p, const void *q) #define DEQUOTE(p) if (*p == '\'') { \ char *ep; \ p++; \ - if ((ep = strchr(p, '\''))) \ + if ((ep = strchr(p, '\'')) != NULL) \ *ep = '\0'; \ } +static void +append_if_new(StringList *list, const char *str) +{ + StringListNode *ln; + + for (ln = list->first; ln != NULL; ln = ln->next) + if (strcmp(ln->datum, str) == 0) + return; + Lst_Append(list, bmake_strdup(str)); +} + Boolean meta_oodate(GNode *gn, Boolean oodate) { @@ -882,20 +1085,30 @@ meta_oodate(GNode *gn, Boolean oodate) char fname1[MAXPATHLEN]; char fname2[MAXPATHLEN]; char fname3[MAXPATHLEN]; + FStr dname; + const char *tname; char *p; - char *cp; char *link_src; char *move_target; static size_t cwdlen = 0; static size_t tmplen = 0; FILE *fp; Boolean needOODATE = FALSE; - Lst missingFiles; - + StringList missingFiles; + Boolean have_filemon = FALSE; + if (oodate) return oodate; /* we're done */ - missingFiles = Lst_Init(FALSE); + dname = Var_Value(gn, ".OBJDIR"); + tname = GNode_VarTarget(gn); + + /* if this succeeds fname3 is realpath of dname */ + if (!meta_needed(gn, dname.str, fname3, FALSE)) + goto oodate_out; + dname.str = fname3; + + Lst_Init(&missingFiles); /* * We need to check if the target is out-of-date. This includes @@ -905,11 +1118,10 @@ meta_oodate(GNode *gn, Boolean oodate) */ Make_DoAllVar(gn); - meta_name(gn, fname, sizeof(fname), NULL, NULL); + meta_name(fname, sizeof fname, dname.str, tname, dname.str); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: %s\n", fname); + DEBUG1(META, "meta_oodate: %s\n", fname); #endif if ((fp = fopen(fname, "r")) != NULL) { @@ -918,33 +1130,32 @@ meta_oodate(GNode *gn, Boolean oodate) int lineno = 0; int lastpid = 0; int pid; - int f = 0; int x; - LstNode ln; - struct stat fs; + StringListNode *cmdNode; + struct cached_stat cst; - if (!buf) { + if (buf == NULL) { bufsz = 8 * BUFSIZ; buf = bmake_malloc(bufsz); } - if (!cwdlen) { - if (getcwd(cwd, sizeof(cwd)) == NULL) + if (cwdlen == 0) { + if (getcwd(cwd, sizeof cwd) == NULL) err(1, "Could not get current working directory"); cwdlen = strlen(cwd); } - strlcpy(lcwd, cwd, sizeof(lcwd)); - strlcpy(latestdir, cwd, sizeof(latestdir)); + strlcpy(lcwd, cwd, sizeof lcwd); + strlcpy(latestdir, cwd, sizeof latestdir); - if (!tmpdir) { + if (tmpdir == NULL) { tmpdir = getTmpdir(); tmplen = strlen(tmpdir); } /* we want to track all the .meta we read */ - Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); + Global_Append(".MAKE.META.FILES", fname); - ln = Lst_First(gn->commands); + cmdNode = gn->commands.first; while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { lineno++; if (buf[x - 1] == '\n') @@ -957,25 +1168,24 @@ meta_oodate(GNode *gn, Boolean oodate) link_src = NULL; move_target = NULL; /* Find the start of the build monitor section. */ - if (!f) { + if (!have_filemon) { if (strncmp(buf, "-- filemon", 10) == 0) { - f = 1; + have_filemon = TRUE; continue; } if (strncmp(buf, "# buildmon", 10) == 0) { - f = 1; + have_filemon = TRUE; continue; } - } + } /* Delimit the record type. */ p = buf; #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); + DEBUG3(META, "%s: %d: %s\n", fname, lineno, buf); #endif strsep(&p, " "); - if (f) { + if (have_filemon) { /* * We are in the 'filemon' output section. * Each record from filemon follows the general form: @@ -995,7 +1205,7 @@ meta_oodate(GNode *gn, Boolean oodate) /* * We need to track pathnames per-process. * - * Each process run by make, starts off in the 'CWD' + * Each process run by make starts off in the 'CWD' * recorded in the .meta file, if it chdirs ('C') * elsewhere we need to track that - but only for * that process. If it forks ('F'), we initialize @@ -1014,28 +1224,25 @@ meta_oodate(GNode *gn, Boolean oodate) CHECK_VALID_META(p); pid = atoi(p); if (pid > 0 && pid != lastpid) { - char *ldir; - char *tp; - + FStr ldir; + if (lastpid > 0) { /* We need to remember these. */ - Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); - Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0); + Global_SetExpand(lcwd_vname, lcwd); + Global_SetExpand(ldir_vname, latestdir); } - snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); - snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); + snprintf(lcwd_vname, sizeof lcwd_vname, LCWD_VNAME_FMT, pid); + snprintf(ldir_vname, sizeof ldir_vname, LDIR_VNAME_FMT, pid); lastpid = pid; - ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); - if (ldir) { - strlcpy(latestdir, ldir, sizeof(latestdir)); - if (tp) - free(tp); + ldir = Var_Value(SCOPE_GLOBAL, ldir_vname); + if (ldir.str != NULL) { + strlcpy(latestdir, ldir.str, sizeof latestdir); + FStr_Done(&ldir); } - ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); - if (ldir) { - strlcpy(lcwd, ldir, sizeof(lcwd)); - if (tp) - free(tp); + ldir = Var_Value(SCOPE_GLOBAL, lcwd_vname); + if (ldir.str != NULL) { + strlcpy(lcwd, ldir.str, sizeof lcwd); + FStr_Done(&ldir); } } /* Skip past the pid. */ @@ -1043,9 +1250,9 @@ meta_oodate(GNode *gn, Boolean oodate) continue; #ifdef DEBUG_META_MODE if (DEBUG(META)) - fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", - fname, lineno, - pid, buf[0], cwd, lcwd, latestdir); + debug_printf("%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + pid, buf[0], cwd, lcwd, latestdir); #endif break; } @@ -1055,8 +1262,8 @@ meta_oodate(GNode *gn, Boolean oodate) /* Process according to record type. */ switch (buf[0]) { case 'X': /* eXit */ - Var_Delete(lcwd_vname, VAR_GLOBAL); - Var_Delete(ldir_vname, VAR_GLOBAL); + Var_DeleteExpand(SCOPE_GLOBAL, lcwd_vname); + Var_DeleteExpand(SCOPE_GLOBAL, ldir_vname); lastpid = 0; /* no need to save ldir_vname */ break; @@ -1067,15 +1274,16 @@ meta_oodate(GNode *gn, Boolean oodate) child = atoi(p); if (child > 0) { - snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); - Var_Set(cldir, lcwd, VAR_GLOBAL, 0); - snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); - Var_Set(cldir, latestdir, VAR_GLOBAL, 0); + snprintf(cldir, sizeof cldir, LCWD_VNAME_FMT, child); + Global_SetExpand(cldir, lcwd); + snprintf(cldir, sizeof cldir, LDIR_VNAME_FMT, child); + Global_SetExpand(cldir, latestdir); #ifdef DEBUG_META_MODE if (DEBUG(META)) - fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", - fname, lineno, - child, cwd, lcwd, latestdir); + debug_printf( + "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + child, cwd, lcwd, latestdir); #endif } } @@ -1083,13 +1291,13 @@ meta_oodate(GNode *gn, Boolean oodate) case 'C': /* Chdir */ /* Update lcwd and latest directory. */ - strlcpy(latestdir, p, sizeof(latestdir)); - strlcpy(lcwd, p, sizeof(lcwd)); - Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); - Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0); + strlcpy(latestdir, p, sizeof latestdir); + strlcpy(lcwd, p, sizeof lcwd); + Global_SetExpand(lcwd_vname, lcwd); + Global_SetExpand(ldir_vname, lcwd); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); + DEBUG4(META, "%s: %d: cwd=%s ldir=%s\n", + fname, lineno, cwd, lcwd); #endif break; @@ -1099,33 +1307,37 @@ meta_oodate(GNode *gn, Boolean oodate) * the src as for 'R'ead * and the target as for 'W'rite. */ - cp = p; /* save this for a second */ - /* now get target */ - if (strsep(&p, " ") == NULL) - continue; - CHECK_VALID_META(p); - move_target = p; - p = cp; + { + char *cp = p; /* save this for a second */ + /* now get target */ + if (strsep(&p, " ") == NULL) + continue; + CHECK_VALID_META(p); + move_target = p; + p = cp; + } /* 'L' and 'M' put single quotes around the args */ DEQUOTE(p); DEQUOTE(move_target); /* FALLTHROUGH */ case 'D': /* unlink */ - if (*p == '/' && !Lst_IsEmpty(missingFiles)) { - /* remove p from the missingFiles list if present */ - if ((ln = Lst_Find(missingFiles, p, string_match)) != NULL) { - char *tp = Lst_Datum(ln); - Lst_Remove(missingFiles, ln); - free(tp); - ln = NULL; /* we're done with it */ + if (*p == '/') { + /* remove any missingFiles entries that match p */ + StringListNode *ln = missingFiles.first; + while (ln != NULL) { + StringListNode *next = ln->next; + if (path_starts_with(ln->datum, p)) { + free(ln->datum); + Lst_Remove(&missingFiles, ln); + } + ln = next; } } if (buf[0] == 'M') { /* the target of the mv is a file 'W'ritten */ #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: M %s -> %s\n", - p, move_target); + DEBUG2(META, "meta_oodate: M %s -> %s\n", + p, move_target); #endif p = move_target; goto check_write; @@ -1146,9 +1358,7 @@ meta_oodate(GNode *gn, Boolean oodate) DEQUOTE(p); DEQUOTE(link_src); #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: L %s -> %s\n", - link_src, p); + DEBUG2(META, "meta_oodate: L %s -> %s\n", link_src, p); #endif /* FALLTHROUGH */ case 'W': /* Write */ @@ -1156,20 +1366,20 @@ meta_oodate(GNode *gn, Boolean oodate) /* * If a file we generated within our bailiwick * but outside of .OBJDIR is missing, - * we need to do it again. + * we need to do it again. */ /* ignore non-absolute paths */ if (*p != '/') break; - if (Lst_IsEmpty(metaBailiwick)) + if (Lst_IsEmpty(&metaBailiwick)) break; /* ignore cwd - normal dependencies handle those */ if (strncmp(p, cwd, cwdlen) == 0) break; - if (!Lst_ForEach(metaBailiwick, prefix_match, p)) + if (!has_any_prefix(p, &metaBailiwick)) break; /* tmpdir might be within */ @@ -1177,19 +1387,21 @@ meta_oodate(GNode *gn, Boolean oodate) break; /* ignore anything containing the string "tmp" */ - if ((strstr("tmp", p))) + /* XXX: The arguments to strstr must be swapped. */ + if (strstr("tmp", p) != NULL) break; - if (stat(p, &fs) < 0) { - Lst_AtEnd(missingFiles, bmake_strdup(p)); + if ((link_src != NULL && cached_lstat(p, &cst) < 0) || + (link_src == NULL && cached_stat(p, &cst) < 0)) { + if (!meta_ignore(gn, p)) + append_if_new(&missingFiles, p); } break; check_link_src: p = link_src; link_src = NULL; #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: L src %s\n", p); + DEBUG1(META, "meta_oodate: L src %s\n", p); #endif /* FALLTHROUGH */ case 'R': /* Read */ @@ -1199,15 +1411,8 @@ meta_oodate(GNode *gn, Boolean oodate) * be part of the dependencies because * they are _expected_ to change. */ - if (*p == '/' && - Lst_ForEach(metaIgnorePaths, prefix_match, p)) { -#ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "meta_oodate: ignoring: %s\n", - p); -#endif + if (meta_ignore(gn, p)) break; - } /* * The rest of the record is the file name. @@ -1217,54 +1422,54 @@ meta_oodate(GNode *gn, Boolean oodate) char *sdirs[4]; char **sdp; int sdx = 0; - int found = 0; + Boolean found = FALSE; if (*p == '/') { sdirs[sdx++] = p; /* done */ } else { if (strcmp(".", p) == 0) - continue; /* no point */ + continue; /* no point */ /* Check vs latestdir */ - snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); + snprintf(fname1, sizeof fname1, "%s/%s", latestdir, p); sdirs[sdx++] = fname1; if (strcmp(latestdir, lcwd) != 0) { /* Check vs lcwd */ - snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); + snprintf(fname2, sizeof fname2, "%s/%s", lcwd, p); sdirs[sdx++] = fname2; } if (strcmp(lcwd, cwd) != 0) { /* Check vs cwd */ - snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); + snprintf(fname3, sizeof fname3, "%s/%s", cwd, p); sdirs[sdx++] = fname3; } } sdirs[sdx++] = NULL; - for (sdp = sdirs; *sdp && !found; sdp++) { + for (sdp = sdirs; *sdp != NULL && !found; sdp++) { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); + DEBUG3(META, "%s: %d: looking for: %s\n", + fname, lineno, *sdp); #endif - if (stat(*sdp, &fs) == 0) { - found = 1; + if (cached_stat(*sdp, &cst) == 0) { + found = TRUE; p = *sdp; } } if (found) { #ifdef DEBUG_META_MODE - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); + DEBUG3(META, "%s: %d: found: %s\n", + fname, lineno, p); #endif - if (!S_ISDIR(fs.st_mode) && - fs.st_mtime > gn->mtime) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); + if (!S_ISDIR(cst.cst_mode) && + cst.cst_mtime > gn->mtime) { + DEBUG3(META, "%s: %d: file '%s' is newer than the target...\n", + fname, lineno, p); oodate = TRUE; - } else if (S_ISDIR(fs.st_mode)) { + } else if (S_ISDIR(cst.cst_mode)) { /* Update the latest directory. */ - realpath(p, latestdir); + cached_realpath(p, latestdir); } } else if (errno == ENOENT && *p == '/' && strncmp(p, cwd, cwdlen) != 0) { @@ -1272,14 +1477,12 @@ meta_oodate(GNode *gn, Boolean oodate) * A referenced file outside of CWD is missing. * We cannot catch every eventuality here... */ - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: file '%s' may have moved?...\n", fname, lineno, p); - oodate = TRUE; + append_if_new(&missingFiles, p); } } if (buf[0] == 'E') { /* previous latestdir is no longer relevant */ - strlcpy(latestdir, lcwd, sizeof(latestdir)); + strlcpy(latestdir, lcwd, sizeof latestdir); } break; default: @@ -1292,29 +1495,31 @@ meta_oodate(GNode *gn, Boolean oodate) * Compare the current command with the one in the * meta data file. */ - if (ln == NULL) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); + if (cmdNode == NULL) { + DEBUG2(META, "%s: %d: there were more build commands in the meta data file than there are now...\n", + fname, lineno); oodate = TRUE; } else { - char *cmd = (char *)Lst_Datum(ln); + const char *cp; + char *cmd = cmdNode->datum; Boolean hasOODATE = FALSE; - if (strstr(cmd, "$?")) + if (strstr(cmd, "$?") != NULL) hasOODATE = TRUE; - else if ((cp = strstr(cmd, ".OODATE"))) { + else if ((cp = strstr(cmd, ".OODATE")) != NULL) { /* check for $[{(].OODATE[:)}] */ if (cp > cmd + 2 && cp[-2] == '$') hasOODATE = TRUE; } if (hasOODATE) { needOODATE = TRUE; - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); + DEBUG2(META, "%s: %d: cannot compare command using .OODATE\n", + fname, lineno); } - cmd = Var_Subst(NULL, cmd, gn, TRUE); + (void)Var_Subst(cmd, gn, VARE_UNDEFERR, &cmd); + /* TODO: handle errors */ - if ((cp = strchr(cmd, '\n'))) { + if ((cp = strchr(cmd, '\n')) != NULL) { int n; /* @@ -1334,66 +1539,85 @@ meta_oodate(GNode *gn, Boolean oodate) warnx("%s: %d: line truncated at %u", fname, lineno, x); break; } - cp = strchr(++cp, '\n'); - } while (cp); + cp = strchr(cp + 1, '\n'); + } while (cp != NULL); if (buf[x - 1] == '\n') buf[x - 1] = '\0'; } - if (!hasOODATE && + if (p != NULL && + !hasOODATE && !(gn->type & OP_NOMETA_CMP) && - strcmp(p, cmd) != 0) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); + (strcmp(p, cmd) != 0)) { + DEBUG4(META, "%s: %d: a build command has changed\n%s\nvs\n%s\n", + fname, lineno, p, cmd); if (!metaIgnoreCMDs) oodate = TRUE; } free(cmd); - ln = Lst_Succ(ln); + cmdNode = cmdNode->next; } } else if (strcmp(buf, "CWD") == 0) { /* * Check if there are extra commands now * that weren't in the meta data file. */ - if (!oodate && ln != NULL) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); + if (!oodate && cmdNode != NULL) { + DEBUG2(META, "%s: %d: there are extra build commands now that weren't in the meta data file\n", + fname, lineno); oodate = TRUE; } + CHECK_VALID_META(p); if (strcmp(p, cwd) != 0) { - if (DEBUG(META)) - fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); + DEBUG4(META, "%s: %d: the current working directory has changed from '%s' to '%s'\n", + fname, lineno, p, curdir); oodate = TRUE; } } } fclose(fp); - if (!Lst_IsEmpty(missingFiles)) { - if (DEBUG(META)) - fprintf(debug_file, "%s: missing files: %s...\n", - fname, (char *)Lst_Datum(Lst_First(missingFiles))); + if (!Lst_IsEmpty(&missingFiles)) { + DEBUG2(META, "%s: missing files: %s...\n", + fname, (char *)missingFiles.first->datum); + oodate = TRUE; + } + if (!oodate && !have_filemon && filemonMissing) { + DEBUG1(META, "%s: missing filemon data\n", fname); oodate = TRUE; - Lst_Destroy(missingFiles, (FreeProc *)free); } } else { - if ((gn->type & OP_META)) { - if (DEBUG(META)) - fprintf(debug_file, "%s: required but missing\n", fname); - oodate = TRUE; + if (writeMeta && (metaMissing || (gn->type & OP_META))) { + const char *cp = NULL; + + /* if target is in .CURDIR we do not need a meta file */ + if (gn->path != NULL && (cp = strrchr(gn->path, '/')) != NULL && + (cp > gn->path)) { + if (strncmp(curdir, gn->path, (size_t)(cp - gn->path)) != 0) { + cp = NULL; /* not in .CURDIR */ + } + } + if (cp == NULL) { + DEBUG1(META, "%s: required but missing\n", fname); + oodate = TRUE; + needOODATE = TRUE; /* assume the worst */ + } } } + + Lst_DoneCall(&missingFiles, free); + if (oodate && needOODATE) { /* * Target uses .OODATE which is empty; or we wouldn't be here. * We have decided it is oodate, so .OODATE needs to be set. * All we can sanely do is set it to .ALLSRC. */ - Var_Delete(OODATE, gn); - Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0); - if (cp) - free(cp); + Var_Delete(gn, OODATE); + Var_Set(gn, OODATE, GNode_VarAllsrc(gn)); } + + oodate_out: + FStr_Done(&dname); return oodate; } @@ -1409,44 +1633,85 @@ meta_compat_start(void) * We need to re-open filemon for each cmd. */ BuildMon *pbm = &Mybm; - + if (pbm->mfp != NULL && useFilemon) { - filemon_open(pbm); + meta_open_filemon(pbm); } else { - pbm->mon_fd = pbm->filemon_fd = -1; + pbm->mon_fd = -1; + pbm->filemon = NULL; } #endif if (pipe(childPipe) < 0) Punt("Cannot create pipe: %s", strerror(errno)); /* Set close-on-exec flag for both */ - (void)fcntl(childPipe[0], F_SETFD, 1); - (void)fcntl(childPipe[1], F_SETFD, 1); + (void)fcntl(childPipe[0], F_SETFD, FD_CLOEXEC); + (void)fcntl(childPipe[1], F_SETFD, FD_CLOEXEC); } void meta_compat_child(void) { meta_job_child(NULL); - if (dup2(childPipe[1], 1) < 0 || - dup2(1, 2) < 0) { - execError("dup2", "pipe"); - _exit(1); - } + if (dup2(childPipe[1], 1) < 0 || dup2(1, 2) < 0) + execDie("dup2", "pipe"); } void -meta_compat_parent(void) +meta_compat_parent(pid_t child) { - FILE *fp; - char buf[BUFSIZ]; - + int outfd, metafd, maxfd, nfds; + char buf[BUFSIZ+1]; + fd_set readfds; + + meta_job_parent(NULL, child); close(childPipe[1]); /* child side */ - fp = fdopen(childPipe[0], "r"); - while (fgets(buf, sizeof(buf), fp)) { - meta_job_output(NULL, buf, ""); - printf("%s", buf); + outfd = childPipe[0]; +#ifdef USE_FILEMON + metafd = Mybm.filemon != NULL ? filemon_readfd(Mybm.filemon) : -1; +#else + metafd = -1; +#endif + maxfd = -1; + if (outfd > maxfd) + maxfd = outfd; + if (metafd > maxfd) + maxfd = metafd; + + while (outfd != -1 || metafd != -1) { + FD_ZERO(&readfds); + if (outfd != -1) { + FD_SET(outfd, &readfds); + } + if (metafd != -1) { + FD_SET(metafd, &readfds); + } + nfds = select(maxfd + 1, &readfds, NULL, NULL, NULL); + if (nfds == -1) { + if (errno == EINTR) + continue; + err(1, "select"); + } + + if (outfd != -1 && FD_ISSET(outfd, &readfds) != 0) do { + /* XXX this is not line-buffered */ + ssize_t nread = read(outfd, buf, sizeof buf - 1); + if (nread == -1) + err(1, "read"); + if (nread == 0) { + close(outfd); + outfd = -1; + break; + } + fwrite(buf, 1, (size_t)nread, stdout); + fflush(stdout); + buf[nread] = '\0'; + meta_job_output(NULL, buf, ""); + } while (/*CONSTCOND*/FALSE); + if (metafd != -1 && FD_ISSET(metafd, &readfds) != 0) { + if (meta_job_event(NULL) <= 0) + metafd = -1; + } } - fclose(fp); } -#endif /* USE_META */ +#endif /* USE_META */ diff --git a/usr.bin/make/meta.h b/usr.bin/make/meta.h index 57c73ca9b..1fc8910d3 100644 --- a/usr.bin/make/meta.h +++ b/usr.bin/make/meta.h @@ -1,23 +1,23 @@ -/* $NetBSD: meta.h,v 1.3 2013/03/23 05:31:29 sjg Exp $ */ +/* $NetBSD: meta.h,v 1.9 2020/12/10 20:49:11 rillig Exp $ */ /* * Things needed for 'meta' mode. */ /* * Copyright (c) 2009-2010, Juniper Networks, Inc. - * + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: + * modification, are permitted provided that the following conditions + * are met: * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. + * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * + * from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -28,28 +28,33 @@ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ typedef struct BuildMon { char meta_fname[MAXPATHLEN]; - int filemon_fd; + struct filemon *filemon; int mon_fd; FILE *mfp; } BuildMon; -extern Boolean useMeta; +struct Job; -struct Job; /* not defined yet */ void meta_init(void); +void meta_finish(void); void meta_mode_init(const char *); void meta_job_start(struct Job *, GNode *); void meta_job_child(struct Job *); -void meta_job_error(struct Job *, GNode *, int, int); +void meta_job_parent(struct Job *, pid_t); +int meta_job_fd(struct Job *); +int meta_job_event(struct Job *); +void meta_job_error(struct Job *, GNode *, Boolean, int); void meta_job_output(struct Job *, char *, const char *); -void meta_cmd_finish(void *); -void meta_job_finish(struct Job *); +int meta_cmd_finish(void *); +int meta_job_finish(struct Job *); Boolean meta_oodate(GNode *, Boolean); void meta_compat_start(void); void meta_compat_child(void); -void meta_compat_parent(void); +void meta_compat_parent(pid_t); + +extern Boolean useMeta; diff --git a/usr.bin/make/metachar.c b/usr.bin/make/metachar.c index 49603383e..dcb049dff 100644 --- a/usr.bin/make/metachar.c +++ b/usr.bin/make/metachar.c @@ -1,6 +1,6 @@ -/* $NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $ */ +/* $NetBSD: metachar.c,v 1.9 2021/01/19 20:51:46 rillig Exp $ */ -/*- +/* * Copyright (c) 2015 The NetBSD Foundation, Inc. * All rights reserved. * @@ -37,52 +37,48 @@ #include #endif -#if defined(__RCSID) && !defined(lint) -__RCSID("$NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $"); -#endif - #include "metachar.h" + +MAKE_RCSID("$NetBSD: metachar.c,v 1.9 2021/01/19 20:51:46 rillig Exp $"); + /* * The following array is used to make a fast determination of which * characters are interpreted specially by the shell. If a command * contains any of these characters, it is executed by the shell, not * directly by us. - * - * perhaps move it to ctype? */ unsigned char _metachar[128] = { -// nul soh stx etx eot enq ack bel +/* nul soh stx etx eot enq ack bel */ 1, 0, 0, 0, 0, 0, 0, 0, -// bs ht nl vt np cr so si +/* bs ht nl vt np cr so si */ 0, 0, 1, 0, 0, 0, 0, 0, -// dle dc1 dc2 dc3 dc4 nak syn etb +/* dle dc1 dc2 dc3 dc4 nak syn etb */ 0, 0, 0, 0, 0, 0, 0, 0, -// can em sub esc fs gs rs us +/* can em sub esc fs gs rs us */ 0, 0, 0, 0, 0, 0, 0, 0, -// sp ! " # $ % & ' +/* sp ! " # $ % & ' */ 0, 1, 1, 1, 1, 0, 1, 1, -// ( ) * + , - . / +/* ( ) * + , - . / */ 1, 1, 1, 0, 0, 0, 0, 0, -// 0 1 2 3 4 5 6 7 +/* 0 1 2 3 4 5 6 7 */ 0, 0, 0, 0, 0, 0, 0, 0, -// 8 9 : ; < = > ? +/* 8 9 : ; < = > ? */ 0, 0, 0, 1, 1, 0, 1, 1, -// @ A B C D E F G +/* @ A B C D E F G */ 0, 0, 0, 0, 0, 0, 0, 0, -// H I J K L M N O +/* H I J K L M N O */ 0, 0, 0, 0, 0, 0, 0, 0, -// P Q R S T U V W +/* P Q R S T U V W */ 0, 0, 0, 0, 0, 0, 0, 0, -// X Y Z [ \ ] ^ _ +/* X Y Z [ \ ] ^ _ */ 0, 0, 0, 1, 1, 1, 1, 0, -// ` a b c d e f g +/* ` a b c d e f g */ 1, 0, 0, 0, 0, 0, 0, 0, -// h i j k l m n o +/* h i j k l m n o */ 0, 0, 0, 0, 0, 0, 0, 0, -// p q r s t u v w +/* p q r s t u v w */ 0, 0, 0, 0, 0, 0, 0, 0, -// x y z { | } ~ del +/* x y z { | } ~ del */ 0, 0, 0, 1, 1, 1, 1, 0, }; - diff --git a/usr.bin/make/metachar.h b/usr.bin/make/metachar.h index db88d6710..04f967229 100644 --- a/usr.bin/make/metachar.h +++ b/usr.bin/make/metachar.h @@ -1,6 +1,6 @@ -/* $NetBSD: metachar.h,v 1.4 2015/06/21 20:26:02 christos Exp $ */ +/* $NetBSD: metachar.h,v 1.15 2021/01/19 20:51:46 rillig Exp $ */ -/*- +/* * Copyright (c) 2015 The NetBSD Foundation, Inc. * All rights reserved. * @@ -28,34 +28,21 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef _METACHAR_H -#define _METACHAR_H +#ifndef MAKE_METACHAR_H +#define MAKE_METACHAR_H -#include +#include "make.h" extern unsigned char _metachar[]; -#define ismeta(c) _metachar[(c) & 0x7f] +#define is_shell_metachar(c) (_metachar[(c) & 0x7f] != 0) -static inline int -hasmeta(const char *cmd) +MAKE_INLINE Boolean +needshell(const char *cmd) { - while (!ismeta(*cmd)) + while (!is_shell_metachar(*cmd) && *cmd != ':' && *cmd != '=') cmd++; - return *cmd != '\0'; } -static inline int -needshell(const char *cmd, int white) -{ - while (!ismeta(*cmd) && *cmd != ':' && *cmd != '=') { - if (white && isspace((unsigned char)*cmd)) - break; - cmd++; - } - - return *cmd != '\0'; -} - -#endif /* _METACHAR_H */ +#endif /* MAKE_METACHAR_H */ diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h index 742d3f057..863e51b41 100644 --- a/usr.bin/make/nonints.h +++ b/usr.bin/make/nonints.h @@ -1,6 +1,6 @@ -/* $NetBSD: nonints.h,v 1.68 2015/05/05 21:51:09 sjg Exp $ */ +/* $NetBSD: nonints.h,v 1.206 2021/03/15 15:39:13 rillig Exp $ */ -/*- +/* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * @@ -34,7 +34,7 @@ * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 */ -/*- +/* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * @@ -73,32 +73,51 @@ */ /* arch.c */ -ReturnStatus Arch_ParseArchive(char **, Lst, GNode *); -void Arch_Touch(GNode *); -void Arch_TouchLib(GNode *); -time_t Arch_MTime(GNode *); -time_t Arch_MemMTime(GNode *); -void Arch_FindLib(GNode *, Lst); -Boolean Arch_LibOODate(GNode *); void Arch_Init(void); void Arch_End(void); -int Arch_IsLib(GNode *); + +Boolean Arch_ParseArchive(char **, GNodeList *, GNode *); +void Arch_Touch(GNode *); +void Arch_TouchLib(GNode *); +void Arch_UpdateMTime(GNode *gn); +void Arch_UpdateMemberMTime(GNode *gn); +void Arch_FindLib(GNode *, SearchPath *); +Boolean Arch_LibOODate(GNode *); +Boolean Arch_IsLib(GNode *); /* compat.c */ -int CompatRunCommand(void *, void *); -void Compat_Run(Lst); -int Compat_Make(void *, void *); +int Compat_RunCommand(const char *, GNode *, StringListNode *); +void Compat_Run(GNodeList *); +void Compat_Make(GNode *, GNode *); /* cond.c */ -struct If; -int Cond_EvalExpression(const struct If *, char *, Boolean *, int, Boolean); -int Cond_Eval(char *); +CondEvalResult Cond_EvalCondition(const char *, Boolean *); +CondEvalResult Cond_EvalLine(const char *); void Cond_restore_depth(unsigned int); unsigned int Cond_save_depth(void); +/* dir.c; see also dir.h */ + +MAKE_INLINE const char * +str_basename(const char *pathname) +{ + const char *lastSlash = strrchr(pathname, '/'); + return lastSlash != NULL ? lastSlash + 1 : pathname; +} + +MAKE_INLINE SearchPath * +SearchPath_New(void) +{ + SearchPath *path = bmake_malloc(sizeof *path); + Lst_Init(&path->dirs); + return path; +} + +void SearchPath_Free(SearchPath *); + /* for.c */ -int For_Eval(char *); -int For_Accum(char *); +int For_Eval(const char *); +Boolean For_Accum(const char *); void For_Run(int); /* job.c */ @@ -106,92 +125,301 @@ void JobReapChild(pid_t, int, Boolean); /* main.c */ void Main_ParseArgLine(const char *); -void MakeMode(const char *); -int main(int, char **); char *Cmd_Exec(const char *, const char **); void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; void DieHorribly(void) MAKE_ATTR_DEAD; -int PrintAddr(void *, void *); void Finish(int) MAKE_ATTR_DEAD; int eunlink(const char *); -void execError(const char *, const char *); +void execDie(const char *, const char *); char *getTmpdir(void); -Boolean getBoolean(const char *, Boolean); +Boolean ParseBoolean(const char *, Boolean); +char *cached_realpath(const char *, char *); /* parse.c */ -void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); -Boolean Parse_AnyExport(void); -Boolean Parse_IsVar(char *); -void Parse_DoVar(char *, GNode *); -void Parse_AddIncludeDir(char *); -void Parse_File(const char *, int); void Parse_Init(void); void Parse_End(void); -void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *); -Lst Parse_MainName(void); + +typedef enum VarAssignOp { + VAR_NORMAL, /* = */ + VAR_SUBST, /* := */ + VAR_SHELL, /* != or :sh= */ + VAR_APPEND, /* += */ + VAR_DEFAULT /* ?= */ +} VarAssignOp; + +typedef struct VarAssign { + char *varname; /* unexpanded */ + VarAssignOp op; + const char *value; /* unexpanded */ +} VarAssign; + +typedef char *(*ReadMoreProc)(void *, size_t *); + +void Parse_Error(ParseErrorLevel, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +Boolean Parse_IsVar(const char *, VarAssign *out_var); +void Parse_DoVar(VarAssign *, GNode *); +void Parse_AddIncludeDir(const char *); +void Parse_File(const char *, int); +void Parse_SetInput(const char *, int, int, ReadMoreProc, void *); +void Parse_MainName(GNodeList *); +int Parse_GetFatals(void); /* str.c */ -char *str_concat(const char *, const char *, int); -char **brk_string(const char *, int *, Boolean, char **); -char *Str_FindSubstring(const char *, const char *); -int Str_Match(const char *, const char *); -char *Str_SYSVMatch(const char *, const char *, int *len); -void Str_SYSVSubst(Buffer *, char *, char *, int); + +/* A read-only string that may need to be freed after use. */ +typedef struct FStr { + const char *str; + void *freeIt; +} FStr; + +/* A modifiable string that may need to be freed after use. */ +typedef struct MFStr { + char *str; + void *freeIt; +} MFStr; + +typedef struct Words { + char **words; + size_t len; + void *freeIt; +} Words; + +/* Return a string that is the sole owner of str. */ +MAKE_INLINE FStr +FStr_InitOwn(char *str) +{ + return (FStr){ str, str }; +} + +/* Return a string that refers to the shared str. */ +MAKE_INLINE FStr +FStr_InitRefer(const char *str) +{ + return (FStr){ str, NULL }; +} + +MAKE_INLINE void +FStr_Done(FStr *fstr) +{ + free(fstr->freeIt); +#ifdef CLEANUP + fstr->str = NULL; + fstr->freeIt = NULL; +#endif +} + +/* Return a string that is the sole owner of str. */ +MAKE_INLINE MFStr +MFStr_InitOwn(char *str) +{ + return (MFStr){ str, str }; +} + +/* Return a string that refers to the shared str. */ +MAKE_INLINE MFStr +MFStr_InitRefer(char *str) +{ + return (MFStr){ str, NULL }; +} + +MAKE_INLINE void +MFStr_Done(MFStr *mfstr) +{ + free(mfstr->freeIt); +#ifdef CLEANUP + mfstr->str = NULL; + mfstr->freeIt = NULL; +#endif +} + +Words Str_Words(const char *, Boolean); +MAKE_INLINE void +Words_Free(Words w) +{ + free(w.words); + free(w.freeIt); +} + +char *str_concat2(const char *, const char *); +char *str_concat3(const char *, const char *, const char *); +char *str_concat4(const char *, const char *, const char *, const char *); +Boolean Str_Match(const char *, const char *); /* suff.c */ -void Suff_ClearSuffixes(void); -Boolean Suff_IsTransform(char *); -GNode *Suff_AddTransform(char *); -int Suff_EndTransform(void *, void *); -void Suff_AddSuffix(char *, GNode **); -Lst Suff_GetPath(char *); -void Suff_DoPaths(void); -void Suff_AddInclude(char *); -void Suff_AddLib(char *); -void Suff_FindDeps(GNode *); -Lst Suff_FindPath(GNode *); -void Suff_SetNull(char *); void Suff_Init(void); void Suff_End(void); + +void Suff_ClearSuffixes(void); +Boolean Suff_IsTransform(const char *); +GNode *Suff_AddTransform(const char *); +void Suff_EndTransform(GNode *); +void Suff_AddSuffix(const char *, GNode **); +SearchPath *Suff_GetPath(const char *); +void Suff_DoPaths(void); +void Suff_AddInclude(const char *); +void Suff_AddLib(const char *); +void Suff_FindDeps(GNode *); +SearchPath *Suff_FindPath(GNode *); +void Suff_SetNull(const char *); void Suff_PrintAll(void); /* targ.c */ void Targ_Init(void); void Targ_End(void); -Lst Targ_List(void); -GNode *Targ_NewGN(const char *); -GNode *Targ_FindNode(const char *, int); -Lst Targ_FindList(Lst, int); -Boolean Targ_Ignore(GNode *); -Boolean Targ_Silent(GNode *); -Boolean Targ_Precious(GNode *); + +void Targ_Stats(void); +GNodeList *Targ_List(void); +GNode *GNode_New(const char *); +GNode *Targ_FindNode(const char *); +GNode *Targ_GetNode(const char *); +GNode *Targ_NewInternalNode(const char *); +GNode *Targ_GetEndNode(void); +void Targ_FindList(GNodeList *, StringList *); +Boolean Targ_Precious(const GNode *); void Targ_SetMain(GNode *); -int Targ_PrintCmd(void *, void *); -int Targ_PrintNode(void *, void *); -char *Targ_FmtTime(time_t); +void Targ_PrintCmds(GNode *); +void Targ_PrintNode(GNode *, int); +void Targ_PrintNodes(GNodeList *, int); +const char *Targ_FmtTime(time_t); void Targ_PrintType(int); void Targ_PrintGraph(int); void Targ_Propagate(void); -void Targ_Propagate_Wait(void); +const char *GNodeMade_Name(GNodeMade); /* var.c */ -void Var_Delete(const char *, GNode *); -void Var_Set(const char *, const char *, GNode *, int); -void Var_Append(const char *, const char *, GNode *); -Boolean Var_Exists(const char *, GNode *); -char *Var_Value(const char *, GNode *, char **); -char *Var_Parse(const char *, GNode *, Boolean, int *, void **); -char *Var_Subst(const char *, const char *, GNode *, Boolean); -char *Var_GetTail(const char *); -char *Var_GetHead(const char *); void Var_Init(void); void Var_End(void); + +typedef struct VarEvalFlags { + + /* + * Expand and evaluate variables during parsing. + * + * Without this flag, the expression is only parsed, without + * evaluating any part of it. + * + * TODO: Document what Var_Parse and Var_Subst return when this flag + * is not set. As of 2021-03-15, they return unspecified, + * inconsistent results. + */ + Boolean wantRes: 1; + + /* + * Treat undefined variables as errors. + * Must only be used in combination with wantRes. + */ + Boolean undefErr: 1; + + /* + * Keep '$$' as '$$' instead of reducing it to a single '$'. + * + * Used in variable assignments using the ':=' operator. It allows + * multiple such assignments to be chained without accidentally + * expanding '$$file' to '$file' in the first assignment and + * interpreting it as '${f}' followed by 'ile' in the next assignment. + */ + Boolean keepDollar: 1; + + /* + * Keep undefined variables as-is instead of expanding them to an + * empty string. + * + * Example for a ':=' assignment: + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * # If .INCLUDES (an undocumented special variable, by the + * # way) is still undefined, the updated CFLAGS becomes + * # "-I.. $(.INCLUDES)". + */ + Boolean keepUndef: 1; + + /* + * Without this padding, GCC 9.3.0 on NetBSD 9.99.80 generates larger + * code than necessary (1.2 kB), masking out the unused bits from the + * int (since that is the default representation of Boolean in make), + * even for initializers consisting entirely of constants. + */ + Boolean : 0; +} VarEvalFlags; + +#define VARE_PARSE_ONLY (VarEvalFlags) { FALSE, FALSE, FALSE, FALSE } +#define VARE_WANTRES (VarEvalFlags) { TRUE, FALSE, FALSE, FALSE } +#define VARE_UNDEFERR (VarEvalFlags) { TRUE, TRUE, FALSE, FALSE } +#define VARE_KEEP_DOLLAR_UNDEF (VarEvalFlags) { TRUE, FALSE, TRUE, TRUE } + +typedef enum VarSetFlags { + VAR_SET_NONE = 0, + + /* do not export */ + VAR_SET_NO_EXPORT = 1 << 0, + + /* Make the variable read-only. No further modification is possible, + * except for another call to Var_Set with the same flag. */ + VAR_SET_READONLY = 1 << 1 +} VarSetFlags; + +/* The state of error handling returned by Var_Parse. */ +typedef enum VarParseResult { + + /* Both parsing and evaluation succeeded. */ + VPR_OK, + + /* Parsing or evaluating failed, with an error message. */ + VPR_ERR, + + /* + * Parsing succeeded, undefined expressions are allowed and the + * expression was still undefined after applying all modifiers. + * No error message is printed in this case. + * + * Some callers handle this case differently, so return this + * information to them, for now. + * + * TODO: Instead of having this special return value, rather ensure + * that VarEvalFlags.keepUndef is processed properly. + */ + VPR_UNDEF + +} VarParseResult; + +typedef enum VarExportMode { + /* .export-env */ + VEM_ENV, + /* .export: Initial export or update an already exported variable. */ + VEM_PLAIN, + /* .export-literal: Do not expand the variable value. */ + VEM_LITERAL +} VarExportMode; + +void Var_Delete(GNode *, const char *); +void Var_DeleteExpand(GNode *, const char *); +void Var_Undef(const char *); +void Var_Set(GNode *, const char *, const char *); +void Var_SetExpand(GNode *, const char *, const char *); +void Var_SetWithFlags(GNode *, const char *, const char *, VarSetFlags); +void Var_SetExpandWithFlags(GNode *, const char *, const char *, VarSetFlags); +void Var_Append(GNode *, const char *, const char *); +void Var_AppendExpand(GNode *, const char *, const char *); +Boolean Var_Exists(GNode *, const char *); +Boolean Var_ExistsExpand(GNode *, const char *); +FStr Var_Value(GNode *, const char *); +const char *GNode_ValueDirect(GNode *, const char *); +VarParseResult Var_Parse(const char **, GNode *, VarEvalFlags, FStr *); +VarParseResult Var_Subst(const char *, GNode *, VarEvalFlags, char **); +void Var_Stats(void); void Var_Dump(GNode *); -void Var_ExportVars(void); -void Var_Export(char *, int); -void Var_UnExport(char *); +void Var_ReexportVars(void); +void Var_Export(VarExportMode, const char *); +void Var_ExportVars(const char *); +void Var_UnExport(Boolean, const char *); + +void Global_Set(const char *, const char *); +void Global_SetExpand(const char *, const char *); +void Global_Append(const char *, const char *); +void Global_Delete(const char *); /* util.c */ -void (*bmake_signal(int, void (*)(int)))(int); +typedef void (*SignalProc)(int); +SignalProc bmake_signal(int, SignalProc); diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c index 204073ae4..b46cb0758 100644 --- a/usr.bin/make/parse.c +++ b/usr.bin/make/parse.c @@ -1,4 +1,4 @@ -/* $NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $ */ +/* $NetBSD: parse.c,v 1.552 2021/03/15 12:15:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,163 +68,119 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: parse.c,v 1.204 2014/09/18 08:06:13 dholland Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * parse.c -- - * Functions to parse a makefile. +/* + * Parsing of makefiles. * - * One function, Parse_Init, must be called before any functions - * in this module are used. After that, the function Parse_File is the - * main entry point and controls most of the other functions in this - * module. + * Parse_File is the main entry point and controls most of the other + * functions in this module. * - * Most important structures are kept in Lsts. Directories for - * the .include "..." function are kept in the 'parseIncPath' Lst, while - * those for the .include <...> are kept in the 'sysIncPath' Lst. The - * targets currently being defined are kept in the 'targets' Lst. - * - * The variables 'fname' and 'lineno' are used to track the name - * of the current file and the line number in that file so that error - * messages can be more meaningful. + * The directories for the .include "..." directive are kept in + * 'parseIncPath', while those for .include <...> are kept in 'sysIncPath'. + * The targets currently being defined are kept in 'targets'. * * Interface: - * Parse_Init Initialization function which must be - * called before anything else in this module - * is used. + * Parse_Init Initialize the module * - * Parse_End Cleanup the module + * Parse_End Clean up the module * - * Parse_File Function used to parse a makefile. It must - * be given the name of the file, which should - * already have been opened, and a function - * to call to read a character from the file. + * Parse_File Parse a top-level makefile. Included files are + * handled by IncludeFile instead. * - * Parse_IsVar Returns TRUE if the given line is a - * variable assignment. Used by MainParseArgs - * to determine if an argument is a target - * or a variable assignment. Used internally - * for pretty much the same thing... + * Parse_IsVar Return TRUE if the given line is a variable + * assignment. Used by MainParseArgs to determine if + * an argument is a target or a variable assignment. + * Used internally for pretty much the same thing. * - * Parse_Error Function called when an error occurs in - * parsing. Used by the variable and - * conditional modules. - * Parse_MainName Returns a Lst of the main target to create. + * Parse_Error Report a parse error, a warning or an informational + * message. + * + * Parse_MainName Returns a list of the main target to create. */ #include -#include #include -#include -#include #include -#include #include -#include - -#ifndef MAP_FILE -#define MAP_FILE 0 -#endif -#ifndef MAP_COPY -#define MAP_COPY MAP_PRIVATE -#endif +#include #include "make.h" -#include "hash.h" #include "dir.h" #include "job.h" -#include "buf.h" #include "pathnames.h" -//////////////////////////////////////////////////////////// -// types and constants +/* "@(#)parse.c 8.3 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: parse.c,v 1.552 2021/03/15 12:15:03 rillig Exp $"); + +/* types and constants */ /* * Structure for a file being read ("included file") */ typedef struct IFile { - char *fname; /* name of file */ - int lineno; /* current line number in file */ - int first_lineno; /* line number of start of text */ - int cond_depth; /* 'if' nesting when file opened */ - char *P_str; /* point to base of string buffer */ - char *P_ptr; /* point to next char of string buffer */ - char *P_end; /* point to the end of string buffer */ - char *(*nextbuf)(void *, size_t *); /* Function to get more data */ - void *nextbuf_arg; /* Opaque arg for nextbuf() */ - struct loadedfile *lf; /* loadedfile object, if any */ + char *fname; /* name of file (relative? absolute?) */ + Boolean fromForLoop; /* simulated .include by the .for loop */ + int lineno; /* current line number in file */ + int first_lineno; /* line number of start of text */ + unsigned int cond_depth; /* 'if' nesting when file opened */ + Boolean depending; /* state of doing_depend on EOF */ + + /* The buffer from which the file's content is read. */ + char *buf_freeIt; + char *buf_ptr; /* next char to be read */ + char *buf_end; + + /* Function to read more data, with a single opaque argument. */ + ReadMoreProc readMore; + void *readMoreArg; + + struct loadedfile *lf; /* loadedfile object, if any */ } IFile; - -/* - * These values are returned by ParseEOF to tell Parse_File whether to - * CONTINUE parsing, i.e. it had only reached the end of an include file, - * or if it's DONE. - */ -#define CONTINUE 1 -#define DONE 0 - /* * Tokens for target attributes */ -typedef enum { - Begin, /* .BEGIN */ - Default, /* .DEFAULT */ - End, /* .END */ - dotError, /* .ERROR */ - Ignore, /* .IGNORE */ - Includes, /* .INCLUDES */ - Interrupt, /* .INTERRUPT */ - Libs, /* .LIBS */ - Meta, /* .META */ - MFlags, /* .MFLAGS or .MAKEFLAGS */ - Main, /* .MAIN and we don't have anything user-specified to - * make */ - NoExport, /* .NOEXPORT */ - NoMeta, /* .NOMETA */ - NoMetaCmp, /* .NOMETA_CMP */ - NoPath, /* .NOPATH */ - Not, /* Not special */ - NotParallel, /* .NOTPARALLEL */ - Null, /* .NULL */ - ExObjdir, /* .OBJDIR */ - Order, /* .ORDER */ - Parallel, /* .PARALLEL */ - ExPath, /* .PATH */ - Phony, /* .PHONY */ +typedef enum ParseSpecial { + SP_ATTRIBUTE, /* Generic attribute */ + SP_BEGIN, /* .BEGIN */ + SP_DEFAULT, /* .DEFAULT */ + SP_DELETE_ON_ERROR, /* .DELETE_ON_ERROR */ + SP_END, /* .END */ + SP_ERROR, /* .ERROR */ + SP_IGNORE, /* .IGNORE */ + SP_INCLUDES, /* .INCLUDES; not mentioned in the manual page */ + SP_INTERRUPT, /* .INTERRUPT */ + SP_LIBS, /* .LIBS; not mentioned in the manual page */ + /* .MAIN and we don't have anything user-specified to make */ + SP_MAIN, + SP_META, /* .META */ + SP_MFLAGS, /* .MFLAGS or .MAKEFLAGS */ + SP_NOMETA, /* .NOMETA */ + SP_NOMETA_CMP, /* .NOMETA_CMP */ + SP_NOPATH, /* .NOPATH */ + SP_NOT, /* Not special */ + SP_NOTPARALLEL, /* .NOTPARALLEL or .NO_PARALLEL */ + SP_NULL, /* .NULL; not mentioned in the manual page */ + SP_OBJDIR, /* .OBJDIR */ + SP_ORDER, /* .ORDER */ + SP_PARALLEL, /* .PARALLEL; not mentioned in the manual page */ + SP_PATH, /* .PATH or .PATH.suffix */ + SP_PHONY, /* .PHONY */ #ifdef POSIX - Posix, /* .POSIX */ + SP_POSIX, /* .POSIX; not mentioned in the manual page */ #endif - Precious, /* .PRECIOUS */ - ExShell, /* .SHELL */ - Silent, /* .SILENT */ - SingleShell, /* .SINGLESHELL */ - Stale, /* .STALE */ - Suffixes, /* .SUFFIXES */ - Wait, /* .WAIT */ - Attribute /* Generic attribute */ + SP_PRECIOUS, /* .PRECIOUS */ + SP_SHELL, /* .SHELL */ + SP_SILENT, /* .SILENT */ + SP_SINGLESHELL, /* .SINGLESHELL; not mentioned in the manual page */ + SP_STALE, /* .STALE */ + SP_SUFFIXES, /* .SUFFIXES */ + SP_WAIT /* .WAIT */ } ParseSpecial; -/* - * Other tokens - */ -#define LPAREN '(' -#define RPAREN ')' +typedef List SearchPathList; +typedef ListNode SearchPathListNode; - -//////////////////////////////////////////////////////////// -// result data +/* result data */ /* * The main target to create. This is the first target on the first @@ -232,36 +188,34 @@ typedef enum { */ static GNode *mainNode; -//////////////////////////////////////////////////////////// -// eval state - -/* targets we're working on */ -static Lst targets; - -#ifdef CLEANUP -/* command lines for targets */ -static Lst targCmds; -#endif +/* eval state */ /* - * specType contains the SPECial TYPE of the current target. It is - * Not if the target is unspecial. If it *is* special, however, the children - * are linked as children of the parent but not vice versa. This variable is - * set in ParseDoDependency + * During parsing, the targets from the left-hand side of the currently + * active dependency line, or NULL if the current line does not belong to a + * dependency line, for example because it is a variable assignment. + * + * See unit-tests/deptgt.mk, keyword "parse.c:targets". */ -static ParseSpecial specType; +static GNodeList *targets; + +#ifdef CLEANUP +/* + * All shell commands for all targets, in no particular order and possibly + * with duplicates. Kept in a separate list since the commands from .USE or + * .USEBEFORE nodes are shared with other GNodes, thereby giving up the + * easily understandable ownership over the allocated strings. + */ +static StringList targCmds = LST_INIT; +#endif /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ -static GNode *predecessor; +static GNode *order_pred; -//////////////////////////////////////////////////////////// -// parser state - -/* true if currently in a dependency line or its commands */ -static Boolean inLine; +/* parser state */ /* number of fatal errors */ static int fatals = 0; @@ -270,133 +224,113 @@ static int fatals = 0; * Variables for doing includes */ -/* current file being read */ -static IFile *curFile; +/* + * The include chain of makefiles. At index 0 is the top-level makefile from + * the command line, followed by the included files or .for loops, up to and + * including the current file. + * + * See PrintStackTrace for how to interpret the data. + */ +static Vector /* of IFile */ includes; -/* stack of IFiles generated by .includes */ -static Lst includes; +static IFile * +GetInclude(size_t i) +{ + return Vector_Get(&includes, i); +} -/* include paths (lists of directories) */ -Lst parseIncPath; /* dirs for "..." includes */ -Lst sysIncPath; /* dirs for <...> includes */ -Lst defIncPath; /* default for sysIncPath */ +/* The file that is currently being read. */ +static IFile * +CurFile(void) +{ + return GetInclude(includes.len - 1); +} -//////////////////////////////////////////////////////////// -// parser tables +/* include paths */ +SearchPath *parseIncPath; /* directories for "..." includes */ +SearchPath *sysIncPath; /* directories for <...> includes */ +SearchPath *defSysIncPath; /* default for sysIncPath */ + +/* parser tables */ /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. The 'spec' field is the ParseSpecial - * type of the keyword ("Not" if the keyword isn't special as a target) while + * type of the keyword (SP_NOT if the keyword isn't special as a target) while * the 'op' field is the operator to apply to the list of targets if the * keyword is used as a source ("0" if the keyword isn't special as a source) */ static const struct { - const char *name; /* Name of keyword */ - ParseSpecial spec; /* Type when used as a target */ - int op; /* Operator when used as a source */ + const char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + GNodeType op; /* Operator when used as a source */ } parseKeywords[] = { -{ ".BEGIN", Begin, 0 }, -{ ".DEFAULT", Default, 0 }, -{ ".END", End, 0 }, -{ ".ERROR", dotError, 0 }, -{ ".EXEC", Attribute, OP_EXEC }, -{ ".IGNORE", Ignore, OP_IGNORE }, -{ ".INCLUDES", Includes, 0 }, -{ ".INTERRUPT", Interrupt, 0 }, -{ ".INVISIBLE", Attribute, OP_INVISIBLE }, -{ ".JOIN", Attribute, OP_JOIN }, -{ ".LIBS", Libs, 0 }, -{ ".MADE", Attribute, OP_MADE }, -{ ".MAIN", Main, 0 }, -{ ".MAKE", Attribute, OP_MAKE }, -{ ".MAKEFLAGS", MFlags, 0 }, -{ ".META", Meta, OP_META }, -{ ".MFLAGS", MFlags, 0 }, -{ ".NOMETA", NoMeta, OP_NOMETA }, -{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP }, -{ ".NOPATH", NoPath, OP_NOPATH }, -{ ".NOTMAIN", Attribute, OP_NOTMAIN }, -{ ".NOTPARALLEL", NotParallel, 0 }, -{ ".NO_PARALLEL", NotParallel, 0 }, -{ ".NULL", Null, 0 }, -{ ".OBJDIR", ExObjdir, 0 }, -{ ".OPTIONAL", Attribute, OP_OPTIONAL }, -{ ".ORDER", Order, 0 }, -{ ".PARALLEL", Parallel, 0 }, -{ ".PATH", ExPath, 0 }, -{ ".PHONY", Phony, OP_PHONY }, + { ".BEGIN", SP_BEGIN, OP_NONE }, + { ".DEFAULT", SP_DEFAULT, OP_NONE }, + { ".DELETE_ON_ERROR", SP_DELETE_ON_ERROR, OP_NONE }, + { ".END", SP_END, OP_NONE }, + { ".ERROR", SP_ERROR, OP_NONE }, + { ".EXEC", SP_ATTRIBUTE, OP_EXEC }, + { ".IGNORE", SP_IGNORE, OP_IGNORE }, + { ".INCLUDES", SP_INCLUDES, OP_NONE }, + { ".INTERRUPT", SP_INTERRUPT, OP_NONE }, + { ".INVISIBLE", SP_ATTRIBUTE, OP_INVISIBLE }, + { ".JOIN", SP_ATTRIBUTE, OP_JOIN }, + { ".LIBS", SP_LIBS, OP_NONE }, + { ".MADE", SP_ATTRIBUTE, OP_MADE }, + { ".MAIN", SP_MAIN, OP_NONE }, + { ".MAKE", SP_ATTRIBUTE, OP_MAKE }, + { ".MAKEFLAGS", SP_MFLAGS, OP_NONE }, + { ".META", SP_META, OP_META }, + { ".MFLAGS", SP_MFLAGS, OP_NONE }, + { ".NOMETA", SP_NOMETA, OP_NOMETA }, + { ".NOMETA_CMP", SP_NOMETA_CMP, OP_NOMETA_CMP }, + { ".NOPATH", SP_NOPATH, OP_NOPATH }, + { ".NOTMAIN", SP_ATTRIBUTE, OP_NOTMAIN }, + { ".NOTPARALLEL", SP_NOTPARALLEL, OP_NONE }, + { ".NO_PARALLEL", SP_NOTPARALLEL, OP_NONE }, + { ".NULL", SP_NULL, OP_NONE }, + { ".OBJDIR", SP_OBJDIR, OP_NONE }, + { ".OPTIONAL", SP_ATTRIBUTE, OP_OPTIONAL }, + { ".ORDER", SP_ORDER, OP_NONE }, + { ".PARALLEL", SP_PARALLEL, OP_NONE }, + { ".PATH", SP_PATH, OP_NONE }, + { ".PHONY", SP_PHONY, OP_PHONY }, #ifdef POSIX -{ ".POSIX", Posix, 0 }, + { ".POSIX", SP_POSIX, OP_NONE }, #endif -{ ".PRECIOUS", Precious, OP_PRECIOUS }, -{ ".RECURSIVE", Attribute, OP_MAKE }, -{ ".SHELL", ExShell, 0 }, -{ ".SILENT", Silent, OP_SILENT }, -{ ".SINGLESHELL", SingleShell, 0 }, -{ ".STALE", Stale, 0 }, -{ ".SUFFIXES", Suffixes, 0 }, -{ ".USE", Attribute, OP_USE }, -{ ".USEBEFORE", Attribute, OP_USEBEFORE }, -{ ".WAIT", Wait, 0 }, + { ".PRECIOUS", SP_PRECIOUS, OP_PRECIOUS }, + { ".RECURSIVE", SP_ATTRIBUTE, OP_MAKE }, + { ".SHELL", SP_SHELL, OP_NONE }, + { ".SILENT", SP_SILENT, OP_SILENT }, + { ".SINGLESHELL", SP_SINGLESHELL, OP_NONE }, + { ".STALE", SP_STALE, OP_NONE }, + { ".SUFFIXES", SP_SUFFIXES, OP_NONE }, + { ".USE", SP_ATTRIBUTE, OP_USE }, + { ".USEBEFORE", SP_ATTRIBUTE, OP_USEBEFORE }, + { ".WAIT", SP_WAIT, OP_NONE }, }; -//////////////////////////////////////////////////////////// -// local functions - -static int ParseIsEscaped(const char *, const char *); -static void ParseErrorInternal(const char *, size_t, int, const char *, ...) - MAKE_ATTR_PRINTFLIKE(4,5); -static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list) - MAKE_ATTR_PRINTFLIKE(5, 0); -static int ParseFindKeyword(const char *); -static int ParseLinkSrc(void *, void *); -static int ParseDoOp(void *, void *); -static void ParseDoSrc(int, const char *); -static int ParseFindMain(void *, void *); -static int ParseAddDir(void *, void *); -static int ParseClearPath(void *, void *); -static void ParseDoDependency(char *); -static int ParseAddCmd(void *, void *); -static void ParseHasCommands(void *); -static void ParseDoInclude(char *); -static void ParseSetParseFile(const char *); -static void ParseSetIncludedFile(void); -#ifdef SYSVINCLUDE -static void ParseTraditionalInclude(char *); -#endif -#ifdef GMAKEEXPORT -static void ParseGmakeExport(char *); -#endif -static int ParseEOF(void); -static char *ParseReadLine(void); -static void ParseFinishLine(void); -static void ParseMark(GNode *); - -//////////////////////////////////////////////////////////// -// file loader +/* file loader */ struct loadedfile { - const char *path; /* name, for error reports */ - char *buf; /* contents buffer */ - size_t len; /* length of contents */ - size_t maplen; /* length of mmap area, or 0 */ - Boolean used; /* XXX: have we used the data yet */ + /* XXX: What is the lifetime of this path? Who manages the memory? */ + const char *path; /* name, for error reports */ + char *buf; /* contents buffer */ + size_t len; /* length of contents */ + Boolean used; /* XXX: have we used the data yet */ }; -/* - * Constructor/destructor for loadedfile - */ +/* XXX: What is the lifetime of the path? Who manages the memory? */ static struct loadedfile * -loadedfile_create(const char *path) +loadedfile_create(const char *path, char *buf, size_t buflen) { struct loadedfile *lf; - lf = bmake_malloc(sizeof(*lf)); - lf->path = (path == NULL ? "(stdin)" : path); - lf->buf = NULL; - lf->len = 0; - lf->maplen = 0; + lf = bmake_malloc(sizeof *lf); + lf->path = path == NULL ? "(stdin)" : path; + lf->buf = buf; + lf->len = buflen; lf->used = FALSE; return lf; } @@ -404,28 +338,22 @@ loadedfile_create(const char *path) static void loadedfile_destroy(struct loadedfile *lf) { - if (lf->buf != NULL) { - if (lf->maplen > 0) { - munmap(lf->buf, lf->maplen); - } else { - free(lf->buf); - } - } + free(lf->buf); free(lf); } /* - * nextbuf() operation for loadedfile, as needed by the weird and twisted - * logic below. Once that's cleaned up, we can get rid of lf->used... + * readMore() operation for loadedfile, as needed by the weird and twisted + * logic below. Once that's cleaned up, we can get rid of lf->used. */ static char * -loadedfile_nextbuf(void *x, size_t *len) +loadedfile_readMore(void *x, size_t *len) { struct loadedfile *lf = x; - if (lf->used) { + if (lf->used) return NULL; - } + lf->used = TRUE; *len = lf->len; return lf->buf; @@ -434,34 +362,31 @@ loadedfile_nextbuf(void *x, size_t *len) /* * Try to get the size of a file. */ -static ReturnStatus +static Boolean load_getsize(int fd, size_t *ret) { struct stat st; - if (fstat(fd, &st) < 0) { - return FAILURE; - } + if (fstat(fd, &st) < 0) + return FALSE; - if (!S_ISREG(st.st_mode)) { - return FAILURE; - } + if (!S_ISREG(st.st_mode)) + return FALSE; /* * st_size is an off_t, which is 64 bits signed; *ret is * size_t, which might be 32 bits unsigned or 64 bits * unsigned. Rather than being elaborate, just punt on - * files that are more than 2^31 bytes. We should never - * see a makefile that size in practice... + * files that are more than 1 GiB. We should never + * see a makefile that size in practice. * * While we're at it reject negative sizes too, just in case. */ - if (st.st_size < 0 || st.st_size > 0x7fffffff) { - return FAILURE; - } + if (st.st_size < 0 || st.st_size > 0x3fffffff) + return FALSE; - *ret = (size_t) st.st_size; - return SUCCESS; + *ret = (size_t)st.st_size; + return TRUE; } /* @@ -471,287 +396,274 @@ load_getsize(int fd, size_t *ret) * being in the caller in another source file, we need to have the fd * passed in already open. Bleh. * - * If the path is NULL use stdin and (to insure against fd leaks) - * assert that the caller passed in -1. + * If the path is NULL, use stdin. */ static struct loadedfile * loadfile(const char *path, int fd) { - struct loadedfile *lf; - long pagesize; - ssize_t result; - size_t bufpos; + ssize_t n; + Buffer buf; + size_t filesize; - lf = loadedfile_create(path); if (path == NULL) { assert(fd == -1); fd = STDIN_FILENO; - } else { -#if 0 /* notyet */ - fd = open(path, O_RDONLY); - if (fd < 0) { - ... - Error("%s: %s", path, strerror(errno)); - exit(1); - } -#endif } - if (load_getsize(fd, &lf->len) == SUCCESS) { - /* found a size, try mmap */ - pagesize = sysconf(_SC_PAGESIZE); - if (pagesize <= 0) { - pagesize = 0x1000; - } - /* round size up to a page */ - lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize); - + if (load_getsize(fd, &filesize)) { /* - * XXX hack for dealing with empty files; remove when - * we're no longer limited by interfacing to the old - * logic elsewhere in this file. + * Avoid resizing the buffer later for no reason. + * + * At the same time leave space for adding a final '\n', + * just in case it is missing in the file. */ - if (lf->maplen == 0) { - lf->maplen = pagesize; - } + filesize++; + } else + filesize = 1024; + Buf_InitSize(&buf, filesize); - /* - * FUTURE: remove PROT_WRITE when the parser no longer - * needs to scribble on the input. - */ - lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE, - MAP_FILE|MAP_COPY, fd, 0); - if (lf->buf != MAP_FAILED) { - /* succeeded */ - if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') { - char *b = malloc(lf->len + 1); - b[lf->len] = '\n'; - memcpy(b, lf->buf, lf->len++); - munmap(lf->buf, lf->maplen); - lf->maplen = 0; - lf->buf = b; + for (;;) { + assert(buf.len <= buf.cap); + if (buf.len == buf.cap) { + if (buf.cap > 0x1fffffff) { + errno = EFBIG; + Error("%s: file too large", path); + exit(2); /* Not 1 so -q can distinguish error */ } - goto done; + Buf_Expand(&buf); } - } - - /* cannot mmap; load the traditional way */ - - lf->maplen = 0; - lf->len = 1024; - lf->buf = bmake_malloc(lf->len); - - bufpos = 0; - while (1) { - assert(bufpos <= lf->len); - if (bufpos == lf->len) { - lf->len *= 2; - lf->buf = bmake_realloc(lf->buf, lf->len); - } - result = read(fd, lf->buf + bufpos, lf->len - bufpos); - if (result < 0) { + assert(buf.len < buf.cap); + n = read(fd, buf.data + buf.len, buf.cap - buf.len); + if (n < 0) { Error("%s: read error: %s", path, strerror(errno)); - exit(1); + exit(2); /* Not 1 so -q can distinguish error */ } - if (result == 0) { + if (n == 0) break; - } - bufpos += result; - } - assert(bufpos <= lf->len); - lf->len = bufpos; - /* truncate malloc region to actual length (maybe not useful) */ - if (lf->len > 0) { - lf->buf = bmake_realloc(lf->buf, lf->len); + buf.len += (size_t)n; } + assert(buf.len <= buf.cap); -done: - if (path != NULL) { + if (!Buf_EndsWith(&buf, '\n')) + Buf_AddByte(&buf, '\n'); + + if (path != NULL) close(fd); + + { + struct loadedfile *lf = loadedfile_create(path, + buf.data, buf.len); + Buf_DoneData(&buf); + return lf; } - return lf; } -//////////////////////////////////////////////////////////// -// old code +static void +PrintStackTrace(void) +{ + const IFile *entries; + size_t i, n; -/*- - *---------------------------------------------------------------------- - * ParseIsEscaped -- - * Check if the current character is escaped on the current line - * - * Results: - * 0 if the character is not backslash escaped, 1 otherwise - * - * Side Effects: - * None - *---------------------------------------------------------------------- - */ -static int + if (!(DEBUG(PARSE))) + return; + + entries = GetInclude(0); + n = includes.len; + if (n == 0) + return; + n--; /* This entry is already in the diagnostic. */ + + /* + * For the IFiles with fromForLoop, lineno seems to be sorted + * backwards. This is because lineno is the number of completely + * parsed lines, which for a .for loop is right after the + * corresponding .endfor. The intuitive line number comes from + * first_lineno instead, which points at the start of the .for loop. + * + * To make the stack trace intuitive, the entry below each chain of + * .for loop entries must be ignored completely since neither its + * lineno nor its first_lineno is useful. Instead, the topmost of + * each chain of .for loop entries needs to be printed twice, once + * with its first_lineno and once with its lineno. + */ + + for (i = n; i-- > 0;) { + const IFile *entry = entries + i; + const char *fname = entry->fname; + Boolean printLineno; + char dirbuf[MAXPATHLEN + 1]; + + if (fname[0] != '/' && strcmp(fname, "(stdin)") != 0) + fname = realpath(fname, dirbuf); + + printLineno = !entry->fromForLoop; + if (i + 1 < n && entries[i + 1].fromForLoop == printLineno) + printLineno = entry->fromForLoop; + + if (printLineno) + debug_printf("\tin .include from %s:%d\n", + fname, entry->lineno); + if (entry->fromForLoop) + debug_printf("\tin .for loop from %s:%d\n", + fname, entry->first_lineno); + } +} + +/* Check if the current character is escaped on the current line. */ +static Boolean ParseIsEscaped(const char *line, const char *c) { - int active = 0; - for (;;) { - if (line == c) - return active; - if (*--c != '\\') - return active; - active = !active; - } + Boolean active = FALSE; + for (;;) { + if (line == c) + return active; + if (*--c != '\\') + return active; + active = !active; + } } -/*- - *---------------------------------------------------------------------- - * ParseFindKeyword -- - * Look in the table of keywords for one matching the given string. - * - * Input: - * str String to find - * - * Results: - * The index of the keyword, or -1 if it isn't there. - * - * Side Effects: - * None - *---------------------------------------------------------------------- +/* + * Add the filename and lineno to the GNode so that we remember where it + * was first defined. + */ +static void +ParseMark(GNode *gn) +{ + IFile *curFile = CurFile(); + gn->fname = curFile->fname; + gn->lineno = curFile->lineno; +} + +/* + * Look in the table of keywords for one matching the given string. + * Return the index of the keyword, or -1 if it isn't there. */ static int ParseFindKeyword(const char *str) { - int start, end, cur; - int diff; + int start = 0; + int end = sizeof parseKeywords / sizeof parseKeywords[0] - 1; - start = 0; - end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; + do { + int curr = start + (end - start) / 2; + int diff = strcmp(str, parseKeywords[curr].name); - do { - cur = start + ((end - start) / 2); - diff = strcmp(str, parseKeywords[cur].name); + if (diff == 0) + return curr; + if (diff < 0) + end = curr - 1; + else + start = curr + 1; + } while (start <= end); - if (diff == 0) { - return (cur); - } else if (diff < 0) { - end = cur - 1; - } else { - start = cur + 1; - } - } while (start <= end); - return (-1); + return -1; } -/*- - * ParseVErrorInternal -- - * Error message abort function for parsing. Prints out the context - * of the error (line number and file) as well as the message with - * two optional arguments. - * - * Results: - * None - * - * Side Effects: - * "fatals" is incremented if the level is PARSE_FATAL. - */ -/* VARARGS */ static void -ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type, - const char *fmt, va_list ap) +PrintLocation(FILE *f, const char *fname, size_t lineno) +{ + char dirbuf[MAXPATHLEN + 1]; + FStr dir, base; + + if (*fname == '/' || strcmp(fname, "(stdin)") == 0) { + (void)fprintf(f, "\"%s\" line %u: ", fname, (unsigned)lineno); + return; + } + + /* Find out which makefile is the culprit. + * We try ${.PARSEDIR} and apply realpath(3) if not absolute. */ + + dir = Var_Value(SCOPE_GLOBAL, ".PARSEDIR"); + if (dir.str == NULL) + dir.str = "."; + if (dir.str[0] != '/') + dir.str = realpath(dir.str, dirbuf); + + base = Var_Value(SCOPE_GLOBAL, ".PARSEFILE"); + if (base.str == NULL) + base.str = str_basename(fname); + + (void)fprintf(f, "\"%s/%s\" line %u: ", + dir.str, base.str, (unsigned)lineno); + + FStr_Done(&base); + FStr_Done(&dir); +} + +static void +ParseVErrorInternal(FILE *f, const char *fname, size_t lineno, + ParseErrorLevel type, const char *fmt, va_list ap) { static Boolean fatal_warning_error_printed = FALSE; (void)fprintf(f, "%s: ", progname); - if (cfname != NULL) { - (void)fprintf(f, "\""); - if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) { - char *cp; - const char *dir; - - /* - * Nothing is more annoying than not knowing - * which Makefile is the culprit. - */ - dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp); - if (dir == NULL || *dir == '\0' || - (*dir == '.' && dir[1] == '\0')) - dir = Var_Value(".CURDIR", VAR_GLOBAL, &cp); - if (dir == NULL) - dir = "."; - - (void)fprintf(f, "%s/%s", dir, cfname); - } else - (void)fprintf(f, "%s", cfname); - - (void)fprintf(f, "\" line %d: ", (int)clineno); - } + if (fname != NULL) + PrintLocation(f, fname, lineno); if (type == PARSE_WARNING) (void)fprintf(f, "warning: "); (void)vfprintf(f, fmt, ap); (void)fprintf(f, "\n"); (void)fflush(f); - if (type == PARSE_FATAL || parseWarnFatal) - fatals += 1; - if (parseWarnFatal && !fatal_warning_error_printed) { + + if (type == PARSE_INFO) + goto print_stack_trace; + if (type == PARSE_WARNING && !opts.parseWarnFatal) + goto print_stack_trace; + fatals++; + if (type == PARSE_WARNING && !fatal_warning_error_printed) { Error("parsing warnings being treated as errors"); fatal_warning_error_printed = TRUE; } + +print_stack_trace: + PrintStackTrace(); } -/*- - * ParseErrorInternal -- - * Error function - * - * Results: - * None - * - * Side Effects: - * None - */ -/* VARARGS */ static void -ParseErrorInternal(const char *cfname, size_t clineno, int type, - const char *fmt, ...) +ParseErrorInternal(const char *fname, size_t lineno, + ParseErrorLevel type, const char *fmt, ...) { va_list ap; - va_start(ap, fmt); (void)fflush(stdout); - ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); + va_start(ap, fmt); + ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); - if (debug_file != stderr && debug_file != stdout) { + if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap); + ParseVErrorInternal(opts.debug_file, fname, lineno, type, + fmt, ap); va_end(ap); } } -/*- - * Parse_Error -- - * External interface to ParseErrorInternal; uses the default filename - * Line number. +/* + * Print a parse error message, including location information. * - * Results: - * None + * If the level is PARSE_FATAL, continue parsing until the end of the + * current top-level makefile, then exit (see Parse_File). * - * Side Effects: - * None + * Fmt is given without a trailing newline. */ -/* VARARGS */ void -Parse_Error(int type, const char *fmt, ...) +Parse_Error(ParseErrorLevel type, const char *fmt, ...) { va_list ap; const char *fname; size_t lineno; - if (curFile == NULL) { + if (includes.len == 0) { fname = NULL; lineno = 0; } else { + IFile *curFile = CurFile(); fname = curFile->fname; - lineno = curFile->lineno; + lineno = (size_t)curFile->lineno; } va_start(ap, fmt); @@ -759,2500 +671,2619 @@ Parse_Error(int type, const char *fmt, ...) ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); va_end(ap); - if (debug_file != stderr && debug_file != stdout) { + if (opts.debug_file != stderr && opts.debug_file != stdout) { va_start(ap, fmt); - ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap); + ParseVErrorInternal(opts.debug_file, fname, lineno, type, + fmt, ap); va_end(ap); } } /* - * ParseMessage - * Parse a .info .warning or .error directive - * - * The input is the line minus the ".". We substitute - * variables, print the message and exit(1) (for .error) or just print - * a warning if the directive is malformed. - */ -static Boolean -ParseMessage(char *line) -{ - int mtype; - - switch(*line) { - case 'i': - mtype = 0; - break; - case 'w': - mtype = PARSE_WARNING; - break; - case 'e': - mtype = PARSE_FATAL; - break; - default: - Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line); - return FALSE; - } - - while (isalpha((u_char)*line)) - line++; - if (!isspace((u_char)*line)) - return FALSE; /* not for us */ - while (isspace((u_char)*line)) - line++; - - line = Var_Subst(NULL, line, VAR_CMD, 0); - Parse_Error(mtype, "%s", line); - free(line); - - if (mtype == PARSE_FATAL) { - /* Terminate immediately. */ - exit(1); - } - return TRUE; -} - -/*- - *--------------------------------------------------------------------- - * ParseLinkSrc -- - * Link the parent node to its new child. Used in a Lst_ForEach by - * ParseDoDependency. If the specType isn't 'Not', the parent - * isn't linked as a parent of the child. - * - * Input: - * pgnp The parent node - * cgpn The child node - * - * Results: - * Always = 0 - * - * Side Effects: - * New elements are added to the parents list of cgn and the - * children list of cgn. the unmade field of pgn is updated - * to reflect the additional child. - *--------------------------------------------------------------------- - */ -static int -ParseLinkSrc(void *pgnp, void *cgnp) -{ - GNode *pgn = (GNode *)pgnp; - GNode *cgn = (GNode *)cgnp; - - if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) - pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts)); - (void)Lst_AtEnd(pgn->children, cgn); - if (specType == Not) - (void)Lst_AtEnd(cgn->parents, pgn); - pgn->unmade += 1; - if (DEBUG(PARSE)) { - fprintf(debug_file, "# %s: added child %s - %s\n", __func__, - pgn->name, cgn->name); - Targ_PrintNode(pgn, 0); - Targ_PrintNode(cgn, 0); - } - return (0); -} - -/*- - *--------------------------------------------------------------------- - * ParseDoOp -- - * Apply the parsed operator to the given target node. Used in a - * Lst_ForEach call by ParseDoDependency once all targets have - * been found and their operator parsed. If the previous and new - * operators are incompatible, a major error is taken. - * - * Input: - * gnp The node to which the operator is to be applied - * opp The operator to apply - * - * Results: - * Always 0 - * - * Side Effects: - * The type field of the node is altered to reflect any new bits in - * the op. - *--------------------------------------------------------------------- - */ -static int -ParseDoOp(void *gnp, void *opp) -{ - GNode *gn = (GNode *)gnp; - int op = *(int *)opp; - /* - * If the dependency mask of the operator and the node don't match and - * the node has actually had an operator applied to it before, and - * the operator actually has some dependency information in it, complain. - */ - if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && - !OP_NOP(gn->type) && !OP_NOP(op)) - { - Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); - return (1); - } - - if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { - /* - * If the node was the object of a :: operator, we need to create a - * new instance of it for the children and commands on this dependency - * line. The new instance is placed on the 'cohorts' list of the - * initial one (note the initial one is not on its own cohorts list) - * and the new instance is linked to all parents of the initial - * instance. - */ - GNode *cohort; - - /* - * Propagate copied bits to the initial node. They'll be propagated - * back to the rest of the cohorts later. - */ - gn->type |= op & ~OP_OPMASK; - - cohort = Targ_FindNode(gn->name, TARG_NOHASH); - if (doing_depend) - ParseMark(cohort); - /* - * Make the cohort invisible as well to avoid duplicating it into - * other variables. True, parents of this target won't tend to do - * anything with their local variables, but better safe than - * sorry. (I think this is pointless now, since the relevant list - * traversals will no longer see this node anyway. -mycroft) - */ - cohort->type = op | OP_INVISIBLE; - (void)Lst_AtEnd(gn->cohorts, cohort); - cohort->centurion = gn; - gn->unmade_cohorts += 1; - snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", - gn->unmade_cohorts); - } 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; - } - - return (0); -} - -/*- - *--------------------------------------------------------------------- - * ParseDoSrc -- - * Given the name of a source, figure out if it is an attribute - * and apply it to the targets if it is. Else decide if there is - * some attribute which should be applied *to* the source because - * of some special target and apply it if so. Otherwise, make the - * source be a child of the targets in the list 'targets' - * - * Input: - * tOp operator (if any) from special targets - * src name of the source to handle - * - * Results: - * None - * - * Side Effects: - * Operator bits may be added to the list of targets or to the source. - * The targets may have a new source added to their lists of children. - *--------------------------------------------------------------------- + * Parse and handle an .info, .warning or .error directive. + * For an .error directive, immediately exit. */ static void -ParseDoSrc(int tOp, const char *src) +ParseMessage(ParseErrorLevel level, const char *levelName, const char *umsg) { - GNode *gn = NULL; - static int wait_number = 0; - char wait_src[16]; + char *xmsg; - if (*src == '.' && isupper ((unsigned char)src[1])) { - int keywd = ParseFindKeyword(src); - if (keywd != -1) { - int op = parseKeywords[keywd].op; - if (op != 0) { - Lst_ForEach(targets, ParseDoOp, &op); + if (umsg[0] == '\0') { + Parse_Error(PARSE_FATAL, "Missing argument for \".%s\"", + levelName); return; - } - if (parseKeywords[keywd].spec == Wait) { - /* - * We add a .WAIT node in the dependency list. - * After any dynamic dependencies (and filename globbing) - * have happened, it is given a dependency on the each - * previous child back to and 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 diag). - */ - snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); - gn = Targ_FindNode(wait_src, TARG_NOHASH); - if (doing_depend) - ParseMark(gn); - gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; - Lst_ForEach(targets, ParseLinkSrc, gn); - return; - } } - } - switch (specType) { - case Main: + (void)Var_Subst(umsg, SCOPE_CMDLINE, VARE_WANTRES, &xmsg); + /* TODO: handle errors */ + + Parse_Error(level, "%s", xmsg); + free(xmsg); + + if (level == PARSE_FATAL) { + PrintOnError(NULL, NULL); + exit(1); + } +} + +/* + * Add the child to the parent's children. + * + * Additionally, add the parent to the child's parents, but only if the + * target is not special. An example for such a special target is .END, + * which does not need to be informed once the child target has been made. + */ +static void +LinkSource(GNode *pgn, GNode *cgn, Boolean isSpecial) +{ + if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&pgn->cohorts)) + pgn = pgn->cohorts.last->datum; + + Lst_Append(&pgn->children, cgn); + pgn->unmade++; + + /* Special targets like .END don't need any children. */ + if (!isSpecial) + Lst_Append(&cgn->parents, pgn); + + if (DEBUG(PARSE)) { + debug_printf("# %s: added child %s - %s\n", + __func__, pgn->name, cgn->name); + Targ_PrintNode(pgn, 0); + Targ_PrintNode(cgn, 0); + } +} + +/* Add the node to each target from the current dependency group. */ +static void +LinkToTargets(GNode *gn, Boolean isSpecial) +{ + GNodeListNode *ln; + + for (ln = targets->first; ln != NULL; ln = ln->next) + LinkSource(ln->datum, gn, isSpecial); +} + +static Boolean +TryApplyDependencyOperator(GNode *gn, GNodeType op) +{ /* - * If we have noted the existence of a .MAIN, it means we need - * to add the sources of said target to the list of things - * to create. The string 'src' is likely to be free, so we - * must make a new copy of it. Note that this will only be - * invoked if the user didn't specify a target on the command - * line. This is to allow #ifmake's to succeed, or something... + * If the node occurred on the left-hand side of a dependency and the + * operator also defines a dependency, they must match. */ - (void)Lst_AtEnd(create, bmake_strdup(src)); + if ((op & OP_OPMASK) && (gn->type & OP_OPMASK) && + ((op & OP_OPMASK) != (gn->type & OP_OPMASK))) { + Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", + gn->name); + return FALSE; + } + + if (op == OP_DOUBLEDEP && (gn->type & OP_OPMASK) == OP_DOUBLEDEP) { + /* + * If the node was of 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. + * + * The new instance is placed on the 'cohorts' list of the + * initial one (note the initial one is not on its own + * cohorts list) and the new instance is linked to all + * parents of the initial instance. + */ + GNode *cohort; + + /* + * Propagate copied bits to the initial node. They'll be + * propagated back to the rest of the cohorts later. + */ + gn->type |= op & ~OP_OPMASK; + + cohort = Targ_NewInternalNode(gn->name); + if (doing_depend) + ParseMark(cohort); + /* + * Make the cohort invisible as well to avoid duplicating it + * into other variables. True, parents of this target won't + * tend to do anything with their local variables, but better + * safe than sorry. + * + * (I think this is pointless now, since the relevant list + * traversals will no longer see this node anyway. -mycroft) + */ + cohort->type = op | OP_INVISIBLE; + Lst_Append(&gn->cohorts, cohort); + cohort->centurion = gn; + gn->unmade_cohorts++; + snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", + (unsigned int)gn->unmade_cohorts % 1000000); + } else { + /* + * 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; + } + + return TRUE; +} + +static void +ApplyDependencyOperator(GNodeType op) +{ + GNodeListNode *ln; + + for (ln = targets->first; ln != NULL; ln = ln->next) + if (!TryApplyDependencyOperator(ln->datum, op)) + break; +} + +/* + * We 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). + */ +static void +ParseDependencySourceWait(Boolean isSpecial) +{ + static int wait_number = 0; + char wait_src[16]; + GNode *gn; + + snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); + gn = Targ_NewInternalNode(wait_src); + if (doing_depend) + ParseMark(gn); + gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; + LinkToTargets(gn, isSpecial); + +} + +static Boolean +ParseDependencySourceKeyword(const char *src, ParseSpecial specType) +{ + int keywd; + GNodeType op; + + if (*src != '.' || !ch_isupper(src[1])) + return FALSE; + + keywd = ParseFindKeyword(src); + if (keywd == -1) + return FALSE; + + op = parseKeywords[keywd].op; + if (op != OP_NONE) { + ApplyDependencyOperator(op); + return TRUE; + } + if (parseKeywords[keywd].spec == SP_WAIT) { + ParseDependencySourceWait(specType != SP_NOT); + return TRUE; + } + return FALSE; +} + +static void +ParseDependencySourceMain(const char *src) +{ + /* + * In a line like ".MAIN: source1 source2", add all sources to the + * list of things to create, but only if the user didn't specify a + * target on the command line and .MAIN occurs for the first time. + * + * See ParseDoDependencyTargetSpecial, branch SP_MAIN. + * See unit-tests/cond-func-make-main.mk. + */ + Lst_Append(&opts.create, bmake_strdup(src)); /* * Add the name to the .TARGETS variable as well, so the user can * employ that, if desired. */ - Var_Append(".TARGETS", src, VAR_GLOBAL); - return; + Global_Append(".TARGETS", src); +} - case Order: +static void +ParseDependencySourceOrder(const char *src) +{ + GNode *gn; /* * Create proper predecessor/successor links between the previous * source and the current one. */ - gn = Targ_FindNode(src, TARG_CREATE); + gn = Targ_GetNode(src); if (doing_depend) - ParseMark(gn); - if (predecessor != NULL) { - (void)Lst_AtEnd(predecessor->order_succ, gn); - (void)Lst_AtEnd(gn->order_pred, predecessor); - if (DEBUG(PARSE)) { - fprintf(debug_file, "# %s: added Order dependency %s - %s\n", - __func__, predecessor->name, gn->name); - Targ_PrintNode(predecessor, 0); - Targ_PrintNode(gn, 0); - } + ParseMark(gn); + if (order_pred != NULL) { + Lst_Append(&order_pred->order_succ, gn); + Lst_Append(&gn->order_pred, order_pred); + if (DEBUG(PARSE)) { + debug_printf("# %s: added Order dependency %s - %s\n", + __func__, order_pred->name, gn->name); + Targ_PrintNode(order_pred, 0); + Targ_PrintNode(gn, 0); + } } /* * The current source now becomes the predecessor for the next one. */ - predecessor = gn; - break; + order_pred = gn; +} + +static void +ParseDependencySourceOther(const char *src, GNodeType tOp, + ParseSpecial specType) +{ + GNode *gn; - default: /* - * If the source is not an attribute, we need to find/create - * a node for it. After that we can apply any operator to it - * from a special target or link it to its parents, as - * appropriate. + * The source is not an attribute, so find/create a node for it. + * After that, apply any operator to it from a special target or + * link it to its parents, as appropriate. * - * In the case of a source that was the object of a :: operator, + * In the case of a source that was the object of a '::' operator, * the attribute is applied to all of its instances (as kept in * the 'cohorts' list of the node) or all the cohorts are linked * to all the targets. */ /* Find/create the 'src' node and attach to all targets */ - gn = Targ_FindNode(src, TARG_CREATE); + gn = Targ_GetNode(src); if (doing_depend) - ParseMark(gn); - if (tOp) { - gn->type |= tOp; - } else { - Lst_ForEach(targets, ParseLinkSrc, gn); - } - break; - } + ParseMark(gn); + if (tOp != OP_NONE) + gn->type |= tOp; + else + LinkToTargets(gn, specType != SP_NOT); } -/*- - *----------------------------------------------------------------------- - * ParseFindMain -- - * Find a real target in the list and set it to be the main one. - * Called by ParseDoDependency when a main target hasn't been found - * yet. +/* + * Given the name of a source in a dependency line, figure out if it is an + * attribute (such as .SILENT) and apply it to the targets if it is. Else + * decide if there is some attribute which should be applied *to* the source + * because of some special target (such as .PHONY) and apply it if so. + * Otherwise, make the source a child of the targets in the list 'targets'. * * Input: - * gnp Node to examine - * - * Results: - * 0 if main not found yet, 1 if it is. - * - * Side Effects: - * mainNode is changed and Targ_SetMain is called. - * - *----------------------------------------------------------------------- - */ -static int -ParseFindMain(void *gnp, void *dummy) -{ - GNode *gn = (GNode *)gnp; - if ((gn->type & OP_NOTARGET) == 0) { - mainNode = gn; - Targ_SetMain(gn); - return (dummy ? 1 : 1); - } else { - return (dummy ? 0 : 0); - } -} - -/*- - *----------------------------------------------------------------------- - * ParseAddDir -- - * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going - * - * Results: - * === 0 - * - * Side Effects: - * See Dir_AddDir. - * - *----------------------------------------------------------------------- - */ -static int -ParseAddDir(void *path, void *name) -{ - (void)Dir_AddDir((Lst) path, (char *)name); - return(0); -} - -/*- - *----------------------------------------------------------------------- - * ParseClearPath -- - * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going - * - * Results: - * === 0 - * - * Side Effects: - * See Dir_ClearPath - * - *----------------------------------------------------------------------- - */ -static int -ParseClearPath(void *path, void *dummy) -{ - Dir_ClearPath((Lst) path); - return(dummy ? 0 : 0); -} - -/*- - *--------------------------------------------------------------------- - * ParseDoDependency -- - * Parse the dependency line in line. - * - * Input: - * line the line to parse - * - * Results: - * None - * - * Side Effects: - * The nodes of the sources are linked as children to the nodes of the - * targets. Some nodes may be created. - * - * We parse a dependency line by first extracting words from the line and - * finding nodes in the list of all targets with that name. This is done - * until a character is encountered which is an operator character. Currently - * these are only ! and :. At this point the operator is parsed and the - * pointer into the line advanced until the first source is encountered. - * The parsed operator is applied to each node in the 'targets' list, - * which is where the nodes found for the targets are kept, by means of - * the ParseDoOp function. - * The sources are read in much the same way as the targets were except - * that now they are expanded using the wildcarding scheme of the C-Shell - * and all instances of the resulting words in the list of all targets - * are found. Each of the resulting nodes is then linked to each of the - * targets as one of its children. - * Certain targets are handled specially. These are the ones detailed - * by the specType variable. - * The storing of transformation rules is also taken care of here. - * A target is recognized as a transformation rule by calling - * Suff_IsTransform. If it is a transformation rule, its node is gotten - * from the suffix module via Suff_AddTransform rather than the standard - * Targ_FindNode in the target module. - *--------------------------------------------------------------------- + * tOp operator (if any) from special targets + * src name of the source to handle */ static void -ParseDoDependency(char *line) +ParseDependencySource(GNodeType tOp, const char *src, ParseSpecial specType) { - char *cp; /* our current position */ - GNode *gn = NULL; /* a general purpose temporary node */ - int op; /* the operator on the line */ - char savec; /* a place to save a character */ - Lst paths; /* List of search paths to alter when parsing - * a list of .PATH targets */ - int tOp; /* operator from special target */ - Lst sources; /* list of archive source names after - * expansion */ - Lst curTargs; /* list of target names to be found and added - * to the targets list */ - char *lstart = line; + if (ParseDependencySourceKeyword(src, specType)) + return; - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseDoDependency(%s)\n", line); - tOp = 0; + if (specType == SP_MAIN) + ParseDependencySourceMain(src); + else if (specType == SP_ORDER) + ParseDependencySourceOrder(src); + else + ParseDependencySourceOther(src, tOp, specType); +} - specType = Not; - paths = NULL; +/* + * If we have yet to decide on a main target to make, in the absence of any + * user input, we want the first target on the first dependency line that is + * actually a real target (i.e. isn't a .USE or .EXEC rule) to be made. + */ +static void +FindMainTarget(void) +{ + GNodeListNode *ln; - curTargs = Lst_Init(FALSE); + if (mainNode != NULL) + return; - /* - * First, grind through the targets. - */ - - do { - /* - * Here LINE points to the beginning of the next word, and - * LSTART points to the actual beginning of the line. - */ - - /* Find the end of the next word. */ - for (cp = line; *cp && (ParseIsEscaped(lstart, cp) || - !(isspace((unsigned char)*cp) || - *cp == '!' || *cp == ':' || *cp == LPAREN)); - cp++) { - if (*cp == '$') { - /* - * Must be a dynamic source (would have been expanded - * otherwise), so call the Var module to parse the puppy - * so we can safely advance beyond it...There should be - * no errors in this, as they would have been discovered - * in the initial Var_Subst and we wouldn't be here. - */ - int length; - void *freeIt; - - (void)Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt); - if (freeIt) - free(freeIt); - cp += length-1; - } + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (!(gn->type & OP_NOTARGET)) { + DEBUG1(MAKE, "Setting main node to \"%s\"\n", gn->name); + mainNode = gn; + Targ_SetMain(gn); + return; + } } +} - /* - * If the word is followed by a left parenthesis, it's the - * name of an object file inside an archive (ar file). - */ - if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) { - /* - * Archives must be handled specially to make sure the OP_ARCHV - * flag is set in their 'type' field, for one thing, and because - * things like "archive(file1.o file2.o file3.o)" are permissible. - * Arch_ParseArchive will set 'line' to be the first non-blank - * after the archive-spec. It creates/finds nodes for the members - * and places them on the given list, returning SUCCESS if all - * went well and FAILURE if there was an error in the - * specification. On error, line should remain untouched. - */ - if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) { - Parse_Error(PARSE_FATAL, - "Error in archive specification: \"%s\"", line); - goto out; - } else { - /* Done with this word; on to the next. */ - continue; - } - } - - if (!*cp) { - /* - * We got to the end of the line while we were still - * looking at targets. - * - * Ending a dependency line without an operator is a Bozo - * no-no. As a heuristic, this is also often triggered by - * undetected conflicts from cvs/rcs merges. - */ - if ((strncmp(line, "<<<<<<", 6) == 0) || - (strncmp(line, "======", 6) == 0) || - (strncmp(line, ">>>>>>", 6) == 0)) +/* + * We got to the end of the line while we were still looking at targets. + * + * Ending a dependency line without an operator is a Bozo no-no. As a + * heuristic, this is also often triggered by undetected conflicts from + * cvs/rcs merges. + */ +static void +ParseErrorNoDependency(const char *lstart) +{ + if ((strncmp(lstart, "<<<<<<", 6) == 0) || + (strncmp(lstart, "======", 6) == 0) || + (strncmp(lstart, ">>>>>>", 6) == 0)) Parse_Error(PARSE_FATAL, "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); - else - Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" - : "Need an operator"); - goto out; + else if (lstart[0] == '.') { + const char *dirstart = lstart + 1; + const char *dirend; + cpp_skip_whitespace(&dirstart); + dirend = dirstart; + while (ch_isalnum(*dirend) || *dirend == '-') + dirend++; + Parse_Error(PARSE_FATAL, "Unknown directive \"%.*s\"", + (int)(dirend - dirstart), dirstart); + } else + Parse_Error(PARSE_FATAL, "Invalid line type"); +} + +static void +ParseDependencyTargetWord(const char **pp, const char *lstart) +{ + const char *cp = *pp; + + while (*cp != '\0') { + if ((ch_isspace(*cp) || *cp == '!' || *cp == ':' || + *cp == '(') && + !ParseIsEscaped(lstart, cp)) + break; + + if (*cp == '$') { + /* + * Must be a dynamic source (would have been expanded + * otherwise), so call the Var module to parse the + * puppy so we can safely advance beyond it. + * + * There should be no errors in this, as they would + * have been discovered in the initial Var_Subst and + * we wouldn't be here. + */ + const char *nested_p = cp; + FStr nested_val; + + (void)Var_Parse(&nested_p, SCOPE_CMDLINE, + VARE_PARSE_ONLY, &nested_val); + /* TODO: handle errors */ + FStr_Done(&nested_val); + cp += nested_p - cp; + } else + cp++; } - /* Insert a null terminator. */ - savec = *cp; - *cp = '\0'; - - /* - * Got the word. See if it's a special target and if so set - * specType to match it. - */ - if (*line == '.' && isupper ((unsigned char)line[1])) { - /* - * See if the target is a special target that must have it - * or its sources handled specially. - */ - int keywd = ParseFindKeyword(line); - if (keywd != -1) { - if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { - Parse_Error(PARSE_FATAL, "Mismatched special targets"); - goto out; - } - - specType = parseKeywords[keywd].spec; - tOp = parseKeywords[keywd].op; + *pp = cp; +} +/* Handle special targets like .PATH, .DEFAULT, .BEGIN, .ORDER. */ +static void +ParseDoDependencyTargetSpecial(ParseSpecial *inout_specType, + const char *targetName, + SearchPathList **inout_paths) +{ + switch (*inout_specType) { + case SP_PATH: + if (*inout_paths == NULL) + *inout_paths = Lst_New(); + Lst_Append(*inout_paths, &dirSearchPath); + break; + case SP_MAIN: /* - * Certain special targets have special semantics: - * .PATH Have to set the dirSearchPath - * variable too - * .MAIN Its sources are only used if - * nothing has been specified to - * create. - * .DEFAULT Need to create a node to hang - * commands on, but we don't want - * it in the graph, nor do we want - * it to be the Main Target, so we - * create it, set OP_NOTMAIN and - * add it to the list, setting - * DEFAULT to the new node for - * later use. We claim the node is - * A transformation rule to make - * life easier later, when we'll - * use Make_HandleUse to actually - * apply the .DEFAULT commands. - * .PHONY The list of targets - * .NOPATH Don't search for file in the path - * .STALE - * .BEGIN - * .END - * .ERROR - * .INTERRUPT Are not to be considered the - * main target. - * .NOTPARALLEL Make only one target at a time. - * .SINGLESHELL Create a shell for each command. - * .ORDER Must set initial predecessor to NULL + * Allow targets from the command line to override the + * .MAIN node. */ - switch (specType) { - case ExPath: - if (paths == NULL) { - paths = Lst_Init(FALSE); - } - (void)Lst_AtEnd(paths, dirSearchPath); - break; - case Main: - if (!Lst_IsEmpty(create)) { - specType = Not; - } - break; - case Begin: - case End: - case Stale: - case dotError: - case Interrupt: - gn = Targ_FindNode(line, TARG_CREATE); - if (doing_depend) + if (!Lst_IsEmpty(&opts.create)) + *inout_specType = SP_NOT; + break; + case SP_BEGIN: + case SP_END: + case SP_STALE: + case SP_ERROR: + case SP_INTERRUPT: { + GNode *gn = Targ_GetNode(targetName); + if (doing_depend) ParseMark(gn); - gn->type |= OP_NOTMAIN|OP_SPECIAL; - (void)Lst_AtEnd(targets, gn); - break; - case Default: - gn = Targ_NewGN(".DEFAULT"); - gn->type |= (OP_NOTMAIN|OP_TRANSFORM); - (void)Lst_AtEnd(targets, gn); - DEFAULT = gn; - break; - case NotParallel: - maxJobs = 1; - break; - case SingleShell: - compatMake = TRUE; - break; - case Order: - predecessor = NULL; - break; - default: - break; - } - } else if (strncmp(line, ".PATH", 5) == 0) { + gn->type |= OP_NOTMAIN | OP_SPECIAL; + Lst_Append(targets, gn); + break; + } + case SP_DEFAULT: { /* - * .PATH has to be handled specially. - * Call on the suffix module to give us a path to - * modify. + * Need to create a node to hang commands on, but we don't + * want it in the graph, nor do we want it to be the Main + * Target. We claim the node is a transformation rule to make + * life easier later, when we'll use Make_HandleUse to + * actually apply the .DEFAULT commands. */ - Lst path; + GNode *gn = GNode_New(".DEFAULT"); + gn->type |= OP_NOTMAIN | OP_TRANSFORM; + Lst_Append(targets, gn); + defaultNode = gn; + break; + } + case SP_DELETE_ON_ERROR: + deleteOnError = TRUE; + break; + case SP_NOTPARALLEL: + opts.maxJobs = 1; + break; + case SP_SINGLESHELL: + opts.compatMake = TRUE; + break; + case SP_ORDER: + order_pred = NULL; + break; + default: + break; + } +} - specType = ExPath; - path = Suff_GetPath(&line[5]); - if (path == NULL) { - Parse_Error(PARSE_FATAL, - "Suffix '%s' not defined (yet)", - &line[5]); - goto out; - } else { - if (paths == NULL) { - paths = Lst_Init(FALSE); - } - (void)Lst_AtEnd(paths, path); - } - } +/* + * .PATH has to be handled specially. + * Call on the suffix module to give us a path to modify. + */ +static Boolean +ParseDoDependencyTargetPath(const char *suffixName, + SearchPathList **inout_paths) +{ + SearchPath *path; + + path = Suff_GetPath(suffixName); + if (path == NULL) { + Parse_Error(PARSE_FATAL, + "Suffix '%s' not defined (yet)", suffixName); + return FALSE; } + if (*inout_paths == NULL) + *inout_paths = Lst_New(); + Lst_Append(*inout_paths, path); + + return TRUE; +} + +/* + * See if it's a special target and if so set specType to match it. + */ +static Boolean +ParseDoDependencyTarget(const char *targetName, + ParseSpecial *inout_specType, + GNodeType *out_tOp, SearchPathList **inout_paths) +{ + int keywd; + + if (!(targetName[0] == '.' && ch_isupper(targetName[1]))) + return TRUE; + /* - * Have word in line. Get or create its node and stick it at - * the end of the targets list + * See if the target is a special target that must have it + * or its sources handled specially. */ - if ((specType == Not) && (*line != '\0')) { - if (Dir_HasWildcards(line)) { + keywd = ParseFindKeyword(targetName); + if (keywd != -1) { + if (*inout_specType == SP_PATH && + parseKeywords[keywd].spec != SP_PATH) { + Parse_Error(PARSE_FATAL, "Mismatched special targets"); + return FALSE; + } + + *inout_specType = parseKeywords[keywd].spec; + *out_tOp = parseKeywords[keywd].op; + + ParseDoDependencyTargetSpecial(inout_specType, targetName, + inout_paths); + + } else if (strncmp(targetName, ".PATH", 5) == 0) { + *inout_specType = SP_PATH; + if (!ParseDoDependencyTargetPath(targetName + 5, inout_paths)) + return FALSE; + } + return TRUE; +} + +static void +ParseDoDependencyTargetMundane(char *targetName, StringList *curTargs) +{ + if (Dir_HasWildcards(targetName)) { /* * Targets are to be sought only in the current directory, * so create an empty path for the thing. Note we need to * use Dir_Destroy in the destruction of the path as the * Dir module could have added a directory to the path... */ - Lst emptyPath = Lst_Init(FALSE); + SearchPath *emptyPath = SearchPath_New(); - Dir_Expand(line, emptyPath, curTargs); + SearchPath_Expand(emptyPath, targetName, curTargs); - Lst_Destroy(emptyPath, Dir_Destroy); - } else { + SearchPath_Free(emptyPath); + } else { /* * No wildcards, but we want to avoid code duplication, * so create a list with the word on it. */ - (void)Lst_AtEnd(curTargs, line); - } + Lst_Append(curTargs, targetName); + } - /* Apply the targets. */ + /* Apply the targets. */ - while(!Lst_IsEmpty(curTargs)) { - char *targName = (char *)Lst_DeQueue(curTargs); - - if (!Suff_IsTransform (targName)) { - gn = Targ_FindNode(targName, TARG_CREATE); - } else { - gn = Suff_AddTransform(targName); - } + while (!Lst_IsEmpty(curTargs)) { + char *targName = Lst_Dequeue(curTargs); + GNode *gn = Suff_IsTransform(targName) + ? Suff_AddTransform(targName) + : Targ_GetNode(targName); if (doing_depend) - ParseMark(gn); + ParseMark(gn); - (void)Lst_AtEnd(targets, gn); - } - } else if (specType == ExPath && *line != '.' && *line != '\0') { - Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); + Lst_Append(targets, gn); } +} - /* Don't need the inserted null terminator any more. */ - *cp = savec; +static void +ParseDoDependencyTargetExtraWarn(char **pp, const char *lstart) +{ + Boolean warning = FALSE; + char *cp = *pp; - /* - * If it is a special type and not .PATH, it's the only target we - * allow on this line... - */ - if (specType != Not && specType != ExPath) { - Boolean warning = FALSE; - - while (*cp && (ParseIsEscaped(lstart, cp) || - ((*cp != '!') && (*cp != ':')))) { - if (ParseIsEscaped(lstart, cp) || - (*cp != ' ' && *cp != '\t')) { - warning = TRUE; - } + while (*cp != '\0') { + if (!ParseIsEscaped(lstart, cp) && (*cp == '!' || *cp == ':')) + break; + if (ParseIsEscaped(lstart, cp) || (*cp != ' ' && *cp != '\t')) + warning = TRUE; cp++; - } - if (warning) { + } + if (warning) Parse_Error(PARSE_WARNING, "Extra target ignored"); - } - } else { - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - } - line = cp; - } while (*line && (ParseIsEscaped(lstart, line) || - ((*line != '!') && (*line != ':')))); - /* - * Don't need the list of target names anymore... - */ - Lst_Destroy(curTargs, NULL); - curTargs = NULL; + *pp = cp; +} - if (!Lst_IsEmpty(targets)) { - switch(specType) { - default: - Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); - break; - case Default: - case Stale: - case Begin: - case End: - case dotError: - case Interrupt: - /* - * These four create nodes on which to hang commands, so - * targets shouldn't be empty... - */ - case Not: - /* - * Nothing special here -- targets can be empty if it wants. - */ - break; - } - } - - /* - * Have now parsed all the target names. Must parse the operator next. The - * result is left in op . - */ - if (*cp == '!') { - op = OP_FORCE; - } else if (*cp == ':') { - if (cp[1] == ':') { - op = OP_DOUBLEDEP; - cp++; - } else { - op = OP_DEPENDS; - } - } else { - Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" - : "Missing dependency operator"); - goto out; - } - - /* Advance beyond the operator */ - cp++; - - /* - * Apply the operator to the target. This is how we remember which - * operator a target was defined with. It fails if the operator - * used isn't consistent across all references. - */ - Lst_ForEach(targets, ParseDoOp, &op); - - /* - * Onward to the sources. - * - * LINE will now point to the first source word, if any, or the - * end of the string if not. - */ - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - - /* - * Several special targets take different actions if present with no - * sources: - * a .SUFFIXES line with no sources clears out all old suffixes - * a .PRECIOUS line makes all targets precious - * a .IGNORE line ignores errors for all targets - * a .SILENT line creates silence when making all targets - * a .PATH removes all directories from the search path(s). - */ - if (!*line) { +static void +ParseDoDependencyCheckSpec(ParseSpecial specType) +{ switch (specType) { - case Suffixes: + default: + Parse_Error(PARSE_WARNING, + "Special and mundane targets don't mix. " + "Mundane ones ignored"); + break; + case SP_DEFAULT: + case SP_STALE: + case SP_BEGIN: + case SP_END: + case SP_ERROR: + case SP_INTERRUPT: + /* + * These create nodes on which to hang commands, so targets + * shouldn't be empty. + */ + case SP_NOT: + /* Nothing special here -- targets can be empty if it wants. */ + break; + } +} + +static Boolean +ParseDoDependencyParseOp(char **pp, const char *lstart, GNodeType *out_op) +{ + const char *cp = *pp; + + if (*cp == '!') { + *out_op = OP_FORCE; + (*pp)++; + return TRUE; + } + + if (*cp == ':') { + if (cp[1] == ':') { + *out_op = OP_DOUBLEDEP; + (*pp) += 2; + } else { + *out_op = OP_DEPENDS; + (*pp)++; + } + return TRUE; + } + + { + const char *msg = lstart[0] == '.' + ? "Unknown directive" : "Missing dependency operator"; + Parse_Error(PARSE_FATAL, "%s", msg); + return FALSE; + } +} + +static void +ClearPaths(SearchPathList *paths) +{ + if (paths != NULL) { + SearchPathListNode *ln; + for (ln = paths->first; ln != NULL; ln = ln->next) + SearchPath_Clear(ln->datum); + } + + Dir_SetPATH(); +} + +static void +ParseDoDependencySourcesEmpty(ParseSpecial specType, SearchPathList *paths) +{ + switch (specType) { + case SP_SUFFIXES: Suff_ClearSuffixes(); break; - case Precious: + case SP_PRECIOUS: allPrecious = TRUE; break; - case Ignore: - ignoreErrors = TRUE; + case SP_IGNORE: + opts.ignoreErrors = TRUE; break; - case Silent: - beSilent = TRUE; + case SP_SILENT: + opts.beSilent = TRUE; break; - case ExPath: - Lst_ForEach(paths, ParseClearPath, NULL); - Dir_SetPATH(); + case SP_PATH: + ClearPaths(paths); break; #ifdef POSIX - case Posix: - Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0); - break; + case SP_POSIX: + Global_Set("%POSIX", "1003.2"); + break; #endif - default: + default: break; } - } else if (specType == MFlags) { +} + +static void +AddToPaths(const char *dir, SearchPathList *paths) +{ + if (paths != NULL) { + SearchPathListNode *ln; + for (ln = paths->first; ln != NULL; ln = ln->next) + (void)SearchPath_Add(ln->datum, dir); + } +} + +/* + * If the target was one that doesn't take files as its sources + * but takes something like suffixes, we take each + * space-separated word on the line as a something and deal + * with it accordingly. + * + * If the target was .SUFFIXES, we take each source as a + * suffix and add it to the list of suffixes maintained by the + * Suff module. + * + * If the target was a .PATH, we add the source as a directory + * to search on the search path. + * + * If it was .INCLUDES, the source is taken to be the suffix of + * files which will be #included and whose search path should + * be present in the .INCLUDES variable. + * + * If it was .LIBS, the source is taken to be the suffix of + * files which are considered libraries and whose search path + * should be present in the .LIBS variable. + * + * If it was .NULL, the source is the suffix to use when a file + * has no valid suffix. + * + * If it was .OBJDIR, the source is a new definition for .OBJDIR, + * and will cause make to do a new chdir to that path. + */ +static void +ParseDoDependencySourceSpecial(ParseSpecial specType, char *word, + SearchPathList *paths) +{ + switch (specType) { + case SP_SUFFIXES: + Suff_AddSuffix(word, &mainNode); + break; + case SP_PATH: + AddToPaths(word, paths); + break; + case SP_INCLUDES: + Suff_AddInclude(word); + break; + case SP_LIBS: + Suff_AddLib(word); + break; + case SP_NULL: + Suff_SetNull(word); + break; + case SP_OBJDIR: + Main_SetObjdir(FALSE, "%s", word); + break; + default: + break; + } +} + +static Boolean +ParseDoDependencyTargets(char **inout_cp, + char **inout_line, + const char *lstart, + ParseSpecial *inout_specType, + GNodeType *inout_tOp, + SearchPathList **inout_paths, + StringList *curTargs) +{ + char *cp; + char *tgt = *inout_line; + char savec; + const char *p; + + for (;;) { + /* + * Here LINE points to the beginning of the next word, and + * LSTART points to the actual beginning of the line. + */ + + /* Find the end of the next word. */ + cp = tgt; + p = cp; + ParseDependencyTargetWord(&p, lstart); + cp += p - cp; + + /* + * If the word is followed by a left parenthesis, it's the + * name of an object file inside an archive (ar file). + */ + if (!ParseIsEscaped(lstart, cp) && *cp == '(') { + /* + * Archives must be handled specially to make sure the + * OP_ARCHV flag is set in their 'type' field, for one + * thing, and because things like "archive(file1.o + * file2.o file3.o)" are permissible. + * + * Arch_ParseArchive will set 'line' to be the first + * non-blank after the archive-spec. It creates/finds + * nodes for the members and places them on the given + * list, returning TRUE if all went well and FALSE if + * there was an error in the specification. On error, + * line should remain untouched. + */ + if (!Arch_ParseArchive(&tgt, targets, SCOPE_CMDLINE)) { + Parse_Error(PARSE_FATAL, + "Error in archive specification: \"%s\"", + tgt); + return FALSE; + } + + cp = tgt; + continue; + } + + if (*cp == '\0') { + ParseErrorNoDependency(lstart); + return FALSE; + } + + /* Insert a null terminator. */ + savec = *cp; + *cp = '\0'; + + if (!ParseDoDependencyTarget(tgt, inout_specType, inout_tOp, + inout_paths)) + return FALSE; + + /* + * Have word in line. Get or create its node and stick it at + * the end of the targets list + */ + if (*inout_specType == SP_NOT && *tgt != '\0') + ParseDoDependencyTargetMundane(tgt, curTargs); + else if (*inout_specType == SP_PATH && *tgt != '.' && + *tgt != '\0') + Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", + tgt); + + /* Don't need the inserted null terminator any more. */ + *cp = savec; + + /* + * If it is a special type and not .PATH, it's the only target + * we allow on this line. + */ + if (*inout_specType != SP_NOT && *inout_specType != SP_PATH) + ParseDoDependencyTargetExtraWarn(&cp, lstart); + else + pp_skip_whitespace(&cp); + + tgt = cp; + if (*tgt == '\0') + break; + if ((*tgt == '!' || *tgt == ':') && + !ParseIsEscaped(lstart, tgt)) + break; + } + + *inout_cp = cp; + *inout_line = tgt; + return TRUE; +} + +static void +ParseDoDependencySourcesSpecial(char *start, char *end, + ParseSpecial specType, SearchPathList *paths) +{ + char savec; + + while (*start != '\0') { + while (*end != '\0' && !ch_isspace(*end)) + end++; + savec = *end; + *end = '\0'; + ParseDoDependencySourceSpecial(specType, start, paths); + *end = savec; + if (savec != '\0') + end++; + pp_skip_whitespace(&end); + start = end; + } +} + +static Boolean +ParseDoDependencySourcesMundane(char *start, char *end, + ParseSpecial specType, GNodeType tOp) +{ + while (*start != '\0') { + /* + * The targets take real sources, so we must beware of archive + * specifications (i.e. things with left parentheses in them) + * and handle them accordingly. + */ + for (; *end != '\0' && !ch_isspace(*end); end++) { + if (*end == '(' && end > start && end[-1] != '$') { + /* + * Only stop for a left parenthesis if it + * isn't at the start of a word (that'll be + * for variable changes later) and isn't + * preceded by a dollar sign (a dynamic + * source). + */ + break; + } + } + + if (*end == '(') { + GNodeList sources = LST_INIT; + if (!Arch_ParseArchive(&start, &sources, + SCOPE_CMDLINE)) { + Parse_Error(PARSE_FATAL, + "Error in source archive spec \"%s\"", + start); + return FALSE; + } + + while (!Lst_IsEmpty(&sources)) { + GNode *gn = Lst_Dequeue(&sources); + ParseDependencySource(tOp, gn->name, specType); + } + Lst_Done(&sources); + end = start; + } else { + if (*end != '\0') { + *end = '\0'; + end++; + } + + ParseDependencySource(tOp, start, specType); + } + pp_skip_whitespace(&end); + start = end; + } + return TRUE; +} + +/* + * Parse a dependency line consisting of targets, followed by a dependency + * operator, optionally followed by sources. + * + * The nodes of the sources are linked as children to the nodes of the + * targets. Nodes are created as necessary. + * + * The operator is applied to each node in the global 'targets' list, + * which is where the nodes found for the targets are kept, by means of + * the ParseDoOp function. + * + * The sources are parsed in much the same way as the targets, except + * that they are expanded using the wildcarding scheme of the C-Shell, + * and a target is created for each expanded word. Each of the resulting + * nodes is then linked to each of the targets as one of its children. + * + * Certain targets and sources such as .PHONY or .PRECIOUS are handled + * specially. These are the ones detailed by the specType variable. + * + * The storing of transformation rules such as '.c.o' is also taken care of + * here. A target is recognized as a transformation rule by calling + * Suff_IsTransform. If it is a transformation rule, its node is gotten + * from the suffix module via Suff_AddTransform rather than the standard + * Targ_FindNode in the target module. + * + * Upon return, the value of the line is unspecified. + */ +static void +ParseDoDependency(char *line) +{ + char *cp; /* our current position */ + GNodeType op; /* the operator on the line */ + SearchPathList *paths; /* search paths to alter when parsing + * a list of .PATH targets */ + GNodeType tOp; /* operator from special target */ + /* target names to be found and added to the targets list */ + StringList curTargs = LST_INIT; + char *lstart = line; + /* - * Call on functions in main.c to deal with these arguments and - * set the initial character to a null-character so the loop to - * get sources won't get anything + * specType contains the SPECial TYPE of the current target. It is + * SP_NOT if the target is unspecial. If it *is* special, however, the + * children are linked as children of the parent but not vice versa. */ - Main_ParseArgLine(line); - *line = '\0'; - } else if (specType == ExShell) { - if (Job_ParseShell(line) != SUCCESS) { - Parse_Error(PARSE_FATAL, "improper shell specification"); - goto out; - } - *line = '\0'; - } else if ((specType == NotParallel) || (specType == SingleShell)) { - *line = '\0'; - } + ParseSpecial specType = SP_NOT; - /* - * NOW GO FOR THE SOURCES - */ - if ((specType == Suffixes) || (specType == ExPath) || - (specType == Includes) || (specType == Libs) || - (specType == Null) || (specType == ExObjdir)) - { - while (*line) { - /* - * If the target was one that doesn't take files as its sources - * but takes something like suffixes, we take each - * space-separated word on the line as a something and deal - * with it accordingly. - * - * If the target was .SUFFIXES, we take each source as a - * suffix and add it to the list of suffixes maintained by the - * Suff module. - * - * If the target was a .PATH, we add the source as a directory - * to search on the search path. - * - * If it was .INCLUDES, the source is taken to be the suffix of - * files which will be #included and whose search path should - * be present in the .INCLUDES variable. - * - * If it was .LIBS, the source is taken to be the suffix of - * files which are considered libraries and whose search path - * should be present in the .LIBS variable. - * - * If it was .NULL, the source is the suffix to use when a file - * has no valid suffix. - * - * If it was .OBJDIR, the source is a new definition for .OBJDIR, - * and will cause make to do a new chdir to that path. - */ - while (*cp && !isspace ((unsigned char)*cp)) { - cp++; - } - savec = *cp; - *cp = '\0'; - switch (specType) { - case Suffixes: - Suff_AddSuffix(line, &mainNode); - break; - case ExPath: - Lst_ForEach(paths, ParseAddDir, line); - break; - case Includes: - Suff_AddInclude(line); - break; - case Libs: - Suff_AddLib(line); - break; - case Null: - Suff_SetNull(line); - break; - case ExObjdir: - Main_SetObjdir(line); - break; - default: - break; - } - *cp = savec; - if (savec != '\0') { - cp++; - } - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - } - if (paths) { - Lst_Destroy(paths, NULL); - } - if (specType == ExPath) - Dir_SetPATH(); - } else { - while (*line) { - /* - * The targets take real sources, so we must beware of archive - * specifications (i.e. things with left parentheses in them) - * and handle them accordingly. - */ - for (; *cp && !isspace ((unsigned char)*cp); cp++) { - if ((*cp == LPAREN) && (cp > line) && (cp[-1] != '$')) { - /* - * Only stop for a left parenthesis if it isn't at the - * start of a word (that'll be for variable changes - * later) and isn't preceded by a dollar sign (a dynamic - * source). - */ - break; - } - } + DEBUG1(PARSE, "ParseDoDependency(%s)\n", line); + tOp = OP_NONE; - if (*cp == LPAREN) { - sources = Lst_Init(FALSE); - if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) { - Parse_Error(PARSE_FATAL, - "Error in source archive spec \"%s\"", line); - goto out; - } + paths = NULL; - while (!Lst_IsEmpty (sources)) { - gn = (GNode *)Lst_DeQueue(sources); - ParseDoSrc(tOp, gn->name); - } - Lst_Destroy(sources, NULL); - cp = line; - } else { - if (*cp) { - *cp = '\0'; - cp += 1; - } - - ParseDoSrc(tOp, line); - } - while (*cp && isspace ((unsigned char)*cp)) { - cp++; - } - line = cp; - } - } - - if (mainNode == NULL) { /* - * If we have yet to decide on a main target to make, in the - * absence of any user input, we want the first target on - * the first dependency line that is actually a real target - * (i.e. isn't a .USE or .EXEC rule) to be made. + * First, grind through the targets. */ - Lst_ForEach(targets, ParseFindMain, NULL); - } + /* XXX: don't use line as an iterator variable */ + if (!ParseDoDependencyTargets(&cp, &line, lstart, &specType, &tOp, + &paths, &curTargs)) + goto out; + + /* + * Don't need the list of target names anymore. + * The targets themselves are now in the global variable 'targets'. + */ + Lst_Done(&curTargs); + Lst_Init(&curTargs); + + if (!Lst_IsEmpty(targets)) + ParseDoDependencyCheckSpec(specType); + + /* + * Have now parsed all the target names. Must parse the operator next. + */ + if (!ParseDoDependencyParseOp(&cp, lstart, &op)) + goto out; + + /* + * Apply the operator to the target. This is how we remember which + * operator a target was defined with. It fails if the operator + * used isn't consistent across all references. + */ + ApplyDependencyOperator(op); + + /* + * Onward to the sources. + * + * LINE will now point to the first source word, if any, or the + * end of the string if not. + */ + pp_skip_whitespace(&cp); + line = cp; /* XXX: 'line' is an inappropriate name */ + + /* + * Several special targets take different actions if present with no + * sources: + * a .SUFFIXES line with no sources clears out all old suffixes + * a .PRECIOUS line makes all targets precious + * a .IGNORE line ignores errors for all targets + * a .SILENT line creates silence when making all targets + * a .PATH removes all directories from the search path(s). + */ + if (line[0] == '\0') { + ParseDoDependencySourcesEmpty(specType, paths); + } else if (specType == SP_MFLAGS) { + /* + * Call on functions in main.c to deal with these arguments and + * set the initial character to a null-character so the loop to + * get sources won't get anything + */ + Main_ParseArgLine(line); + *line = '\0'; + } else if (specType == SP_SHELL) { + if (!Job_ParseShell(line)) { + Parse_Error(PARSE_FATAL, + "improper shell specification"); + goto out; + } + *line = '\0'; + } else if (specType == SP_NOTPARALLEL || specType == SP_SINGLESHELL || + specType == SP_DELETE_ON_ERROR) { + *line = '\0'; + } + + /* Now go for the sources. */ + if (specType == SP_SUFFIXES || specType == SP_PATH || + specType == SP_INCLUDES || specType == SP_LIBS || + specType == SP_NULL || specType == SP_OBJDIR) { + ParseDoDependencySourcesSpecial(line, cp, specType, paths); + if (paths != NULL) { + Lst_Free(paths); + paths = NULL; + } + if (specType == SP_PATH) + Dir_SetPATH(); + } else { + assert(paths == NULL); + if (!ParseDoDependencySourcesMundane(line, cp, specType, tOp)) + goto out; + } + + FindMainTarget(); out: - if (curTargs) - Lst_Destroy(curTargs, NULL); + if (paths != NULL) + Lst_Free(paths); + Lst_Done(&curTargs); } -/*- - *--------------------------------------------------------------------- - * Parse_IsVar -- - * Return TRUE if the passed line is a variable assignment. A variable - * assignment consists of a single word followed by optional whitespace - * followed by either a += or an = operator. - * This function is used both by the Parse_File function and main when - * parsing the command-line arguments. +typedef struct VarAssignParsed { + const char *nameStart; /* unexpanded */ + const char *nameEnd; /* before operator adjustment */ + const char *eq; /* the '=' of the assignment operator */ +} VarAssignParsed; + +/* + * Determine the assignment operator and adjust the end of the variable + * name accordingly. + */ +static void +AdjustVarassignOp(const VarAssignParsed *pvar, const char *value, + VarAssign *out_var) +{ + const char *op = pvar->eq; + const char *const name = pvar->nameStart; + VarAssignOp type; + + if (op > name && op[-1] == '+') { + type = VAR_APPEND; + op--; + + } else if (op > name && op[-1] == '?') { + op--; + type = VAR_DEFAULT; + + } else if (op > name && op[-1] == ':') { + op--; + type = VAR_SUBST; + + } else if (op > name && op[-1] == '!') { + op--; + type = VAR_SHELL; + + } else { + type = VAR_NORMAL; +#ifdef SUNSHCMD + while (op > name && ch_isspace(op[-1])) + op--; + + if (op >= name + 3 && op[-3] == ':' && op[-2] == 's' && + op[-1] == 'h') { + type = VAR_SHELL; + op -= 3; + } +#endif + } + + { + const char *nameEnd = pvar->nameEnd < op ? pvar->nameEnd : op; + out_var->varname = bmake_strsedup(pvar->nameStart, nameEnd); + out_var->op = type; + out_var->value = value; + } +} + +/* + * Parse a variable assignment, consisting of a single-word variable name, + * optional whitespace, an assignment operator, optional whitespace and the + * variable value. * - * Input: - * line the line to check + * Note: There is a lexical ambiguity with assignment modifier characters + * in variable names. This routine interprets the character before the = + * as a modifier. Therefore, an assignment like + * C++=/usr/bin/CC + * is interpreted as "C+ +=" instead of "C++ =". * - * Results: - * TRUE if it is. FALSE if it ain't - * - * Side Effects: - * none - *--------------------------------------------------------------------- + * Used for both lines in a file and command line arguments. */ Boolean -Parse_IsVar(char *line) +Parse_IsVar(const char *p, VarAssign *out_var) { - Boolean wasSpace = FALSE; /* set TRUE if found a space */ - char ch; - int level = 0; -#define ISEQOPERATOR(c) \ - (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) + VarAssignParsed pvar; + const char *firstSpace = NULL; + int level = 0; - /* - * Skip to variable name - */ - for (;(*line == ' ') || (*line == '\t'); line++) - continue; + cpp_skip_hspace(&p); /* Skip to variable name */ - /* Scan for one of the assignment operators outside a variable expansion */ - while ((ch = *line++) != 0) { - if (ch == '(' || ch == '{') { - level++; - continue; - } - if (ch == ')' || ch == '}') { - level--; - continue; - } - if (level != 0) - continue; - while (ch == ' ' || ch == '\t') { - ch = *line++; - wasSpace = TRUE; - } -#ifdef SUNSHCMD - if (ch == ':' && strncmp(line, "sh", 2) == 0) { - line += 2; - continue; - } + /* + * During parsing, the '+' of the '+=' operator is initially parsed + * as part of the variable name. It is later corrected, as is the + * ':sh' modifier. Of these two (nameEnd and op), the earlier one + * determines the actual end of the variable name. + */ + pvar.nameStart = p; +#ifdef CLEANUP + pvar.nameEnd = NULL; + pvar.eq = NULL; #endif - if (ch == '=') - return TRUE; - if (*line == '=' && ISEQOPERATOR(ch)) - return TRUE; - if (wasSpace) - return FALSE; - } - return FALSE; + /* + * Scan for one of the assignment operators outside a variable + * expansion. + */ + while (*p != '\0') { + char ch = *p++; + if (ch == '(' || ch == '{') { + level++; + continue; + } + if (ch == ')' || ch == '}') { + level--; + continue; + } + + if (level != 0) + continue; + + if (ch == ' ' || ch == '\t') + if (firstSpace == NULL) + firstSpace = p - 1; + while (ch == ' ' || ch == '\t') + ch = *p++; + +#ifdef SUNSHCMD + if (ch == ':' && p[0] == 's' && p[1] == 'h') { + p += 2; + continue; + } +#endif + if (ch == '=') { + pvar.eq = p - 1; + pvar.nameEnd = firstSpace != NULL ? firstSpace : p - 1; + cpp_skip_whitespace(&p); + AdjustVarassignOp(&pvar, p, out_var); + return TRUE; + } + if (*p == '=' && + (ch == '+' || ch == ':' || ch == '?' || ch == '!')) { + pvar.eq = p; + pvar.nameEnd = firstSpace != NULL ? firstSpace : p; + p++; + cpp_skip_whitespace(&p); + AdjustVarassignOp(&pvar, p, out_var); + return TRUE; + } + if (firstSpace != NULL) + return FALSE; + } + + return FALSE; } -/*- - *--------------------------------------------------------------------- - * Parse_DoVar -- - * Take the variable assignment in the passed line and do it in the - * global context. - * - * Note: There is a lexical ambiguity with assignment modifier characters - * in variable names. This routine interprets the character before the = - * as a modifier. Therefore, an assignment like - * C++=/usr/bin/CC - * is interpreted as "C+ +=" instead of "C++ =". - * - * Input: - * line a line guaranteed to be a variable assignment. - * This reduces error checks - * ctxt Context in which to do the assignment - * - * Results: - * none - * - * Side Effects: - * the variable structure of the given variable name is altered in the - * global context. - *--------------------------------------------------------------------- +/* + * Check for syntax errors such as unclosed expressions or unknown modifiers. */ -void -Parse_DoVar(char *line, GNode *ctxt) +static void +VarCheckSyntax(VarAssignOp type, const char *uvalue, GNode *scope) { - char *cp; /* pointer into line */ - enum { - VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL - } type; /* Type of assignment */ - char *opc; /* ptr to operator character to - * null-terminate the variable name */ - Boolean freeCp = FALSE; /* TRUE if cp needs to be freed, - * i.e. if any variable expansion was - * performed */ - int depth; + if (opts.strict) { + if (type != VAR_SUBST && strchr(uvalue, '$') != NULL) { + char *expandedValue; - /* - * Skip to variable name - */ - while ((*line == ' ') || (*line == '\t')) { - line++; - } - - /* - * Skip to operator character, nulling out whitespace as we go - * XXX Rather than counting () and {} we should look for $ and - * then expand the variable. - */ - for (depth = 0, cp = line + 1; depth != 0 || *cp != '='; cp++) { - if (*cp == '(' || *cp == '{') { - depth++; - continue; + (void)Var_Subst(uvalue, scope, VARE_PARSE_ONLY, + &expandedValue); + /* TODO: handle errors */ + free(expandedValue); + } } - if (*cp == ')' || *cp == '}') { - depth--; - continue; - } - if (depth == 0 && isspace ((unsigned char)*cp)) { - *cp = '\0'; - } - } - opc = cp-1; /* operator is the previous character */ - *cp++ = '\0'; /* nuke the = */ +} - /* - * Check operator type - */ - switch (*opc) { - case '+': - type = VAR_APPEND; - *opc = '\0'; - break; - - case '?': - /* - * If the variable already has a value, we don't do anything. - */ - *opc = '\0'; - if (Var_Exists(line, ctxt)) { - return; - } else { - type = VAR_NORMAL; - } - break; - - case ':': - type = VAR_SUBST; - *opc = '\0'; - break; - - case '!': - type = VAR_SHELL; - *opc = '\0'; - break; - - default: -#ifdef SUNSHCMD - while (opc > line && *opc != ':') - opc--; - - if (strncmp(opc, ":sh", 3) == 0) { - type = VAR_SHELL; - *opc = '\0'; - break; - } -#endif - type = VAR_NORMAL; - break; - } - - while (isspace ((unsigned char)*cp)) { - cp++; - } - - if (type == VAR_APPEND) { - Var_Append(line, cp, ctxt); - } else if (type == VAR_SUBST) { - /* - * Allow variables in the old value to be undefined, but leave their - * invocation alone -- this is done by forcing oldVars to be false. - * XXX: This can cause recursive variables, but that's not hard to do, - * and this allows someone to do something like - * - * CFLAGS = $(.INCLUDES) - * CFLAGS := -I.. $(CFLAGS) - * - * And not get an error. - */ - Boolean oldOldVars = oldVars; - - oldVars = FALSE; +static void +VarAssign_EvalSubst(GNode *scope, const char *name, const char *uvalue, + FStr *out_avalue) +{ + char *evalue; /* * make sure that we set the variable the first time to nothing - * so that it gets substituted! + * so that it gets substituted. + * + * TODO: Add a test that demonstrates why this code is needed, + * apart from making the debug log longer. */ - if (!Var_Exists(line, ctxt)) - Var_Set(line, "", ctxt, 0); + if (!Var_ExistsExpand(scope, name)) + Var_SetExpand(scope, name, ""); - cp = Var_Subst(NULL, cp, ctxt, FALSE); - oldVars = oldOldVars; - freeCp = TRUE; + (void)Var_Subst(uvalue, scope, VARE_KEEP_DOLLAR_UNDEF, &evalue); + /* TODO: handle errors */ - Var_Set(line, cp, ctxt, 0); - } else if (type == VAR_SHELL) { - char *res; - const char *error; + Var_SetExpand(scope, name, evalue); - if (strchr(cp, '$') != NULL) { - /* - * There's a dollar sign in the command, so perform variable - * expansion on the whole thing. The resulting string will need - * freeing when we're done, so set freeCmd to TRUE. - */ - cp = Var_Subst(NULL, cp, VAR_CMD, TRUE); - freeCp = TRUE; + *out_avalue = FStr_InitOwn(evalue); +} + +static void +VarAssign_EvalShell(const char *name, const char *uvalue, GNode *scope, + FStr *out_avalue) +{ + FStr cmd; + const char *errfmt; + char *cmdOut; + + cmd = FStr_InitRefer(uvalue); + if (strchr(cmd.str, '$') != NULL) { + char *expanded; + (void)Var_Subst(cmd.str, SCOPE_CMDLINE, VARE_UNDEFERR, + &expanded); + /* TODO: handle errors */ + cmd = FStr_InitOwn(expanded); } - res = Cmd_Exec(cp, &error); - Var_Set(line, res, ctxt, 0); - free(res); + cmdOut = Cmd_Exec(cmd.str, &errfmt); + Var_SetExpand(scope, name, cmdOut); + *out_avalue = FStr_InitOwn(cmdOut); - if (error) - Parse_Error(PARSE_WARNING, error, cp); - } else { - /* - * Normal assignment -- just do it. - */ - Var_Set(line, cp, ctxt, 0); - } - if (strcmp(line, MAKEOVERRIDES) == 0) - Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ - else if (strcmp(line, ".CURDIR") == 0) { - /* - * Somone is being (too?) clever... - * Let's pretend they know what they are doing and - * re-initialize the 'cur' Path. - */ - Dir_InitCur(cp); - Dir_SetPATH(); - } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) { - Job_SetPrefix(); - } else if (strcmp(line, MAKE_EXPORTED) == 0) { - Var_Export(cp, 0); - } - if (freeCp) - free(cp); + if (errfmt != NULL) + Parse_Error(PARSE_WARNING, errfmt, cmd.str); + + FStr_Done(&cmd); +} + +/* + * Perform a variable assignment. + * + * The actual value of the variable is returned in *out_TRUE_avalue. + * Especially for VAR_SUBST and VAR_SHELL this can differ from the literal + * value. + * + * Return whether the assignment was actually performed, which is usually + * the case. It is only skipped if the operator is '?=' and the variable + * already exists. + */ +static Boolean +VarAssign_Eval(const char *name, VarAssignOp op, const char *uvalue, + GNode *scope, FStr *out_TRUE_avalue) +{ + FStr avalue = FStr_InitRefer(uvalue); + + if (op == VAR_APPEND) + Var_AppendExpand(scope, name, uvalue); + else if (op == VAR_SUBST) + VarAssign_EvalSubst(scope, name, uvalue, &avalue); + else if (op == VAR_SHELL) + VarAssign_EvalShell(name, uvalue, scope, &avalue); + else { + if (op == VAR_DEFAULT && Var_ExistsExpand(scope, name)) + return FALSE; + + /* Normal assignment -- just do it. */ + Var_SetExpand(scope, name, uvalue); + } + + *out_TRUE_avalue = avalue; + return TRUE; +} + +static void +VarAssignSpecial(const char *name, const char *avalue) +{ + if (strcmp(name, MAKEOVERRIDES) == 0) + Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ + else if (strcmp(name, ".CURDIR") == 0) { + /* + * Someone is being (too?) clever... + * Let's pretend they know what they are doing and + * re-initialize the 'cur' CachedDir. + */ + Dir_InitCur(avalue); + Dir_SetPATH(); + } else if (strcmp(name, MAKE_JOB_PREFIX) == 0) + Job_SetPrefix(); + else if (strcmp(name, MAKE_EXPORTED) == 0) + Var_ExportVars(avalue); +} + +/* Perform the variable variable assignment in the given scope. */ +void +Parse_DoVar(VarAssign *var, GNode *scope) +{ + FStr avalue; /* actual value (maybe expanded) */ + + VarCheckSyntax(var->op, var->value, scope); + if (VarAssign_Eval(var->varname, var->op, var->value, scope, &avalue)) { + VarAssignSpecial(var->varname, avalue.str); + FStr_Done(&avalue); + } + + free(var->varname); } /* - * ParseMaybeSubMake -- - * Scan the command string to see if it a possible submake node - * Input: - * cmd the command to scan - * Results: - * TRUE if the command is possibly a submake, FALSE if not. + * See if the command possibly calls a sub-make by using the variable + * expressions ${.MAKE}, ${MAKE} or the plain word "make". */ static Boolean -ParseMaybeSubMake(const char *cmd) +MaybeSubMake(const char *cmd) { - size_t i; - static struct { - const char *name; - size_t len; - } vals[] = { -#define MKV(A) { A, sizeof(A) - 1 } - MKV("${MAKE}"), - MKV("${.MAKE}"), - MKV("$(MAKE)"), - MKV("$(.MAKE)"), - MKV("make"), - }; - for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) { - char *ptr; - if ((ptr = strstr(cmd, vals[i].name)) == NULL) - continue; - if ((ptr == cmd || !isalnum((unsigned char)ptr[-1])) - && !isalnum((unsigned char)ptr[vals[i].len])) - return TRUE; - } - return FALSE; + const char *start; + + for (start = cmd; *start != '\0'; start++) { + const char *p = start; + char endc; + + /* XXX: What if progname != "make"? */ + if (p[0] == 'm' && p[1] == 'a' && p[2] == 'k' && p[3] == 'e') + if (start == cmd || !ch_isalnum(p[-1])) + if (!ch_isalnum(p[4])) + return TRUE; + + if (*p != '$') + continue; + p++; + + if (*p == '{') + endc = '}'; + else if (*p == '(') + endc = ')'; + else + continue; + p++; + + if (*p == '.') /* Accept either ${.MAKE} or ${MAKE}. */ + p++; + + if (p[0] == 'M' && p[1] == 'A' && p[2] == 'K' && p[3] == 'E') + if (p[4] == endc) + return TRUE; + } + return FALSE; } -/*- - * ParseAddCmd -- - * Lst_ForEach function to add a command line to all targets +/* + * Append the command to the target node. * - * Input: - * gnp the node to which the command is to be added - * cmd the command to add - * - * Results: - * Always 0 - * - * Side Effects: - * A new element is added to the commands list of the node, - * and the node can be marked as a submake node if the command is - * determined to be that. - */ -static int -ParseAddCmd(void *gnp, void *cmd) -{ - GNode *gn = (GNode *)gnp; - - /* Add to last (ie current) cohort for :: targets */ - if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) - gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts)); - - /* if target already supplied, ignore commands */ - if (!(gn->type & OP_HAS_COMMANDS)) { - (void)Lst_AtEnd(gn->commands, cmd); - if (ParseMaybeSubMake(cmd)) - gn->type |= OP_SUBMAKE; - ParseMark(gn); - } else { -#ifdef notyet - /* XXX: We cannot do this until we fix the tree */ - (void)Lst_AtEnd(gn->commands, cmd); - Parse_Error(PARSE_WARNING, - "overriding commands for target \"%s\"; " - "previous commands defined at %s: %d ignored", - gn->name, gn->fname, gn->lineno); -#else - Parse_Error(PARSE_WARNING, - "duplicate script for target \"%s\" ignored", - gn->name); - ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING, - "using previous script for \"%s\" defined here", - gn->name); -#endif - } - return(0); -} - -/*- - *----------------------------------------------------------------------- - * ParseHasCommands -- - * Callback procedure for Parse_File when destroying the list of - * targets on the last dependency line. Marks a target as already - * having commands if it does, to keep from having shell commands - * on multiple dependency lines. - * - * Input: - * gnp Node to examine - * - * Results: - * None - * - * Side Effects: - * OP_HAS_COMMANDS may be set for the target. - * - *----------------------------------------------------------------------- + * The node may be marked as a submake node if the command is determined to + * be that. */ static void -ParseHasCommands(void *gnp) +ParseAddCmd(GNode *gn, char *cmd) { - GNode *gn = (GNode *)gnp; - if (!Lst_IsEmpty(gn->commands)) { - gn->type |= OP_HAS_COMMANDS; - } + /* Add to last (ie current) cohort for :: targets */ + if ((gn->type & OP_DOUBLEDEP) && gn->cohorts.last != NULL) + gn = gn->cohorts.last->datum; + + /* if target already supplied, ignore commands */ + if (!(gn->type & OP_HAS_COMMANDS)) { + Lst_Append(&gn->commands, cmd); + if (MaybeSubMake(cmd)) + gn->type |= OP_SUBMAKE; + ParseMark(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: %d ignored", + gn->name, gn->fname, gn->lineno); +#else + Parse_Error(PARSE_WARNING, + "duplicate script for target \"%s\" ignored", + gn->name); + ParseErrorInternal(gn->fname, (size_t)gn->lineno, PARSE_WARNING, + "using previous script for \"%s\" defined here", + gn->name); +#endif + } } -/*- - *----------------------------------------------------------------------- - * Parse_AddIncludeDir -- - * Add a directory to the path searched for included makefiles - * bracketed by double-quotes. Used by functions in main.c - * - * Input: - * dir The name of the directory to add - * - * Results: - * None. - * - * Side Effects: - * The directory is appended to the list. - * - *----------------------------------------------------------------------- +/* + * Add a directory to the path searched for included makefiles bracketed + * by double-quotes. */ void -Parse_AddIncludeDir(char *dir) +Parse_AddIncludeDir(const char *dir) { - (void)Dir_AddDir(parseIncPath, dir); + (void)SearchPath_Add(parseIncPath, dir); } -/*- - *--------------------------------------------------------------------- - * ParseDoInclude -- - * Push to another file. +/* + * Handle one of the .[-ds]include directives by remembering the current file + * and pushing the included file on the stack. After the included file has + * finished, parsing continues with the including file; see Parse_SetInput + * and ParseEOF. * - * The input is the line minus the `.'. A file spec is a string - * enclosed in <> or "". The former is looked for only in sysIncPath. - * The latter in . and the directories specified by -I command line - * options - * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFILE are altered for the new file - *--------------------------------------------------------------------- + * System includes are looked up in sysIncPath, any other includes are looked + * up in the parsedir and then in the directories specified by the -I command + * line options. */ - static void -Parse_include_file(char *file, Boolean isSystem, int silent) +IncludeFile(char *file, Boolean isSystem, Boolean depinc, Boolean silent) { - struct loadedfile *lf; - char *fullname; /* full pathname of file */ - char *newName; - char *prefEnd, *incdir; - int fd; - int i; + struct loadedfile *lf; + char *fullname; /* full pathname of file */ + char *newName; + char *slash, *incdir; + int fd; + int i; - /* - * Now we know the file's name and its search path, we attempt to - * find the durn thing. A return of NULL indicates the file don't - * exist. - */ - fullname = file[0] == '/' ? bmake_strdup(file) : NULL; + fullname = file[0] == '/' ? bmake_strdup(file) : NULL; - if (fullname == NULL && !isSystem) { - /* - * Include files contained in double-quotes are first searched for - * relative to the including file's location. We don't want to - * cd there, of course, so we just tack on the old file's - * leading path components and call Dir_FindFile to see if - * we can locate the beast. - */ + if (fullname == NULL && !isSystem) { + /* + * Include files contained in double-quotes are first searched + * relative to the including file's location. We don't want to + * cd there, of course, so we just tack on the old file's + * leading path components and call Dir_FindFile to see if + * we can locate the file. + */ - incdir = bmake_strdup(curFile->fname); - prefEnd = strrchr(incdir, '/'); - if (prefEnd != NULL) { - *prefEnd = '\0'; - /* Now do lexical processing of leading "../" on the filename */ - for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { - prefEnd = strrchr(incdir + 1, '/'); - if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0) - break; - *prefEnd = '\0'; - } - newName = str_concat(incdir, file + i, STR_ADDSLASH); - fullname = Dir_FindFile(newName, parseIncPath); - if (fullname == NULL) - fullname = Dir_FindFile(newName, dirSearchPath); - free(newName); + incdir = bmake_strdup(CurFile()->fname); + slash = strrchr(incdir, '/'); + if (slash != NULL) { + *slash = '\0'; + /* + * Now do lexical processing of leading "../" on the + * filename. + */ + for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { + slash = strrchr(incdir + 1, '/'); + if (slash == NULL || strcmp(slash, "/..") == 0) + break; + *slash = '\0'; + } + newName = str_concat3(incdir, "/", file + i); + fullname = Dir_FindFile(newName, parseIncPath); + if (fullname == NULL) + fullname = Dir_FindFile(newName, + &dirSearchPath); + free(newName); + } + free(incdir); + + if (fullname == NULL) { + /* + * Makefile wasn't found in same directory as included + * makefile. + * + * Search for it first on the -I search path, then on + * the .PATH search path, if not found in a -I + * directory. If we have a suffix-specific path, we + * should use that. + */ + const char *suff; + SearchPath *suffPath = NULL; + + if ((suff = strrchr(file, '.')) != NULL) { + suffPath = Suff_GetPath(suff); + if (suffPath != NULL) + fullname = Dir_FindFile(file, suffPath); + } + if (fullname == NULL) { + fullname = Dir_FindFile(file, parseIncPath); + if (fullname == NULL) + fullname = Dir_FindFile(file, + &dirSearchPath); + } + } + } + + /* Looking for a system file or file still not found */ + if (fullname == NULL) { + /* + * Look for it on the system path + */ + SearchPath *path = Lst_IsEmpty(&sysIncPath->dirs) + ? defSysIncPath : sysIncPath; + fullname = Dir_FindFile(file, path); } - free(incdir); if (fullname == NULL) { - /* - * Makefile wasn't found in same directory as included makefile. - * Search for it first on the -I search path, - * then on the .PATH search path, if not found in a -I directory. - * If we have a suffix specific path we should use that. - */ - char *suff; - Lst suffPath = NULL; - - if ((suff = strrchr(file, '.'))) { - suffPath = Suff_GetPath(suff); - if (suffPath != NULL) { - fullname = Dir_FindFile(file, suffPath); - } - } - if (fullname == NULL) { - fullname = Dir_FindFile(file, parseIncPath); - if (fullname == NULL) { - fullname = Dir_FindFile(file, dirSearchPath); - } - } + if (!silent) + Parse_Error(PARSE_FATAL, "Could not find %s", file); + return; + } + + /* Actually open the file... */ + fd = open(fullname, O_RDONLY); + if (fd == -1) { + if (!silent) + Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); + free(fullname); + return; + } + + /* load it */ + lf = loadfile(fullname, fd); + + /* Start reading from this file next */ + Parse_SetInput(fullname, 0, -1, loadedfile_readMore, lf); + CurFile()->lf = lf; + if (depinc) + doing_depend = depinc; /* only turn it on */ +} + +static void +ParseDoInclude(char *directive) +{ + char endc; /* the character which ends the file spec */ + char *cp; /* current position in file spec */ + Boolean silent = directive[0] != 'i'; + char *file = directive + (silent ? 8 : 7); + + /* Skip to delimiter character so we know where to look */ + pp_skip_hspace(&file); + + if (*file != '"' && *file != '<') { + Parse_Error(PARSE_FATAL, + ".include filename must be delimited by '\"' or '<'"); + return; } - } - /* Looking for a system file or file still not found */ - if (fullname == NULL) { /* - * Look for it on the system path + * Set the search path on which to find the include file based on the + * characters which bracket its name. Angle-brackets imply it's + * a system Makefile while double-quotes imply it's a user makefile */ - fullname = Dir_FindFile(file, - Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); - } + if (*file == '<') + endc = '>'; + else + endc = '"'; - if (fullname == NULL) { - if (!silent) - Parse_Error(PARSE_FATAL, "Could not find %s", file); - return; - } + /* Skip to matching delimiter */ + for (cp = ++file; *cp != '\0' && *cp != endc; cp++) + continue; - /* Actually open the file... */ - fd = open(fullname, O_RDONLY); - if (fd == -1) { - if (!silent) - Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); - free(fullname); - return; - } + if (*cp != endc) { + Parse_Error(PARSE_FATAL, + "Unclosed .include filename. '%c' expected", endc); + return; + } - /* load it */ - lf = loadfile(fullname, fd); + *cp = '\0'; - ParseSetIncludedFile(); - /* Start reading from this file next */ - Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf); - curFile->lf = lf; + /* + * Substitute for any variables in the filename before trying to + * find the file. + */ + (void)Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES, &file); + /* TODO: handle errors */ + + IncludeFile(file, endc == '>', directive[0] == 'd', silent); + free(file); } -static void -ParseDoInclude(char *line) -{ - char endc; /* the character which ends the file spec */ - char *cp; /* current position in file spec */ - int silent = (*line != 'i') ? 1 : 0; - char *file = &line[7 + silent]; - - /* Skip to delimiter character so we know where to look */ - while (*file == ' ' || *file == '\t') - file++; - - if (*file != '"' && *file != '<') { - Parse_Error(PARSE_FATAL, - ".include filename must be delimited by '\"' or '<'"); - return; - } - - /* - * Set the search path on which to find the include file based on the - * characters which bracket its name. Angle-brackets imply it's - * a system Makefile while double-quotes imply it's a user makefile - */ - if (*file == '<') { - endc = '>'; - } else { - endc = '"'; - } - - /* Skip to matching delimiter */ - for (cp = ++file; *cp && *cp != endc; cp++) - continue; - - if (*cp != endc) { - Parse_Error(PARSE_FATAL, - "Unclosed %cinclude filename. '%c' expected", - '.', endc); - return; - } - *cp = '\0'; - - /* - * Substitute for any variables in the file name before trying to - * find the thing. - */ - file = Var_Subst(NULL, file, VAR_CMD, FALSE); - - Parse_include_file(file, endc == '>', silent); - free(file); -} - - -/*- - *--------------------------------------------------------------------- - * ParseSetIncludedFile -- - * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE - * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR - * - * Results: - * None - * - * Side Effects: - * The .INCLUDEDFROMFILE variable is overwritten by the contents - * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten - * by the contents of .PARSEDIR - *--------------------------------------------------------------------- +/* + * Split filename into dirname + basename, then assign these to the + * given variables. */ static void -ParseSetIncludedFile(void) +SetFilenameVars(const char *filename, const char *dirvar, const char *filevar) { - char *pf, *fp = NULL; - char *pd, *dp = NULL; + const char *slash, *basename; + FStr dirname; - pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &fp); - Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL, 0); - pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &dp); - Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL, 0); + slash = strrchr(filename, '/'); + if (slash == NULL) { + dirname = FStr_InitRefer(curdir); + basename = filename; + } else { + dirname = FStr_InitOwn(bmake_strsedup(filename, slash)); + basename = slash + 1; + } - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' " - "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf); + Global_SetExpand(dirvar, dirname.str); + Global_SetExpand(filevar, basename); - if (fp) - free(fp); - if (dp) - free(dp); + DEBUG5(PARSE, "%s: ${%s} = `%s' ${%s} = `%s'\n", + __func__, dirvar, dirname.str, filevar, basename); + FStr_Done(&dirname); } -/*- - *--------------------------------------------------------------------- - * ParseSetParseFile -- - * Set the .PARSEDIR and .PARSEFILE variables to the dirname and - * basename of the given filename + +/* + * Return the immediately including file. * - * Results: - * None - * - * Side Effects: - * The .PARSEDIR and .PARSEFILE variables are overwritten by the - * dirname and basename of the given filename. - *--------------------------------------------------------------------- + * This is made complicated since the .for loop is implemented as a special + * kind of .include; see For_Run. */ +static const char * +GetActuallyIncludingFile(void) +{ + size_t i; + const IFile *incs = GetInclude(0); + + for (i = includes.len; i >= 2; i--) + if (!incs[i - 1].fromForLoop) + return incs[i - 2].fname; + return NULL; +} + +/* Set .PARSEDIR, .PARSEFILE, .INCLUDEDFROMDIR and .INCLUDEDFROMFILE. */ static void ParseSetParseFile(const char *filename) { - char *slash, *dirname; - const char *pd, *pf; - int len; + const char *including; - slash = strrchr(filename, '/'); - if (slash == NULL) { - Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL, 0); - Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL, 0); - dirname= NULL; - } else { - len = slash - filename; - dirname = bmake_malloc(len + 1); - memcpy(dirname, filename, len); - dirname[len] = '\0'; - Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL, 0); - Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL, 0); - } - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n", - __func__, pd, pf); - free(dirname); + SetFilenameVars(filename, ".PARSEDIR", ".PARSEFILE"); + + including = GetActuallyIncludingFile(); + if (including != NULL) { + SetFilenameVars(including, + ".INCLUDEDFROMDIR", ".INCLUDEDFROMFILE"); + } else { + Global_Delete(".INCLUDEDFROMDIR"); + Global_Delete(".INCLUDEDFROMFILE"); + } +} + +static Boolean +StrContainsWord(const char *str, const char *word) +{ + size_t strLen = strlen(str); + size_t wordLen = strlen(word); + const char *p, *end; + + if (strLen < wordLen) + return FALSE; /* str is too short to contain word */ + + end = str + strLen - wordLen; + for (p = str; p != NULL; p = strchr(p, ' ')) { + if (*p == ' ') + p++; + if (p > end) + return FALSE; /* cannot contain word */ + + if (memcmp(p, word, wordLen) == 0 && + (p[wordLen] == '\0' || p[wordLen] == ' ')) + return TRUE; + } + return FALSE; } /* - * Track the makefiles we read - so makefiles can - * set dependencies on them. - * Avoid adding anything more than once. + * XXX: Searching through a set of words with this linear search is + * inefficient for variables that contain thousands of words. + * + * XXX: The paths in this list don't seem to be normalized in any way. */ +static Boolean +VarContainsWord(const char *varname, const char *word) +{ + FStr val = Var_Value(SCOPE_GLOBAL, varname); + Boolean found = val.str != NULL && StrContainsWord(val.str, word); + FStr_Done(&val); + return found; +} +/* + * Track the makefiles we read - so makefiles can set dependencies on them. + * Avoid adding anything more than once. + * + * Time complexity: O(n) per call, in total O(n^2), where n is the number + * of makefiles that have been loaded. + */ static void ParseTrackInput(const char *name) { - char *old; - char *fp = NULL; - size_t name_len = strlen(name); - - old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp); - if (old) { - /* does it contain name? */ - for (; old != NULL; old = strchr(old, ' ')) { - if (*old == ' ') - old++; - if (memcmp(old, name, name_len) == 0 - && (old[name_len] == 0 || old[name_len] == ' ')) - goto cleanup; - } - } - Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL); - cleanup: - if (fp) { - free(fp); - } + if (!VarContainsWord(MAKE_MAKEFILES, name)) + Global_Append(MAKE_MAKEFILES, name); } -/*- - *--------------------------------------------------------------------- - * Parse_setInput -- - * Start Parsing from the given source +/* + * Start parsing from the given source. * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFile are altered for the new file - *--------------------------------------------------------------------- + * The given file is added to the includes stack. */ void -Parse_SetInput(const char *name, int line, int fd, - char *(*nextbuf)(void *, size_t *), void *arg) +Parse_SetInput(const char *name, int lineno, int fd, + ReadMoreProc readMore, void *readMoreArg) { - char *buf; - size_t len; + IFile *curFile; + char *buf; + size_t len; + Boolean fromForLoop = name == NULL; - if (name == NULL) - name = curFile->fname; - else - ParseTrackInput(name); + if (fromForLoop) + name = CurFile()->fname; + else + ParseTrackInput(name); - if (DEBUG(PARSE)) - fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n", - __func__, name, line, fd, nextbuf, arg); + DEBUG3(PARSE, "Parse_SetInput: %s %s, line %d\n", + readMore == loadedfile_readMore ? "file" : ".for loop in", + name, lineno); - if (fd == -1 && nextbuf == NULL) - /* sanity */ - return; + if (fd == -1 && readMore == NULL) + /* sanity */ + return; - if (curFile != NULL) - /* Save exiting file info */ - Lst_AtFront(includes, curFile); + curFile = Vector_Push(&includes); + curFile->fname = bmake_strdup(name); + curFile->fromForLoop = fromForLoop; + curFile->lineno = lineno; + curFile->first_lineno = lineno; + curFile->readMore = readMore; + curFile->readMoreArg = readMoreArg; + curFile->lf = NULL; + curFile->depending = doing_depend; /* restore this on EOF */ - /* Allocate and fill in new structure */ - curFile = bmake_malloc(sizeof *curFile); + assert(readMore != NULL); - /* - * Once the previous state has been saved, we can get down to reading - * the new file. We set up the name of the file to be the absolute - * name of the include file so error messages refer to the right - * place. - */ - curFile->fname = bmake_strdup(name); - curFile->lineno = line; - curFile->first_lineno = line; - curFile->nextbuf = nextbuf; - curFile->nextbuf_arg = arg; - curFile->lf = NULL; + /* Get first block of input data */ + buf = curFile->readMore(curFile->readMoreArg, &len); + if (buf == NULL) { + /* Was all a waste of time ... */ + if (curFile->fname != NULL) + free(curFile->fname); + free(curFile); + return; + } + curFile->buf_freeIt = buf; + curFile->buf_ptr = buf; + curFile->buf_end = buf + len; - assert(nextbuf != NULL); - - /* Get first block of input data */ - buf = curFile->nextbuf(curFile->nextbuf_arg, &len); - if (buf == NULL) { - /* Was all a waste of time ... */ - if (curFile->fname) - free(curFile->fname); - free(curFile); - return; - } - curFile->P_str = buf; - curFile->P_ptr = buf; - curFile->P_end = buf+len; - - curFile->cond_depth = Cond_save_depth(); - ParseSetParseFile(name); + curFile->cond_depth = Cond_save_depth(); + ParseSetParseFile(name); } +/* Check if the directive is an include directive. */ +static Boolean +IsInclude(const char *dir, Boolean sysv) +{ + if (dir[0] == 's' || dir[0] == '-' || (dir[0] == 'd' && !sysv)) + dir++; + + if (strncmp(dir, "include", 7) != 0) + return FALSE; + + /* Space is not mandatory for BSD .include */ + return !sysv || ch_isspace(dir[7]); +} + + #ifdef SYSVINCLUDE -/*- - *--------------------------------------------------------------------- - * ParseTraditionalInclude -- - * Push to another file. - * - * The input is the current line. The file name(s) are - * following the "include". - * - * Results: - * None - * - * Side Effects: - * A structure is added to the includes Lst and readProc, lineno, - * fname and curFILE are altered for the new file - *--------------------------------------------------------------------- - */ +/* Check if the line is a SYSV include directive. */ +static Boolean +IsSysVInclude(const char *line) +{ + const char *p; + + if (!IsInclude(line, TRUE)) + return FALSE; + + /* Avoid interpreting a dependency line as an include */ + for (p = line; (p = strchr(p, ':')) != NULL;) { + + /* end of line -> it's a dependency */ + if (*++p == '\0') + return FALSE; + + /* '::' operator or ': ' -> it's a dependency */ + if (*p == ':' || ch_isspace(*p)) + return FALSE; + } + return TRUE; +} + +/* Push to another file. The line points to the word "include". */ static void ParseTraditionalInclude(char *line) { - char *cp; /* current position in file spec */ - int done = 0; - int silent = (line[0] != 'i') ? 1 : 0; - char *file = &line[silent + 7]; - char *all_files; + char *cp; /* current position in file spec */ + Boolean done = FALSE; + Boolean silent = line[0] != 'i'; + char *file = line + (silent ? 8 : 7); + char *all_files; - if (DEBUG(PARSE)) { - fprintf(debug_file, "%s: %s\n", __func__, file); - } + DEBUG2(PARSE, "%s: %s\n", __func__, file); - /* - * Skip over whitespace - */ - while (isspace((unsigned char)*file)) - file++; + pp_skip_whitespace(&file); - /* - * Substitute for any variables in the file name before trying to - * find the thing. - */ - all_files = Var_Subst(NULL, file, VAR_CMD, FALSE); + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + (void)Var_Subst(file, SCOPE_CMDLINE, VARE_WANTRES, &all_files); + /* TODO: handle errors */ - if (*file == '\0') { - Parse_Error(PARSE_FATAL, - "Filename missing from \"include\""); - return; - } + if (*file == '\0') { + Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); + goto out; + } - for (file = all_files; !done; file = cp + 1) { - /* Skip to end of line or next whitespace */ - for (cp = file; *cp && !isspace((unsigned char) *cp); cp++) - continue; + for (file = all_files; !done; file = cp + 1) { + /* Skip to end of line or next whitespace */ + for (cp = file; *cp != '\0' && !ch_isspace(*cp); cp++) + continue; - if (*cp) - *cp = '\0'; - else - done = 1; + if (*cp != '\0') + *cp = '\0'; + else + done = TRUE; - Parse_include_file(file, FALSE, silent); - } - free(all_files); + IncludeFile(file, FALSE, FALSE, silent); + } +out: + free(all_files); } #endif #ifdef GMAKEEXPORT -/*- - *--------------------------------------------------------------------- - * ParseGmakeExport -- - * Parse export = - * - * And set the environment with it. - * - * Results: - * None - * - * Side Effects: - * None - *--------------------------------------------------------------------- - */ +/* Parse "export =", and actually export it. */ static void ParseGmakeExport(char *line) { - char *variable = &line[6]; - char *value; + char *variable = line + 6; + char *value; - if (DEBUG(PARSE)) { - fprintf(debug_file, "%s: %s\n", __func__, variable); - } + DEBUG2(PARSE, "%s: %s\n", __func__, variable); - /* - * Skip over whitespace - */ - while (isspace((unsigned char)*variable)) - variable++; + pp_skip_whitespace(&variable); - for (value = variable; *value && *value != '='; value++) - continue; + for (value = variable; *value != '\0' && *value != '='; value++) + continue; - if (*value != '=') { - Parse_Error(PARSE_FATAL, - "Variable/Value missing from \"export\""); - return; - } - *value++ = '\0'; /* terminate variable */ + if (*value != '=') { + Parse_Error(PARSE_FATAL, + "Variable/Value missing from \"export\""); + return; + } + *value++ = '\0'; /* terminate variable */ - /* - * Expand the value before putting it in the environment. - */ - value = Var_Subst(NULL, value, VAR_CMD, FALSE); - setenv(variable, value, 1); + /* + * Expand the value before putting it in the environment. + */ + (void)Var_Subst(value, SCOPE_CMDLINE, VARE_WANTRES, &value); + /* TODO: handle errors */ + + setenv(variable, value, 1); + free(value); } #endif -/*- - *--------------------------------------------------------------------- - * ParseEOF -- - * Called when EOF is reached in the current file. If we were reading - * an include file, the includes stack is popped and things set up - * to go back to reading the previous file at the previous location. +/* + * 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. * * Results: - * CONTINUE if there's more to do. DONE if not. - * - * Side Effects: - * The old curFILE, is closed. The includes list is shortened. - * lineno, curFILE, and fname are changed if CONTINUE is returned. - *--------------------------------------------------------------------- + * TRUE to continue parsing, i.e. it had only reached the end of an + * included file, FALSE if the main file has been parsed completely. */ -static int +static Boolean ParseEOF(void) { - char *ptr; - size_t len; + char *ptr; + size_t len; + IFile *curFile = CurFile(); - assert(curFile->nextbuf != NULL); + assert(curFile->readMore != NULL); - /* get next input buffer, if any */ - ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); - curFile->P_ptr = ptr; - curFile->P_str = ptr; - curFile->P_end = ptr + len; - curFile->lineno = curFile->first_lineno; - if (ptr != NULL) { - /* Iterate again */ - return CONTINUE; - } + doing_depend = curFile->depending; /* restore this */ + /* get next input buffer, if any */ + ptr = curFile->readMore(curFile->readMoreArg, &len); + curFile->buf_ptr = ptr; + curFile->buf_freeIt = ptr; + curFile->buf_end = ptr == NULL ? NULL : ptr + len; + curFile->lineno = curFile->first_lineno; + if (ptr != NULL) + return TRUE; /* Iterate again */ - /* Ensure the makefile (or loop) didn't have mismatched conditionals */ - Cond_restore_depth(curFile->cond_depth); + /* Ensure the makefile (or loop) didn't have mismatched conditionals */ + Cond_restore_depth(curFile->cond_depth); - if (curFile->lf != NULL) { - loadedfile_destroy(curFile->lf); - curFile->lf = NULL; - } + if (curFile->lf != NULL) { + loadedfile_destroy(curFile->lf); + curFile->lf = NULL; + } - /* Dispose of curFile info */ - /* Leak curFile->fname because all the gnodes have pointers to it */ - free(curFile->P_str); - free(curFile); + /* Dispose of curFile info */ + /* Leak curFile->fname because all the GNodes have pointers to it. */ + free(curFile->buf_freeIt); + Vector_Pop(&includes); - curFile = Lst_DeQueue(includes); + if (includes.len == 0) { + /* We've run out of input */ + Global_Delete(".PARSEDIR"); + Global_Delete(".PARSEFILE"); + Global_Delete(".INCLUDEDFROMDIR"); + Global_Delete(".INCLUDEDFROMFILE"); + return FALSE; + } - if (curFile == NULL) { - /* We've run out of input */ - Var_Delete(".PARSEDIR", VAR_GLOBAL); - Var_Delete(".PARSEFILE", VAR_GLOBAL); - Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); - Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); - return DONE; - } - - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n", + curFile = CurFile(); + DEBUG2(PARSE, "ParseEOF: returning to file %s, line %d\n", curFile->fname, curFile->lineno); - /* Restore the PARSEDIR/PARSEFILE variables */ - ParseSetParseFile(curFile->fname); - return (CONTINUE); + ParseSetParseFile(curFile->fname); + return TRUE; } -#define PARSE_RAW 1 -#define PARSE_SKIP 2 +typedef enum ParseRawLineResult { + PRLR_LINE, + PRLR_EOF, + PRLR_ERROR +} ParseRawLineResult; -static char * -ParseGetLine(int flags, int *length) +/* + * Parse until the end of a line, taking into account lines that end with + * backslash-newline. + */ +static ParseRawLineResult +ParseRawLine(IFile *curFile, char **out_line, char **out_line_end, + char **out_firstBackslash, char **out_firstComment) { - IFile *cf = curFile; - char *ptr; - char ch; - char *line; - char *line_end; - char *escaped; - char *comment; - char *tp; + char *line = curFile->buf_ptr; + char *p = line; + char *line_end = line; + char *firstBackslash = NULL; + char *firstComment = NULL; + ParseRawLineResult res = PRLR_LINE; + + curFile->lineno++; - /* Loop through blank lines and comment lines */ - for (;;) { - cf->lineno++; - line = cf->P_ptr; - ptr = line; - line_end = line; - escaped = NULL; - comment = NULL; for (;;) { - if (cf->P_end != NULL && ptr == cf->P_end) { - /* end of buffer */ - ch = 0; - break; - } - ch = *ptr; - if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { - if (cf->P_end == NULL) - /* End of string (aka for loop) data */ - break; - /* see if there is more we can parse */ - while (ptr++ < cf->P_end) { - if ((ch = *ptr) == '\n') { - if (ptr > line && ptr[-1] == '\\') - continue; - Parse_Error(PARSE_WARNING, - "Zero byte read from file, skipping rest of line."); + char ch; + + if (p == curFile->buf_end) { + res = PRLR_EOF; break; - } } - if (cf->nextbuf != NULL) { - /* - * End of this buffer; return EOF and outer logic - * will get the next one. (eww) - */ - break; + + ch = *p; + if (ch == '\0' || + (ch == '\\' && p + 1 < curFile->buf_end && p[1] == '\0')) { + Parse_Error(PARSE_FATAL, "Zero byte read from file"); + return PRLR_ERROR; } - Parse_Error(PARSE_FATAL, "Zero byte read from file"); - return NULL; - } - if (ch == '\\') { - /* Don't treat next character as special, remember first one */ - if (escaped == NULL) - escaped = ptr; - if (ptr[1] == '\n') - cf->lineno++; - ptr += 2; - line_end = ptr; - continue; - } - if (ch == '#' && comment == NULL) { - /* Remember first '#' for comment stripping */ - /* Unless previous char was '[', as in modifier :[#] */ - if (!(ptr > line && ptr[-1] == '[')) - comment = line_end; - } - ptr++; - if (ch == '\n') - break; - if (!isspace((unsigned char)ch)) - /* We are not interested in trailing whitespace */ - line_end = ptr; + /* Treat next character after '\' as literal. */ + if (ch == '\\') { + if (firstBackslash == NULL) + firstBackslash = p; + if (p[1] == '\n') { + curFile->lineno++; + if (p + 2 == curFile->buf_end) { + line_end = p; + *line_end = '\n'; + p += 2; + continue; + } + } + p += 2; + line_end = p; + assert(p <= curFile->buf_end); + continue; + } + + /* + * Remember the first '#' for comment stripping, unless + * the previous char was '[', as in the modifier ':[#]'. + */ + if (ch == '#' && firstComment == NULL && + !(p > line && p[-1] == '[')) + firstComment = line_end; + + p++; + if (ch == '\n') + break; + + /* We are not interested in trailing whitespace. */ + if (!ch_isspace(ch)) + line_end = p; } - /* Save next 'to be processed' location */ - cf->P_ptr = ptr; - - /* Check we have a non-comment, non-blank line */ - if (line_end == line || comment == line) { - if (ch == 0) - /* At end of file */ - return NULL; - /* Parse another line */ - continue; - } - - /* We now have a line of data */ - *line_end = 0; - - if (flags & PARSE_RAW) { - /* Leave '\' (etc) in line buffer (eg 'for' lines) */ - *length = line_end - line; - return line; - } - - if (flags & PARSE_SKIP) { - /* Completely ignore non-directives */ - if (line[0] != '.') - continue; - /* We could do more of the .else/.elif/.endif checks here */ - } - break; - } - - /* Brutally ignore anything after a non-escaped '#' in non-commands */ - if (comment != NULL && line[0] != '\t') { - line_end = comment; - *line_end = 0; - } - - /* If we didn't see a '\\' then the in-situ data is fine */ - if (escaped == NULL) { - *length = line_end - line; - return line; - } - - /* Remove escapes from '\n' and '#' */ - tp = ptr = escaped; - escaped = line; - for (; ; *tp++ = ch) { - ch = *ptr++; - if (ch != '\\') { - if (ch == 0) - break; - continue; - } - - ch = *ptr++; - if (ch == 0) { - /* Delete '\\' at end of buffer */ - tp--; - break; - } - - if (ch == '#' && line[0] != '\t') - /* Delete '\\' from before '#' on non-command lines */ - continue; - - if (ch != '\n') { - /* Leave '\\' in buffer for later */ - *tp++ = '\\'; - /* Make sure we don't delete an escaped ' ' from the line end */ - escaped = tp + 1; - continue; - } - - /* Escaped '\n' replace following whitespace with a single ' ' */ - while (ptr[0] == ' ' || ptr[0] == '\t') - ptr++; - ch = ' '; - } - - /* Delete any trailing spaces - eg from empty continuations */ - while (tp > escaped && isspace((unsigned char)tp[-1])) - tp--; - - *tp = 0; - *length = tp - line; - return line; + *out_line = line; + curFile->buf_ptr = p; + *out_line_end = line_end; + *out_firstBackslash = firstBackslash; + *out_firstComment = firstComment; + return res; } -/*- - *--------------------------------------------------------------------- - * ParseReadLine -- - * Read an entire line from the input file. Called only by Parse_File. +/* + * Beginning at start, unescape '\#' to '#' and replace backslash-newline + * with a single space. + */ +static void +UnescapeBackslash(char *line, char *start) +{ + char *src = start; + char *dst = start; + char *spaceStart = line; + + for (;;) { + char ch = *src++; + if (ch != '\\') { + if (ch == '\0') + break; + *dst++ = ch; + continue; + } + + ch = *src++; + if (ch == '\0') { + /* Delete '\\' at end of buffer */ + dst--; + break; + } + + /* Delete '\\' from before '#' on non-command lines */ + if (ch == '#' && line[0] != '\t') { + *dst++ = ch; + continue; + } + + if (ch != '\n') { + /* Leave '\\' in buffer for later */ + *dst++ = '\\'; + /* + * Make sure we don't delete an escaped ' ' from the + * line end. + */ + spaceStart = dst + 1; + *dst++ = ch; + continue; + } + + /* + * Escaped '\n' -- replace following whitespace with a single + * ' '. + */ + pp_skip_hspace(&src); + *dst++ = ' '; + } + + /* Delete any trailing spaces - eg from empty continuations */ + while (dst > spaceStart && ch_isspace(dst[-1])) + dst--; + *dst = '\0'; +} + +typedef enum GetLineMode { + /* + * Return the next line that is neither empty nor a comment. + * Backslash line continuations are folded into a single space. + * A trailing comment, if any, is discarded. + */ + GLM_NONEMPTY, + + /* + * Return the next line, even if it is empty or a comment. + * Preserve backslash-newline to keep the line numbers correct. + * + * Used in .for loops to collect the body of the loop while waiting + * for the corresponding .endfor. + */ + GLM_FOR_BODY, + + /* + * Return the next line that starts with a dot. + * Backslash line continuations are folded into a single space. + * A trailing comment, if any, is discarded. + * + * Used in .if directives to skip over irrelevant branches while + * waiting for the corresponding .endif. + */ + GLM_DOT +} GetLineMode; + +/* Return the next "interesting" logical line from the current file. */ +static char * +ParseGetLine(GetLineMode mode) +{ + IFile *curFile = CurFile(); + char *line; + char *line_end; + char *firstBackslash; + char *firstComment; + + for (;;) { + ParseRawLineResult res = ParseRawLine(curFile, + &line, &line_end, &firstBackslash, &firstComment); + if (res == PRLR_ERROR) + return NULL; + + if (line_end == line || firstComment == line) { + if (res == PRLR_EOF) + return NULL; + if (mode != GLM_FOR_BODY) + continue; + } + + /* We now have a line of data */ + assert(ch_isspace(*line_end)); + *line_end = '\0'; + + if (mode == GLM_FOR_BODY) + return line; /* Don't join the physical lines. */ + + if (mode == GLM_DOT && line[0] != '.') + continue; + break; + } + + /* Brutally ignore anything after a non-escaped '#' in non-commands. */ + if (firstComment != NULL && line[0] != '\t') + *firstComment = '\0'; + + /* If we didn't see a '\\' then the in-situ data is fine. */ + if (firstBackslash == NULL) + return line; + + /* Remove escapes from '\n' and '#' */ + UnescapeBackslash(line, firstBackslash); + + return line; +} + +static Boolean +ParseSkippedBranches(void) +{ + char *line; + + while ((line = ParseGetLine(GLM_DOT)) != NULL) { + if (Cond_EvalLine(line) == COND_PARSE) + break; + /* + * 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 + * ParseDependency can, and to prevent code + * duplication, these would need to be called + * with a flag called onlyCheckSyntax. + * + * See directive-elif.mk for details. + */ + } + + return line != NULL; +} + +static Boolean +ParseForLoop(const char *line) +{ + int rval; + int firstLineno; + + rval = For_Eval(line); + if (rval == 0) + return FALSE; /* Not a .for line */ + if (rval < 0) + return TRUE; /* Syntax error - error printed, ignore line */ + + firstLineno = CurFile()->lineno; + + /* Accumulate loop lines until matching .endfor */ + do { + line = ParseGetLine(GLM_FOR_BODY); + if (line == NULL) { + Parse_Error(PARSE_FATAL, + "Unexpected end of file in for loop."); + break; + } + } while (For_Accum(line)); + + For_Run(firstLineno); /* Stash each iteration as a new 'input file' */ + + return TRUE; /* Read next line from for-loop buffer */ +} + +/* + * 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. * * Results: - * A line w/o its newline - * - * Side Effects: - * Only those associated with reading a character - *--------------------------------------------------------------------- + * A line without its newline and without any trailing whitespace, + * or NULL. */ static char * ParseReadLine(void) { - char *line; /* Result */ - int lineLength; /* Length of result */ - int lineno; /* Saved line # */ - int rval; + char *line; - for (;;) { - line = ParseGetLine(0, &lineLength); - if (line == NULL) - return NULL; + for (;;) { + line = ParseGetLine(GLM_NONEMPTY); + if (line == NULL) + return NULL; - if (line[0] != '.') - return line; + if (line[0] != '.') + return line; - /* - * The line might be a conditional. Ask the conditional module - * about it and act accordingly - */ - switch (Cond_Eval(line)) { - case COND_SKIP: - /* Skip to next conditional that evaluates to COND_PARSE. */ - do { - line = ParseGetLine(PARSE_SKIP, &lineLength); - } while (line && Cond_Eval(line) != COND_PARSE); - if (line == NULL) - break; - continue; - case COND_PARSE: - continue; - case COND_INVALID: /* Not a conditional line */ - /* Check for .for loops */ - rval = For_Eval(line); - if (rval == 0) - /* Not a .for line */ - break; - if (rval < 0) - /* Syntax error - error printed, ignore line */ - continue; - /* Start of a .for loop */ - lineno = curFile->lineno; - /* Accumulate loop lines until matching .endfor */ - do { - line = ParseGetLine(PARSE_RAW, &lineLength); - if (line == NULL) { - Parse_Error(PARSE_FATAL, - "Unexpected end of file in for loop."); - break; + /* + * The line might be a conditional. Ask the conditional module + * about it and act accordingly + */ + switch (Cond_EvalLine(line)) { + case COND_SKIP: + if (!ParseSkippedBranches()) + return NULL; + continue; + case COND_PARSE: + continue; + case COND_INVALID: /* Not a conditional line */ + if (ParseForLoop(line)) + continue; + break; } - } while (For_Accum(line)); - /* Stash each iteration as a new 'input file' */ - For_Run(lineno); - /* Read next line from for-loop buffer */ - continue; + return line; } - return (line); - } } -/*- - *----------------------------------------------------------------------- - * ParseFinishLine -- - * Handle the end of a dependency group. - * - * Results: - * Nothing. - * - * Side Effects: - * inLine set FALSE. 'targets' list destroyed. - * - *----------------------------------------------------------------------- +static void +FinishDependencyGroup(void) +{ + GNodeListNode *ln; + + if (targets == NULL) + return; + + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + + Suff_EndTransform(gn); + + /* + * Mark the target as already having commands if it does, to + * keep from having shell commands on multiple dependency + * lines. + */ + if (!Lst_IsEmpty(&gn->commands)) + gn->type |= OP_HAS_COMMANDS; + } + + Lst_Free(targets); + targets = NULL; +} + +/* Add the command to each target from the current dependency spec. */ +static void +ParseLine_ShellCommand(const char *p) +{ + cpp_skip_whitespace(&p); + if (*p == '\0') + return; /* skip empty commands */ + + if (targets == NULL) { + Parse_Error(PARSE_FATAL, + "Unassociated shell command \"%s\"", p); + return; + } + + { + char *cmd = bmake_strdup(p); + GNodeListNode *ln; + + for (ln = targets->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + ParseAddCmd(gn, cmd); + } +#ifdef CLEANUP + Lst_Append(&targCmds, cmd); +#endif + } +} + +MAKE_INLINE Boolean +IsDirective(const char *dir, size_t dirlen, const char *name) +{ + return dirlen == strlen(name) && memcmp(dir, name, dirlen) == 0; +} + +/* + * See if the line starts with one of the known directives, and if so, handle + * the directive. + */ +static Boolean +ParseDirective(char *line) +{ + char *cp = line + 1; + const char *dir, *arg; + size_t dirlen; + + pp_skip_whitespace(&cp); + if (IsInclude(cp, FALSE)) { + ParseDoInclude(cp); + return TRUE; + } + + dir = cp; + while (ch_isalpha(*cp) || *cp == '-') + cp++; + dirlen = (size_t)(cp - dir); + + if (*cp != '\0' && !ch_isspace(*cp)) + return FALSE; + + pp_skip_whitespace(&cp); + arg = cp; + + if (IsDirective(dir, dirlen, "undef")) { + Var_Undef(cp); + return TRUE; + } else if (IsDirective(dir, dirlen, "export")) { + Var_Export(VEM_PLAIN, arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "export-env")) { + Var_Export(VEM_ENV, arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "export-literal")) { + Var_Export(VEM_LITERAL, arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "unexport")) { + Var_UnExport(FALSE, arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "unexport-env")) { + Var_UnExport(TRUE, arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "info")) { + ParseMessage(PARSE_INFO, "info", arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "warning")) { + ParseMessage(PARSE_WARNING, "warning", arg); + return TRUE; + } else if (IsDirective(dir, dirlen, "error")) { + ParseMessage(PARSE_FATAL, "error", arg); + return TRUE; + } + return FALSE; +} + +static Boolean +ParseVarassign(const char *line) +{ + VarAssign var; + + if (!Parse_IsVar(line, &var)) + return FALSE; + + FinishDependencyGroup(); + Parse_DoVar(&var, SCOPE_GLOBAL); + return TRUE; +} + +static char * +FindSemicolon(char *p) +{ + int level = 0; + + for (; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') { + p++; + continue; + } + + if (*p == '$' && (p[1] == '(' || p[1] == '{')) + level++; + else if (level > 0 && (*p == ')' || *p == '}')) + level--; + else if (level == 0 && *p == ';') + break; + } + return p; +} + +/* + * dependency -> target... op [source...] + * op -> ':' | '::' | '!' */ static void -ParseFinishLine(void) +ParseDependency(char *line) { - if (inLine) { - Lst_ForEach(targets, Suff_EndTransform, NULL); - Lst_Destroy(targets, ParseHasCommands); - targets = NULL; - inLine = FALSE; - } + VarEvalFlags eflags; + 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 avoid ';' inside substitution patterns. + */ + { + char *semicolon = FindSemicolon(line); + if (*semicolon != '\0') { + /* Terminate the dependency list at the ';' */ + *semicolon = '\0'; + shellcmd = semicolon + 1; + } + } + + /* + * We now know it's a dependency line so it needs to have all + * variables expanded before being parsed. + * + * XXX: Ideally the dependency line would first be split into + * its left-hand side, dependency operator and right-hand side, + * and then each side would be expanded on its own. This would + * allow for the left-hand side to allow only defined variables + * and to allow variables on the right-hand side to be undefined + * as well. + * + * Parsing the line first would also prevent that targets + * generated from variable expressions are interpreted as the + * dependency operator, such as in "target${:U\:} middle: source", + * in which the middle is interpreted as a source, not a target. + */ + + /* In lint mode, allow undefined variables to appear in + * dependency lines. + * + * Ideally, only the right-hand side would allow undefined + * variables since it is common to have optional dependencies. + * Having undefined variables on the left-hand side is more + * unusual though. Since both sides are expanded in a single + * pass, there is not much choice what to do here. + * + * In normal mode, it does not matter whether undefined + * variables are allowed or not since as of 2020-09-14, + * Var_Parse does not print any parse errors in such a case. + * It simply returns the special empty string var_Error, + * which cannot be detected in the result of Var_Subst. */ + eflags = opts.strict ? VARE_WANTRES : VARE_UNDEFERR; + (void)Var_Subst(line, SCOPE_CMDLINE, eflags, &expanded_line); + /* TODO: handle errors */ + + /* Need a fresh list for the target nodes */ + if (targets != NULL) + Lst_Free(targets); + targets = Lst_New(); + + ParseDoDependency(expanded_line); + free(expanded_line); + + if (shellcmd != NULL) + ParseLine_ShellCommand(shellcmd); } - -/*- - *--------------------------------------------------------------------- - * Parse_File -- - * Parse a file into its component parts, incorporating it into the - * current dependency graph. This is the main function and controls - * almost every other function in this module - * - * Input: - * name the name of the file being read - * fd Open file to makefile to parse - * - * Results: - * None - * - * Side Effects: - * closes fd. - * Loads. Nodes are added to the list of all targets, nodes and links - * are added to the dependency graph. etc. etc. etc. - *--------------------------------------------------------------------- - */ -void -Parse_File(const char *name, int fd) +static void +ParseLine(char *line) { - char *cp; /* pointer into the line */ - char *line; /* the line we're working on */ - struct loadedfile *lf; + /* + * 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; - lf = loadfile(name, fd); - - inLine = FALSE; - fatals = 0; - - if (name == NULL) { - name = "(stdin)"; - } - - Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); - curFile->lf = lf; - - do { - for (; (line = ParseReadLine()) != NULL; ) { - if (DEBUG(PARSE)) - fprintf(debug_file, "ParseReadLine (%d): '%s'\n", - curFile->lineno, line); - if (*line == '.') { - /* - * Lines that begin with the special character may be - * include or undef directives. - * On the other hand they can be suffix rules (.c.o: ...) - * or just dependencies for filenames that start '.'. - */ - for (cp = line + 1; isspace((unsigned char)*cp); cp++) { - continue; - } - if (strncmp(cp, "include", 7) == 0 || - ((cp[0] == 's' || cp[0] == '-') && - strncmp(&cp[1], "include", 7) == 0)) { - ParseDoInclude(cp); - continue; - } - if (strncmp(cp, "undef", 5) == 0) { - char *cp2; - for (cp += 5; isspace((unsigned char) *cp); cp++) - continue; - for (cp2 = cp; !isspace((unsigned char) *cp2) && - (*cp2 != '\0'); cp2++) - continue; - *cp2 = '\0'; - Var_Delete(cp, VAR_GLOBAL); - continue; - } else if (strncmp(cp, "export", 6) == 0) { - for (cp += 6; isspace((unsigned char) *cp); cp++) - continue; - Var_Export(cp, 1); - continue; - } else if (strncmp(cp, "unexport", 8) == 0) { - Var_UnExport(cp); - continue; - } else if (strncmp(cp, "info", 4) == 0 || - strncmp(cp, "error", 5) == 0 || - strncmp(cp, "warning", 7) == 0) { - if (ParseMessage(cp)) - continue; - } - } - - if (*line == '\t') { - /* - * If a line starts with a tab, it can only hope to be - * a creation command. - */ - cp = line + 1; - shellCommand: - for (; isspace ((unsigned char)*cp); cp++) { - continue; - } - if (*cp) { - if (!inLine) - Parse_Error(PARSE_FATAL, - "Unassociated shell command \"%s\"", - cp); - /* - * So long as it's not a blank line and we're actually - * in a dependency spec, add the command to the list of - * commands of all targets in the dependency spec - */ - if (targets) { - cp = bmake_strdup(cp); - Lst_ForEach(targets, ParseAddCmd, cp); -#ifdef CLEANUP - Lst_AtEnd(targCmds, cp); -#endif - } - } - continue; - } + if (line[0] == '\t') { + ParseLine_ShellCommand(line + 1); + return; + } #ifdef SYSVINCLUDE - if (((strncmp(line, "include", 7) == 0 && - isspace((unsigned char) line[7])) || - ((line[0] == 's' || line[0] == '-') && - strncmp(&line[1], "include", 7) == 0 && - isspace((unsigned char) line[8]))) && - strchr(line, ':') == NULL) { + if (IsSysVInclude(line)) { /* * It's an S3/S5-style "include". */ ParseTraditionalInclude(line); - continue; - } + return; + } #endif + #ifdef GMAKEEXPORT - if (strncmp(line, "export", 6) == 0 && - isspace((unsigned char) line[6]) && - strchr(line, ':') == NULL) { + if (strncmp(line, "export", 6) == 0 && ch_isspace(line[6]) && + strchr(line, ':') == NULL) { /* * It's a Gmake "export". */ ParseGmakeExport(line); - continue; - } -#endif - if (Parse_IsVar(line)) { - ParseFinishLine(); - Parse_DoVar(line, VAR_GLOBAL); - continue; - } - -#ifndef POSIX - /* - * To make life easier on novices, if the line is indented we - * first make sure the line has a dependency operator in it. - * If it doesn't have an operator and we're in a dependency - * line's script, we assume it's actually a shell command - * and add it to the current list of targets. - */ - cp = line; - if (isspace((unsigned char) line[0])) { - while ((*cp != '\0') && isspace((unsigned char) *cp)) - cp++; - while (*cp && (ParseIsEscaped(line, cp) || - (*cp != ':') && (*cp != '!'))) { - cp++; - } - if (*cp == '\0') { - if (inLine) { - Parse_Error(PARSE_WARNING, - "Shell command needs a leading tab"); - goto shellCommand; - } - } - } -#endif - ParseFinishLine(); - - /* - * For some reason - probably to make the parser impossible - - * a ';' can be used to separate commands from dependencies. - * Attempt to avoid ';' inside substitution patterns. - */ - { - int level = 0; - - for (cp = line; *cp != 0; cp++) { - if (*cp == '\\' && cp[1] != 0) { - cp++; - continue; - } - if (*cp == '$' && - (cp[1] == '(' || cp[1] == '{')) { - level++; - continue; - } - if (level > 0) { - if (*cp == ')' || *cp == '}') { - level--; - continue; - } - } else if (*cp == ';') { - break; - } - } - } - if (*cp != 0) - /* Terminate the dependency list at the ';' */ - *cp++ = 0; - else - cp = NULL; - - /* - * We now know it's a dependency line so it needs to have all - * variables expanded before being parsed. Tell the variable - * module to complain if some variable is undefined... - */ - line = Var_Subst(NULL, line, VAR_CMD, TRUE); - - /* - * Need a non-circular list for the target nodes - */ - if (targets) - Lst_Destroy(targets, NULL); - - targets = Lst_Init(FALSE); - inLine = TRUE; - - ParseDoDependency(line); - free(line); - - /* If there were commands after a ';', add them now */ - if (cp != NULL) { - goto shellCommand; - } + return; } - /* - * Reached EOF, but it may be just EOF of an include file... - */ - } while (ParseEOF() == CONTINUE); +#endif - if (fatals) { - (void)fflush(stdout); - (void)fprintf(stderr, - "%s: Fatal errors encountered -- cannot continue", - progname); - PrintOnError(NULL, NULL); - exit(1); - } + if (ParseVarassign(line)) + return; + + FinishDependencyGroup(); + + ParseDependency(line); } -/*- - *--------------------------------------------------------------------- - * Parse_Init -- - * initialize the parsing module +/* + * Parse a top-level makefile, incorporating its content into the global + * dependency graph. * - * Results: - * none - * - * Side Effects: - * the parseIncPath list is initialized... - *--------------------------------------------------------------------- + * Input: + * name The name of the file being read + * fd The open file to parse; will be closed at the end */ void +Parse_File(const char *name, int fd) +{ + char *line; /* the line we're working on */ + struct loadedfile *lf; + + lf = loadfile(name, fd); + + assert(targets == NULL); + + if (name == NULL) + name = "(stdin)"; + + Parse_SetInput(name, 0, -1, loadedfile_readMore, lf); + CurFile()->lf = lf; + + do { + while ((line = ParseReadLine()) != NULL) { + DEBUG2(PARSE, "ParseReadLine (%d): '%s'\n", + CurFile()->lineno, line); + ParseLine(line); + } + /* Reached EOF, but it may be just EOF of an include file. */ + } while (ParseEOF()); + + FinishDependencyGroup(); + + if (fatals != 0) { + (void)fflush(stdout); + (void)fprintf(stderr, + "%s: Fatal errors encountered -- cannot continue", + progname); + PrintOnError(NULL, NULL); + exit(1); + } +} + +/* Initialize the parsing module. */ +void Parse_Init(void) { - mainNode = NULL; - parseIncPath = Lst_Init(FALSE); - sysIncPath = Lst_Init(FALSE); - defIncPath = Lst_Init(FALSE); - includes = Lst_Init(FALSE); -#ifdef CLEANUP - targCmds = Lst_Init(FALSE); -#endif + mainNode = NULL; + parseIncPath = SearchPath_New(); + sysIncPath = SearchPath_New(); + defSysIncPath = SearchPath_New(); + Vector_Init(&includes, sizeof(IFile)); } +/* Clean up the parsing module. */ void Parse_End(void) { #ifdef CLEANUP - Lst_Destroy(targCmds, (FreeProc *)free); - if (targets) - Lst_Destroy(targets, NULL); - Lst_Destroy(defIncPath, Dir_Destroy); - Lst_Destroy(sysIncPath, Dir_Destroy); - Lst_Destroy(parseIncPath, Dir_Destroy); - Lst_Destroy(includes, NULL); /* Should be empty now */ + Lst_DoneCall(&targCmds, free); + assert(targets == NULL); + SearchPath_Free(defSysIncPath); + SearchPath_Free(sysIncPath); + SearchPath_Free(parseIncPath); + assert(includes.len == 0); + Vector_Done(&includes); #endif } -/*- - *----------------------------------------------------------------------- - * Parse_MainName -- - * Return a Lst of the main target to create for main()'s sake. If - * no such target exists, we Punt with an obnoxious error message. - * - * Results: - * A Lst of the single node to create. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- +/* + * Return a list containing the single main target to create. + * If no such target exists, we Punt with an obnoxious error message. */ -Lst -Parse_MainName(void) +void +Parse_MainName(GNodeList *mainList) { - Lst mainList; /* result list */ + if (mainNode == NULL) + Punt("no target to make."); - mainList = Lst_Init(FALSE); + Lst_Append(mainList, mainNode); + if (mainNode->type & OP_DOUBLEDEP) + Lst_AppendAll(mainList, &mainNode->cohorts); - if (mainNode == NULL) { - Punt("no target to make."); - /*NOTREACHED*/ - } else if (mainNode->type & OP_DOUBLEDEP) { - (void)Lst_AtEnd(mainList, mainNode); - Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW); - } - else - (void)Lst_AtEnd(mainList, mainNode); - Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); - return (mainList); + Global_Append(".TARGETS", mainNode->name); } -/*- - *----------------------------------------------------------------------- - * ParseMark -- - * Add the filename and lineno to the GNode so that we remember - * where it was first defined. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- - */ -static void -ParseMark(GNode *gn) +int +Parse_GetFatals(void) { - gn->fname = curFile->fname; - gn->lineno = curFile->lineno; + return fatals; } diff --git a/usr.bin/make/pathnames.h b/usr.bin/make/pathnames.h index 12c4f3d7b..3c7000ca1 100644 --- a/usr.bin/make/pathnames.h +++ b/usr.bin/make/pathnames.h @@ -1,4 +1,4 @@ -/* $NetBSD: pathnames.h,v 1.17 2009/04/11 09:41:18 apb Exp $ */ +/* $NetBSD: pathnames.h,v 1.18 2020/11/29 09:27:40 rillig Exp $ */ /* * Copyright (c) 1990, 1993 @@ -39,15 +39,15 @@ #include #endif -#define _PATH_OBJDIR "obj" -#define _PATH_OBJDIRPREFIX "/usr/obj" +#define _PATH_OBJDIR "obj" +#define _PATH_OBJDIRPREFIX "/usr/obj" #ifndef _PATH_DEFSHELLDIR -#define _PATH_DEFSHELLDIR "/bin" +#define _PATH_DEFSHELLDIR "/bin" #endif -#define _PATH_DEFSYSMK "sys.mk" +#define _PATH_DEFSYSMK "sys.mk" #ifndef _PATH_DEFSYSPATH -#define _PATH_DEFSYSPATH "/usr/share/mk" +#define _PATH_DEFSYSPATH "/usr/share/mk" #endif #ifndef _PATH_TMP -#define _PATH_TMP "/tmp/" /* with trailing slash */ +#define _PATH_TMP "/tmp/" /* with trailing slash */ #endif diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h deleted file mode 100644 index 6ec4fe2e4..000000000 --- a/usr.bin/make/sprite.h +++ /dev/null @@ -1,116 +0,0 @@ -/* $NetBSD: sprite.h,v 1.11 2009/01/23 21:26:30 dsl Exp $ */ - -/* - * Copyright (c) 1988, 1989, 1990, 1993 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93 - */ - -/* - * Copyright (c) 1989 by Berkeley Softworks - * All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Adam de Boor. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93 - */ - -/* - * sprite.h -- - * - * Common constants and type declarations for Sprite. - */ - -#ifndef _SPRITE -#define _SPRITE - - -/* - * A boolean type is defined as an integer, not an enum. This allows a - * boolean argument to be an expression that isn't strictly 0 or 1 valued. - */ - -typedef int Boolean; -#ifndef TRUE -#define TRUE 1 -#endif /* TRUE */ -#ifndef FALSE -#define FALSE 0 -#endif /* FALSE */ - -/* - * Functions that must return a status can return a ReturnStatus to - * indicate success or type of failure. - */ - -typedef int ReturnStatus; - -/* - * The following statuses overlap with the first 2 generic statuses - * defined in status.h: - * - * SUCCESS There was no error. - * FAILURE There was a general error. - */ - -#define SUCCESS 0x00000000 -#define FAILURE 0x00000001 - -#endif /* _SPRITE */ diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c index 0260447f8..c486df6d3 100644 --- a/usr.bin/make/str.c +++ b/usr.bin/make/str.c @@ -1,6 +1,6 @@ -/* $NetBSD: str.c,v 1.35 2014/02/12 01:35:56 sjg Exp $ */ +/* $NetBSD: str.c,v 1.81 2021/02/01 22:36:28 rillig Exp $ */ -/*- +/* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * @@ -32,7 +32,7 @@ * SUCH DAMAGE. */ -/*- +/* * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * @@ -68,186 +68,176 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: str.c,v 1.35 2014/02/12 01:35:56 sjg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90"; -#else -__RCSID("$NetBSD: str.c,v 1.35 2014/02/12 01:35:56 sjg Exp $"); -#endif -#endif /* not lint */ -#endif - #include "make.h" -/*- - * str_concat -- - * concatenate the two strings, inserting a space or slash between them, - * freeing them if requested. - * - * returns -- - * the resulting string in allocated space. - */ +/* "@(#)str.c 5.8 (Berkeley) 6/1/90" */ +MAKE_RCSID("$NetBSD: str.c,v 1.81 2021/02/01 22:36:28 rillig Exp $"); + +/* Return the concatenation of s1 and s2, freshly allocated. */ char * -str_concat(const char *s1, const char *s2, int flags) +str_concat2(const char *s1, const char *s2) { - int len1, len2; - char *result; - - /* get the length of both strings */ - len1 = strlen(s1); - len2 = strlen(s2); - - /* allocate length plus separator plus EOS */ - result = bmake_malloc((u_int)(len1 + len2 + 2)); - - /* copy first string into place */ + size_t len1 = strlen(s1); + size_t len2 = strlen(s2); + char *result = bmake_malloc(len1 + len2 + 1); memcpy(result, s1, len1); - - /* add separator character */ - if (flags & STR_ADDSPACE) { - result[len1] = ' '; - ++len1; - } else if (flags & STR_ADDSLASH) { - result[len1] = '/'; - ++len1; - } - - /* copy second string plus EOS into place */ memcpy(result + len1, s2, len2 + 1); - - return(result); + return result; } -/*- - * brk_string -- - * Fracture a string into an array of words (as delineated by tabs or - * spaces) taking quotation marks into account. Leading tabs/spaces - * are ignored. - * - * If expand is TRUE, quotes are removed and escape sequences - * such as \r, \t, etc... are expanded. - * - * returns -- - * Pointer to the array of pointers to the words. - * Memory containing the actual words in *buffer. - * Both of these must be free'd by the caller. - * Number of words in *store_argc. - */ -char ** -brk_string(const char *str, int *store_argc, Boolean expand, char **buffer) +/* Return the concatenation of s1, s2 and s3, freshly allocated. */ +char * +str_concat3(const char *s1, const char *s2, const char *s3) { - int argc, ch; - char inquote, *start, *t; - const char *p; - int len; - int argmax = 50, curlen = 0; - char **argv; + size_t len1 = strlen(s1); + size_t len2 = strlen(s2); + size_t len3 = strlen(s3); + char *result = bmake_malloc(len1 + len2 + len3 + 1); + memcpy(result, s1, len1); + memcpy(result + len1, s2, len2); + memcpy(result + len1 + len2, s3, len3 + 1); + return result; +} - /* skip leading space chars. */ - for (; *str == ' ' || *str == '\t'; ++str) - continue; +/* Return the concatenation of s1, s2, s3 and s4, freshly allocated. */ +char * +str_concat4(const char *s1, const char *s2, const char *s3, const char *s4) +{ + size_t len1 = strlen(s1); + size_t len2 = strlen(s2); + size_t len3 = strlen(s3); + size_t len4 = strlen(s4); + char *result = bmake_malloc(len1 + len2 + len3 + len4 + 1); + memcpy(result, s1, len1); + memcpy(result + len1, s2, len2); + memcpy(result + len1 + len2, s3, len3); + memcpy(result + len1 + len2 + len3, s4, len4 + 1); + return result; +} - /* allocate room for a copy of the string */ - if ((len = strlen(str) + 1) > curlen) - *buffer = bmake_malloc(curlen = len); +/* + * Fracture a string into an array of words (as delineated by tabs or spaces) + * taking quotation marks into account. + * + * 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. + * + * Returns the fractured words, which must be freed later using Words_Free, + * unless the returned Words.words was NULL. + */ +Words +Str_Words(const char *str, Boolean expand) +{ + size_t str_len; + char *words_buf; + size_t words_cap; + char **words; + size_t words_len; + char inquote; + char *word_start; + char *word_end; + const char *str_p; - /* - * initial argmax based on len - */ - argmax = MAX((len / 5), 50); - argv = bmake_malloc((argmax + 1) * sizeof(char *)); + /* XXX: why only hspace, not whitespace? */ + cpp_skip_hspace(&str); /* skip leading space chars. */ + + /* words_buf holds the words, separated by '\0'. */ + str_len = strlen(str); + words_buf = bmake_malloc(str_len + 1); + + words_cap = str_len / 5 > 50 ? str_len / 5 : 50; + words = bmake_malloc((words_cap + 1) * sizeof(char *)); /* * copy the string; at the same time, parse backslashes, - * quotes and build the argument list. + * quotes and build the word list. */ - argc = 0; + words_len = 0; inquote = '\0'; - for (p = str, start = t = *buffer;; ++p) { - switch(ch = *p) { + word_start = words_buf; + word_end = words_buf; + for (str_p = str;; str_p++) { + char ch = *str_p; + switch (ch) { case '"': case '\'': - if (inquote) { + if (inquote != '\0') { if (inquote == ch) inquote = '\0'; else break; - } - else { - inquote = (char) ch; + } else { + inquote = ch; /* Don't miss "" or '' */ - if (start == NULL && p[1] == inquote) { + if (word_start == NULL && str_p[1] == inquote) { if (!expand) { - start = t; - *t++ = ch; + word_start = word_end; + *word_end++ = ch; } else - start = t + 1; - p++; + word_start = word_end + 1; + str_p++; inquote = '\0'; break; } } if (!expand) { - if (!start) - start = t; - *t++ = ch; + if (word_start == NULL) + word_start = word_end; + *word_end++ = ch; } continue; case ' ': case '\t': case '\n': - if (inquote) + if (inquote != '\0') break; - if (!start) + if (word_start == NULL) continue; /* FALLTHROUGH */ case '\0': /* - * end of a token -- make sure there's enough argv + * end of a token -- make sure there's enough words * space and save off a pointer. */ - if (!start) - goto done; + if (word_start == NULL) + goto done; - *t++ = '\0'; - if (argc == argmax) { - argmax *= 2; /* ramp up fast */ - argv = (char **)bmake_realloc(argv, - (argmax + 1) * sizeof(char *)); + *word_end++ = '\0'; + if (words_len == words_cap) { + size_t new_size; + words_cap *= 2; /* ramp up fast */ + new_size = (words_cap + 1) * sizeof(char *); + words = bmake_realloc(words, new_size); } - argv[argc++] = start; - start = NULL; + words[words_len++] = word_start; + word_start = NULL; if (ch == '\n' || ch == '\0') { - if (expand && inquote) { - free(argv); - free(*buffer); - *buffer = NULL; - return NULL; + if (expand && inquote != '\0') { + free(words); + free(words_buf); + return (Words){ NULL, 0, NULL }; } goto done; } continue; case '\\': if (!expand) { - if (!start) - start = t; - *t++ = '\\'; - if (*(p+1) == '\0') /* catch '\' at end of line */ + if (word_start == NULL) + word_start = word_end; + *word_end++ = '\\'; + /* catch '\' at end of line */ + if (str_p[1] == '\0') continue; - ch = *++p; + ch = *++str_p; break; } - switch (ch = *++p) { + switch (ch = *++str_p) { case '\0': case '\n': /* hmmm; fix it up as best we can */ ch = '\\'; - --p; + str_p--; break; case 'b': ch = '\b'; @@ -267,248 +257,119 @@ brk_string(const char *str, int *store_argc, Boolean expand, char **buffer) } break; } - if (!start) - start = t; - *t++ = (char) ch; + if (word_start == NULL) + word_start = word_end; + *word_end++ = ch; } -done: argv[argc] = NULL; - *store_argc = argc; - return(argv); +done: + words[words_len] = NULL; /* useful for argv */ + return (Words){ words, words_len, words_buf }; } /* - * Str_FindSubstring -- See if a string contains a particular substring. + * Str_Match -- Test if a string matches a pattern like "*.[ch]". + * The following special characters are known *?\[] (as in fnmatch(3)). * - * Input: - * string String to search. - * substring Substring to find in string. - * - * Results: If string contains substring, the return value is the location of - * the first matching instance of substring in string. If string doesn't - * contain substring, the return value is NULL. Matching is done on an exact - * character-for-character basis with no wildcards or special characters. - * - * Side effects: None. + * XXX: this function does not detect or report malformed patterns. */ -char * -Str_FindSubstring(const char *string, const char *substring) +Boolean +Str_Match(const char *str, const char *pat) { - const char *a, *b; - - /* - * First scan quickly through the two strings looking for a single- - * character match. When it's found, then compare the rest of the - * substring. - */ - - for (b = substring; *string != 0; string += 1) { - if (*string != *b) - continue; - a = string; - for (;;) { - if (*b == 0) - return UNCONST(string); - if (*a++ != *b++) - break; - } - b = substring; - } - return NULL; -} - -/* - * Str_Match -- - * - * See if a particular string matches a particular pattern. - * - * Results: Non-zero is returned if string matches pattern, 0 otherwise. The - * matching operation permits the following special characters in the - * pattern: *?\[] (see the man page for details on what these mean). - * - * XXX this function does not detect or report malformed patterns. - * - * Side effects: None. - */ -int -Str_Match(const char *string, const char *pattern) -{ - char c2; - for (;;) { /* * See if we're at the end of both the pattern and the - * string. If, we succeeded. If we're at the end of the + * string. If so, we succeeded. If we're at the end of the * pattern but not at the end of the string, we failed. */ - if (*pattern == 0) - return(!*string); - if (*string == 0 && *pattern != '*') - return(0); + if (*pat == '\0') + return *str == '\0'; + if (*str == '\0' && *pat != '*') + return FALSE; + /* - * Check for a "*" as the next pattern character. It matches - * any substring. We handle this by calling ourselves - * recursively for each postfix of string, until either we - * match or we reach the end of the string. + * A '*' in the pattern matches any substring. We handle this + * by calling ourselves for each suffix of the string. */ - if (*pattern == '*') { - pattern += 1; - if (*pattern == 0) - return(1); - while (*string != 0) { - if (Str_Match(string, pattern)) - return(1); - ++string; + if (*pat == '*') { + pat++; + while (*pat == '*') + pat++; + if (*pat == '\0') + return TRUE; + while (*str != '\0') { + if (Str_Match(str, pat)) + return TRUE; + str++; } - return(0); + return FALSE; } - /* - * Check for a "?" as the next pattern character. It matches - * any single character. - */ - if (*pattern == '?') + + /* A '?' in the pattern matches any single character. */ + if (*pat == '?') goto thisCharOK; + /* - * Check for a "[" as the next pattern character. It is - * followed by a list of characters that are acceptable, or - * by a range (two characters separated by "-"). + * A '[' in the pattern matches a character from a list. + * The '[' is followed by the list of acceptable characters, + * or by ranges (two characters separated by '-'). In these + * character lists, the backslash is an ordinary character. */ - if (*pattern == '[') { - ++pattern; + if (*pat == '[') { + Boolean neg = pat[1] == '^'; + pat += neg ? 2 : 1; + for (;;) { - if ((*pattern == ']') || (*pattern == 0)) - return(0); - if (*pattern == *string) - break; - if (pattern[1] == '-') { - c2 = pattern[2]; - if (c2 == 0) - return(0); - if ((*pattern <= *string) && - (c2 >= *string)) + if (*pat == ']' || *pat == '\0') { + if (neg) break; - if ((*pattern >= *string) && - (c2 <= *string)) - break; - pattern += 2; + return FALSE; } - ++pattern; + /* + * XXX: This naive comparison makes the + * control flow of the pattern parser + * dependent on the actual value of the + * string. This is unpredictable. It may be + * though that the code only looks wrong but + * actually all code paths result in the same + * behavior. This needs further tests. + */ + if (*pat == *str) + break; + if (pat[1] == '-') { + if (pat[2] == '\0') + return neg; + if (*pat <= *str && pat[2] >= *str) + break; + if (*pat >= *str && pat[2] <= *str) + break; + pat += 2; + } + pat++; } - while ((*pattern != ']') && (*pattern != 0)) - ++pattern; + if (neg && *pat != ']' && *pat != '\0') + return FALSE; + while (*pat != ']' && *pat != '\0') + pat++; + if (*pat == '\0') + pat--; goto thisCharOK; } + /* - * If the next pattern character is '/', just strip off the - * '/' so we do exact matching on the character that follows. + * A backslash in the pattern matches the character following + * it exactly. */ - if (*pattern == '\\') { - ++pattern; - if (*pattern == 0) - return(0); + if (*pat == '\\') { + pat++; + if (*pat == '\0') + return FALSE; } - /* - * There's no special character. Just make sure that the - * next characters of each string match. - */ - if (*pattern != *string) - return(0); -thisCharOK: ++pattern; - ++string; + + if (*pat != *str) + return FALSE; + + thisCharOK: + pat++; + str++; } } - - -/*- - *----------------------------------------------------------------------- - * Str_SYSVMatch -- - * Check word against pattern for a match (% is wild), - * - * Input: - * word Word to examine - * pattern Pattern to examine against - * len Number of characters to substitute - * - * Results: - * Returns the beginning position of a match or null. The number - * of characters matched is returned in len. - * - * Side Effects: - * None - * - *----------------------------------------------------------------------- - */ -char * -Str_SYSVMatch(const char *word, const char *pattern, int *len) -{ - const char *p = pattern; - const char *w = word; - const char *m; - - if (*p == '\0') { - /* Null pattern is the whole string */ - *len = strlen(w); - return UNCONST(w); - } - - if ((m = strchr(p, '%')) != NULL) { - /* check that the prefix matches */ - for (; p != m && *w && *w == *p; w++, p++) - continue; - - if (p != m) - return NULL; /* No match */ - - if (*++p == '\0') { - /* No more pattern, return the rest of the string */ - *len = strlen(w); - return UNCONST(w); - } - } - - m = w; - - /* Find a matching tail */ - do - if (strcmp(p, w) == 0) { - *len = w - m; - return UNCONST(m); - } - while (*w++ != '\0'); - - return NULL; -} - - -/*- - *----------------------------------------------------------------------- - * Str_SYSVSubst -- - * Substitute '%' on the pattern with len characters from src. - * If the pattern does not contain a '%' prepend len characters - * from src. - * - * Results: - * None - * - * Side Effects: - * Places result on buf - * - *----------------------------------------------------------------------- - */ -void -Str_SYSVSubst(Buffer *buf, char *pat, char *src, int len) -{ - char *m; - - if ((m = strchr(pat, '%')) != NULL) { - /* Copy the prefix */ - Buf_AddBytes(buf, m - pat, pat); - /* skip the % */ - pat = m + 1; - } - - /* Copy the pattern */ - Buf_AddBytes(buf, len, src); - - /* append the rest */ - Buf_AddBytes(buf, strlen(pat), pat); -} diff --git a/usr.bin/make/strlist.c b/usr.bin/make/strlist.c deleted file mode 100644 index 3fb2f7dbb..000000000 --- a/usr.bin/make/strlist.c +++ /dev/null @@ -1,93 +0,0 @@ -/* $NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $ */ - -/*- - * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by David Laight. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of The NetBSD Foundation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $"; -#else -#include -#ifndef lint -__RCSID("$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $"); -#endif /* not lint */ -#endif - -#include -#include -#include "strlist.h" -#include "make_malloc.h" - -void -strlist_init(strlist_t *sl) -{ - sl->sl_num = 0; - sl->sl_max = 0; - sl->sl_items = NULL; -} - -void -strlist_clean(strlist_t *sl) -{ - char *str; - int i; - - STRLIST_FOREACH(str, sl, i) - free(str); - free(sl->sl_items); - - sl->sl_num = 0; - sl->sl_max = 0; - sl->sl_items = NULL; -} - -void -strlist_add_str(strlist_t *sl, char *str, unsigned int info) -{ - unsigned int n; - strlist_item_t *items; - - if (str == NULL) - return; - - n = sl->sl_num + 1; - sl->sl_num = n; - items = sl->sl_items; - if (n >= sl->sl_max) { - items = bmake_realloc(items, (n + 7) * sizeof *sl->sl_items); - sl->sl_items = items; - sl->sl_max = n + 6; - } - items += n - 1; - items->si_str = str; - items->si_info = info; - items[1].si_str = NULL; /* STRLIST_FOREACH() terminator */ -} diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c index d9ba25e91..0b27d33e5 100644 --- a/usr.bin/make/suff.c +++ b/usr.bin/make/suff.c @@ -1,4 +1,4 @@ -/* $NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $ */ +/* $NetBSD: suff.c,v 1.348 2021/03/15 12:15:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,2593 +68,2111 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94"; -#else -__RCSID("$NetBSD: suff.c,v 1.73 2014/09/07 20:55:34 joerg Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * suff.c -- - * Functions to maintain suffix lists and find implicit dependents - * using suffix transformation rules +/* + * Maintain suffix lists and find implicit dependents using suffix + * transformation rules such as ".c.o". * * Interface: - * Suff_Init Initialize all things to do with suffixes. + * Suff_Init Initialize the module. * - * Suff_End Cleanup the module + * Suff_End Clean up the module. * - * Suff_DoPaths This function is used to make life easier - * when searching for a file according to its - * suffix. It takes the global search path, - * as defined using the .PATH: target, and appends - * its directories to the path of each of the - * defined suffixes, as specified using - * .PATH: targets. In addition, all - * directories given for suffixes labeled as - * include files or libraries, using the .INCLUDES - * or .LIBS targets, are played with using - * Dir_MakeFlags to create the .INCLUDES and - * .LIBS global variables. + * Suff_DoPaths Extend the search path of each suffix to include the + * default search path. * - * Suff_ClearSuffixes Clear out all the suffixes and defined - * transformations. + * Suff_ClearSuffixes + * Clear out all the suffixes and transformations. * - * Suff_IsTransform Return TRUE if the passed string is the lhs - * of a transformation rule. + * Suff_IsTransform + * See if the passed string is a transformation rule. * - * Suff_AddSuffix Add the passed string as another known suffix. + * Suff_AddSuffix Add the passed string as another known suffix. * - * Suff_GetPath Return the search path for the given suffix. + * Suff_GetPath Return the search path for the given suffix. * - * Suff_AddInclude Mark the given suffix as denoting an include - * file. + * Suff_AddInclude + * Mark the given suffix as denoting an include file. * - * Suff_AddLib Mark the given suffix as denoting a library. + * Suff_AddLib Mark the given suffix as denoting a library. * - * Suff_AddTransform Add another transformation to the suffix - * graph. Returns GNode suitable for framing, I - * mean, tacking commands, attributes, etc. on. + * Suff_AddTransform + * Add another transformation to the suffix graph. * - * Suff_SetNull Define the suffix to consider the suffix of - * any file that doesn't have a known one. + * Suff_SetNull Define the suffix to consider the suffix of + * any file that doesn't have a known one. * - * Suff_FindDeps Find implicit sources for and the location of - * a target based on its suffix. Returns the - * bottom-most node added to the graph or NULL - * if the target had no implicit sources. + * Suff_FindDeps Find implicit sources for and the location of + * a target based on its suffix. Returns the + * bottom-most node added to the graph or NULL + * if the target had no implicit sources. * - * Suff_FindPath Return the appropriate path to search in - * order to find the node. + * Suff_FindPath Return the appropriate path to search in order to + * find the node. */ -#include -#include "make.h" -#include "hash.h" -#include "dir.h" +#include "make.h" +#include "dir.h" -static Lst sufflist; /* Lst of suffixes */ +/* "@(#)suff.c 8.4 (Berkeley) 3/21/94" */ +MAKE_RCSID("$NetBSD: suff.c,v 1.348 2021/03/15 12:15:03 rillig Exp $"); + +typedef List SuffixList; +typedef ListNode SuffixListNode; + +typedef List CandidateList; +typedef ListNode CandidateListNode; + +/* The defined suffixes, such as '.c', '.o', '.l'. */ +static SuffixList sufflist = LST_INIT; #ifdef CLEANUP -static Lst suffClean; /* Lst of suffixes to be cleaned */ +/* The suffixes to be cleaned up at the end. */ +static SuffixList suffClean = LST_INIT; #endif -static Lst srclist; /* Lst of sources */ -static Lst transforms; /* Lst of transformation rules */ - -static int sNum = 0; /* Counter for assigning suffix numbers */ /* - * Structure describing an individual suffix. + * The transformation rules, such as '.c.o' to transform '.c' into '.o', + * or simply '.c' to transform 'file.c' into 'file'. */ -typedef struct _Suff { - char *name; /* The suffix itself */ - int nameLen; /* Length of the suffix */ - short flags; /* Type of suffix */ -#define SUFF_INCLUDE 0x01 /* One which is #include'd */ -#define SUFF_LIBRARY 0x02 /* One which contains a library */ -#define SUFF_NULL 0x04 /* The empty suffix */ - Lst searchPath; /* The path along which files of this suffix - * may be found */ - int sNum; /* The suffix number */ - int refCount; /* Reference count of list membership */ - Lst parents; /* Suffixes we have a transformation to */ - Lst children; /* Suffixes we have a transformation from */ - Lst ref; /* List of lists this suffix is referenced */ -} Suff; +static GNodeList transforms = LST_INIT; /* - * for SuffSuffIsSuffix + * Counter for assigning suffix numbers. + * TODO: What are these suffix numbers used for? */ -typedef struct { - char *ename; /* The end of the name */ - int len; /* Length of the name */ -} SuffixCmpData; +static int sNum = 0; + +typedef enum SuffixFlags { + SUFF_NONE = 0, + + /* + * This suffix marks include files. Their search path ends up in the + * undocumented special variable '.INCLUDES'. + */ + SUFF_INCLUDE = 1 << 0, + + /* + * This suffix marks library files. Their search path ends up in the + * undocumented special variable '.LIBS'. + */ + SUFF_LIBRARY = 1 << 1, + + /* + * The empty suffix. + * + * XXX: What is the difference between the empty suffix and the null + * suffix? + * + * XXX: Why is SUFF_NULL needed at all? Wouldn't nameLen == 0 mean + * the same? + */ + SUFF_NULL = 1 << 2 + +} SuffixFlags; + +ENUM_FLAGS_RTTI_3(SuffixFlags, + SUFF_INCLUDE, SUFF_LIBRARY, SUFF_NULL); + +typedef List SuffixListList; /* - * Structure used in the search for implied sources. + * A suffix such as ".c" or ".o" that is used in suffix transformation rules + * such as ".c.o:". */ -typedef struct _Src { - char *file; /* The file to look for */ - char *pref; /* Prefix from which file was formed */ - Suff *suff; /* The suffix on the file */ - struct _Src *parent; /* The Src for which this is a source */ - GNode *node; /* The node describing the file */ - int children; /* Count of existing children (so we don't free - * this thing too early or never nuke it) */ +typedef struct Suffix { + /* The suffix itself, such as ".c" */ + char *name; + /* Length of the name, to avoid strlen calls */ + size_t nameLen; + /* Type of suffix */ + SuffixFlags flags; + /* The path along which files of this suffix may be found */ + SearchPath *searchPath; + /* The suffix number; TODO: document the purpose of this number */ + int sNum; + /* Reference count of list membership and several other places */ + int refCount; + /* Suffixes we have a transformation to */ + SuffixList parents; + /* Suffixes we have a transformation from */ + SuffixList children; + + /* 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; + +/* + * A candidate when searching for implied sources. + * + * For example, when "src.o" is to be made, a typical candidate is "src.c" + * via the transformation rule ".c.o". If that doesn't exist, maybe there is + * another transformation rule ".pas.c" that would make "src.pas" an indirect + * candidate as well. The first such chain that leads to an existing file or + * node is finally chosen to be made. + */ +typedef struct Candidate { + /* The file or node to look for. */ + char *file; + /* The prefix from which file was formed. + * Its memory is shared among all candidates. */ + char *prefix; + /* The suffix on the file. */ + Suffix *suff; + + /* The candidate that can be made from this, + * or NULL for the top-level candidate. */ + struct Candidate *parent; + /* The node describing the file. */ + GNode *node; + + /* Count of existing children, only used for memory management, so we + * don't free this candidate too early or too late. */ + int numChildren; #ifdef DEBUG_SRC - Lst cp; /* Debug; children list */ + CandidateList childrenList; #endif -} Src; +} Candidate; + +typedef struct CandidateSearcher { + + CandidateList list; + + /* + * TODO: Add HashSet for seen entries, to avoid endless loops such as + * in suff-transform-endless.mk. + */ + +} CandidateSearcher; + + +/* TODO: Document the difference between nullSuff and emptySuff. */ +/* The NULL suffix for this run */ +static Suffix *nullSuff; +/* The empty suffix required for POSIX single-suffix transformation rules */ +static Suffix *emptySuff; + + +static Suffix * +Suffix_Ref(Suffix *suff) +{ + suff->refCount++; + return suff; +} + +/* Change the value of a Suffix variable, adjusting the reference counts. */ +static void +Suffix_Reassign(Suffix **var, Suffix *suff) +{ + if (*var != NULL) + (*var)->refCount--; + *var = suff; + suff->refCount++; +} + +/* Set a Suffix variable to NULL, adjusting the reference count. */ +static void +Suffix_Unassign(Suffix **var) +{ + if (*var != NULL) + (*var)->refCount--; + *var = NULL; +} /* - * A structure for passing more than one argument to the Lst-library-invoked - * function... - */ -typedef struct { - Lst l; - Src *s; -} LstSrc; - -typedef struct { - GNode **gn; - Suff *s; - Boolean r; -} GNodeSuff; - -static Suff *suffNull; /* The NULL suffix for this run */ -static Suff *emptySuff; /* The empty suffix required for POSIX - * single-suffix transformation rules */ - - -static const char *SuffStrIsPrefix(const char *, const char *); -static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *); -static int SuffSuffIsSuffixP(const void *, const void *); -static int SuffSuffHasNameP(const void *, const void *); -static int SuffSuffIsPrefix(const void *, const void *); -static int SuffGNHasNameP(const void *, const void *); -static void SuffUnRef(void *, void *); -static void SuffFree(void *); -static void SuffInsert(Lst, Suff *); -static void SuffRemove(Lst, Suff *); -static Boolean SuffParseTransform(char *, Suff **, Suff **); -static int SuffRebuildGraph(void *, void *); -static int SuffScanTargets(void *, void *); -static int SuffAddSrc(void *, void *); -static int SuffRemoveSrc(Lst); -static void SuffAddLevel(Lst, Src *); -static Src *SuffFindThem(Lst, Lst); -static Src *SuffFindCmds(Src *, Lst); -static void SuffExpandChildren(LstNode, GNode *); -static void SuffExpandWildcards(LstNode, GNode *); -static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *); -static void SuffFindDeps(GNode *, Lst); -static void SuffFindArchiveDeps(GNode *, Lst); -static void SuffFindNormalDeps(GNode *, Lst); -static int SuffPrintName(void *, void *); -static int SuffPrintSuff(void *, void *); -static int SuffPrintTrans(void *, void *); - - /*************** Lst Predicates ****************/ -/*- - *----------------------------------------------------------------------- - * SuffStrIsPrefix -- - * See if pref is a prefix of str. - * - * Input: - * pref possible prefix - * str string to check - * - * Results: - * NULL if it ain't, pointer to character in str after prefix if so - * - * Side Effects: - * None - *----------------------------------------------------------------------- + * See if pref is a prefix of str. + * Return NULL if it ain't, pointer to character in str after prefix if so. */ static const char * -SuffStrIsPrefix(const char *pref, const char *str) +StrTrimPrefix(const char *pref, const char *str) { - while (*str && *pref == *str) { - pref++; - str++; - } + while (*str != '\0' && *pref == *str) { + pref++; + str++; + } - return (*pref ? NULL : str); + return *pref != '\0' ? NULL : str; } -/*- - *----------------------------------------------------------------------- - * SuffSuffIsSuffix -- - * See if suff is a suffix of str. sd->ename should point to THE END - * of the string to check. (THE END == the null byte) - * - * Input: - * s possible suffix - * sd string to examine - * - * Results: - * NULL if it ain't, pointer to character in str before suffix if - * it is. - * - * Side Effects: - * None - *----------------------------------------------------------------------- +/* + * See if suff is a suffix of str, and if so, return the pointer to the suffix + * in str, which at the same time marks the end of the prefix. */ -static char * -SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd) +static const char * +StrTrimSuffix(const char *str, size_t strLen, const char *suff, size_t suffLen) { - char *p1; /* Pointer into suffix name */ - char *p2; /* Pointer into string being examined */ + const char *suffInStr; + size_t i; - if (sd->len < s->nameLen) - return NULL; /* this string is shorter than the suffix */ + if (strLen < suffLen) + return NULL; - p1 = s->name + s->nameLen; - p2 = sd->ename; + suffInStr = str + strLen - suffLen; + for (i = 0; i < suffLen; i++) + if (suff[i] != suffInStr[i]) + return NULL; - while (p1 >= s->name && *p1 == *p2) { - p1--; - p2--; - } - - return (p1 == s->name - 1 ? p2 : NULL); + return suffInStr; } -/*- - *----------------------------------------------------------------------- - * SuffSuffIsSuffixP -- - * Predicate form of SuffSuffIsSuffix. Passed as the callback function - * to Lst_Find. - * - * Results: - * 0 if the suffix is the one desired, non-zero if not. - * - * Side Effects: - * None. - * - *----------------------------------------------------------------------- +/* + * See if suff is a suffix of name, and if so, return the end of the prefix + * in name. */ -static int -SuffSuffIsSuffixP(const void *s, const void *sd) +static const char * +Suffix_TrimSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { - return(!SuffSuffIsSuffix(s, sd)); + return StrTrimSuffix(nameEnd - nameLen, nameLen, + suff->name, suff->nameLen); } -/*- - *----------------------------------------------------------------------- - * SuffSuffHasNameP -- - * Callback procedure for finding a suffix based on its name. Used by - * Suff_GetPath. - * - * Input: - * s Suffix to check - * sd Desired name - * - * Results: - * 0 if the suffix is of the given name. non-zero otherwise. - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -static int -SuffSuffHasNameP(const void *s, const void *sname) +static Boolean +Suffix_IsSuffix(const Suffix *suff, size_t nameLen, const char *nameEnd) { - return (strcmp(sname, ((const Suff *)s)->name)); + return Suffix_TrimSuffix(suff, nameLen, nameEnd) != NULL; } -/*- - *----------------------------------------------------------------------- - * SuffSuffIsPrefix -- - * See if the suffix described by s is a prefix of the string. Care - * must be taken when using this to search for transformations and - * what-not, since there could well be two suffixes, one of which - * is a prefix of the other... - * - * Input: - * s suffix to compare - * str string to examine - * - * Results: - * 0 if s is a prefix of str. non-zero otherwise - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -static int -SuffSuffIsPrefix(const void *s, const void *str) +static Suffix * +FindSuffixByNameLen(const char *name, size_t nameLen) { - return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL; + SuffixListNode *ln; + + for (ln = sufflist.first; ln != NULL; ln = ln->next) { + Suffix *suff = ln->datum; + if (suff->nameLen == nameLen && + memcmp(suff->name, name, nameLen) == 0) + return suff; + } + return NULL; } -/*- - *----------------------------------------------------------------------- - * SuffGNHasNameP -- - * See if the graph node has the desired name - * - * Input: - * gn current node we're looking at - * name name we're looking for - * - * Results: - * 0 if it does. non-zero if it doesn't - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -static int -SuffGNHasNameP(const void *gn, const void *name) +static Suffix * +FindSuffixByName(const char *name) { - return (strcmp(name, ((const GNode *)gn)->name)); + return FindSuffixByNameLen(name, strlen(name)); } - /*********** Maintenance Functions ************/ +static GNode * +FindTransformByName(const char *name) +{ + GNodeListNode *ln; + + for (ln = transforms.first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (strcmp(gn->name, name) == 0) + return gn; + } + return NULL; +} static void -SuffUnRef(void *lp, void *sp) +SuffixList_Unref(SuffixList *list, Suffix *suff) { - Lst l = (Lst) lp; - - LstNode ln = Lst_Member(l, sp); - if (ln != NULL) { - Lst_Remove(l, ln); - ((Suff *)sp)->refCount--; - } + SuffixListNode *ln = Lst_FindDatum(list, suff); + if (ln != NULL) { + Lst_Remove(list, ln); + suff->refCount--; + } } -/*- - *----------------------------------------------------------------------- - * SuffFree -- - * Free up all memory associated with the given suffix structure. - * - * Results: - * none - * - * Side Effects: - * the suffix entry is detroyed - *----------------------------------------------------------------------- - */ +/* Free up all memory associated with the given suffix structure. */ static void -SuffFree(void *sp) +Suffix_Free(Suffix *suff) { - Suff *s = (Suff *)sp; - if (s == suffNull) - suffNull = NULL; + if (suff == nullSuff) + nullSuff = NULL; - if (s == emptySuff) - emptySuff = NULL; + if (suff == emptySuff) + emptySuff = NULL; -#ifdef notdef - /* We don't delete suffixes in order, so we cannot use this */ - if (s->refCount) - Punt("Internal error deleting suffix `%s' with refcount = %d", s->name, - s->refCount); +#if 0 + /* We don't delete suffixes in order, so we cannot use this */ + if (suff->refCount != 0) + Punt("Internal error deleting suffix `%s' with refcount = %d", + suff->name, suff->refCount); #endif - Lst_Destroy(s->ref, NULL); - Lst_Destroy(s->children, NULL); - Lst_Destroy(s->parents, NULL); - Lst_Destroy(s->searchPath, Dir_Destroy); + Lst_Done(&suff->ref); + Lst_Done(&suff->children); + Lst_Done(&suff->parents); + SearchPath_Free(suff->searchPath); - free(s->name); - free(s); + free(suff->name); + free(suff); } -/*- - *----------------------------------------------------------------------- - * SuffRemove -- - * Remove the suffix into the list - * - * Results: - * None - * - * Side Effects: - * The reference count for the suffix is decremented and the - * suffix is possibly freed - *----------------------------------------------------------------------- +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) +{ + SuffixList_Unref(list, suff); + if (suff->refCount == 0) { + /* XXX: can lead to suff->refCount == -1 */ + SuffixList_Unref(&sufflist, suff); + DEBUG1(SUFF, "Removing suffix \"%s\"\n", suff->name); + SuffFree(suff); + } +} + +/* + * Insert the suffix into the list, keeping the list ordered by suffix + * number. */ static void -SuffRemove(Lst l, Suff *s) +SuffixList_Insert(SuffixList *list, Suffix *suff) { - SuffUnRef(l, s); - if (s->refCount == 0) { - SuffUnRef(sufflist, s); - SuffFree(s); - } + SuffixListNode *ln; + Suffix *listSuff = NULL; + + for (ln = list->first; ln != NULL; ln = ln->next) { + listSuff = ln->datum; + if (listSuff->sNum >= suff->sNum) + break; + } + + if (ln == NULL) { + DEBUG2(SUFF, "inserting \"%s\" (%d) at end of list\n", + suff->name, suff->sNum); + Lst_Append(list, Suffix_Ref(suff)); + 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); + } } - -/*- - *----------------------------------------------------------------------- - * SuffInsert -- - * Insert the suffix into the list keeping the list ordered by suffix - * numbers. - * - * Input: - * l the list where in s should be inserted - * s the suffix to insert - * - * Results: - * None - * - * Side Effects: - * The reference count of the suffix is incremented - *----------------------------------------------------------------------- - */ + static void -SuffInsert(Lst l, Suff *s) +Relate(Suffix *srcSuff, Suffix *targSuff) { - LstNode ln; /* current element in l we're examining */ - Suff *s2 = NULL; /* the suffix descriptor in this element */ - - if (Lst_Open(l) == FAILURE) { - return; - } - while ((ln = Lst_Next(l)) != NULL) { - s2 = (Suff *)Lst_Datum(ln); - if (s2->sNum >= s->sNum) { - break; - } - } - - Lst_Close(l); - if (DEBUG(SUFF)) { - fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum); - } - if (ln == NULL) { - if (DEBUG(SUFF)) { - fprintf(debug_file, "at end of list\n"); - } - (void)Lst_AtEnd(l, s); - s->refCount++; - (void)Lst_AtEnd(s->ref, l); - } else if (s2->sNum != s->sNum) { - if (DEBUG(SUFF)) { - fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum); - } - (void)Lst_InsertBefore(l, ln, s); - s->refCount++; - (void)Lst_AtEnd(s->ref, l); - } else if (DEBUG(SUFF)) { - fprintf(debug_file, "already there\n"); - } + SuffixList_Insert(&targSuff->children, srcSuff); + SuffixList_Insert(&srcSuff->parents, targSuff); } -/*- - *----------------------------------------------------------------------- - * Suff_ClearSuffixes -- - * This is gross. Nuke the list of suffixes but keep all transformation - * rules around. The transformation graph is destroyed in this process, - * but we leave the list of rules so when a new graph is formed the rules - * will remain. - * This function is called from the parse module when a - * .SUFFIXES:\n line is encountered. - * - * Results: - * none - * - * Side Effects: - * the sufflist and its graph nodes are destroyed - *----------------------------------------------------------------------- +static Suffix * +Suffix_New(const char *name) +{ + Suffix *suff = bmake_malloc(sizeof *suff); + + suff->name = bmake_strdup(name); + suff->nameLen = strlen(suff->name); + suff->searchPath = SearchPath_New(); + Lst_Init(&suff->children); + Lst_Init(&suff->parents); + Lst_Init(&suff->ref); + suff->sNum = sNum++; + suff->flags = SUFF_NONE; + suff->refCount = 1; /* XXX: why 1? It's not assigned anywhere yet. */ + + return suff; +} + +/* + * Nuke the list of suffixes but keep all transformation rules around. The + * transformation graph is destroyed in this process, but we leave the list + * of rules so when a new graph is formed, the rules will remain. This + * function is called when a line '.SUFFIXES:' with an empty suffixes list is + * encountered in a makefile. */ void Suff_ClearSuffixes(void) { #ifdef CLEANUP - Lst_Concat(suffClean, sufflist, LST_CONCLINK); + Lst_MoveAll(&suffClean, &sufflist); #endif - sufflist = Lst_Init(FALSE); - sNum = 0; - suffNull = emptySuff; + DEBUG0(SUFF, "Clearing all suffixes\n"); + Lst_Init(&sufflist); + sNum = 0; + if (nullSuff != NULL) + SuffFree(nullSuff); + emptySuff = nullSuff = Suffix_New(""); + + SearchPath_AddAll(nullSuff->searchPath, &dirSearchPath); + nullSuff->flags = SUFF_NULL; } -/*- - *----------------------------------------------------------------------- - * SuffParseTransform -- - * Parse a transformation string to find its two component suffixes. +/* + * Parse a transformation string such as ".c.o" to find its two component + * suffixes (the source ".c" and the target ".o"). If there are no such + * suffixes, try a single-suffix transformation as well. * - * Input: - * str String being parsed - * srcPtr Place to store source of trans. - * targPtr Place to store target of trans. - * - * Results: - * TRUE if the string is a valid transformation and FALSE otherwise. - * - * Side Effects: - * The passed pointers are overwritten. - * - *----------------------------------------------------------------------- + * Return TRUE if the string is a valid transformation. */ static Boolean -SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr) +ParseTransform(const char *str, Suffix **out_src, Suffix **out_targ) { - LstNode srcLn; /* element in suffix list of trans source*/ - Suff *src; /* Source of transformation */ - LstNode targLn; /* element in suffix list of trans target*/ - char *str2; /* Extra pointer (maybe target suffix) */ - LstNode singleLn; /* element in suffix list of any suffix - * that exactly matches str */ - Suff *single = NULL;/* Source of possible transformation to - * null suffix */ + SuffixListNode *ln; + Suffix *single = NULL; - srcLn = NULL; - singleLn = NULL; + /* + * Loop looking first for a suffix that matches the start of the + * string and then for one that exactly matches the rest of it. If + * we can find two that meet these criteria, we've successfully + * parsed the string. + */ + for (ln = sufflist.first; ln != NULL; ln = ln->next) { + Suffix *src = ln->datum; - /* - * Loop looking first for a suffix that matches the start of the - * string and then for one that exactly matches the rest of it. If - * we can find two that meet these criteria, we've successfully - * parsed the string. - */ - for (;;) { - if (srcLn == NULL) { - srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix); - } else { - srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str, - SuffSuffIsPrefix); + if (StrTrimPrefix(src->name, str) == NULL) + continue; + + if (str[src->nameLen] == '\0') { + single = src; + } else { + Suffix *targ = FindSuffixByName(str + src->nameLen); + if (targ != NULL) { + *out_src = src; + *out_targ = targ; + return TRUE; + } + } } - if (srcLn == NULL) { - /* - * Ran out of source suffixes -- no such rule - */ - if (singleLn != NULL) { + + if (single != NULL) { /* - * Not so fast Mr. Smith! There was a suffix that encompassed - * the entire string, so we assume it was a transformation - * to the null suffix (thank you POSIX). We still prefer to - * find a double rule over a singleton, hence we leave this - * check until the end. + * There was a suffix that encompassed the entire string, so we + * assume it was a transformation to the null suffix (thank you + * POSIX; search for "single suffix" or "single-suffix"). * - * XXX: Use emptySuff over suffNull? + * We still prefer to find a double rule over a singleton, + * hence we leave this check until the end. + * + * XXX: Use emptySuff over nullSuff? */ - *srcPtr = single; - *targPtr = suffNull; - return(TRUE); - } - return (FALSE); + *out_src = single; + *out_targ = nullSuff; + return TRUE; } - src = (Suff *)Lst_Datum(srcLn); - str2 = str + src->nameLen; - if (*str2 == '\0') { - single = src; - singleLn = srcLn; - } else { - targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP); - if (targLn != NULL) { - *srcPtr = src; - *targPtr = (Suff *)Lst_Datum(targLn); - return (TRUE); - } - } - } + return FALSE; } -/*- - *----------------------------------------------------------------------- - * Suff_IsTransform -- - * Return TRUE if the given string is a transformation rule - * - * - * Input: - * str string to check - * - * Results: - * TRUE if the string is a concatenation of two known suffixes. - * FALSE otherwise - * - * Side Effects: - * None - *----------------------------------------------------------------------- +/* + * Return TRUE if the given string is a transformation rule, that is, a + * concatenation of two known suffixes such as ".c.o" or a single suffix + * such as ".o". */ Boolean -Suff_IsTransform(char *str) +Suff_IsTransform(const char *str) { - Suff *src, *targ; + Suffix *src, *targ; - return (SuffParseTransform(str, &src, &targ)); + return ParseTransform(str, &src, &targ); } -/*- - *----------------------------------------------------------------------- - * Suff_AddTransform -- - * Add the transformation rule described by the line to the - * list of rules and place the transformation itself in the graph +/* + * Add the transformation rule to the list of rules and place the + * transformation itself in the graph. + * + * The transformation is linked to the two suffixes mentioned in the name. * * Input: - * line name of transformation to add + * name must have the form ".from.to" or just ".from" * * Results: - * The node created for the transformation in the transforms list - * - * Side Effects: - * The node is placed on the end of the transforms Lst and links are - * made between the two suffixes mentioned in the target name - *----------------------------------------------------------------------- + * The created or existing transformation node in the transforms list */ GNode * -Suff_AddTransform(char *line) +Suff_AddTransform(const char *name) { - GNode *gn; /* GNode of transformation rule */ - Suff *s, /* source suffix */ - *t; /* target suffix */ - LstNode ln; /* Node for existing transformation */ + Suffix *srcSuff; + Suffix *targSuff; -#if defined(__minix) - /* Prevent complains from GCC at -O3 optimisation level. */ - s = NULL; - t = NULL; -#endif /* defined(__minix) */ + GNode *gn = FindTransformByName(name); + if (gn == NULL) { + /* + * Make a new graph node for the transformation. It will be + * filled in by the Parse module. + */ + gn = GNode_New(name); + Lst_Append(&transforms, gn); + } else { + /* + * New specification for transformation rule. Just nuke the + * old list of commands so they can be filled in again. We + * don't actually free the commands themselves, because a + * given command can be attached to several different + * transformations. + */ + Lst_Done(&gn->commands); + Lst_Init(&gn->commands); + Lst_Done(&gn->children); + Lst_Init(&gn->children); + } - ln = Lst_Find(transforms, line, SuffGNHasNameP); - if (ln == NULL) { - /* - * Make a new graph node for the transformation. It will be filled in - * by the Parse module. - */ - gn = Targ_NewGN(line); - (void)Lst_AtEnd(transforms, gn); - } else { - /* - * New specification for transformation rule. Just nuke the old list - * of commands so they can be filled in again... We don't actually - * free the commands themselves, because a given command can be - * attached to several different transformations. - */ - gn = (GNode *)Lst_Datum(ln); - Lst_Destroy(gn->commands, NULL); - Lst_Destroy(gn->children, NULL); - gn->commands = Lst_Init(FALSE); - gn->children = Lst_Init(FALSE); - } + gn->type = OP_TRANSFORM; - gn->type = OP_TRANSFORM; + { + /* TODO: Avoid the redundant parsing here. */ + Boolean ok = ParseTransform(name, &srcSuff, &targSuff); + assert(ok); + (void)ok; + } - (void)SuffParseTransform(line, &s, &t); + /* Link the two together in the proper relationship and order. */ + DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n", + srcSuff->name, targSuff->name); + Relate(srcSuff, targSuff); - /* - * link the two together in the proper relationship and order - */ - if (DEBUG(SUFF)) { - fprintf(debug_file, "defining transformation from `%s' to `%s'\n", - s->name, t->name); - } - SuffInsert(t->children, s); - SuffInsert(s->parents, t); - - return (gn); + return gn; } -/*- - *----------------------------------------------------------------------- - * Suff_EndTransform -- - * Handle the finish of a transformation definition, removing the - * transformation from the graph if it has neither commands nor - * sources. This is a callback procedure for the Parse module via - * Lst_ForEach +/* + * Handle the finish of a transformation definition, removing the + * transformation from the graph if it has neither commands nor sources. + * + * If the node has no commands or children, the children and parents lists + * of the affected suffixes are altered. * * Input: - * gnp Node for transformation - * dummy Node for transformation - * - * Results: - * === 0 - * - * Side Effects: - * If the node has no commands or children, the children and parents - * lists of the affected suffixes are altered. - * - *----------------------------------------------------------------------- + * gn Node for transformation */ -int -Suff_EndTransform(void *gnp, void *dummy) +void +Suff_EndTransform(GNode *gn) { - GNode *gn = (GNode *)gnp; + Suffix *srcSuff, *targSuff; + SuffixList *srcSuffParents; - if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) - gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts)); - if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && - Lst_IsEmpty(gn->children)) - { - Suff *s, *t; + if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty(&gn->cohorts)) + gn = gn->cohorts.last->datum; + + if (!(gn->type & OP_TRANSFORM)) + return; + + if (!Lst_IsEmpty(&gn->commands) || !Lst_IsEmpty(&gn->children)) { + DEBUG1(SUFF, "transformation %s complete\n", gn->name); + return; + } /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules. (e.g. .DEFAULT) */ - if (SuffParseTransform(gn->name, &s, &t)) { - Lst p; + if (!ParseTransform(gn->name, &srcSuff, &targSuff)) + return; - if (DEBUG(SUFF)) { - fprintf(debug_file, "deleting transformation from `%s' to `%s'\n", - s->name, t->name); - } + DEBUG2(SUFF, "deleting incomplete transformation from `%s' to `%s'\n", + srcSuff->name, targSuff->name); - /* - * Store s->parents because s could be deleted in SuffRemove - */ - p = s->parents; - - /* - * Remove the source from the target's children list. We check for a - * nil return to handle a beanhead saying something like - * .c.o .c.o: - * - * We'll be called twice when the next target is seen, but .c and .o - * are only linked once... - */ - SuffRemove(t->children, s); - - /* - * Remove the target from the source's parents list - */ - SuffRemove(p, t); - } - } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) { - fprintf(debug_file, "transformation %s complete\n", gn->name); - } - - return(dummy ? 0 : 0); + /* + * Remember the parents since srcSuff could be deleted in + * SuffixList_Remove. + */ + srcSuffParents = &srcSuff->parents; + SuffixList_Remove(&targSuff->children, srcSuff); + SuffixList_Remove(srcSuffParents, targSuff); } -/*- - *----------------------------------------------------------------------- - * SuffRebuildGraph -- - * Called from Suff_AddSuffix via Lst_ForEach to search through the - * list of existing transformation rules and rebuild the transformation - * graph when it has been destroyed by Suff_ClearSuffixes. If the - * given rule is a transformation involving this suffix and another, - * existing suffix, the proper relationship is established between - * the two. +/* + * Called from Suff_AddSuffix to search through the list of + * existing transformation rules and rebuild the transformation graph when + * it has been destroyed by Suff_ClearSuffixes. If the given rule is a + * transformation involving this suffix and another, existing suffix, the + * proper relationship is established between the two. + * + * The appropriate links will be made between this suffix and others if + * transformation rules exist for it. * * Input: - * transformp Transformation to test - * sp Suffix to rebuild - * - * Results: - * Always 0. - * - * Side Effects: - * The appropriate links will be made between this suffix and - * others if transformation rules exist for it. - * - *----------------------------------------------------------------------- + * transform Transformation to test + * suff Suffix to rebuild */ -static int -SuffRebuildGraph(void *transformp, void *sp) +static void +RebuildGraph(GNode *transform, Suffix *suff) { - GNode *transform = (GNode *)transformp; - Suff *s = (Suff *)sp; - char *cp; - LstNode ln; - Suff *s2; - SuffixCmpData sd; + const char *name = transform->name; + size_t nameLen = strlen(name); + const char *toName; - /* - * First see if it is a transformation from this suffix. - */ - cp = UNCONST(SuffStrIsPrefix(s->name, transform->name)); - if (cp != NULL) { - ln = Lst_Find(sufflist, cp, SuffSuffHasNameP); - if (ln != NULL) { - /* - * Found target. Link in and return, since it can't be anything - * else. - */ - s2 = (Suff *)Lst_Datum(ln); - SuffInsert(s2->children, s); - SuffInsert(s->parents, s2); - return(0); + /* See if it is a transformation from this suffix to another suffix. */ + toName = StrTrimPrefix(suff->name, name); + if (toName != NULL) { + Suffix *to = FindSuffixByName(toName); + if (to != NULL) { + Relate(suff, to); + return; + } } - } - /* - * Not from, maybe to? - */ - sd.len = strlen(transform->name); - sd.ename = transform->name + sd.len; - cp = SuffSuffIsSuffix(s, &sd); - if (cp != NULL) { - /* - * Null-terminate the source suffix in order to find it. - */ - cp[1] = '\0'; - ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP); - /* - * Replace the start of the target suffix - */ - cp[1] = s->name[0]; - if (ln != NULL) { - /* - * Found it -- establish the proper relationship - */ - s2 = (Suff *)Lst_Datum(ln); - SuffInsert(s->children, s2); - SuffInsert(s2->parents, s); + /* See if it is a transformation from another suffix to this suffix. */ + toName = Suffix_TrimSuffix(suff, nameLen, name + nameLen); + if (toName != NULL) { + Suffix *from = FindSuffixByNameLen(name, + (size_t)(toName - name)); + if (from != NULL) + Relate(from, suff); } - } - return(0); } -/*- - *----------------------------------------------------------------------- - * SuffScanTargets -- - * Called from Suff_AddSuffix via Lst_ForEach to search through the - * list of existing targets and find if any of the existing targets - * can be turned into a transformation rule. +/* + * During Suff_AddSuffix, search through the list of existing targets and find + * if any of the existing targets can be turned into a transformation rule. + * + * If such a target is found and the target is the current main target, the + * main target is set to NULL and the next target examined (if that exists) + * becomes the main target. * * Results: - * 1 if a new main target has been selected, 0 otherwise. - * - * Side Effects: - * If such a target is found and the target is the current main - * target, the main target is set to NULL and the next target - * examined (if that exists) becomes the main target. - * - *----------------------------------------------------------------------- + * TRUE iff a new main target has been selected. */ -static int -SuffScanTargets(void *targetp, void *gsp) +static Boolean +UpdateTarget(GNode *target, GNode **inout_main, Suffix *suff, + Boolean *inout_removedMain) { - GNode *target = (GNode *)targetp; - GNodeSuff *gs = (GNodeSuff *)gsp; - Suff *s, *t; - char *ptr; + Suffix *srcSuff, *targSuff; + char *ptr; - if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) { - *gs->gn = target; - Targ_SetMain(target); - return 1; - } - - if ((unsigned int)target->type == OP_TRANSFORM) - return 0; - - if ((ptr = strstr(target->name, gs->s->name)) == NULL || - ptr == target->name) - return 0; - - if (SuffParseTransform(target->name, &s, &t)) { - if (*gs->gn == target) { - gs->r = TRUE; - *gs->gn = NULL; - Targ_SetMain(NULL); + if (*inout_main == NULL && *inout_removedMain && + !(target->type & OP_NOTARGET)) { + DEBUG1(MAKE, "Setting main node to \"%s\"\n", target->name); + *inout_main = target; + Targ_SetMain(target); + /* + * XXX: Why could it be a good idea to return TRUE here? + * The main task of this function is to turn ordinary nodes + * into transformations, no matter whether or not a new .MAIN + * node has been found. + */ + /* + * XXX: Even when changing this to FALSE, none of the existing + * unit tests fails. + */ + return TRUE; } - Lst_Destroy(target->children, NULL); - target->children = Lst_Init(FALSE); - target->type = OP_TRANSFORM; + + if (target->type == OP_TRANSFORM) + return FALSE; + /* - * link the two together in the proper relationship and order + * XXX: What about a transformation ".cpp.c"? If ".c" is added as + * a new suffix, it seems wrong that this transformation would be + * skipped just because ".c" happens to be a prefix of ".cpp". */ - if (DEBUG(SUFF)) { - fprintf(debug_file, "defining transformation from `%s' to `%s'\n", - s->name, t->name); + ptr = strstr(target->name, suff->name); + if (ptr == NULL) + return FALSE; + + /* + * XXX: In suff-rebuild.mk, in the line '.SUFFIXES: .c .b .a', this + * condition prevents the rule '.b.c' from being added again during + * Suff_AddSuffix(".b"). + * + * XXX: Removing this paragraph makes suff-add-later.mk use massive + * amounts of memory. + */ + if (ptr == target->name) + return FALSE; + + if (ParseTransform(target->name, &srcSuff, &targSuff)) { + if (*inout_main == target) { + DEBUG1(MAKE, + "Setting main node from \"%s\" back to null\n", + target->name); + *inout_removedMain = TRUE; + *inout_main = NULL; + Targ_SetMain(NULL); + } + Lst_Done(&target->children); + Lst_Init(&target->children); + target->type = OP_TRANSFORM; + + /* + * Link the two together in the proper relationship and order. + */ + DEBUG2(SUFF, "defining transformation from `%s' to `%s'\n", + srcSuff->name, targSuff->name); + Relate(srcSuff, targSuff); } - SuffInsert(t->children, s); - SuffInsert(s->parents, t); - } - return 0; + return FALSE; } -/*- - *----------------------------------------------------------------------- - * Suff_AddSuffix -- - * Add the suffix in string to the end of the list of known suffixes. - * Should we restructure the suffix graph? Make doesn't... +/* + * Look at all existing targets to see if adding this suffix will make one + * of the current targets mutate into a suffix rule. + * + * This is ugly, but other makes treat all targets that start with a '.' as + * suffix rules. + */ +static void +UpdateTargets(GNode **inout_main, Suffix *suff) +{ + Boolean removedMain = FALSE; + GNodeListNode *ln; + + for (ln = Targ_List()->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (UpdateTarget(gn, inout_main, suff, &removedMain)) + break; + } +} + +/* + * 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: - * str the name of the suffix to add - * - * Results: - * None - * - * Side Effects: - * A GNode is created for the suffix and a Suff 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. - *----------------------------------------------------------------------- + * name the name of the suffix to add */ void -Suff_AddSuffix(char *str, GNode **gn) +Suff_AddSuffix(const char *name, GNode **inout_main) { - Suff *s; /* new suffix descriptor */ - LstNode ln; - GNodeSuff gs; + GNodeListNode *ln; - ln = Lst_Find(sufflist, str, SuffSuffHasNameP); - if (ln == NULL) { - s = bmake_malloc(sizeof(Suff)); + Suffix *suff = FindSuffixByName(name); + if (suff != NULL) + return; - s->name = bmake_strdup(str); - s->nameLen = strlen(s->name); - s->searchPath = Lst_Init(FALSE); - s->children = Lst_Init(FALSE); - s->parents = Lst_Init(FALSE); - s->ref = Lst_Init(FALSE); - s->sNum = sNum++; - s->flags = 0; - s->refCount = 1; + suff = Suffix_New(name); + Lst_Append(&sufflist, suff); + DEBUG1(SUFF, "Adding suffix \"%s\"\n", suff->name); + + UpdateTargets(inout_main, suff); - (void)Lst_AtEnd(sufflist, s); - /* - * We also look at our existing targets list to see if adding - * this suffix will make one of our current targets mutate into - * a suffix rule. This is ugly, but other makes treat all targets - * that start with a . as suffix rules. - */ - gs.gn = gn; - gs.s = s; - gs.r = FALSE; - Lst_ForEach(Targ_List(), SuffScanTargets, &gs); /* * Look for any existing transformations from or to this suffix. * XXX: Only do this after a Suff_ClearSuffixes? */ - Lst_ForEach(transforms, SuffRebuildGraph, s); - } + for (ln = transforms.first; ln != NULL; ln = ln->next) + RebuildGraph(ln->datum, suff); } -/*- - *----------------------------------------------------------------------- - * Suff_GetPath -- - * Return the search path for the given suffix, if it's defined. - * - * Results: - * The searchPath for the desired suffix or NULL if the suffix isn't - * defined. - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -Lst -Suff_GetPath(char *sname) +/* Return the search path for the given suffix, or NULL. */ +SearchPath * +Suff_GetPath(const char *sname) { - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); - if (ln == NULL) { - return NULL; - } else { - s = (Suff *)Lst_Datum(ln); - return (s->searchPath); - } + Suffix *suff = FindSuffixByName(sname); + return suff != NULL ? suff->searchPath : NULL; } -/*- - *----------------------------------------------------------------------- - * Suff_DoPaths -- - * Extend the search paths for all suffixes to include the default - * search path. +/* + * Extend the search paths for all suffixes to include the default search + * path (dirSearchPath). * - * Results: - * None. + * The default search path can be defined using the special target '.PATH'. + * The search path of each suffix can be defined using the special target + * '.PATH'. * - * Side Effects: - * The searchPath field of all the suffixes is extended by the - * directories in dirSearchPath. If paths were specified for the - * ".h" suffix, the directories are stuffed into a global variable - * called ".INCLUDES" with each directory preceded by a -I. The same - * is done for the ".a" suffix, except the variable is called - * ".LIBS" and the flag is -L. - *----------------------------------------------------------------------- + * If paths were specified for the ".h" suffix, the directories are stuffed + * into a global variable called ".INCLUDES" with each directory preceded by + * '-I'. The same is done for the ".a" suffix, except the variable is called + * ".LIBS" and the flag is '-L'. */ void Suff_DoPaths(void) { - Suff *s; - LstNode ln; - char *ptr; - Lst inIncludes; /* Cumulative .INCLUDES path */ - Lst inLibs; /* Cumulative .LIBS path */ + SuffixListNode *ln; + char *flags; + SearchPath *includesPath = SearchPath_New(); + SearchPath *libsPath = SearchPath_New(); - if (Lst_Open(sufflist) == FAILURE) { - return; - } - - inIncludes = Lst_Init(FALSE); - inLibs = Lst_Init(FALSE); - - while ((ln = Lst_Next(sufflist)) != NULL) { - s = (Suff *)Lst_Datum(ln); - if (!Lst_IsEmpty (s->searchPath)) { + for (ln = sufflist.first; ln != NULL; ln = ln->next) { + Suffix *suff = ln->datum; + if (!Lst_IsEmpty(&suff->searchPath->dirs)) { #ifdef INCLUDES - if (s->flags & SUFF_INCLUDE) { - Dir_Concat(inIncludes, s->searchPath); - } -#endif /* INCLUDES */ + if (suff->flags & SUFF_INCLUDE) + SearchPath_AddAll(includesPath, + suff->searchPath); +#endif #ifdef LIBRARIES - if (s->flags & SUFF_LIBRARY) { - Dir_Concat(inLibs, s->searchPath); - } -#endif /* LIBRARIES */ - Dir_Concat(s->searchPath, dirSearchPath); - } else { - Lst_Destroy(s->searchPath, Dir_Destroy); - s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir); + if (suff->flags & SUFF_LIBRARY) + SearchPath_AddAll(libsPath, suff->searchPath); +#endif + SearchPath_AddAll(suff->searchPath, &dirSearchPath); + } else { + SearchPath_Free(suff->searchPath); + suff->searchPath = Dir_CopyDirSearchPath(); + } } - } - Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL, 0); - free(ptr); - Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0); - free(ptr); + flags = SearchPath_ToFlags(includesPath, "-I"); + Global_Set(".INCLUDES", flags); + free(flags); - Lst_Destroy(inIncludes, Dir_Destroy); - Lst_Destroy(inLibs, Dir_Destroy); + flags = SearchPath_ToFlags(libsPath, "-L"); + Global_Set(".LIBS", flags); + free(flags); - Lst_Close(sufflist); + SearchPath_Free(includesPath); + SearchPath_Free(libsPath); } -/*- - *----------------------------------------------------------------------- - * Suff_AddInclude -- - * Add the given suffix as a type of file which gets included. - * Called from the parse module when a .INCLUDES line is parsed. - * The suffix must have already been defined. - * - * Input: - * sname Name of the suffix to mark - * - * Results: - * None. - * - * Side Effects: - * The SUFF_INCLUDE bit is set in the suffix's flags field - * - *----------------------------------------------------------------------- +/* + * Add the given suffix as a type of file which gets included. + * Called when a '.INCLUDES: .h' line is parsed. + * To have an effect, the suffix must already exist. + * This affects the magic variable '.INCLUDES'. */ void -Suff_AddInclude(char *sname) +Suff_AddInclude(const char *suffName) { - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); - if (ln != NULL) { - s = (Suff *)Lst_Datum(ln); - s->flags |= SUFF_INCLUDE; - } + Suffix *suff = FindSuffixByName(suffName); + if (suff != NULL) + suff->flags |= SUFF_INCLUDE; } -/*- - *----------------------------------------------------------------------- - * Suff_AddLib -- - * Add the given suffix as a type of file which is a library. - * Called from the parse module when parsing a .LIBS line. The - * suffix must have been defined via .SUFFIXES before this is - * called. - * - * Input: - * sname Name of the suffix to mark - * - * Results: - * None. - * - * Side Effects: - * The SUFF_LIBRARY bit is set in the suffix's flags field - * - *----------------------------------------------------------------------- +/* + * Add the given suffix as a type of file which is a library. + * Called when a '.LIBS: .a' line is parsed. + * To have an effect, the suffix must already exist. + * This affects the magic variable '.LIBS'. */ void -Suff_AddLib(char *sname) +Suff_AddLib(const char *suffName) { - LstNode ln; - Suff *s; - - ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); - if (ln != NULL) { - s = (Suff *)Lst_Datum(ln); - s->flags |= SUFF_LIBRARY; - } + Suffix *suff = FindSuffixByName(suffName); + if (suff != NULL) + suff->flags |= SUFF_LIBRARY; } - /********** Implicit Source Search Functions *********/ +/********** Implicit Source Search Functions *********/ -/*- - *----------------------------------------------------------------------- - * SuffAddSrc -- - * Add a suffix as a Src structure to the given list with its parent - * being the given Src structure. If the suffix is the null suffix, - * the prefix is used unaltered as the file name in the Src structure. - * - * Input: - * sp suffix for which to create a Src structure - * lsp list and parent for the new Src - * - * Results: - * always returns 0 - * - * Side Effects: - * A Src structure is created and tacked onto the end of the list - *----------------------------------------------------------------------- - */ -static int -SuffAddSrc(void *sp, void *lsp) +static void +CandidateSearcher_Init(CandidateSearcher *cs) { - Suff *s = (Suff *)sp; - LstSrc *ls = (LstSrc *)lsp; - Src *s2; /* new Src structure */ - Src *targ; /* Target structure */ - - targ = ls->s; - - if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { - /* - * If the suffix has been marked as the NULL suffix, also create a Src - * structure for a file with no suffix attached. Two birds, and all - * that... - */ - s2 = bmake_malloc(sizeof(Src)); - s2->file = bmake_strdup(targ->pref); - s2->pref = targ->pref; - s2->parent = targ; - s2->node = NULL; - s2->suff = s; - s->refCount++; - s2->children = 0; - targ->children += 1; - (void)Lst_AtEnd(ls->l, s2); -#ifdef DEBUG_SRC - s2->cp = Lst_Init(FALSE); - Lst_AtEnd(targ->cp, s2); - fprintf(debug_file, "1 add %x %x to %x:", targ, s2, ls->l); - Lst_ForEach(ls->l, PrintAddr, NULL); - fprintf(debug_file, "\n"); -#endif - } - s2 = bmake_malloc(sizeof(Src)); - s2->file = str_concat(targ->pref, s->name, 0); - s2->pref = targ->pref; - s2->parent = targ; - s2->node = NULL; - s2->suff = s; - s->refCount++; - s2->children = 0; - targ->children += 1; - (void)Lst_AtEnd(ls->l, s2); -#ifdef DEBUG_SRC - s2->cp = Lst_Init(FALSE); - Lst_AtEnd(targ->cp, s2); - fprintf(debug_file, "2 add %x %x to %x:", targ, s2, ls->l); - Lst_ForEach(ls->l, PrintAddr, NULL); - fprintf(debug_file, "\n"); -#endif - - return(0); + Lst_Init(&cs->list); } -/*- - *----------------------------------------------------------------------- - * SuffAddLevel -- - * Add all the children of targ as Src structures to the given list - * - * Input: - * l list to which to add the new level - * targ Src structure to use as the parent - * - * Results: - * None - * - * Side Effects: - * Lots of structures are created and added to the list - *----------------------------------------------------------------------- +static void +CandidateSearcher_Done(CandidateSearcher *cs) +{ + Lst_Done(&cs->list); +} + +static void +CandidateSearcher_Add(CandidateSearcher *cs, Candidate *cand) +{ + /* TODO: filter duplicates */ + Lst_Append(&cs->list, cand); +} + +static void +CandidateSearcher_AddIfNew(CandidateSearcher *cs, Candidate *cand) +{ + /* TODO: filter duplicates */ + if (Lst_FindDatum(&cs->list, cand) == NULL) + Lst_Append(&cs->list, cand); +} + +static void +CandidateSearcher_MoveAll(CandidateSearcher *cs, CandidateList *list) +{ + /* TODO: filter duplicates */ + Lst_MoveAll(&cs->list, list); +} + + +#ifdef DEBUG_SRC +static void +CandidateList_PrintAddrs(CandidateList *list) +{ + CandidateListNode *ln; + + for (ln = list->first; ln != NULL; ln = ln->next) { + Candidate *cand = ln->datum; + debug_printf(" %p:%s", cand, cand->file); + } + debug_printf("\n"); +} +#endif + +static Candidate * +Candidate_New(char *name, char *prefix, Suffix *suff, Candidate *parent, + GNode *gn) +{ + Candidate *cand = bmake_malloc(sizeof *cand); + + cand->file = name; + cand->prefix = prefix; + cand->suff = Suffix_Ref(suff); + cand->parent = parent; + cand->node = gn; + cand->numChildren = 0; +#ifdef DEBUG_SRC + Lst_Init(&cand->childrenList); +#endif + + return cand; +} + +/* Add a new candidate to the list. */ +/*ARGSUSED*/ +static void +CandidateList_Add(CandidateList *list, char *srcName, Candidate *targ, + Suffix *suff, const char *debug_tag) +{ + Candidate *cand = Candidate_New(srcName, targ->prefix, suff, targ, + NULL); + targ->numChildren++; + Lst_Append(list, cand); + +#ifdef DEBUG_SRC + Lst_Append(&targ->childrenList, cand); + debug_printf("%s add suff %p:%s candidate %p:%s to list %p:", + debug_tag, targ, targ->file, cand, cand->file, list); + CandidateList_PrintAddrs(list); +#endif +} + +/* + * Add all candidates to the list that can be formed by applying a suffix to + * the candidate. */ static void -SuffAddLevel(Lst l, Src *targ) +CandidateList_AddCandidatesFor(CandidateList *list, Candidate *cand) { - LstSrc ls; + SuffixListNode *ln; + for (ln = cand->suff->children.first; ln != NULL; ln = ln->next) { + Suffix *suff = ln->datum; - ls.s = targ; - ls.l = l; + if ((suff->flags & SUFF_NULL) && suff->name[0] != '\0') { + /* + * If the suffix has been marked as the NULL suffix, + * also create a candidate for a file with no suffix + * attached. + */ + CandidateList_Add(list, bmake_strdup(cand->prefix), + cand, suff, "1"); + } - Lst_ForEach(targ->suff->children, SuffAddSrc, &ls); + CandidateList_Add(list, str_concat2(cand->prefix, suff->name), + cand, suff, "2"); + } } -/*- - *---------------------------------------------------------------------- - * SuffRemoveSrc -- - * Free all src structures in list that don't have a reference count - * - * Results: - * Ture if an src was removed - * - * Side Effects: - * The memory is free'd. - *---------------------------------------------------------------------- +/* + * Free the first candidate in the list that is not referenced anymore. + * Return whether a candidate was removed. */ -static int -SuffRemoveSrc(Lst l) +static Boolean +RemoveCandidate(CandidateList *srcs) { - LstNode ln; - Src *s; - int t = 0; + CandidateListNode *ln; - if (Lst_Open(l) == FAILURE) { - return 0; - } #ifdef DEBUG_SRC - fprintf(debug_file, "cleaning %lx: ", (unsigned long) l); - Lst_ForEach(l, PrintAddr, NULL); - fprintf(debug_file, "\n"); + debug_printf("cleaning list %p:", srcs); + CandidateList_PrintAddrs(srcs); #endif + for (ln = srcs->first; ln != NULL; ln = ln->next) { + Candidate *src = ln->datum; - while ((ln = Lst_Next(l)) != NULL) { - s = (Src *)Lst_Datum(ln); - if (s->children == 0) { - free(s->file); - if (!s->parent) - free(s->pref); - else { + if (src->numChildren == 0) { + if (src->parent == NULL) + free(src->prefix); + else { #ifdef DEBUG_SRC - LstNode ln = Lst_Member(s->parent->cp, s); - if (ln != NULL) - Lst_Remove(s->parent->cp, ln); + /* XXX: Lst_RemoveDatum */ + CandidateListNode *ln2; + ln2 = Lst_FindDatum(&src->parent->childrenList, + src); + if (ln2 != NULL) + Lst_Remove(&src->parent->childrenList, + ln2); #endif - --s->parent->children; - } + src->parent->numChildren--; + } #ifdef DEBUG_SRC - fprintf(debug_file, "free: [l=%x] p=%x %d\n", l, s, s->children); - Lst_Destroy(s->cp, NULL); + debug_printf("free: list %p src %p:%s children %d\n", + srcs, src, src->file, src->numChildren); + Lst_Done(&src->childrenList); +#endif + Lst_Remove(srcs, ln); + free(src->file); + free(src); + return TRUE; + } +#ifdef DEBUG_SRC + else { + debug_printf("keep: list %p src %p:%s children %d:", + srcs, src, src->file, src->numChildren); + CandidateList_PrintAddrs(&src->childrenList); + } #endif - Lst_Remove(l, ln); - free(s); - t |= 1; - Lst_Close(l); - return TRUE; } -#ifdef DEBUG_SRC - else { - fprintf(debug_file, "keep: [l=%x] p=%x %d: ", l, s, s->children); - Lst_ForEach(s->cp, PrintAddr, NULL); - fprintf(debug_file, "\n"); - } -#endif - } - Lst_Close(l); - - return t; + return FALSE; } -/*- - *----------------------------------------------------------------------- - * SuffFindThem -- - * Find the first existing file/target in the list srcs - * - * Input: - * srcs list of Src structures to search through - * - * Results: - * The lowest structure in the chain of transformations - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -static Src * -SuffFindThem(Lst srcs, Lst slst) +/* Find the first existing file/target in srcs. */ +static Candidate * +FindThem(CandidateList *srcs, CandidateSearcher *cs) { - Src *s; /* current Src */ - Src *rs; /* returned Src */ - char *ptr; + HashSet seen; - rs = NULL; + HashSet_Init(&seen); - while (!Lst_IsEmpty (srcs)) { - s = (Src *)Lst_DeQueue(srcs); + while (!Lst_IsEmpty(srcs)) { + Candidate *src = Lst_Dequeue(srcs); - if (DEBUG(SUFF)) { - fprintf(debug_file, "\ttrying %s...", s->file); +#ifdef DEBUG_SRC + debug_printf("remove from list %p src %p:%s\n", + srcs, src, src->file); +#endif + DEBUG1(SUFF, "\ttrying %s...", src->file); + + /* + * A file is considered to exist if either a node exists in the + * graph for it or the file actually exists. + */ + if (Targ_FindNode(src->file) != NULL) { + found: + HashSet_Done(&seen); + DEBUG0(SUFF, "got it\n"); + return src; + } + + { + char *file = Dir_FindFile(src->file, + src->suff->searchPath); + if (file != NULL) { + free(file); + goto found; + } + } + + DEBUG0(SUFF, "not there\n"); + + if (HashSet_Add(&seen, src->file)) + CandidateList_AddCandidatesFor(srcs, src); + else { + DEBUG1(SUFF, "FindThem: skipping duplicate \"%s\"\n", + src->file); + } + + CandidateSearcher_Add(cs, src); } + HashSet_Done(&seen); + return NULL; +} + +/* + * See if any of the children of the candidate's GNode is one from which the + * target can be transformed. If there is one, a candidate is put together + * for it and returned. + */ +static Candidate * +FindCmds(Candidate *targ, CandidateSearcher *cs) +{ + GNodeListNode *gln; + GNode *tgn; /* Target GNode */ + GNode *sgn; /* Source GNode */ + size_t prefLen; /* The length of the defined prefix */ + Suffix *suff; /* Suffix of the matching candidate */ + Candidate *ret; /* Return value */ + + tgn = targ->node; + prefLen = strlen(targ->prefix); + + for (gln = tgn->children.first; gln != NULL; gln = gln->next) { + const char *base; + + sgn = gln->datum; + + if (sgn->type & OP_OPTIONAL && Lst_IsEmpty(&tgn->commands)) { + /* + * We haven't looked to see if .OPTIONAL files exist + * yet, so don't use one as the implicit source. + * This allows us to use .OPTIONAL in .depend files so + * make won't complain "don't know how to make xxx.h" + * when a dependent file has been moved/deleted. + */ + continue; + } + + base = str_basename(sgn->name); + if (strncmp(base, targ->prefix, prefLen) != 0) + continue; + /* The node matches the prefix, see if it has a known suffix. */ + suff = FindSuffixByName(base + prefLen); + if (suff == NULL) + continue; + + /* + * It even has a known suffix, see if there's a transformation + * defined between the node's suffix and the target's suffix. + * + * XXX: Handle multi-stage transformations here, too. + */ + + if (Lst_FindDatum(&suff->parents, targ->suff) != NULL) + break; + } + + if (gln == NULL) + return NULL; + + ret = Candidate_New(bmake_strdup(sgn->name), targ->prefix, suff, targ, + sgn); + targ->numChildren++; +#ifdef DEBUG_SRC + debug_printf("3 add targ %p:%s ret %p:%s\n", + targ, targ->file, ret, ret->file); + Lst_Append(&targ->childrenList, ret); +#endif + CandidateSearcher_Add(cs, ret); + DEBUG1(SUFF, "\tusing existing source %s\n", sgn->name); + return ret; +} + +static void +ExpandWildcards(GNodeListNode *cln, GNode *pgn) +{ + GNode *cgn = cln->datum; + StringList expansions; + + if (!Dir_HasWildcards(cgn->name)) + return; + /* - * A file is considered to exist if either a node exists in the - * graph for it or the file actually exists. + * Expand the word along the chosen path */ - if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) { -#ifdef DEBUG_SRC - fprintf(debug_file, "remove %x from %x\n", s, srcs); -#endif - rs = s; - break; + Lst_Init(&expansions); + SearchPath_Expand(Suff_FindPath(cgn), cgn->name, &expansions); + + while (!Lst_IsEmpty(&expansions)) { + GNode *gn; + /* + * Fetch next expansion off the list and find its GNode + */ + char *cp = Lst_Dequeue(&expansions); + + DEBUG1(SUFF, "%s...", cp); + gn = Targ_GetNode(cp); + + /* Add gn to the parents child list before the original child */ + Lst_InsertBefore(&pgn->children, cln, gn); + Lst_Append(&gn->parents, pgn); + pgn->unmade++; } - if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) { - rs = s; -#ifdef DEBUG_SRC - fprintf(debug_file, "remove %x from %x\n", s, srcs); -#endif - free(ptr); - break; - } + Lst_Done(&expansions); - if (DEBUG(SUFF)) { - fprintf(debug_file, "not there\n"); - } + DEBUG0(SUFF, "\n"); - SuffAddLevel(srcs, s); - Lst_AtEnd(slst, s); - } - - if (DEBUG(SUFF) && rs) { - fprintf(debug_file, "got it\n"); - } - return (rs); + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + pgn->unmade--; + Lst_Remove(&pgn->children, cln); + Lst_Remove(&cgn->parents, Lst_FindDatum(&cgn->parents, pgn)); } -/*- - *----------------------------------------------------------------------- - * SuffFindCmds -- - * See if any of the children of the target in the Src structure is - * one from which the target can be transformed. If there is one, - * a Src structure is put together for it and returned. +/* + * Break the result into a vector of strings whose nodes we can find, then + * add those nodes to the members list. * - * Input: - * targ Src structure to play with - * - * Results: - * The Src structure of the "winning" child, or NULL if no such beast. - * - * Side Effects: - * A Src structure may be allocated. - * - *----------------------------------------------------------------------- + * Unfortunately, we can't use Str_Words because it doesn't understand about + * variable expressions with spaces in them. */ -static Src * -SuffFindCmds(Src *targ, Lst slst) +static void +ExpandChildrenRegular(char *cp, GNode *pgn, GNodeList *members) { - LstNode ln; /* General-purpose list node */ - GNode *t, /* Target GNode */ - *s; /* Source GNode */ - int prefLen;/* The length of the defined prefix */ - Suff *suff; /* Suffix on matching beastie */ - Src *ret; /* Return value */ - char *cp; + char *start; - t = targ->node; - (void)Lst_Open(t->children); - prefLen = strlen(targ->pref); + pp_skip_hspace(&cp); + start = cp; + while (*cp != '\0') { + if (*cp == ' ' || *cp == '\t') { + GNode *gn; + /* + * White-space -- terminate element, find the node, + * add it, skip any further spaces. + */ + *cp++ = '\0'; + gn = Targ_GetNode(start); + Lst_Append(members, gn); + pp_skip_hspace(&cp); + /* Continue at the next non-space. */ + start = cp; + } else if (*cp == '$') { + /* Skip over the variable expression. */ + const char *nested_p = cp; + FStr junk; - for (;;) { - ln = Lst_Next(t->children); - if (ln == NULL) { - Lst_Close(t->children); - return NULL; - } - s = (GNode *)Lst_Datum(ln); + (void)Var_Parse(&nested_p, pgn, VARE_PARSE_ONLY, &junk); + /* TODO: handle errors */ + if (junk.str == var_Error) { + Parse_Error(PARSE_FATAL, + "Malformed variable expression at \"%s\"", + cp); + cp++; + } else { + cp += nested_p - cp; + } - if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) { - /* - * We haven't looked to see if .OPTIONAL files exist yet, so - * don't use one as the implicit source. - * This allows us to use .OPTIONAL in .depend files so make won't - * complain "don't know how to make xxx.h' when a dependent file - * has been moved/deleted. - */ - continue; + FStr_Done(&junk); + } else if (cp[0] == '\\' && cp[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; + } else { + cp++; + } } - cp = strrchr(s->name, '/'); - if (cp == NULL) { - cp = s->name; - } else { - cp++; + if (cp != start) { + /* + * Stuff left over -- add it to the list too + */ + GNode *gn = Targ_GetNode(start); + Lst_Append(members, gn); } - if (strncmp(cp, targ->pref, prefLen) != 0) - continue; - /* - * The node matches the prefix ok, see if it has a known - * suffix. - */ - ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP); - if (ln == NULL) - continue; - /* - * It even has a known suffix, see if there's a transformation - * defined between the node's suffix and the target's suffix. - * - * XXX: Handle multi-stage transformations here, too. - */ - suff = (Suff *)Lst_Datum(ln); - - if (Lst_Member(suff->parents, targ->suff) != NULL) - break; - } - - /* - * Hot Damn! Create a new Src structure to describe - * this transformation (making sure to duplicate the - * source node's name so Suff_FindDeps can free it - * again (ick)), and return the new structure. - */ - ret = bmake_malloc(sizeof(Src)); - ret->file = bmake_strdup(s->name); - ret->pref = targ->pref; - ret->suff = suff; - suff->refCount++; - ret->parent = targ; - ret->node = s; - ret->children = 0; - targ->children += 1; -#ifdef DEBUG_SRC - ret->cp = Lst_Init(FALSE); - fprintf(debug_file, "3 add %x %x\n", targ, ret); - Lst_AtEnd(targ->cp, ret); -#endif - Lst_AtEnd(slst, ret); - if (DEBUG(SUFF)) { - fprintf(debug_file, "\tusing existing source %s\n", s->name); - } - return (ret); } -/*- - *----------------------------------------------------------------------- - * SuffExpandChildren -- - * Expand the names of any children of a given node that contain - * variable invocations or file wildcards into actual targets. +/* + * Expand the names of any children of a given node that contain variable + * expressions or file wildcards into actual targets. + * + * The expanded node is removed from the parent's list of children, and the + * parent's unmade counter is decremented, but other nodes may be added. * * Input: * cln Child to examine * pgn Parent node being processed - * - * Results: - * === 0 (continue) - * - * Side Effects: - * The expanded node is removed from the parent's list of children, - * and the parent's unmade counter is decremented, but other nodes - * may be added. - * - *----------------------------------------------------------------------- */ static void -SuffExpandChildren(LstNode cln, GNode *pgn) +ExpandChildren(GNodeListNode *cln, GNode *pgn) { - GNode *cgn = (GNode *)Lst_Datum(cln); - GNode *gn; /* New source 8) */ - char *cp; /* Expanded value */ + GNode *cgn = cln->datum; + char *cp; /* Expanded value */ - if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ)) - /* It is all too hard to process the result of .ORDER */ - return; + if (!Lst_IsEmpty(&cgn->order_pred) || !Lst_IsEmpty(&cgn->order_succ)) + /* It is all too hard to process the result of .ORDER */ + return; - if (cgn->type & OP_WAIT) - /* Ignore these (& OP_PHONY ?) */ - return; + if (cgn->type & OP_WAIT) + /* Ignore these (& OP_PHONY ?) */ + return; - /* - * First do variable expansion -- this takes precedence over - * wildcard expansion. If the result contains wildcards, they'll be gotten - * to later since the resulting words are tacked on to the end of - * the children list. - */ - if (strchr(cgn->name, '$') == NULL) { - SuffExpandWildcards(cln, pgn); - return; - } + /* + * First do variable expansion -- this takes precedence over wildcard + * expansion. If the result contains wildcards, they'll be gotten to + * later since the resulting words are tacked on to the end of the + * children list. + */ + if (strchr(cgn->name, '$') == NULL) { + ExpandWildcards(cln, pgn); + return; + } - if (DEBUG(SUFF)) { - fprintf(debug_file, "Expanding \"%s\"...", cgn->name); - } - cp = Var_Subst(NULL, cgn->name, pgn, TRUE); + DEBUG1(SUFF, "Expanding \"%s\"...", cgn->name); + (void)Var_Subst(cgn->name, pgn, VARE_UNDEFERR, &cp); + /* TODO: handle errors */ - if (cp != NULL) { - Lst members = Lst_Init(FALSE); + { + GNodeList members = LST_INIT; - if (cgn->type & OP_ARCHV) { - /* - * Node was an archive(member) target, so we want to call - * on the Arch module to find the nodes for us, expanding - * variables in the parent's context. - */ - char *sacrifice = cp; - - (void)Arch_ParseArchive(&sacrifice, members, pgn); - } else { - /* - * Break the result into a vector of strings whose nodes - * we can find, then add those nodes to the members list. - * Unfortunately, we can't use brk_string b/c it - * doesn't understand about variable specifications with - * spaces in them... - */ - char *start; - char *initcp = cp; /* For freeing... */ - - for (start = cp; *start == ' ' || *start == '\t'; start++) - continue; - for (cp = start; *cp != '\0'; cp++) { - if (*cp == ' ' || *cp == '\t') { - /* - * White-space -- terminate element, find the node, - * add it, skip any further spaces. - */ - *cp++ = '\0'; - gn = Targ_FindNode(start, TARG_CREATE); - (void)Lst_AtEnd(members, gn); - while (*cp == ' ' || *cp == '\t') { - cp++; - } - /* - * Adjust cp for increment at start of loop, but - * set start to first non-space. - */ - start = cp--; - } else if (*cp == '$') { - /* - * Start of a variable spec -- contact variable module - * to find the end so we can skip over it. - */ - char *junk; - int len; - void *freeIt; - - junk = Var_Parse(cp, pgn, TRUE, &len, &freeIt); - if (junk != var_Error) { - cp += len - 1; - } - - if (freeIt) - free(freeIt); - } else if (*cp == '\\' && *cp != '\0') { - /* - * Escaped something -- skip over it - */ - cp++; + if (cgn->type & OP_ARCHV) { + /* + * Node was an 'archive(member)' target, so + * call on the Arch module to find the nodes for us, + * expanding variables in the parent's scope. + */ + char *p = cp; + (void)Arch_ParseArchive(&p, &members, pgn); + } else { + ExpandChildrenRegular(cp, pgn, &members); } - } - if (cp != start) { /* - * Stuff left over -- add it to the list too + * Add all elements of the members list to the parent node. */ - gn = Targ_FindNode(start, TARG_CREATE); - (void)Lst_AtEnd(members, gn); - } - /* - * Point cp back at the beginning again so the variable value - * can be freed. - */ - cp = initcp; + 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); } - /* - * Add all elements of the members list to the parent node. - */ - while(!Lst_IsEmpty(members)) { - gn = (GNode *)Lst_DeQueue(members); - - if (DEBUG(SUFF)) { - fprintf(debug_file, "%s...", gn->name); - } - /* Add gn to the parents child list before the original child */ - (void)Lst_InsertBefore(pgn->children, cln, gn); - (void)Lst_AtEnd(gn->parents, pgn); - pgn->unmade++; - /* Expand wildcards on new node */ - SuffExpandWildcards(Lst_Prev(cln), pgn); - } - Lst_Destroy(members, NULL); + DEBUG0(SUFF, "\n"); /* - * Free the result + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. */ - free(cp); - } - if (DEBUG(SUFF)) { - fprintf(debug_file, "\n"); - } - - /* - * Now the source is expanded, remove it from the list of children to - * keep it from being processed. - */ - pgn->unmade--; - Lst_Remove(pgn->children, cln); - Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn)); + pgn->unmade--; + Lst_Remove(&pgn->children, cln); + Lst_Remove(&cgn->parents, Lst_FindDatum(&cgn->parents, pgn)); } static void -SuffExpandWildcards(LstNode cln, GNode *pgn) +ExpandAllChildren(GNode *gn) { - GNode *cgn = (GNode *)Lst_Datum(cln); - GNode *gn; /* New source 8) */ - char *cp; /* Expanded value */ - Lst explist; /* List of expansions */ + GNodeListNode *ln, *nln; - if (!Dir_HasWildcards(cgn->name)) - return; - - /* - * Expand the word along the chosen path - */ - explist = Lst_Init(FALSE); - Dir_Expand(cgn->name, Suff_FindPath(cgn), explist); - - while (!Lst_IsEmpty(explist)) { - /* - * Fetch next expansion off the list and find its GNode - */ - cp = (char *)Lst_DeQueue(explist); - - if (DEBUG(SUFF)) { - fprintf(debug_file, "%s...", cp); + for (ln = gn->children.first; ln != NULL; ln = nln) { + nln = ln->next; + ExpandChildren(ln, gn); } - gn = Targ_FindNode(cp, TARG_CREATE); - - /* Add gn to the parents child list before the original child */ - (void)Lst_InsertBefore(pgn->children, cln, gn); - (void)Lst_AtEnd(gn->parents, pgn); - pgn->unmade++; - } - - /* - * Nuke what's left of the list - */ - Lst_Destroy(explist, NULL); - - if (DEBUG(SUFF)) { - fprintf(debug_file, "\n"); - } - - /* - * Now the source is expanded, remove it from the list of children to - * keep it from being processed. - */ - pgn->unmade--; - Lst_Remove(pgn->children, cln); - Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn)); } -/*- - *----------------------------------------------------------------------- - * Suff_FindPath -- - * Find a path along which to expand the node. +/* + * Find a path along which to expand the node. * - * If the word has a known suffix, use that path. - * If it has no known suffix, use the default system search path. + * 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. - * - * Side Effects: - * XXX: We could set the suffix here so that we don't have to scan - * again. - * - *----------------------------------------------------------------------- */ -Lst -Suff_FindPath(GNode* gn) +SearchPath * +Suff_FindPath(GNode *gn) { - Suff *suff = gn->suffix; + Suffix *suff = gn->suffix; - if (suff == NULL) { - SuffixCmpData sd; /* Search string data */ - LstNode ln; - sd.len = strlen(gn->name); - sd.ename = gn->name + sd.len; - ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP); + if (suff == NULL) { + char *name = gn->name; + size_t nameLen = strlen(gn->name); + SuffixListNode *ln; + for (ln = sufflist.first; ln != NULL; ln = ln->next) + if (Suffix_IsSuffix(ln->datum, nameLen, name + nameLen)) + break; - if (DEBUG(SUFF)) { - fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name); + DEBUG1(SUFF, "Wildcard expanding \"%s\"...", gn->name); + if (ln != NULL) + suff = ln->datum; + /* + * XXX: Here we can save the suffix so we don't have to do + * this again. + */ } - if (ln != NULL) - suff = (Suff *)Lst_Datum(ln); - /* XXX: Here we can save the suffix so we don't have to do this again */ - } - if (suff != NULL) { - if (DEBUG(SUFF)) { - fprintf(debug_file, "suffix is \"%s\"...", suff->name); + if (suff != NULL) { + DEBUG1(SUFF, "suffix is \"%s\"...\n", suff->name); + return suff->searchPath; + } else { + DEBUG0(SUFF, "\n"); + return &dirSearchPath; /* Use default search path */ } - return suff->searchPath; - } else { - /* - * Use default search path - */ - return dirSearchPath; - } } -/*- - *----------------------------------------------------------------------- - * SuffApplyTransform -- - * Apply a transformation rule, given the source and target nodes - * and suffixes. +/* + * Apply a transformation rule, given the source and target nodes and + * suffixes. * - * Input: - * tGn Target node - * sGn Source node - * t Target suffix - * s Source suffix + * The source and target are linked and the commands from the transformation + * are added to the target node's commands list. The target also inherits all + * the sources for the transformation rule. * * Results: * TRUE if successful, FALSE if not. - * - * Side Effects: - * The source and target are linked and the commands from the - * transformation are added to the target node's commands list. - * All attributes but OP_DEPMASK and OP_TRANSFORM are applied - * to the target. The target also inherits all the sources for - * the transformation rule. - * - *----------------------------------------------------------------------- */ static Boolean -SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) +ApplyTransform(GNode *tgn, GNode *sgn, Suffix *tsuff, Suffix *ssuff) { - LstNode ln, nln; /* General node */ - char *tname; /* Name of transformation rule */ - GNode *gn; /* Node for same */ + GNodeListNode *ln; + char *tname; /* Name of transformation rule */ + GNode *gn; /* Node for the transformation rule */ - /* - * Form the proper links between the target and source. - */ - (void)Lst_AtEnd(tGn->children, sGn); - (void)Lst_AtEnd(sGn->parents, tGn); - tGn->unmade += 1; + /* Form the proper links between the target and source. */ + Lst_Append(&tgn->children, sgn); + Lst_Append(&sgn->parents, tgn); + tgn->unmade++; - /* - * Locate the transformation rule itself - */ - tname = str_concat(s->name, t->name, 0); - ln = Lst_Find(transforms, tname, SuffGNHasNameP); - free(tname); + /* Locate the transformation rule itself. */ + tname = str_concat2(ssuff->name, tsuff->name); + gn = FindTransformByName(tname); + free(tname); + + /* This can happen when linking an OP_MEMBER and OP_ARCHV node. */ + if (gn == NULL) + return FALSE; + + DEBUG3(SUFF, "\tapplying %s -> %s to \"%s\"\n", + ssuff->name, tsuff->name, tgn->name); + + /* Record last child; Make_HandleUse may add child nodes. */ + ln = tgn->children.last; + + /* Apply the rule. */ + Make_HandleUse(gn, tgn); + + /* Deal with wildcards and variables in any acquired sources. */ + ln = ln != NULL ? ln->next : NULL; + while (ln != NULL) { + GNodeListNode *nln = ln->next; + ExpandChildren(ln, tgn); + ln = nln; + } - if (ln == NULL) { /* - * Not really such a transformation rule (can happen when we're - * called to link an OP_MEMBER and OP_ARCHV node), so return - * FALSE. + * Keep track of another parent to which this node is transformed so + * the .IMPSRC variable can be set correctly for the parent. */ - return(FALSE); - } + Lst_Append(&sgn->implicitParents, tgn); - gn = (GNode *)Lst_Datum(ln); - - if (DEBUG(SUFF)) { - fprintf(debug_file, "\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); - } - - /* - * Record last child for expansion purposes - */ - ln = Lst_Last(tGn->children); - - /* - * Pass the buck to Make_HandleUse to apply the rule - */ - (void)Make_HandleUse(gn, tGn); - - /* - * Deal with wildcards and variables in any acquired sources - */ - for (ln = Lst_Succ(ln); ln != NULL; ln = nln) { - nln = Lst_Succ(ln); - SuffExpandChildren(ln, tGn); - } - - /* - * Keep track of another parent to which this beast is transformed so - * the .IMPSRC variable can be set correctly for the parent. - */ - (void)Lst_AtEnd(sGn->iParents, tGn); - - return(TRUE); + return TRUE; } +/* + * Member has a known suffix, so look for a transformation rule from + * it to a possible suffix of the archive. + * + * Rather than searching through the entire list, we just look at + * suffixes to which the member's suffix may be transformed. + */ +static void +ExpandMember(GNode *gn, const char *eoarch, GNode *mem, Suffix *memSuff) +{ + GNodeListNode *ln; + size_t nameLen = (size_t)(eoarch - gn->name); -/*- - *----------------------------------------------------------------------- - * SuffFindArchiveDeps -- - * Locate dependencies for an OP_ARCHV node. + /* Use first matching suffix... */ + for (ln = memSuff->parents.first; ln != NULL; ln = ln->next) + if (Suffix_IsSuffix(ln->datum, nameLen, eoarch)) + break; + + if (ln != NULL) { + /* Got one -- apply it */ + Suffix *suff = ln->datum; + if (!ApplyTransform(gn, mem, suff, memSuff)) { + DEBUG2(SUFF, "\tNo transformation from %s -> %s\n", + memSuff->name, suff->name); + } + } +} + +static void FindDeps(GNode *, CandidateSearcher *); + +/* + * Locate dependencies for an OP_ARCHV node. * * Input: * gn Node for which to locate dependencies * - * Results: - * None - * * Side Effects: * Same as Suff_FindDeps - * - *----------------------------------------------------------------------- */ static void -SuffFindArchiveDeps(GNode *gn, Lst slst) +FindDepsArchive(GNode *gn, CandidateSearcher *cs) { - char *eoarch; /* End of archive portion */ - char *eoname; /* End of member portion */ - GNode *mem; /* Node for member */ - static const char *copy[] = { - /* Variables to be copied from the member node */ - TARGET, /* Must be first */ - PREFIX, /* Must be second */ - }; - int i; /* Index into copy and vals */ - Suff *ms; /* Suffix descriptor for member */ - char *name; /* Start of member's name */ + char *eoarch; /* End of archive portion */ + char *eoname; /* End of member portion */ + GNode *mem; /* Node for member */ + Suffix *memSuff; + const char *name; /* Start of member's name */ - /* - * The node is an archive(member) pair. so we must find a - * suffix for both of them. - */ - eoarch = strchr(gn->name, '('); - eoname = strchr(eoarch, ')'); - - *eoname = '\0'; /* Nuke parentheses during suffix search */ - *eoarch = '\0'; /* So a suffix can be found */ - - name = eoarch + 1; - - /* - * To simplify things, call Suff_FindDeps recursively on the member now, - * so we can simply compare the member's .PREFIX and .TARGET variables - * to locate its suffix. This allows us to figure out the suffix to - * use for the archive without having to do a quadratic search over the - * suffix list, backtracking for each one... - */ - mem = Targ_FindNode(name, TARG_CREATE); - SuffFindDeps(mem, slst); - - /* - * Create the link between the two nodes right off - */ - (void)Lst_AtEnd(gn->children, mem); - (void)Lst_AtEnd(mem->parents, gn); - gn->unmade += 1; - - /* - * Copy in the variables from the member node to this one. - */ - for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) { - char *p1; - Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn, 0); - if (p1) - free(p1); - - } - - ms = mem->suffix; - if (ms == NULL) { /* - * Didn't know what it was -- use .NULL suffix if not in make mode + * The node is an archive(member) pair. so we must find a + * suffix for both of them. */ - if (DEBUG(SUFF)) { - fprintf(debug_file, "using null suffix\n"); + eoarch = strchr(gn->name, '('); + eoname = strchr(eoarch, ')'); + + /* + * Caller guarantees the format `libname(member)', via + * Arch_ParseArchive. + */ + assert(eoarch != NULL); + assert(eoname != NULL); + + *eoname = '\0'; /* Nuke parentheses during suffix search */ + *eoarch = '\0'; /* So a suffix can be found */ + + name = eoarch + 1; + + /* + * To simplify things, call Suff_FindDeps recursively on the member + * now, so we can simply compare the member's .PREFIX and .TARGET + * variables to locate its suffix. This allows us to figure out the + * suffix to use for the archive without having to do a quadratic + * search over the suffix list, backtracking for each one. + */ + mem = Targ_GetNode(name); + FindDeps(mem, cs); + + /* Create the link between the two nodes right off. */ + Lst_Append(&gn->children, mem); + Lst_Append(&mem->parents, gn); + gn->unmade++; + + /* Copy in the variables from the member node to this one. */ + Var_Set(gn, PREFIX, GNode_VarPrefix(mem)); + Var_Set(gn, TARGET, GNode_VarTarget(mem)); + + memSuff = mem->suffix; + if (memSuff == NULL) { /* Didn't know what it was. */ + DEBUG0(SUFF, "using null suffix\n"); + memSuff = nullSuff; } - ms = suffNull; - } - /* - * Set the other two local variables required for this target. - */ - Var_Set(MEMBER, name, gn, 0); - Var_Set(ARCHIVE, gn->name, gn, 0); - - if (ms != NULL) { - /* - * Member has a known suffix, so look for a transformation rule from - * it to a possible suffix of the archive. Rather than searching - * through the entire list, we just look at suffixes to which the - * member's suffix may be transformed... - */ - LstNode ln; - SuffixCmpData sd; /* Search string data */ + /* Set the other two local variables required for this target. */ + Var_Set(gn, MEMBER, name); + Var_Set(gn, ARCHIVE, gn->name); + /* Set $@ for compatibility with other makes. */ + Var_Set(gn, TARGET, gn->name); /* - * Use first matching suffix... + * Now we've got the important local variables set, expand any sources + * that still contain variables or wildcards in their names. */ - sd.len = eoarch - gn->name; - sd.ename = eoarch; - ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP); + ExpandAllChildren(gn); - if (ln != NULL) { - /* - * Got one -- apply it - */ - if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) && - DEBUG(SUFF)) - { - fprintf(debug_file, "\tNo transformation from %s -> %s\n", - ms->name, ((Suff *)Lst_Datum(ln))->name); - } - } - } + if (memSuff != NULL) + ExpandMember(gn, eoarch, mem, memSuff); - /* - * Replace the opening and closing parens now we've no need of the separate - * pieces. - */ - *eoarch = '('; *eoname = ')'; + /* + * Replace the opening and closing parens now we've no need of the + * separate pieces. + */ + *eoarch = '('; + *eoname = ')'; - /* - * Pretend gn appeared to the left of a dependency operator so - * the user needn't provide a transformation from the member to the - * archive. - */ - if (OP_NOP(gn->type)) { - gn->type |= OP_DEPENDS; - } + /* + * Pretend gn appeared to the left of a dependency operator so the + * user needn't provide a transformation from the member to the + * archive. + */ + if (!GNode_IsTarget(gn)) + gn->type |= OP_DEPENDS; - /* - * Flag the member as such so we remember to look in the archive for - * its modification time. - */ - mem->type |= OP_MEMBER; + /* + * Flag the member as such so we remember to look in the archive for + * its modification time. The OP_JOIN | OP_MADE is needed because + * this target should never get made. + */ + mem->type |= OP_MEMBER | OP_JOIN | OP_MADE; } -/*- - *----------------------------------------------------------------------- - * SuffFindNormalDeps -- - * Locate implicit dependencies for regular targets. +/* + * If the node is a library, it is the arch module's job to find it + * and set the TARGET variable accordingly. We merely provide the + * search path, assuming all libraries end in ".a" (if the suffix + * hasn't been defined, there's nothing we can do for it, so we just + * set the TARGET variable to the node's name in order to give it a + * value). + */ +static void +FindDepsLib(GNode *gn) +{ + Suffix *suff = FindSuffixByName(LIBSUFF); + if (suff != NULL) { + Suffix_Reassign(&gn->suffix, suff); + Arch_FindLib(gn, suff->searchPath); + } else { + Suffix_Unassign(&gn->suffix); + Var_Set(gn, TARGET, gn->name); + } + + /* + * Because a library (-lfoo) target doesn't follow the standard + * filesystem conventions, we don't set the regular variables for + * the thing. .PREFIX is simply made empty. + */ + Var_Set(gn, PREFIX, ""); +} + +static void +FindDepsRegularKnown(const char *name, size_t nameLen, GNode *gn, + CandidateList *srcs, CandidateList *targs) +{ + SuffixListNode *ln; + Candidate *targ; + char *pref; + + for (ln = sufflist.first; ln != NULL; ln = ln->next) { + Suffix *suff = ln->datum; + if (!Suffix_IsSuffix(suff, nameLen, name + nameLen)) + continue; + + pref = bmake_strldup(name, (size_t)(nameLen - suff->nameLen)); + targ = Candidate_New(bmake_strdup(gn->name), pref, suff, NULL, + gn); + + CandidateList_AddCandidatesFor(srcs, targ); + + /* Record the target so we can nuke it. */ + Lst_Append(targs, targ); + } +} + +static void +FindDepsRegularUnknown(GNode *gn, const char *sopref, + CandidateList *srcs, CandidateList *targs) +{ + Candidate *targ; + + if (!Lst_IsEmpty(targs) || nullSuff == NULL) + return; + + DEBUG1(SUFF, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name); + + targ = Candidate_New(bmake_strdup(gn->name), bmake_strdup(sopref), + nullSuff, NULL, gn); + + /* + * Only use the default suffix rules if we don't have commands + * defined for this gnode; traditional make programs used to not + * define suffix rules if the gnode had children but we don't do + * this anymore. + */ + if (Lst_IsEmpty(&gn->commands)) + CandidateList_AddCandidatesFor(srcs, targ); + else { + DEBUG0(SUFF, "not "); + } + + DEBUG0(SUFF, "adding suffix rules\n"); + + Lst_Append(targs, targ); +} + +/* + * Deal with finding the thing on the default search path. We always do + * that, not only if the node is only a source (not on the lhs of a + * dependency operator or [XXX] it has neither children or commands) as + * the old pmake did. + */ +static void +FindDepsRegularPath(GNode *gn, Candidate *targ) +{ + if (gn->type & (OP_PHONY | OP_NOPATH)) + return; + + free(gn->path); + gn->path = Dir_FindFile(gn->name, + (targ == NULL ? &dirSearchPath : + targ->suff->searchPath)); + if (gn->path == NULL) + return; + + Var_Set(gn, TARGET, gn->path); + + if (targ != NULL) { + /* + * Suffix known for the thing -- trim the suffix off + * the path to form the proper .PREFIX variable. + */ + size_t savep = strlen(gn->path) - targ->suff->nameLen; + char savec; + + Suffix_Reassign(&gn->suffix, targ->suff); + + savec = gn->path[savep]; + gn->path[savep] = '\0'; + + Var_Set(gn, PREFIX, str_basename(gn->path)); + + gn->path[savep] = savec; + } else { + /* + * The .PREFIX gets the full path if the target has no + * known suffix. + */ + Suffix_Unassign(&gn->suffix); + Var_Set(gn, PREFIX, str_basename(gn->path)); + } +} + +/* + * Locate implicit dependencies for regular targets. * * Input: * gn Node for which to find sources * - * Results: - * None. - * * Side Effects: - * Same as Suff_FindDeps... - * - *----------------------------------------------------------------------- + * Same as Suff_FindDeps */ static void -SuffFindNormalDeps(GNode *gn, Lst slst) +FindDepsRegular(GNode *gn, CandidateSearcher *cs) { - char *eoname; /* End of name */ - char *sopref; /* Start of prefix */ - LstNode ln, nln; /* Next suffix node to check */ - Lst srcs; /* List of sources at which to look */ - Lst targs; /* List of targets to which things can be - * transformed. They all have the same file, - * but different suff and pref fields */ - Src *bottom; /* Start of found transformation path */ - Src *src; /* General Src pointer */ - char *pref; /* Prefix to use */ - Src *targ; /* General Src target pointer */ - SuffixCmpData sd; /* Search string data */ + /* List of sources at which to look */ + CandidateList srcs = LST_INIT; + /* + * List of targets to which things can be transformed. + * They all have the same file, but different suff and prefix fields. + */ + CandidateList targs = LST_INIT; + Candidate *bottom; /* Start of found transformation path */ + Candidate *src; + Candidate *targ; + const char *name = gn->name; + size_t nameLen = strlen(name); - sd.len = strlen(gn->name); - sd.ename = eoname = gn->name + sd.len; - - sopref = gn->name; - - /* - * Begin at the beginning... - */ - ln = Lst_First(sufflist); - srcs = Lst_Init(FALSE); - targs = Lst_Init(FALSE); - - /* - * We're caught in a catch-22 here. On the one hand, we want to use any - * transformation implied by the target's sources, but we can't examine - * the sources until we've expanded any variables/wildcards they may hold, - * and we can't do that until we've set up the target's local variables - * and we can't do that until we know what the proper suffix for the - * target is (in case there are two suffixes one of which is a suffix of - * the other) and we can't know that until we've found its implied - * source, which we may not want to use if there's an existing source - * that implies a different transformation. - * - * In an attempt to get around this, which may not work all the time, - * but should work most of the time, we look for implied sources first, - * checking transformations to all possible suffixes of the target, - * use what we find to set the target's local variables, expand the - * children, then look for any overriding transformations they imply. - * Should we find one, we discard the one we found before. - */ - bottom = NULL; - targ = NULL; - - if (!(gn->type & OP_PHONY)) { - - while (ln != NULL) { - /* - * Look for next possible suffix... - */ - ln = Lst_FindFrom(sufflist, ln, &sd, SuffSuffIsSuffixP); - - if (ln != NULL) { - int prefLen; /* Length of the prefix */ - - /* - * Allocate a Src structure to which things can be transformed - */ - targ = bmake_malloc(sizeof(Src)); - targ->file = bmake_strdup(gn->name); - targ->suff = (Suff *)Lst_Datum(ln); - targ->suff->refCount++; - targ->node = gn; - targ->parent = NULL; - targ->children = 0; #ifdef DEBUG_SRC - targ->cp = Lst_Init(FALSE); + DEBUG1(SUFF, "FindDepsRegular \"%s\"\n", gn->name); #endif - /* - * Allocate room for the prefix, whose end is found by - * subtracting the length of the suffix from - * the end of the name. - */ - prefLen = (eoname - targ->suff->nameLen) - sopref; - targ->pref = bmake_malloc(prefLen + 1); - memcpy(targ->pref, sopref, prefLen); - targ->pref[prefLen] = '\0'; + /* + * We're caught in a catch-22 here. On the one hand, we want to use + * any transformation implied by the target's sources, but we can't + * examine the sources until we've expanded any variables/wildcards + * they may hold, and we can't do that until we've set up the + * target's local variables and we can't do that until we know what + * the proper suffix for the target is (in case there are two + * suffixes one of which is a suffix of the other) and we can't know + * that until we've found its implied source, which we may not want + * to use if there's an existing source that implies a different + * transformation. + * + * In an attempt to get around this, which may not work all the time, + * but should work most of the time, we look for implied sources + * first, checking transformations to all possible suffixes of the + * target, use what we find to set the target's local variables, + * expand the children, then look for any overriding transformations + * they imply. Should we find one, we discard the one we found before. + */ + bottom = NULL; + targ = NULL; + + if (!(gn->type & OP_PHONY)) { + + FindDepsRegularKnown(name, nameLen, gn, &srcs, &targs); + + /* Handle target of unknown suffix... */ + FindDepsRegularUnknown(gn, name, &srcs, &targs); /* - * Add nodes from which the target can be made + * Using the list of possible sources built up from the target + * suffix(es), try and find an existing file/target that + * matches. */ - SuffAddLevel(srcs, targ); + bottom = FindThem(&srcs, cs); - /* - * Record the target so we can nuke it - */ - (void)Lst_AtEnd(targs, targ); + if (bottom == NULL) { + /* + * No known transformations -- use the first suffix + * found for setting the local variables. + */ + if (targs.first != NULL) + targ = targs.first->datum; + else + targ = NULL; + } else { + /* + * Work up the transformation path to find the suffix + * of the target to which the transformation was made. + */ + for (targ = bottom; + targ->parent != NULL; targ = targ->parent) + continue; + } + } - /* - * Search from this suffix's successor... - */ - ln = Lst_Succ(ln); - } + Var_Set(gn, TARGET, GNode_Path(gn)); + Var_Set(gn, PREFIX, targ != NULL ? targ->prefix : gn->name); + + /* + * Now we've got the important local variables set, expand any sources + * that still contain variables or wildcards in their names. + */ + { + GNodeListNode *ln, *nln; + for (ln = gn->children.first; ln != NULL; ln = nln) { + nln = ln->next; + ExpandChildren(ln, gn); + } + } + + if (targ == NULL) { + DEBUG1(SUFF, "\tNo valid suffix on %s\n", gn->name); + + sfnd_abort: + FindDepsRegularPath(gn, targ); + goto sfnd_return; } /* - * Handle target of unknown suffix... + * If the suffix indicates that the target is a library, mark that in + * the node's type field. */ - if (Lst_IsEmpty(targs) && suffNull != NULL) { - if (DEBUG(SUFF)) { - fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name); - } - - targ = bmake_malloc(sizeof(Src)); - targ->file = bmake_strdup(gn->name); - targ->suff = suffNull; - targ->suff->refCount++; - targ->node = gn; - targ->parent = NULL; - targ->children = 0; - targ->pref = bmake_strdup(sopref); -#ifdef DEBUG_SRC - targ->cp = Lst_Init(FALSE); -#endif - - /* - * Only use the default suffix rules if we don't have commands - * defined for this gnode; traditional make programs used to - * not define suffix rules if the gnode had children but we - * don't do this anymore. - */ - if (Lst_IsEmpty(gn->commands)) - SuffAddLevel(srcs, targ); - else { - if (DEBUG(SUFF)) - fprintf(debug_file, "not "); - } - - if (DEBUG(SUFF)) - fprintf(debug_file, "adding suffix rules\n"); - - (void)Lst_AtEnd(targs, targ); - } + if (targ->suff->flags & SUFF_LIBRARY) + gn->type |= OP_LIB; /* - * Using the list of possible sources built up from the target - * suffix(es), try and find an existing file/target that matches. + * Check for overriding transformation rule implied by sources */ - bottom = SuffFindThem(srcs, slst); + if (!Lst_IsEmpty(&gn->children)) { + src = FindCmds(targ, cs); + + if (src != NULL) { + /* + * Free up all the candidates in the transformation + * path, up to but not including the parent node. + */ + while (bottom != NULL && bottom->parent != NULL) { + CandidateSearcher_AddIfNew(cs, bottom); + bottom = bottom->parent; + } + bottom = src; + } + } if (bottom == NULL) { - /* - * No known transformations -- use the first suffix found - * for setting the local variables. - */ - if (!Lst_IsEmpty(targs)) { - targ = (Src *)Lst_Datum(Lst_First(targs)); - } else { - targ = NULL; - } - } else { - /* - * Work up the transformation path to find the suffix of the - * target to which the transformation was made. - */ - for (targ = bottom; targ->parent != NULL; targ = targ->parent) - continue; - } - } - - Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); - - pref = (targ != NULL) ? targ->pref : gn->name; - Var_Set(PREFIX, pref, gn, 0); - - /* - * Now we've got the important local variables set, expand any sources - * that still contain variables or wildcards in their names. - */ - for (ln = Lst_First(gn->children); ln != NULL; ln = nln) { - nln = Lst_Succ(ln); - SuffExpandChildren(ln, gn); - } - - if (targ == NULL) { - if (DEBUG(SUFF)) { - fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name); + /* No idea from where it can come -- return now. */ + goto sfnd_abort; } -sfnd_abort: /* - * Deal with finding the thing on the default search path. We - * always do that, not only if the node is only a source (not - * on the lhs of a dependency operator or [XXX] it has neither - * children or commands) as the old pmake did. + * We now have a list of candidates headed by 'bottom' and linked via + * their 'parent' pointers. What we do next is create links between + * source and target nodes (which may or may not have been created) + * and set the necessary local variables in each target. + * + * The commands for each target are set from the commands of the + * transformation rule used to get from the src suffix to the targ + * suffix. Note that this causes the commands list of the original + * node, gn, to be replaced with the commands of the final + * transformation rule. */ - if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) { - free(gn->path); - gn->path = Dir_FindFile(gn->name, - (targ == NULL ? dirSearchPath : - targ->suff->searchPath)); - if (gn->path != NULL) { - char *ptr; - Var_Set(TARGET, gn->path, gn, 0); + if (bottom->node == NULL) + bottom->node = Targ_GetNode(bottom->file); - if (targ != NULL) { - /* - * Suffix known for the thing -- trim the suffix off - * the path to form the proper .PREFIX variable. - */ - int savep = strlen(gn->path) - targ->suff->nameLen; - char savec; + for (src = bottom; src->parent != NULL; src = src->parent) { + targ = src->parent; - if (gn->suffix) - gn->suffix->refCount--; - gn->suffix = targ->suff; - gn->suffix->refCount++; + Suffix_Reassign(&src->node->suffix, src->suff); - savec = gn->path[savep]; - gn->path[savep] = '\0'; + if (targ->node == NULL) + targ->node = Targ_GetNode(targ->file); - if ((ptr = strrchr(gn->path, '/')) != NULL) - ptr++; - else - ptr = gn->path; + ApplyTransform(targ->node, src->node, + targ->suff, src->suff); - Var_Set(PREFIX, ptr, gn, 0); - - gn->path[savep] = savec; - } else { - /* - * The .PREFIX gets the full path if the target has - * no known suffix. - */ - if (gn->suffix) - gn->suffix->refCount--; - gn->suffix = NULL; - - if ((ptr = strrchr(gn->path, '/')) != NULL) - ptr++; - else - ptr = gn->path; - - Var_Set(PREFIX, ptr, gn, 0); + if (targ->node != gn) { + /* + * Finish off the dependency-search process for any + * nodes between bottom and gn (no point in questing + * around the filesystem for their implicit source + * when it's already known). Note that the node + * can't have any sources that need expanding, since + * SuffFindThem will stop on an existing node, so all + * we need to do is set the standard variables. + */ + targ->node->type |= OP_DEPS_FOUND; + Var_Set(targ->node, PREFIX, targ->prefix); + Var_Set(targ->node, TARGET, targ->node->name); } - } } - goto sfnd_return; - } + Suffix_Reassign(&gn->suffix, src->suff); - /* - * If the suffix indicates that the target is a library, mark that in - * the node's type field. - */ - if (targ->suff->flags & SUFF_LIBRARY) { - gn->type |= OP_LIB; - } - - /* - * Check for overriding transformation rule implied by sources - */ - if (!Lst_IsEmpty(gn->children)) { - src = SuffFindCmds(targ, slst); - - if (src != NULL) { - /* - * Free up all the Src structures in the transformation path - * up to, but not including, the parent node. - */ - while (bottom && bottom->parent != NULL) { - if (Lst_Member(slst, bottom) == NULL) { - Lst_AtEnd(slst, bottom); - } - bottom = bottom->parent; - } - bottom = src; - } - } - - if (bottom == NULL) { /* - * No idea from where it can come -- return now. + * Nuke the transformation path and the candidates left over in the + * two lists. */ - goto sfnd_abort; - } - - /* - * We now have a list of Src structures headed by 'bottom' and linked via - * their 'parent' pointers. What we do next is create links between - * source and target nodes (which may or may not have been created) - * and set the necessary local variables in each target. The - * commands for each target are set from the commands of the - * transformation rule used to get from the src suffix to the targ - * suffix. Note that this causes the commands list of the original - * node, gn, to be replaced by the commands of the final - * transformation rule. Also, the unmade field of gn is incremented. - * Etc. - */ - if (bottom->node == NULL) { - bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); - } - - for (src = bottom; src->parent != NULL; src = src->parent) { - targ = src->parent; - - if (src->node->suffix) - src->node->suffix->refCount--; - src->node->suffix = src->suff; - src->node->suffix->refCount++; - - if (targ->node == NULL) { - targ->node = Targ_FindNode(targ->file, TARG_CREATE); - } - - SuffApplyTransform(targ->node, src->node, - targ->suff, src->suff); - - if (targ->node != gn) { - /* - * Finish off the dependency-search process for any nodes - * between bottom and gn (no point in questing around the - * filesystem for their implicit source when it's already - * known). Note that the node can't have any sources that - * need expanding, since SuffFindThem will stop on an existing - * node, so all we need to do is set the standard and System V - * variables. - */ - targ->node->type |= OP_DEPS_FOUND; - - Var_Set(PREFIX, targ->pref, targ->node, 0); - - Var_Set(TARGET, targ->node->name, targ->node, 0); - } - } - - if (gn->suffix) - gn->suffix->refCount--; - gn->suffix = src->suff; - gn->suffix->refCount++; - - /* - * Nuke the transformation path and the Src structures left over in the - * two lists. - */ sfnd_return: - if (bottom) - if (Lst_Member(slst, bottom) == NULL) - Lst_AtEnd(slst, bottom); + if (bottom != NULL) + CandidateSearcher_AddIfNew(cs, bottom); - while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) - continue; + while (RemoveCandidate(&srcs) || RemoveCandidate(&targs)) + continue; - Lst_Concat(slst, srcs, LST_CONCLINK); - Lst_Concat(slst, targs, LST_CONCLINK); + CandidateSearcher_MoveAll(cs, &srcs); + CandidateSearcher_MoveAll(cs, &targs); } - -/*- - *----------------------------------------------------------------------- - * Suff_FindDeps -- - * Find implicit sources for the target described by the graph node - * gn - * - * Results: - * Nothing. - * - * Side Effects: - * Nodes are added to the graph below the passed-in node. The nodes - * are marked to have their IMPSRC variable filled in. The - * PREFIX variable is set for the given node and all its - * implied children. - * - * Notes: - * The path found by this target is the shortest path in the - * transformation graph, which may pass through non-existent targets, - * to an existing target. The search continues on all paths from the - * root suffix until a file is found. I.e. if there's a path - * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but - * the .c and .l files don't, the search will branch out in - * all directions from .o and again from all the nodes on the - * next level until the .l,v node is encountered. - * - *----------------------------------------------------------------------- - */ - -void -Suff_FindDeps(GNode *gn) +static void +CandidateSearcher_CleanUp(CandidateSearcher *cs) { - - SuffFindDeps(gn, srclist); - while (SuffRemoveSrc(srclist)) - continue; + while (RemoveCandidate(&cs->list)) + continue; + assert(Lst_IsEmpty(&cs->list)); } /* - * Input: - * gn node we're dealing with + * Find implicit sources for the target. * + * Nodes are added to the graph as children of the passed-in node. The nodes + * are marked to have their IMPSRC variable filled in. The PREFIX variable + * is set for the given node and all its implied children. + * + * The path found by this target is the shortest path in the transformation + * graph, which may pass through nonexistent targets, to an existing target. + * The search continues on all paths from the root suffix until a file is + * found. I.e. if there's a path .o -> .c -> .l -> .l,v from the root and the + * .l,v file exists but the .c and .l files don't, the search will branch out + * in all directions from .o and again from all the nodes on the next level + * until the .l,v node is encountered. */ -static void -SuffFindDeps(GNode *gn, Lst slst) +void +Suff_FindDeps(GNode *gn) { - if (gn->type & OP_DEPS_FOUND) { - /* - * If dependencies already found, no need to do it again... - */ - return; - } else { - gn->type |= OP_DEPS_FOUND; - } - /* - * Make sure we have these set, may get revised below. - */ - Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); - Var_Set(PREFIX, gn->name, gn, 0); + CandidateSearcher cs; - if (DEBUG(SUFF)) { - fprintf(debug_file, "SuffFindDeps (%s)\n", gn->name); - } + CandidateSearcher_Init(&cs); - if (gn->type & OP_ARCHV) { - SuffFindArchiveDeps(gn, slst); - } else if (gn->type & OP_LIB) { - /* - * If the node is a library, it is the arch module's job to find it - * and set the TARGET variable accordingly. We merely provide the - * search path, assuming all libraries end in ".a" (if the suffix - * hasn't been defined, there's nothing we can do for it, so we just - * set the TARGET variable to the node's name in order to give it a - * value). - */ - LstNode ln; - Suff *s; + FindDeps(gn, &cs); - ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP); - if (gn->suffix) - gn->suffix->refCount--; - if (ln != NULL) { - gn->suffix = s = (Suff *)Lst_Datum(ln); - gn->suffix->refCount++; - Arch_FindLib(gn, s->searchPath); - } else { - gn->suffix = NULL; - Var_Set(TARGET, gn->name, gn, 0); - } - /* - * Because a library (-lfoo) target doesn't follow the standard - * filesystem conventions, we don't set the regular variables for - * the thing. .PREFIX is simply made empty... - */ - Var_Set(PREFIX, "", gn, 0); - } else { - SuffFindNormalDeps(gn, slst); - } + CandidateSearcher_CleanUp(&cs); + CandidateSearcher_Done(&cs); } -/*- - *----------------------------------------------------------------------- - * Suff_SetNull -- - * Define which suffix is the null suffix. +static void +FindDeps(GNode *gn, CandidateSearcher *cs) +{ + if (gn->type & OP_DEPS_FOUND) + return; + gn->type |= OP_DEPS_FOUND; + + /* Make sure we have these set, may get revised below. */ + Var_Set(gn, TARGET, GNode_Path(gn)); + Var_Set(gn, PREFIX, gn->name); + + DEBUG1(SUFF, "SuffFindDeps \"%s\"\n", gn->name); + + if (gn->type & OP_ARCHV) + FindDepsArchive(gn, cs); + else if (gn->type & OP_LIB) + FindDepsLib(gn); + else + FindDepsRegular(gn, cs); +} + +/* + * Define which suffix is the null suffix. + * + * Need to handle the changing of the null suffix gracefully so the old + * transformation rules don't just go away. * * Input: * name Name of null suffix - * - * Results: - * None. - * - * Side Effects: - * 'suffNull' is altered. - * - * Notes: - * Need to handle the changing of the null suffix gracefully so the - * old transformation rules don't just go away. - * - *----------------------------------------------------------------------- */ void -Suff_SetNull(char *name) +Suff_SetNull(const char *name) { - Suff *s; - LstNode ln; - - ln = Lst_Find(sufflist, name, SuffSuffHasNameP); - if (ln != NULL) { - s = (Suff *)Lst_Datum(ln); - if (suffNull != NULL) { - suffNull->flags &= ~SUFF_NULL; + Suffix *suff = FindSuffixByName(name); + if (suff == NULL) { + Parse_Error(PARSE_WARNING, + "Desired null suffix %s not defined.", + name); + return; } - s->flags |= SUFF_NULL; - /* - * XXX: Here's where the transformation mangling would take place - */ - suffNull = s; - } else { - Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", - name); - } + + if (nullSuff != NULL) + nullSuff->flags &= ~(unsigned)SUFF_NULL; + suff->flags |= SUFF_NULL; + /* XXX: Here's where the transformation mangling would take place. */ + nullSuff = suff; } -/*- - *----------------------------------------------------------------------- - * Suff_Init -- - * Initialize suffixes module - * - * Results: - * None - * - * Side Effects: - * Many - *----------------------------------------------------------------------- - */ +/* Initialize the suffixes module. */ void Suff_Init(void) { - sufflist = Lst_Init(FALSE); -#ifdef CLEANUP - suffClean = Lst_Init(FALSE); -#endif - srclist = Lst_Init(FALSE); - transforms = Lst_Init(FALSE); - - sNum = 0; - /* - * Create null suffix for single-suffix rules (POSIX). The thing doesn't - * actually go on the suffix list or everyone will think that's its - * suffix. - */ - emptySuff = suffNull = bmake_malloc(sizeof(Suff)); - - suffNull->name = bmake_strdup(""); - suffNull->nameLen = 0; - suffNull->searchPath = Lst_Init(FALSE); - Dir_Concat(suffNull->searchPath, dirSearchPath); - suffNull->children = Lst_Init(FALSE); - suffNull->parents = Lst_Init(FALSE); - suffNull->ref = Lst_Init(FALSE); - suffNull->sNum = sNum++; - suffNull->flags = SUFF_NULL; - suffNull->refCount = 1; - + /* + * Create null suffix for single-suffix rules (POSIX). The thing + * doesn't actually go on the suffix list or everyone will think + * that's its suffix. + */ + Suff_ClearSuffixes(); } -/*- - *---------------------------------------------------------------------- - * Suff_End -- - * Cleanup the this module - * - * Results: - * None - * - * Side Effects: - * The memory is free'd. - *---------------------------------------------------------------------- - */ - +/* Clean up the suffixes module. */ void Suff_End(void) { #ifdef CLEANUP - Lst_Destroy(sufflist, SuffFree); - Lst_Destroy(suffClean, SuffFree); - if (suffNull) - SuffFree(suffNull); - Lst_Destroy(srclist, NULL); - Lst_Destroy(transforms, NULL); + Lst_DoneCall(&sufflist, SuffFree); + Lst_DoneCall(&suffClean, SuffFree); + if (nullSuff != NULL) + SuffFree(nullSuff); + Lst_Done(&transforms); #endif } -/********************* DEBUGGING FUNCTIONS **********************/ - -static int SuffPrintName(void *s, void *dummy) +static void +PrintSuffNames(const char *prefix, SuffixList *suffs) { - fprintf(debug_file, "%s ", ((Suff *)s)->name); - return (dummy ? 0 : 0); -} + SuffixListNode *ln; -static int -SuffPrintSuff(void *sp, void *dummy) -{ - Suff *s = (Suff *)sp; - int flags; - int flag; - - fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount); - - flags = s->flags; - if (flags) { - fputs(" (", debug_file); - while (flags) { - flag = 1 << (ffs(flags) - 1); - flags &= ~flag; - switch (flag) { - case SUFF_NULL: - fprintf(debug_file, "NULL"); - break; - case SUFF_INCLUDE: - fprintf(debug_file, "INCLUDE"); - break; - case SUFF_LIBRARY: - fprintf(debug_file, "LIBRARY"); - break; - } - fputc(flags ? '|' : ')', debug_file); + debug_printf("#\t%s: ", prefix); + for (ln = suffs->first; ln != NULL; ln = ln->next) { + Suffix *suff = ln->datum; + debug_printf("%s ", suff->name); } - } - fputc('\n', debug_file); - fprintf(debug_file, "#\tTo: "); - Lst_ForEach(s->parents, SuffPrintName, NULL); - fputc('\n', debug_file); - fprintf(debug_file, "#\tFrom: "); - Lst_ForEach(s->children, SuffPrintName, NULL); - fputc('\n', debug_file); - fprintf(debug_file, "#\tSearch Path: "); - Dir_PrintPath(s->searchPath); - fputc('\n', debug_file); - return (dummy ? 0 : 0); + debug_printf("\n"); } -static int -SuffPrintTrans(void *tp, void *dummy) +static void +Suffix_Print(Suffix *suff) { - GNode *t = (GNode *)tp; + debug_printf("# \"%s\" (num %d, ref %d)", + suff->name, suff->sNum, suff->refCount); + if (suff->flags != 0) { + char flags_buf[SuffixFlags_ToStringSize]; - fprintf(debug_file, "%-16s: ", t->name); - Targ_PrintType(t->type); - fputc('\n', debug_file); - Lst_ForEach(t->commands, Targ_PrintCmd, NULL); - fputc('\n', debug_file); - return(dummy ? 0 : 0); + debug_printf(" (%s)", + SuffixFlags_ToString(flags_buf, suff->flags)); + } + debug_printf("\n"); + + PrintSuffNames("To", &suff->parents); + PrintSuffNames("From", &suff->children); + + debug_printf("#\tSearch Path: "); + SearchPath_Print(suff->searchPath); + debug_printf("\n"); +} + +static void +PrintTransformation(GNode *t) +{ + debug_printf("%-16s:", t->name); + Targ_PrintType(t->type); + debug_printf("\n"); + Targ_PrintCmds(t); + debug_printf("\n"); } void Suff_PrintAll(void) { - fprintf(debug_file, "#*** Suffixes:\n"); - Lst_ForEach(sufflist, SuffPrintSuff, NULL); + debug_printf("#*** Suffixes:\n"); + { + SuffixListNode *ln; + for (ln = sufflist.first; ln != NULL; ln = ln->next) + Suffix_Print(ln->datum); + } - fprintf(debug_file, "#*** Transformations:\n"); - Lst_ForEach(transforms, SuffPrintTrans, NULL); + debug_printf("#*** Transformations:\n"); + { + GNodeListNode *ln; + for (ln = transforms.first; ln != NULL; ln = ln->next) + PrintTransformation(ln->datum); + } } diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c index 5db5477ca..30cc929b4 100644 --- a/usr.bin/make/targ.c +++ b/usr.bin/make/targ.c @@ -1,4 +1,4 @@ -/* $NetBSD: targ.c,v 1.60 2015/05/25 09:01:06 manu Exp $ */ +/* $NetBSD: targ.c,v 1.166 2021/02/22 20:38:55 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990, 1993 @@ -68,781 +68,550 @@ * SUCH DAMAGE. */ -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: targ.c,v 1.60 2015/05/25 09:01:06 manu Exp $"; -#else -#include -#ifndef lint -#if 0 -static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94"; -#else -__RCSID("$NetBSD: targ.c,v 1.60 2015/05/25 09:01:06 manu Exp $"); -#endif -#endif /* not lint */ -#endif - -/*- - * targ.c -- - * Functions for maintaining the Lst allTargets. Target nodes are - * kept in two structures: a Lst, maintained by the list library, and a - * hash table, maintained by the hash library. +/* + * Maintaining the targets and sources, which are both implemented as GNode. * * Interface: - * Targ_Init Initialization procedure. + * Targ_Init Initialize the module. * - * Targ_End Cleanup the module + * Targ_End Clean up the module. * - * Targ_List Return the list of all targets so far. + * Targ_List Return the list of all targets so far. * - * Targ_NewGN Create a new GNode for the passed target - * (string). The node is *not* placed in the - * hash table, though all its fields are - * initialized. + * GNode_New Create a new GNode for the passed target + * (string). The node is *not* placed in the + * hash table, though all its fields are + * initialized. * - * Targ_FindNode Find the node for a given target, creating - * and storing it if it doesn't exist and the - * flags are right (TARG_CREATE) + * Targ_FindNode Find the node, or return NULL. * - * Targ_FindList Given a list of names, find nodes for all - * of them. If a name doesn't exist and the - * TARG_NOCREATE flag was given, an error message - * is printed. Else, if a name doesn't exist, - * its node is created. + * Targ_GetNode Find the node, or create it. * - * Targ_Ignore Return TRUE if errors should be ignored when - * creating the given target. + * Targ_NewInternalNode + * Create an internal node. * - * Targ_Silent Return TRUE if we should be silent when - * creating the given target. + * Targ_FindList Given a list of names, find nodes for all + * of them, creating them as necessary. * - * Targ_Precious Return TRUE if the target is precious and - * should not be removed if we are interrupted. + * Targ_Precious Return TRUE if the target is precious and + * should not be removed if we are interrupted. * - * Targ_Propagate Propagate information between related - * nodes. Should be called after the - * makefiles are parsed but before any - * action is taken. + * Targ_Propagate Propagate information between related nodes. + * Should be called after the makefiles are parsed + * but before any action is taken. * * Debugging: - * Targ_PrintGraph Print out the entire graphm all variables - * and statistics for the directory cache. Should - * print something for suffixes, too, but... + * Targ_PrintGraph + * Print out the entire graph, all variables and + * statistics for the directory cache. Should print + * something for suffixes, too, but... */ -#include -#include +#include -#include "make.h" -#include "hash.h" -#include "dir.h" +#include "make.h" +#include "dir.h" -static Lst allTargets; /* the list of all targets found so far */ -#ifdef CLEANUP -static Lst allGNs; /* List of all the GNodes */ -#endif -static Hash_Table targets; /* a hash table of same */ +/* "@(#)targ.c 8.2 (Berkeley) 3/19/94" */ +MAKE_RCSID("$NetBSD: targ.c,v 1.166 2021/02/22 20:38:55 rillig Exp $"); -#define HTSIZE 191 /* initial size of hash table */ - -static int TargPrintOnlySrc(void *, void *); -static int TargPrintName(void *, void *); -#ifdef CLEANUP -static void TargFreeGN(void *); -#endif -static int TargPropagateCohort(void *, void *); -static int TargPropagateNode(void *, void *); - -/*- - *----------------------------------------------------------------------- - * Targ_Init -- - * Initialize this module - * - * Results: - * None - * - * Side Effects: - * The allTargets list and the targets hash table are initialized - *----------------------------------------------------------------------- +/* + * All target nodes that appeared on the left-hand side of one of the + * dependency operators ':', '::', '!'. */ +static GNodeList allTargets = LST_INIT; +static HashTable allTargetsByName; + +#ifdef CLEANUP +static GNodeList allNodes = LST_INIT; + +static void GNode_Free(void *); +#endif + void Targ_Init(void) { - allTargets = Lst_Init(FALSE); - Hash_InitTable(&targets, HTSIZE); + HashTable_Init(&allTargetsByName); } -/*- - *----------------------------------------------------------------------- - * Targ_End -- - * Finalize this module - * - * Results: - * None - * - * Side Effects: - * All lists and gnodes are cleared - *----------------------------------------------------------------------- - */ void Targ_End(void) { + Targ_Stats(); #ifdef CLEANUP - Lst_Destroy(allTargets, NULL); - if (allGNs) - Lst_Destroy(allGNs, TargFreeGN); - Hash_DeleteTable(&targets); + Lst_Done(&allTargets); + HashTable_Done(&allTargetsByName); + Lst_DoneCall(&allNodes, GNode_Free); #endif } -/*- - *----------------------------------------------------------------------- - * Targ_List -- - * Return the list of all targets - * - * Results: - * The list of all targets. - * - * Side Effects: - * None - *----------------------------------------------------------------------- +void +Targ_Stats(void) +{ + HashTable_DebugStats(&allTargetsByName, "targets"); +} + +/* + * Return the list of all targets, which are all nodes that appear on the + * left-hand side of a dependency declaration such as "target: source". + * The returned list does not contain pure sources. */ -Lst +GNodeList * Targ_List(void) { - return allTargets; + return &allTargets; } -/*- - *----------------------------------------------------------------------- - * Targ_NewGN -- - * Create and initialize a new graph node +/* + * Create a new graph node, but don't register it anywhere. * - * Input: - * name the name to stick in the new node + * Graph nodes that appear on the left-hand side of a dependency line such + * as "target: source" are called targets. XXX: In some cases (like the + * .ALLTARGETS variable), all nodes are called targets as well, even if they + * never appear on the left-hand side. This is a mistake. * - * Results: - * An initialized graph node with the name field filled with a copy - * of the passed name - * - * Side Effects: - * The gnode is added to the list of all gnodes. - *----------------------------------------------------------------------- + * Typical names for graph nodes are: + * "src.c" (an ordinary file) + * "clean" (a .PHONY target) + * ".END" (a special hook target) + * "-lm" (a library) + * "libc.a(isspace.o)" (an archive member) */ GNode * -Targ_NewGN(const char *name) +GNode_New(const char *name) { - GNode *gn; + GNode *gn; - gn = bmake_malloc(sizeof(GNode)); - gn->name = bmake_strdup(name); - gn->uname = NULL; - gn->path = NULL; - if (name[0] == '-' && name[1] == 'l') { - gn->type = OP_LIB; - } else { - gn->type = 0; - } - gn->unmade = 0; - gn->unmade_cohorts = 0; - gn->cohort_num[0] = 0; - gn->centurion = NULL; - gn->made = UNMADE; - gn->flags = 0; - gn->checked = 0; - gn->mtime = 0; - gn->cmgn = NULL; - gn->iParents = Lst_Init(FALSE); - gn->cohorts = Lst_Init(FALSE); - gn->parents = Lst_Init(FALSE); - gn->children = Lst_Init(FALSE); - gn->order_pred = Lst_Init(FALSE); - gn->order_succ = Lst_Init(FALSE); - Hash_InitTable(&gn->context, 0); - gn->commands = Lst_Init(FALSE); - gn->suffix = NULL; - gn->lineno = 0; - gn->fname = NULL; + gn = bmake_malloc(sizeof *gn); + gn->name = bmake_strdup(name); + gn->uname = NULL; + gn->path = NULL; + gn->type = name[0] == '-' && name[1] == 'l' ? OP_LIB : OP_NONE; + gn->flags = GNF_NONE; + gn->made = UNMADE; + gn->unmade = 0; + gn->mtime = 0; + gn->youngestChild = NULL; + Lst_Init(&gn->implicitParents); + Lst_Init(&gn->parents); + Lst_Init(&gn->children); + Lst_Init(&gn->order_pred); + Lst_Init(&gn->order_succ); + Lst_Init(&gn->cohorts); + gn->cohort_num[0] = '\0'; + gn->unmade_cohorts = 0; + gn->centurion = NULL; + gn->checked_seqno = 0; + HashTable_Init(&gn->vars); + Lst_Init(&gn->commands); + gn->suffix = NULL; + gn->fname = NULL; + gn->lineno = 0; #ifdef CLEANUP - if (allGNs == NULL) - allGNs = Lst_Init(FALSE); - Lst_AtEnd(allGNs, gn); + Lst_Append(&allNodes, gn); #endif - return (gn); + return gn; } #ifdef CLEANUP -/*- - *----------------------------------------------------------------------- - * TargFreeGN -- - * Destroy a GNode - * - * Results: - * None. - * - * Side Effects: - * None. - *----------------------------------------------------------------------- - */ static void -TargFreeGN(void *gnp) +GNode_Free(void *gnp) { - GNode *gn = (GNode *)gnp; + GNode *gn = gnp; - - free(gn->name); - if (gn->uname) + free(gn->name); free(gn->uname); - if (gn->path) free(gn->path); - /* gn->fname points to name allocated when file was opened, don't free */ - Lst_Destroy(gn->iParents, NULL); - Lst_Destroy(gn->cohorts, NULL); - Lst_Destroy(gn->parents, NULL); - Lst_Destroy(gn->children, NULL); - Lst_Destroy(gn->order_succ, NULL); - Lst_Destroy(gn->order_pred, NULL); - Hash_DeleteTable(&gn->context); - Lst_Destroy(gn->commands, NULL); - free(gn); + /* Don't free gn->youngestChild since it is not owned by this node. */ + + /* + * In the following lists, only free the list nodes, but not the + * GNodes in them since these are not owned by this node. + */ + Lst_Done(&gn->implicitParents); + Lst_Done(&gn->parents); + Lst_Done(&gn->children); + Lst_Done(&gn->order_pred); + Lst_Done(&gn->order_succ); + Lst_Done(&gn->cohorts); + + /* + * Do not free the variables themselves, even though they are owned + * by this node. + * + * XXX: For the nodes that represent targets or sources (and not + * SCOPE_GLOBAL), it should be safe to free the variables as well, + * since each node manages the memory for all its variables itself. + * + * XXX: The GNodes that are only used as variable scopes (SCOPE_CMD, + * SCOPE_GLOBAL, SCOPE_INTERNAL) are not freed at all (see Var_End, + * where they are not mentioned). These might be freed at all, if + * their variable values are indeed not used anywhere else (see + * Trace_Init for the only suspicious use). + */ + HashTable_Done(&gn->vars); + + /* + * Do not free the commands themselves, as they may be shared with + * other nodes. + */ + Lst_Done(&gn->commands); + + /* + * gn->suffix is not owned by this node. + * + * XXX: gn->suffix should be unreferenced here. This requires a + * thorough check that the reference counting is done correctly in + * all places, otherwise a suffix might be freed too early. + */ + + free(gn); } #endif +/* Get the existing global node, or return NULL. */ +GNode * +Targ_FindNode(const char *name) +{ + return HashTable_FindValue(&allTargetsByName, name); +} -/*- - *----------------------------------------------------------------------- - * Targ_FindNode -- - * Find a node in the list using the given name for matching +/* Get the existing global node, or create it. */ +GNode * +Targ_GetNode(const char *name) +{ + Boolean isNew; + HashEntry *he = HashTable_CreateEntry(&allTargetsByName, name, &isNew); + if (!isNew) + return HashEntry_Get(he); + + { + GNode *gn = Targ_NewInternalNode(name); + HashEntry_Set(he, gn); + return gn; + } +} + +/* + * Create a node, register it in .ALLTARGETS but don't store it in the + * table of global nodes. This means it cannot be found by name. * - * Input: - * name the name to find - * flags flags governing events when target not - * found - * - * Results: - * The node in the list if it was. If it wasn't, return NULL of - * flags was TARG_NOCREATE or the newly created and initialized node - * if it was TARG_CREATE - * - * Side Effects: - * Sometimes a node is created and added to the list - *----------------------------------------------------------------------- + * This is used for internal nodes, such as cohorts or .WAIT nodes. */ GNode * -Targ_FindNode(const char *name, int flags) +Targ_NewInternalNode(const char *name) { - GNode *gn; /* node in that element */ - Hash_Entry *he = NULL; /* New or used hash entry for node */ - Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ - /* an entry for the node */ - - if (!(flags & (TARG_CREATE | TARG_NOHASH))) { - he = Hash_FindEntry(&targets, name); - if (he == NULL) - return NULL; - return (GNode *)Hash_GetValue(he); - } - - if (!(flags & TARG_NOHASH)) { - he = Hash_CreateEntry(&targets, name, &isNew); - if (!isNew) - return (GNode *)Hash_GetValue(he); - } - - gn = Targ_NewGN(name); - if (!(flags & TARG_NOHASH)) - Hash_SetValue(he, gn); - Var_Append(".ALLTARGETS", name, VAR_GLOBAL); - (void)Lst_AtEnd(allTargets, gn); - if (doing_depend) - gn->flags |= FROM_DEPEND; - return gn; + GNode *gn = GNode_New(name); + Global_Append(".ALLTARGETS", name); + Lst_Append(&allTargets, gn); + DEBUG1(TARG, "Adding \"%s\" to all targets.\n", gn->name); + if (doing_depend) + gn->flags |= FROM_DEPEND; + return gn; } -/*- - *----------------------------------------------------------------------- - * Targ_FindList -- - * Make a complete list of GNodes from the given list of names - * - * Input: - * name list of names to find - * flags flags used if no node is found for a given name - * - * Results: - * A complete list of graph nodes corresponding to all instances of all - * the names in names. - * - * Side Effects: - * If flags is TARG_CREATE, nodes will be created for all names in - * names which do not yet have graph nodes. If flags is TARG_NOCREATE, - * an error message will be printed for each name which can't be found. - * ----------------------------------------------------------------------- +/* + * Return the .END node, which contains the commands to be run when + * everything else has been made. */ -Lst -Targ_FindList(Lst names, int flags) +GNode * +Targ_GetEndNode(void) { - Lst nodes; /* result list */ - LstNode ln; /* name list element */ - GNode *gn; /* node in tLn */ - char *name; + /* + * Save the node locally to avoid having to search for it all + * the time. + */ + static GNode *endNode = NULL; - nodes = Lst_Init(FALSE); - - if (Lst_Open(names) == FAILURE) { - return (nodes); - } - while ((ln = Lst_Next(names)) != NULL) { - name = (char *)Lst_Datum(ln); - gn = Targ_FindNode(name, flags); - if (gn != NULL) { - /* - * Note: Lst_AtEnd must come before the Lst_Concat so the nodes - * are added to the list in the order in which they were - * encountered in the makefile. - */ - (void)Lst_AtEnd(nodes, gn); - } else if (flags == TARG_NOCREATE) { - Error("\"%s\" -- target unknown.", name); + if (endNode == NULL) { + endNode = Targ_GetNode(".END"); + endNode->type = OP_SPECIAL; } - } - Lst_Close(names); - return (nodes); + return endNode; } -/*- - *----------------------------------------------------------------------- - * Targ_Ignore -- - * Return true if should ignore errors when creating gn - * - * Input: - * gn node to check for - * - * Results: - * TRUE if should ignore errors - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ -Boolean -Targ_Ignore(GNode *gn) +/* Add the named nodes to the list, creating them as necessary. */ +void +Targ_FindList(GNodeList *gns, StringList *names) { - if (ignoreErrors || gn->type & OP_IGNORE) { - return (TRUE); - } else { - return (FALSE); - } + StringListNode *ln; + + for (ln = names->first; ln != NULL; ln = ln->next) { + const char *name = ln->datum; + GNode *gn = Targ_GetNode(name); + Lst_Append(gns, gn); + } } -/*- - *----------------------------------------------------------------------- - * Targ_Silent -- - * Return true if be silent when creating gn - * - * Input: - * gn node to check for - * - * Results: - * TRUE if should be silent - * - * Side Effects: - * None - *----------------------------------------------------------------------- - */ +/* See if the given target is precious. */ Boolean -Targ_Silent(GNode *gn) +Targ_Precious(const GNode *gn) { - if (beSilent || gn->type & OP_SILENT) { - return (TRUE); - } else { - return (FALSE); - } + /* XXX: Why are '::' targets precious? */ + return allPrecious || gn->type & (OP_PRECIOUS | OP_DOUBLEDEP); } -/*- - *----------------------------------------------------------------------- - * Targ_Precious -- - * See if the given target is precious - * - * Input: - * gn the node to check - * - * Results: - * TRUE if it is precious. FALSE otherwise - * - * Side Effects: - * None - *----------------------------------------------------------------------- +/* + * The main target to be made; only for debugging output. + * See mainNode in parse.c for the definitive source. */ -Boolean -Targ_Precious(GNode *gn) -{ - if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) { - return (TRUE); - } else { - return (FALSE); - } -} +static GNode *mainTarg; -/******************* DEBUG INFO PRINTING ****************/ - -static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ -/*- - *----------------------------------------------------------------------- - * Targ_SetMain -- - * Set our idea of the main target we'll be creating. Used for - * debugging output. - * - * Input: - * gn The main target we'll create - * - * Results: - * None. - * - * Side Effects: - * "mainTarg" is set to the main target's node. - *----------------------------------------------------------------------- - */ +/* Remember the main target to make; only used for debugging. */ void Targ_SetMain(GNode *gn) { - mainTarg = gn; + mainTarg = gn; } -static int -TargPrintName(void *gnp, void *pflags MAKE_ATTR_UNUSED) +static void +PrintNodeNames(GNodeList *gnodes) { - GNode *gn = (GNode *)gnp; + GNodeListNode *ln; - fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num); - - return 0; + for (ln = gnodes->first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + debug_printf(" %s%s", gn->name, gn->cohort_num); + } } - -int -Targ_PrintCmd(void *cmd, void *dummy) +static void +PrintNodeNamesLine(const char *label, GNodeList *gnodes) { - fprintf(debug_file, "\t%s\n", (char *)cmd); - return (dummy ? 0 : 0); + if (Lst_IsEmpty(gnodes)) + return; + debug_printf("# %s:", label); + PrintNodeNames(gnodes); + debug_printf("\n"); } -/*- - *----------------------------------------------------------------------- - * Targ_FmtTime -- - * Format a modification time in some reasonable way and return it. - * - * Results: - * The time reformatted. - * - * Side Effects: - * The time is placed in a static area, so it is overwritten - * with each call. - * - *----------------------------------------------------------------------- +void +Targ_PrintCmds(GNode *gn) +{ + StringListNode *ln; + + for (ln = gn->commands.first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + debug_printf("\t%s\n", cmd); + } +} + +/* + * Format a modification time in some reasonable way and return it. + * The formatted time is placed in a static area, so it is overwritten + * with each call. */ -char * +const char * Targ_FmtTime(time_t tm) { - struct tm *parts; - static char buf[128]; + static char buf[128]; - parts = localtime(&tm); - (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); - return(buf); + struct tm *parts = localtime(&tm); + (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); + return buf; } -/*- - *----------------------------------------------------------------------- - * Targ_PrintType -- - * Print out a type field giving only those attributes the user can - * set. - * - * Results: - * - * Side Effects: - * - *----------------------------------------------------------------------- - */ +/* Print out a type field giving only those attributes the user can set. */ void Targ_PrintType(int type) { - int tbit; + int tbit; -#define PRINTBIT(attr) case CONCAT(OP_,attr): fprintf(debug_file, "." #attr " "); break -#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #attr " "); break + type &= ~OP_OPMASK; - type &= ~OP_OPMASK; + while (type != 0) { + tbit = 1 << (ffs(type) - 1); + type &= ~tbit; - while (type) { - tbit = 1 << (ffs(type) - 1); - type &= ~tbit; - - switch(tbit) { - PRINTBIT(OPTIONAL); - PRINTBIT(USE); - PRINTBIT(EXEC); - PRINTBIT(IGNORE); - PRINTBIT(PRECIOUS); - PRINTBIT(SILENT); - PRINTBIT(MAKE); - PRINTBIT(JOIN); - PRINTBIT(INVISIBLE); - PRINTBIT(NOTMAIN); - PRINTDBIT(LIB); - /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ - case OP_MEMBER: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break; - PRINTDBIT(ARCHV); - PRINTDBIT(MADE); - PRINTDBIT(PHONY); + switch (tbit) { +#define PRINTBIT(bit, attr) case bit: debug_printf(" " attr); break +#define PRINTDBIT(bit, attr) case bit: DEBUG0(TARG, " " attr); break + PRINTBIT(OP_OPTIONAL, ".OPTIONAL"); + PRINTBIT(OP_USE, ".USE"); + PRINTBIT(OP_EXEC, ".EXEC"); + PRINTBIT(OP_IGNORE, ".IGNORE"); + PRINTBIT(OP_PRECIOUS, ".PRECIOUS"); + PRINTBIT(OP_SILENT, ".SILENT"); + PRINTBIT(OP_MAKE, ".MAKE"); + PRINTBIT(OP_JOIN, ".JOIN"); + PRINTBIT(OP_INVISIBLE, ".INVISIBLE"); + PRINTBIT(OP_NOTMAIN, ".NOTMAIN"); + PRINTDBIT(OP_LIB, ".LIB"); + PRINTDBIT(OP_MEMBER, ".MEMBER"); + PRINTDBIT(OP_ARCHV, ".ARCHV"); + PRINTDBIT(OP_MADE, ".MADE"); + PRINTDBIT(OP_PHONY, ".PHONY"); +#undef PRINTBIT +#undef PRINTDBIT + } + } +} + +const char * +GNodeMade_Name(GNodeMade made) +{ + switch (made) { + case UNMADE: return "unmade"; + case DEFERRED: return "deferred"; + case REQUESTED: return "requested"; + case BEINGMADE: return "being made"; + case MADE: return "made"; + case UPTODATE: return "up-to-date"; + case ERROR: return "error when made"; + case ABORTED: return "aborted"; + default: return "unknown enum_made value"; } - } } static const char * -made_name(enum enum_made made) +GNode_OpName(const GNode *gn) { - switch (made) { - case UNMADE: return "unmade"; - case DEFERRED: return "deferred"; - case REQUESTED: return "requested"; - case BEINGMADE: return "being made"; - case MADE: return "made"; - case UPTODATE: return "up-to-date"; - case ERROR: return "error when made"; - case ABORTED: return "aborted"; - default: return "unknown enum_made value"; - } -} - -/*- - *----------------------------------------------------------------------- - * TargPrintNode -- - * print the contents of a node - *----------------------------------------------------------------------- - */ -int -Targ_PrintNode(void *gnp, void *passp) -{ - GNode *gn = (GNode *)gnp; - int pass = passp ? *(int *)passp : 0; - - fprintf(debug_file, "# %s%s, flags %x, type %x, made %d\n", - gn->name, gn->cohort_num, gn->flags, gn->type, gn->made); - if (gn->flags == 0) - return 0; - - if (!OP_NOP(gn->type)) { - fprintf(debug_file, "#\n"); - if (gn == mainTarg) { - fprintf(debug_file, "# *** MAIN TARGET ***\n"); - } - if (pass >= 2) { - if (gn->unmade) { - fprintf(debug_file, "# %d unmade children\n", gn->unmade); - } else { - fprintf(debug_file, "# No unmade children\n"); - } - if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { - if (gn->mtime != 0) { - fprintf(debug_file, "# last modified %s: %s\n", - Targ_FmtTime(gn->mtime), - made_name(gn->made)); - } else if (gn->made != UNMADE) { - fprintf(debug_file, "# non-existent (maybe): %s\n", - made_name(gn->made)); - } else { - fprintf(debug_file, "# unmade\n"); - } - } - if (!Lst_IsEmpty (gn->iParents)) { - fprintf(debug_file, "# implicit parents: "); - Lst_ForEach(gn->iParents, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } - } else { - if (gn->unmade) - fprintf(debug_file, "# %d unmade children\n", gn->unmade); - } - if (!Lst_IsEmpty (gn->parents)) { - fprintf(debug_file, "# parents: "); - Lst_ForEach(gn->parents, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } - if (!Lst_IsEmpty (gn->order_pred)) { - fprintf(debug_file, "# order_pred: "); - Lst_ForEach(gn->order_pred, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } - if (!Lst_IsEmpty (gn->order_succ)) { - fprintf(debug_file, "# order_succ: "); - Lst_ForEach(gn->order_succ, TargPrintName, NULL); - fprintf(debug_file, "\n"); - } - - fprintf(debug_file, "%-16s", gn->name); switch (gn->type & OP_OPMASK) { - case OP_DEPENDS: - fprintf(debug_file, ": "); break; - case OP_FORCE: - fprintf(debug_file, "! "); break; - case OP_DOUBLEDEP: - fprintf(debug_file, ":: "); break; + case OP_DEPENDS: + return ":"; + case OP_FORCE: + return "!"; + case OP_DOUBLEDEP: + return "::"; } - Targ_PrintType(gn->type); - Lst_ForEach(gn->children, TargPrintName, NULL); - fprintf(debug_file, "\n"); - Lst_ForEach(gn->commands, Targ_PrintCmd, NULL); - fprintf(debug_file, "\n\n"); - if (gn->type & OP_DOUBLEDEP) { - Lst_ForEach(gn->cohorts, Targ_PrintNode, &pass); - } - } - return (0); + return ""; } -/*- - *----------------------------------------------------------------------- - * TargPrintOnlySrc -- - * Print only those targets that are just a source. - * - * Results: - * 0. - * - * Side Effects: - * The name of each file is printed preceded by #\t - * - *----------------------------------------------------------------------- - */ -static int -TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED) +/* Print the contents of a node. */ +void +Targ_PrintNode(GNode *gn, int pass) { - GNode *gn = (GNode *)gnp; - if (!OP_NOP(gn->type)) - return 0; + debug_printf("# %s%s", gn->name, gn->cohort_num); + GNode_FprintDetails(opts.debug_file, ", ", gn, "\n"); + if (gn->flags == 0) + return; - fprintf(debug_file, "#\t%s [%s] ", - gn->name, gn->path ? gn->path : gn->name); - Targ_PrintType(gn->type); - fprintf(debug_file, "\n"); + if (!GNode_IsTarget(gn)) + return; - return 0; + debug_printf("#\n"); + if (gn == mainTarg) + debug_printf("# *** MAIN TARGET ***\n"); + + if (pass >= 2) { + if (gn->unmade > 0) + debug_printf("# %d unmade children\n", gn->unmade); + else + debug_printf("# No unmade children\n"); + if (!(gn->type & (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC))) { + if (gn->mtime != 0) { + debug_printf("# last modified %s: %s\n", + Targ_FmtTime(gn->mtime), + GNodeMade_Name(gn->made)); + } else if (gn->made != UNMADE) { + debug_printf("# nonexistent (maybe): %s\n", + GNodeMade_Name(gn->made)); + } else + debug_printf("# unmade\n"); + } + PrintNodeNamesLine("implicit parents", &gn->implicitParents); + } else { + if (gn->unmade != 0) + debug_printf("# %d unmade children\n", gn->unmade); + } + + PrintNodeNamesLine("parents", &gn->parents); + PrintNodeNamesLine("order_pred", &gn->order_pred); + PrintNodeNamesLine("order_succ", &gn->order_succ); + + debug_printf("%-16s%s", gn->name, GNode_OpName(gn)); + Targ_PrintType(gn->type); + PrintNodeNames(&gn->children); + debug_printf("\n"); + Targ_PrintCmds(gn); + debug_printf("\n\n"); + if (gn->type & OP_DOUBLEDEP) + Targ_PrintNodes(&gn->cohorts, pass); } -/*- - *----------------------------------------------------------------------- - * Targ_PrintGraph -- - * print the entire graph. heh heh - * +void +Targ_PrintNodes(GNodeList *gnodes, int pass) +{ + GNodeListNode *ln; + + for (ln = gnodes->first; ln != NULL; ln = ln->next) + Targ_PrintNode(ln->datum, pass); +} + +/* Print only those targets that are just a source. */ +static void +PrintOnlySources(void) +{ + GNodeListNode *ln; + + for (ln = allTargets.first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + if (GNode_IsTarget(gn)) + continue; + + debug_printf("#\t%s [%s]", gn->name, GNode_Path(gn)); + Targ_PrintType(gn->type); + debug_printf("\n"); + } +} + +/* * Input: - * pass Which pass this is. 1 => no processing - * 2 => processing done - * - * Results: - * none - * - * Side Effects: - * lots o' output - *----------------------------------------------------------------------- + * pass 1 => before processing + * 2 => after processing + * 3 => after processing, an error occurred */ void Targ_PrintGraph(int pass) { - fprintf(debug_file, "#*** Input graph:\n"); - Lst_ForEach(allTargets, Targ_PrintNode, &pass); - fprintf(debug_file, "\n\n"); - fprintf(debug_file, "#\n# Files that are only sources:\n"); - Lst_ForEach(allTargets, TargPrintOnlySrc, NULL); - fprintf(debug_file, "#*** Global Variables:\n"); - Var_Dump(VAR_GLOBAL); - fprintf(debug_file, "#*** Command-line Variables:\n"); - Var_Dump(VAR_CMD); - fprintf(debug_file, "\n"); - Dir_PrintDirectories(); - fprintf(debug_file, "\n"); - Suff_PrintAll(); + debug_printf("#*** Input graph:\n"); + Targ_PrintNodes(&allTargets, pass); + debug_printf("\n"); + debug_printf("\n"); + + debug_printf("#\n"); + debug_printf("# Files that are only sources:\n"); + PrintOnlySources(); + + debug_printf("#*** Global Variables:\n"); + Var_Dump(SCOPE_GLOBAL); + + debug_printf("#*** Command-line Variables:\n"); + Var_Dump(SCOPE_CMDLINE); + + debug_printf("\n"); + Dir_PrintDirectories(); + debug_printf("\n"); + + Suff_PrintAll(); } -/*- - *----------------------------------------------------------------------- - * TargPropagateNode -- - * Propagate information from a single node to related nodes if - * appropriate. +/* + * Propagate some type information to cohort nodes (those from the '::' + * dependency operator). * - * Input: - * gnp The node that we are processing. - * - * Results: - * Always returns 0, for the benefit of Lst_ForEach(). - * - * Side Effects: - * Information is propagated from this node to cohort or child - * nodes. - * - * If the node was defined with "::", then TargPropagateCohort() - * will be called for each cohort node. - * - * If the node has recursive predecessors, then - * TargPropagateRecpred() will be called for each recursive - * predecessor. - *----------------------------------------------------------------------- - */ -static int -TargPropagateNode(void *gnp, void *junk MAKE_ATTR_UNUSED) -{ - GNode *gn = (GNode *)gnp; - - if (gn->type & OP_DOUBLEDEP) - Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp); - return (0); -} - -/*- - *----------------------------------------------------------------------- - * TargPropagateCohort -- - * Propagate some bits in the type mask from a node to - * a related cohort node. - * - * Input: - * cnp The node that we are processing. - * gnp Another node that has cnp as a cohort. - * - * Results: - * Always returns 0, for the benefit of Lst_ForEach(). - * - * Side Effects: - * cnp's type bitmask is modified to incorporate some of the - * bits from gnp's type bitmask. (XXX need a better explanation.) - *----------------------------------------------------------------------- - */ -static int -TargPropagateCohort(void *cgnp, void *pgnp) -{ - GNode *cgn = (GNode *)cgnp; - GNode *pgn = (GNode *)pgnp; - - cgn->type |= pgn->type & ~OP_OPMASK; - return (0); -} - -/*- - *----------------------------------------------------------------------- - * Targ_Propagate -- - * Propagate information between related nodes. Should be called - * after the makefiles are parsed but before any action is taken. - * - * Results: - * none - * - * Side Effects: - * Information is propagated between related nodes throughout the - * graph. - *----------------------------------------------------------------------- + * Should be called after the makefiles are parsed but before any action is + * taken. */ void Targ_Propagate(void) { - Lst_ForEach(allTargets, TargPropagateNode, NULL); + GNodeListNode *ln, *cln; + + for (ln = allTargets.first; ln != NULL; ln = ln->next) { + GNode *gn = ln->datum; + GNodeType type = gn->type; + + if (!(type & OP_DOUBLEDEP)) + continue; + + for (cln = gn->cohorts.first; cln != NULL; cln = cln->next) { + GNode *cohort = cln->datum; + + cohort->type |= type & ~OP_OPMASK; + } + } } diff --git a/usr.bin/make/test-variants.sh b/usr.bin/make/test-variants.sh new file mode 100644 index 000000000..01a7759b0 --- /dev/null +++ b/usr.bin/make/test-variants.sh @@ -0,0 +1,196 @@ +#! /bin/sh +# $NetBSD: test-variants.sh,v 1.9 2021/02/15 07:42:35 rillig Exp $ +# +# Build several variants of make and run the tests on them. +# +# The output of this test suite must be inspected manually to see the +# interesting details. The main purpose is to list the available build +# options. + +set -eu + +failed="no" + +fail() { + echo "failed" + failed="yes" +} + +testcase() { + echo "===> Running $*" + + env -i PATH="$PATH" USETOOLS="no" "$@" \ + sh -ce "make -s cleandir" \ + && env -i PATH="$PATH" USETOOLS="no" "$@" \ + sh -ce "make -ks all" \ + && size *.o make \ + && env -i PATH="$PATH" USETOOLS="no" MALLOC_OPTIONS="JA" \ + _MKMSG_TEST=":" "$@" \ + sh -ce "make -s test" \ + || fail +} + + +testcase # just the plain default options + +# See whether the Boolean type is only used as a single-bit type. +# By default it is aliased to int, and int is already used for any other +# purpose. +# +testcase USER_CPPFLAGS="-DUSE_DOUBLE_BOOLEAN" + +# Ensure that variables of type Boolean are not assigned integers. +# The only valid values are TRUE and FALSE. +# +testcase USER_CPPFLAGS="-DUSE_UCHAR_BOOLEAN" + +# Ensure that variables of type Boolean are not used as array index. +# +testcase USER_CPPFLAGS="-DUSE_CHAR_BOOLEAN" + +# Try a different compiler, with slightly different warnings and error +# messages. One feature that is missing from GCC is a little stricter +# checks for enums. +# +testcase HAVE_LLVM="yes" + +testcase USE_GCC8="yes" + +testcase USE_GCC9="yes" + +testcase USE_GCC10="yes" GCC10BASE="$HOME/pkg/gcc10" + +# for selecting emalloc +testcase TOOLDIR="" + +testcase USE_FILEMON="dev" + +testcase USE_FILEMON="ktrace" + +testcase USE_META="no" + +testcase USER_CPPFLAGS="-DCLEANUP" + +testcase USER_CPPFLAGS="-DDEBUG_REFCNT" + +testcase USER_CPPFLAGS="-DDEBUG_HASH_LOOKUP" + +testcase USER_CPPFLAGS="-DDEBUG_META_MODE" + +testcase USER_CPPFLAGS="-DDEBUG_REALPATH_CACHE" + +# Produces lots of debugging output. +# +#testcase USER_CPPFLAGS="-DDEBUG_SRC" + +#testcase USER_CPPFLAGS="-UGMAKEEXPORT" + +# NetBSD 8.0 x86_64 +# In file included from arch.c:135:0: +# /usr/include/sys/param.h:357:0: error: "MAXPATHLEN" redefined [-Werror] +# #define MAXPATHLEN PATH_MAX +#testcase USER_CPPFLAGS="-DMAXPATHLEN=20" + +# In this variant, the unit tests that use the :C modifier obviously fail. +# +testcase USER_CPPFLAGS="-DNO_REGEX" + +# NetBSD 8.0 x86_64 says: +# In file included from /usr/include/sys/param.h:115:0, +# from arch.c:135: +# /usr/include/sys/syslimits.h:60:0: error: "PATH_MAX" redefined [-Werror] +# #define PATH_MAX 1024 /* max bytes in pathname */ +#testcase USER_CPPFLAGS="-DPATH_MAX=20" + +# config.h:124:0: error: "POSIX" redefined [-Werror] +#testcase USER_CPPFLAGS="-DPOSIX" + +# config.h:115:0: error: "RECHECK" redefined [-Werror] +#testcase USER_CPPFLAGS="-DRECHECK" + +# This higher optimization level may trigger additional "may be used +# uninitialized" errors. Could be combined with other compilers as well. +# +testcase USER_CFLAGS="-O3" + +testcase USER_CFLAGS="-O0 -ggdb" + +# The make source code is _intended_ to be compatible with C90. +# It uses some C99 features though. +# To really port it to C90, it would have to be in a C90 environment +# in which the system headers don't aggressively depend on C99. +# +# NetBSD 8.0 x86_64: +# /usr/include/sys/cdefs.h:618:22: error: ISO C90 does not support 'long long' [-Werror=long-long] +# /usr/include/sys/endian.h:205:1: error: use of C99 long long integer constant [-Werror=long-long] +# /usr/include/pthread_types.h:128:3: error: ISO C90 doesn't support unnamed structs/unions [-Werror=pedantic] +# var.c:3027:2: error: initializer element is not computable at load time [-Werror=pedantic] +# var.c:2532:5: error: ISO C does not support '__PRETTY_FUNCTION__' predefined identifier [-Werror=pedantic] +# var.c:1574:15: error: ISO C90 does not support the 'z' gnu_printf length modifier [-Werror=format=] +# var.c:137:33: error: anonymous variadic macros were introduced in C99 [-Werror=variadic-macros] +# parse.c:2473:22: error: format '%p' expects argument of type 'void *', but argument 7 has type 'char * (*)(void *, size_t *) {aka char * (*)(void *, long unsigned int *)}' [-Werror=format=] +# +#testcase USER_CFLAGS="-std=c90 -ansi -pedantic" USER_CPPFLAGS="-Dinline=" + +# Ensure that every inline function is declared as MAKE_ATTR_UNUSED. +testcase USER_CPPFLAGS="-Dinline=" + +testcase USER_CFLAGS="-std=c90" USER_CPPFLAGS="-Dinline=" + +#testcase USER_CFLAGS="-std=c90 -pedantic" USER_CPPFLAGS="-Dinline=" + +testcase USER_CFLAGS="-ansi" USER_CPPFLAGS="-Dinline=" + +# config.h does not allow overriding these features +#testcase USER_CPPFLAGS="-UUSE_IOVEC" + +# Ensure that there are only side-effect-free conditions in the assert +# macro, or at least none that affect the outcome of the tests. +# +testcase USER_CPPFLAGS="-DNDEBUG" + +# Only in native mode, make dares to use a shortcut in Compat_RunCommand +# that circumvents the shell and instead calls execvp directly. +# Another effect is that the shell is run with -q, which prevents the +# -x and -v flags from echoing the commands from profile files. +testcase USER_CPPFLAGS="-UMAKE_NATIVE -DHAVE_STRERROR -DHAVE_SETENV -DHAVE_VSNPRINTF" + +# Running the code coverage using gcov takes a long time. Most of this +# time is spent in gcov_read_unsigned because gcov_open sets the .gcda +# file to unbuffered, which means that every single byte needs its own +# system call to be read. +# +# Combining USE_COVERAGE with USE_GCC10 or HAVE_LLVM does not work since +# these fail to link with the coverage library. +# +# Turning the optimization off is required because of: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96622 +# +#testcase USE_COVERAGE="yes" USER_CFLAGS="-O0 -ggdb" + +testcase USE_FORT="yes" + +# Ensure that the tests can be specified either as relative filenames or +# as absolute filenames. +testcase USE_ABSOLUTE_TESTNAMES="yes" + +# This test is the result of reading through the GCC "Warning Options" +# documentation, noting down everything that sounded interesting. +# +testcase USE_GCC10=yes GCC10BASE=$HOME/pkg/gcc10 USER_CFLAGS="\ +-Wmisleading-indentation -Wmissing-attributes -Wmissing-braces \ +-Wextra -Wnonnull -Wnonnull-compare -Wnull-dereference -Wimplicit \ +-Wimplicit-fallthrough=4 -Wignored-qualifiers -Wunused \ +-Wunused-but-set-variable -Wunused-parameter -Wduplicated-branches \ +-Wduplicated-cond -Wunused-macros -Wcast-function-type -Wconversion \ +-Wenum-compare -Wmissing-field-initializers -Wredundant-decls \ +-Wno-error=conversion -Wno-error=sign-conversion -Wno-error=unused-macros \ +-Wno-error=unused-parameter -Wno-error=duplicated-branches" + +for shell in /usr/pkg/bin/bash /usr/pkg/bin/dash; do + if [ -x "$shell" ]; then + testcase USER_CPPFLAGS=-DDEFSHELL_CUSTOM="\\\"$shell\\\"" + fi +done + +test "$failed" = "no" diff --git a/usr.bin/make/trace.c b/usr.bin/make/trace.c index 267177ff5..840c7995e 100644 --- a/usr.bin/make/trace.c +++ b/usr.bin/make/trace.c @@ -1,6 +1,6 @@ -/* $NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $ */ +/* $NetBSD: trace.c,v 1.28 2021/02/05 05:15:12 rillig Exp $ */ -/*- +/* * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * @@ -29,17 +29,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ - -#ifndef MAKE_NATIVE -static char rcsid[] = "$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $"; -#else -#include -#ifndef lint -__RCSID("$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $"); -#endif /* not lint */ -#endif - -/*- +/* * trace.c -- * handle logging of trace events generated by various parts of make. * @@ -54,16 +44,15 @@ __RCSID("$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $"); #include -#include -#include - #include "make.h" #include "job.h" #include "trace.h" +MAKE_RCSID("$NetBSD: trace.c,v 1.28 2021/02/05 05:15:12 rillig Exp $"); + static FILE *trfile; static pid_t trpid; -char *trwd; +const char *trwd; static const char *evname[] = { "BEG", @@ -77,10 +66,13 @@ static const char *evname[] = { void Trace_Init(const char *pathname) { - char *p1; if (pathname != NULL) { + FStr curDir; trpid = getpid(); - trwd = Var_Value(".CURDIR", VAR_GLOBAL, &p1); + /* XXX: This variable may get overwritten later, which + * would make trwd point to undefined behavior. */ + curDir = Var_Value(SCOPE_GLOBAL, ".CURDIR"); + trwd = curDir.str; trfile = fopen(pathname, "a"); } @@ -90,7 +82,7 @@ void Trace_Log(TrEvent event, Job *job) { struct timeval rightnow; - + if (trfile == NULL) return; @@ -101,8 +93,11 @@ Trace_Log(TrEvent event, Job *job) jobTokensRunning, evname[event], trpid, trwd); if (job != NULL) { - fprintf(trfile, " %s %d %x %x", job->node->name, - job->pid, job->flags, job->node->type); + char flags[4]; + + Job_FlagsToString(job, flags, sizeof flags); + fprintf(trfile, " %s %d %s %x", job->node->name, + job->pid, flags, job->node->type); } fputc('\n', trfile); fflush(trfile); diff --git a/usr.bin/make/trace.h b/usr.bin/make/trace.h index dc0fc6cc4..063fb26ab 100644 --- a/usr.bin/make/trace.h +++ b/usr.bin/make/trace.h @@ -1,6 +1,6 @@ -/* $NetBSD: trace.h,v 1.3 2008/04/28 20:24:14 martin Exp $ */ +/* $NetBSD: trace.h,v 1.6 2021/01/19 20:51:46 rillig Exp $ */ -/*- +/* * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * @@ -29,12 +29,15 @@ * POSSIBILITY OF SUCH DAMAGE. */ -/*- +/* * trace.h -- * Definitions pertaining to the tracing of jobs in parallel mode. */ -typedef enum { +#ifndef MAKE_TRACE_H +#define MAKE_TRACE_H + +typedef enum TrEvent { MAKESTART, MAKEEND, MAKEERROR, @@ -47,3 +50,4 @@ void Trace_Init(const char *); void Trace_Log(TrEvent, Job *); void Trace_End(void); +#endif diff --git a/usr.bin/make/unit-tests/.cvsignore b/usr.bin/make/unit-tests/.cvsignore new file mode 100644 index 000000000..4927bd25b --- /dev/null +++ b/usr.bin/make/unit-tests/.cvsignore @@ -0,0 +1,3 @@ +*.out +*.rawout +*.status diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile index 10503b93d..3d0437427 100644 --- a/usr.bin/make/unit-tests/Makefile +++ b/usr.bin/make/unit-tests/Makefile @@ -1,120 +1,696 @@ -# $NetBSD: Makefile,v 1.52 2015/05/05 21:51:09 sjg Exp $ +# $NetBSD: Makefile,v 1.273 2021/03/14 11:49:37 rillig Exp $ # # Unit tests for make(1) -# The main targets are: -# -# all: run all the tests -# test: run 'all', and compare to expected results -# accept: move generated output to expected results # -# Adding a test case. +# The main targets are: +# +# all: +# run all the tests +# test: +# run 'all', and compare to expected results +# accept: +# move generated output to expected results +# +# Settable variables +# +# TEST_MAKE +# The make program to be tested. +# +# +# Adding a test case +# # Each feature should get its own set of tests in its own suitably # named makefile (*.mk), with its own set of expected results (*.exp), -# and it should be added to the TESTNAMES list. -# +# and it should be added to the TESTS list. +# +# Any added files must also be added to src/distrib/sets/lists/tests/mi. +# To do that, just run "make sync-mi" in this directory. +# +# A few *.mk files are helper files for other tests (such as include-sub.mk) +# and are thus not added to TESTS. Such files must be ignored in +# src/tests/usr.bin/make/t_make.sh. +# .MAIN: all -UNIT_TESTS:= ${.PARSEDIR} -.PATH: ${UNIT_TESTS} +# we use these below but we might be an older make +.MAKE.OS?= ${uname -s:L:sh} +.MAKE.UID?= ${id -u:L:sh} # Each test is in a sub-makefile. # Keep the list sorted. -TESTNAMES= \ - comment \ - cond1 \ - cond2 \ - error \ - export \ - export-all \ - export-env \ - doterror \ - dotwait \ - forloop \ - forsubst \ - hash \ - misc \ - moderrs \ - modmatch \ - modmisc \ - modorder \ - modts \ - modword \ - order \ - posix \ - qequals \ - sunshcmd \ - sysv \ - ternary \ - unexport \ - unexport-env \ - varcmd \ - varmisc \ - varshell +# Any test that is commented out must be ignored in +# src/tests/usr.bin/make/t_make.sh as well. +TESTS+= archive +TESTS+= archive-suffix +TESTS+= cmd-errors +TESTS+= cmd-errors-jobs +TESTS+= cmd-errors-lint +TESTS+= cmd-interrupt +TESTS+= cmdline +TESTS+= cmdline-redirect-stdin +TESTS+= cmdline-undefined +TESTS+= comment +TESTS+= compat-error +TESTS+= cond-cmp-numeric +TESTS+= cond-cmp-numeric-eq +TESTS+= cond-cmp-numeric-ge +TESTS+= cond-cmp-numeric-gt +TESTS+= cond-cmp-numeric-le +TESTS+= cond-cmp-numeric-lt +TESTS+= cond-cmp-numeric-ne +TESTS+= cond-cmp-string +TESTS+= cond-cmp-unary +TESTS+= cond-eof +TESTS+= cond-func +TESTS+= cond-func-commands +TESTS+= cond-func-defined +TESTS+= cond-func-empty +TESTS+= cond-func-exists +TESTS+= cond-func-make +TESTS+= cond-func-make-main +TESTS+= cond-func-target +TESTS+= cond-late +TESTS+= cond-op +TESTS+= cond-op-and +TESTS+= cond-op-and-lint +TESTS+= cond-op-not +TESTS+= cond-op-or +TESTS+= cond-op-or-lint +TESTS+= cond-op-parentheses +TESTS+= cond-short +TESTS+= cond-token-number +TESTS+= cond-token-plain +TESTS+= cond-token-string +TESTS+= cond-token-var +TESTS+= cond-undef-lint +TESTS+= cond1 +TESTS+= counter +TESTS+= counter-append +TESTS+= dep +TESTS+= dep-colon +TESTS+= dep-colon-bug-cross-file +TESTS+= dep-double-colon +TESTS+= dep-double-colon-indep +TESTS+= dep-exclam +TESTS+= dep-none +TESTS+= dep-percent +TESTS+= dep-var +TESTS+= dep-wildcards +TESTS+= depsrc +TESTS+= depsrc-end +TESTS+= depsrc-exec +TESTS+= depsrc-ignore +TESTS+= depsrc-made +TESTS+= depsrc-make +TESTS+= depsrc-meta +TESTS+= depsrc-nometa +TESTS+= depsrc-nometa_cmp +TESTS+= depsrc-nopath +TESTS+= depsrc-notmain +TESTS+= depsrc-optional +TESTS+= depsrc-phony +TESTS+= depsrc-precious +TESTS+= depsrc-recursive +TESTS+= depsrc-silent +TESTS+= depsrc-use +TESTS+= depsrc-usebefore +TESTS+= depsrc-usebefore-double-colon +TESTS+= depsrc-wait +TESTS+= deptgt +TESTS+= deptgt-begin +TESTS+= deptgt-begin-fail +TESTS+= deptgt-begin-fail-indirect +TESTS+= deptgt-default +TESTS+= deptgt-delete_on_error +TESTS+= deptgt-end +TESTS+= deptgt-end-fail +TESTS+= deptgt-end-fail-all +TESTS+= deptgt-end-fail-indirect +TESTS+= deptgt-end-jobs +TESTS+= deptgt-error +TESTS+= deptgt-ignore +TESTS+= deptgt-interrupt +TESTS+= deptgt-main +TESTS+= deptgt-makeflags +TESTS+= deptgt-no_parallel +TESTS+= deptgt-nopath +TESTS+= deptgt-notparallel +TESTS+= deptgt-objdir +TESTS+= deptgt-order +TESTS+= deptgt-path +TESTS+= deptgt-path-suffix +TESTS+= deptgt-phony +TESTS+= deptgt-precious +TESTS+= deptgt-shell +TESTS+= deptgt-silent +TESTS+= deptgt-stale +TESTS+= deptgt-suffixes +TESTS+= dir +TESTS+= dir-expand-path +TESTS+= directive +TESTS+= directive-dinclude +TESTS+= directive-elif +TESTS+= directive-elifdef +TESTS+= directive-elifmake +TESTS+= directive-elifndef +TESTS+= directive-elifnmake +TESTS+= directive-else +TESTS+= directive-endfor +TESTS+= directive-endif +TESTS+= directive-error +TESTS+= directive-export +TESTS+= directive-export-env +TESTS+= directive-export-impl +TESTS+= directive-export-gmake +TESTS+= directive-export-literal +TESTS+= directive-for +TESTS+= directive-for-errors +TESTS+= directive-for-escape +TESTS+= directive-for-generating-endif +TESTS+= directive-for-lines +TESTS+= directive-for-null +TESTS+= directive-hyphen-include +TESTS+= directive-if +TESTS+= directive-if-nested +TESTS+= directive-ifdef +TESTS+= directive-ifmake +TESTS+= directive-ifndef +TESTS+= directive-ifnmake +TESTS+= directive-include +TESTS+= directive-include-fatal +TESTS+= directive-info +TESTS+= directive-misspellings +TESTS+= directive-sinclude +TESTS+= directive-undef +TESTS+= directive-unexport +TESTS+= directive-unexport-env +TESTS+= directive-warning +TESTS+= dollar +TESTS+= doterror +TESTS+= dotwait +TESTS+= envfirst +TESTS+= error +TESTS+= # escape # broken by reverting POSIX changes +TESTS+= export +TESTS+= export-all +TESTS+= export-env +TESTS+= export-variants +TESTS+= forloop +TESTS+= forsubst +TESTS+= gnode-submake +TESTS+= hanoi-include +TESTS+= impsrc +TESTS+= include-main +TESTS+= job-flags +TESTS+= job-output-long-lines +TESTS+= jobs-empty-commands +TESTS+= jobs-error-indirect +TESTS+= jobs-error-nested +TESTS+= jobs-error-nested-make +TESTS+= lint +TESTS+= make-exported +TESTS+= meta-cmd-cmp +TESTS+= moderrs +TESTS+= modmatch +TESTS+= modmisc +TESTS+= modts +TESTS+= modword +.if ${.MAKE.UID} > 0 +TESTS+= objdir-writable +.endif +TESTS+= opt +TESTS+= opt-backwards +TESTS+= opt-chdir +TESTS+= opt-debug +TESTS+= opt-debug-all +TESTS+= opt-debug-archive +TESTS+= opt-debug-curdir +TESTS+= opt-debug-cond +TESTS+= opt-debug-dir +TESTS+= opt-debug-errors +TESTS+= opt-debug-file +TESTS+= opt-debug-for +TESTS+= opt-debug-graph1 +TESTS+= opt-debug-graph2 +TESTS+= opt-debug-graph3 +TESTS+= opt-debug-hash +TESTS+= opt-debug-jobs +TESTS+= opt-debug-lint +TESTS+= opt-debug-loud +TESTS+= opt-debug-meta +TESTS+= opt-debug-making +TESTS+= opt-debug-no-rm +TESTS+= opt-debug-parse +TESTS+= opt-debug-suff +TESTS+= opt-debug-targets +TESTS+= opt-debug-varraw +TESTS+= opt-debug-var +TESTS+= opt-debug-x-trace +TESTS+= opt-define +TESTS+= opt-env +TESTS+= opt-file +TESTS+= opt-ignore +TESTS+= opt-include-dir +TESTS+= opt-jobs +TESTS+= opt-jobs-internal +TESTS+= opt-jobs-no-action +TESTS+= opt-keep-going +TESTS+= opt-keep-going-multiple +TESTS+= opt-m-include-dir +TESTS+= opt-no-action +TESTS+= opt-no-action-at-all +TESTS+= opt-no-action-runflags +TESTS+= opt-no-action-touch +TESTS+= opt-query +TESTS+= opt-raw +TESTS+= opt-silent +TESTS+= opt-touch +TESTS+= opt-touch-jobs +TESTS+= opt-tracefile +TESTS+= opt-var-expanded +TESTS+= opt-var-literal +TESTS+= opt-warnings-as-errors +TESTS+= opt-where-am-i +TESTS+= opt-x-reduce-exported +TESTS+= order +TESTS+= parse-var +TESTS+= phony-end +TESTS+= posix +TESTS+= # posix1 # broken by reverting POSIX changes +TESTS+= recursive +TESTS+= sh +TESTS+= sh-dots +TESTS+= sh-errctl +TESTS+= sh-flags +TESTS+= sh-jobs +TESTS+= sh-jobs-error +TESTS+= sh-leading-at +TESTS+= sh-leading-hyphen +TESTS+= sh-leading-plus +TESTS+= sh-meta-chars +TESTS+= sh-multi-line +TESTS+= sh-single-line +TESTS+= shell-csh +TESTS+= shell-custom +TESTS+= shell-ksh +TESTS+= shell-sh +TESTS+= suff-add-later +TESTS+= suff-clear-regular +TESTS+= suff-clear-single +TESTS+= suff-incomplete +TESTS+= suff-lookup +TESTS+= suff-main +TESTS+= suff-main-several +TESTS+= suff-phony +TESTS+= suff-rebuild +TESTS+= suff-self +TESTS+= suff-transform-debug +TESTS+= suff-transform-endless +TESTS+= suff-transform-expand +TESTS+= suff-transform-select +TESTS+= sunshcmd +TESTS+= ternary +TESTS+= unexport +TESTS+= unexport-env +TESTS+= use-inference +TESTS+= var-class +TESTS+= var-class-cmdline +TESTS+= var-class-env +TESTS+= var-class-global +TESTS+= var-class-local +TESTS+= var-class-local-legacy +TESTS+= var-eval-short +TESTS+= var-op +TESTS+= var-op-append +TESTS+= var-op-assign +TESTS+= var-op-default +TESTS+= var-op-expand +TESTS+= var-op-shell +TESTS+= var-op-sunsh +TESTS+= var-recursive +TESTS+= varcmd +TESTS+= vardebug +TESTS+= varfind +TESTS+= varmisc +TESTS+= varmod +TESTS+= varmod-assign +TESTS+= varmod-defined +TESTS+= varmod-edge +TESTS+= varmod-exclam-shell +TESTS+= varmod-extension +TESTS+= varmod-gmtime +TESTS+= varmod-hash +TESTS+= varmod-head +TESTS+= varmod-ifelse +TESTS+= varmod-indirect +TESTS+= varmod-l-name-to-value +TESTS+= varmod-localtime +TESTS+= varmod-loop +TESTS+= varmod-match +TESTS+= varmod-match-escape +TESTS+= varmod-no-match +TESTS+= varmod-order +TESTS+= varmod-order-reverse +TESTS+= varmod-order-shuffle +TESTS+= varmod-path +TESTS+= varmod-quote +TESTS+= varmod-quote-dollar +TESTS+= varmod-range +TESTS+= varmod-remember +TESTS+= varmod-root +TESTS+= varmod-select-words +TESTS+= varmod-shell +TESTS+= varmod-subst +TESTS+= varmod-subst-regex +TESTS+= varmod-sun-shell +TESTS+= varmod-sysv +TESTS+= varmod-tail +TESTS+= varmod-to-abs +TESTS+= varmod-to-lower +TESTS+= varmod-to-many-words +TESTS+= varmod-to-one-word +TESTS+= varmod-to-separator +TESTS+= varmod-to-upper +TESTS+= varmod-undefined +TESTS+= varmod-unique +TESTS+= varname +TESTS+= varname-dollar +TESTS+= varname-dot-alltargets +TESTS+= varname-dot-curdir +TESTS+= varname-dot-includes +TESTS+= varname-dot-includedfromdir +TESTS+= varname-dot-includedfromfile +TESTS+= varname-dot-libs +TESTS+= varname-dot-make-dependfile +TESTS+= varname-dot-make-expand_variables +TESTS+= varname-dot-make-exported +TESTS+= varname-dot-make-jobs +TESTS+= varname-dot-make-jobs-prefix +TESTS+= varname-dot-make-level +TESTS+= varname-dot-make-makefile_preference +TESTS+= varname-dot-make-makefiles +TESTS+= varname-dot-make-meta-bailiwick +TESTS+= varname-dot-make-meta-created +TESTS+= varname-dot-make-meta-files +TESTS+= varname-dot-make-meta-ignore_filter +TESTS+= varname-dot-make-meta-ignore_paths +TESTS+= varname-dot-make-meta-ignore_patterns +TESTS+= varname-dot-make-meta-prefix +TESTS+= varname-dot-make-mode +TESTS+= varname-dot-make-path_filemon +TESTS+= varname-dot-make-pid +TESTS+= varname-dot-make-ppid +TESTS+= varname-dot-make-save_dollars +TESTS+= varname-dot-makeflags +TESTS+= varname-dot-makeoverrides +TESTS+= varname-dot-newline +TESTS+= varname-dot-objdir +TESTS+= varname-dot-parsedir +TESTS+= varname-dot-parsefile +TESTS+= varname-dot-path +TESTS+= varname-dot-shell +TESTS+= varname-dot-targets +TESTS+= varname-empty +TESTS+= varname-make +TESTS+= varname-make_print_var_on_error +TESTS+= varname-make_print_var_on_error-jobs +TESTS+= varname-makefile +TESTS+= varname-makeflags +TESTS+= varname-pwd +TESTS+= varname-vpath +TESTS+= varparse-dynamic +TESTS+= varparse-errors +TESTS+= varparse-mod +TESTS+= varparse-undef-partial +TESTS+= varquote -# these tests were broken by referting POSIX chanegs -STRICT_POSIX_TESTS = \ - escape \ - impsrc \ - phony-end \ - posix1 \ - suffixes +# Ideas for more tests: +# char-0020-space.mk +# char-005C-backslash.mk +# escape-cond-str.mk +# escape-cond-func-arg.mk +# escape-cond-func-arg.mk +# escape-varmod.mk +# escape-varmod-define.mk +# escape-varmod-match.mk +# escape-varname.mk +# escape-varassign-varname.mk +# escape-varassign-varname-cmdline.mk +# escape-varassign-value.mk +# escape-varassign-value-cmdline.mk +# escape-dependency-source.mk +# escape-dependency-target.mk +# escape-for-varname.mk +# escape-for-item.mk +# posix-*.mk (see posix.mk and posix1.mk) -# Override make flags for certain tests -flags.doterror= -flags.order=-j1 +# Additional environment variables for some of the tests. +# The base environment is -i PATH="$PATH". +ENV.depsrc-optional+= TZ=UTC +ENV.envfirst= FROM_ENV=value-from-env +ENV.varmisc= FROM_ENV=env +ENV.varmisc+= FROM_ENV_BEFORE=env +ENV.varmisc+= FROM_ENV_AFTER=env +ENV.varmod-localtime+= TZ=Europe/Berlin +ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2 -OUTFILES= ${TESTNAMES:S/$/.out/} +# Override make flags for some of the tests; default is -k. +# If possible, write ".MAKEFLAGS: -dv" in the test .mk file instead of +# settings FLAGS.test=-dv here, since that is closer to the test code. +FLAGS.cond-func-make= via-cmdline +FLAGS.directive-ifmake= first second +FLAGS.doterror= # none, especially not -k +FLAGS.jobs-error-indirect= # none, especially not -k +FLAGS.jobs-error-nested= # none, especially not -k +FLAGS.jobs-error-nested-make= # none, especially not -k +FLAGS.varname-empty= -dv '$${:U}=cmdline-u' '=cmdline-plain' + +# Some tests need extra postprocessing. +SED_CMDS.dir= ${:D remove output from -DCLEANUP mode } +SED_CMDS.dir+= -e '/^OpenDirs_Done:/d' +SED_CMDS.dir+= -e '/^CachedDir /d' +SED_CMDS.export= -e '/^[^=_A-Za-z0-9]*=/d' +SED_CMDS.export-all= ${SED_CMDS.export} +SED_CMDS.export-env= ${SED_CMDS.export} +SED_CMDS.job-output-long-lines= \ + ${:D Job separators on their own line are ok. } \ + -e '/^--- job-[ab] ---$$/d' \ + ${:D Plain output lines are ok as well. } \ + ${:D They may come in multiples of 1024 or as 10000. } \ + -e '/^aa*$$/d' \ + -e '/^bb*$$/d' \ + ${:D The following lines should rather not occur since the job } \ + ${:D marker should always be at the beginning of the line. } \ + -e '/^aa*--- job-b ---$$/d' \ + -e '/^bb*--- job-a ---$$/d' +SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1} +SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2} +SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3} +SED_CMDS.opt-debug-jobs= -e 's,([0-9][0-9]*),(),' +SED_CMDS.opt-debug-jobs+= -e 's,pid [0-9][0-9]*,pid ,' +SED_CMDS.opt-debug-jobs+= -e 's,Process [0-9][0-9]*,Process ,' +SED_CMDS.opt-debug-jobs+= -e 's,JobFinish: [0-9][0-9]*,JobFinish: ,' +SED_CMDS.opt-debug-jobs+= -e 's,Command: ${.SHELL:T},Command: ,' +# The "-q" may be there or not, see jobs.c, variable shells. +SED_CMDS.opt-debug-jobs+= -e 's,^\(.Command: \) -q,\1,' +SED_CMDS.opt-debug-lint+= ${STD_SED_CMDS.regex} +SED_CMDS.opt-jobs-no-action= ${STD_SED_CMDS.hide-from-output} +SED_CMDS.opt-no-action-runflags= ${STD_SED_CMDS.hide-from-output} +# For Compat_RunCommand, useShell == FALSE. +SED_CMDS.sh-dots= -e 's,^.*\.\.\.:.*,,' +# For Compat_RunCommand, useShell == TRUE. +SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,,' +SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1,' +SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj} +SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output} +SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1} +SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1} +SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1} +SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell} +SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,' +SED_CMDS.vardebug+= -e 's,${.SHELL},,' +SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex} +SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: ",' +SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: ",' +SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g' +SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g' +SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g' +SED_CMDS.varname-empty= -e 's,${.CURDIR},,g' +SED_CMDS.varname-empty+= -e '/\.PARSEDIR/d' +SED_CMDS.varname-empty+= -e '/\.SHELL/d' + +# Some tests need an additional round of postprocessing. +POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/' +POSTPROC.gnode-submake= awk '/Input graph/, /^$$/' + +# Some tests reuse other tests, which makes them unnecessarily fragile. +export-all.rawout: export.mk +unexport.rawout: export.mk +unexport-env.rawout: export.mk + +# End of the configuration section. + +# Some standard sed commands, to be used in the SED_CMDS above. + +# Omit details such as process IDs from the output of the -dg1 option. +STD_SED_CMDS.dg1= -e 's,${.CURDIR}$$,,' +STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, ,' +STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE *=\) .*,\1
,' +STD_SED_CMDS.dg1+= -e 's,^\(\.MAKE\.[A-Z_]* *=\) .*,\1
,' +STD_SED_CMDS.dg1+= -e 's,^\(MACHINE[_ARCH]* *=\) .*,\1
,' +STD_SED_CMDS.dg1+= -e 's,^\(MAKE *=\) .*,\1
,' +STD_SED_CMDS.dg1+= -e 's,^\(\.SHELL *=\) .*,\1
,' + +STD_SED_CMDS.dg2= ${STD_SED_CMDS.dg1} +STD_SED_CMDS.dg2+= -e 's,\(last modified\) ..:..:.. ... ..\, ....,\1 ,' +STD_SED_CMDS.dg3= ${STD_SED_CMDS.dg2} + +# Omit details such as process IDs from the output of the -dj option. +STD_SED_CMDS.dj= \ + -e '/Process/d;/JobFinish:/d' \ + -e 's,^\(Job_TokenWithdraw\)([0-9]*),\1(),' \ + -e 's,^([0-9][0-9]*) \(withdrew token\),() \1,' \ + -e 's, \(pid\) [0-9][0-9]*, \1 ,' \ + -e 's,^\( Command:\) .*,\1 ,' + +# Reduce the noise for tests running with the -n option, since there is no +# other way to suppress the echoing of the commands. +STD_SED_CMDS.hide-from-output= \ + -e '/^echo hide-from-output/d' \ + -e 's,hide-from-output ,,' \ + -e 's,hide-from-output,,' + +# Normalize the output for error messages from the shell. +# +# $shell -c '...' +# NetBSD sh ...: not found +# NetBSD ksh ksh: ...: not found +# bash 5.0.18 bash: ...: command not found +# bash 5.1.0 bash: line 1: ...: command not found +# dash dash: 1: ...: not found +# +# $shell -c '< /nonexistent' +# NetBSD sh sh: cannot open /nonexistent: no such file +# NetBSD ksh ksh: cannot open /nonexistent: No such file or directory +# bash 5.0.18 bash: /nonexistent: No such file or directory +# bash 5.1.0 bash: line 1: /nonexistent: No such file or directory +# dash dash: 1: cannot open /nonexistent: No such file +# +# echo '< /nonexistent' | $shell +# NetBSD sh sh: cannot open /nonexistent: no such file +# NetBSD ksh ksh: [1]: cannot open /nonexistent: No such file or directory +# bash 5.0.18 bash: line 1: /nonexistent: No such file or directory +# bash 5.1.0 bash: line 1: /nonexistent: No such file or directory +# dash dash: 1: cannot open /nonexistent: No such file +# +STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,' +STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,' +STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,' + +# The actual error messages for a failed regcomp or regexec differ between the +# implementations. +STD_SED_CMDS.regex= \ + -e 's,\(Regex compilation error:\).*,\1 (details omitted),' + +# End of the configuration helpers section. + +UNIT_TESTS:= ${.PARSEDIR} +.PATH: ${UNIT_TESTS} + +.if ${USE_ABSOLUTE_TESTNAMES:Uno} == yes +OUTFILES= ${TESTS:@test@${.CURDIR:tA}/${test}.out@} +.else +OUTFILES= ${TESTS:=.out} +.endif all: ${OUTFILES} -CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp -CLEANFILES += obj*.[och] lib*.a # posix1.mk -CLEANFILES += issue* .[ab]* # suffixes.mk -CLEANRECURSIVE += dir dummy # posix1.mk +CLEANFILES= *.rawout *.out *.status *.tmp *.core *.tmp +CLEANFILES+= obj*.[och] lib*.a # posix1.mk +CLEANFILES+= issue* .[ab]* # suffixes.mk +CLEANDIRS= dir dummy # posix1.mk clean: rm -f ${CLEANFILES} -.if !empty(CLEANRECURSIVE) - rm -rf ${CLEANRECURSIVE} -.endif + rm -rf ${CLEANDIRS} -TEST_MAKE?= ${.MAKE} -TOOL_SED?= sed +TEST_MAKE?= ${.MAKE} +TOOL_SED?= sed # ensure consistent results from sort(1) -LC_ALL= C -LANG= C +LC_ALL= C +LANG= C .export LANG LC_ALL -# the tests are actually done with sub-makes. + +# for many tests we need a TMPDIR that will not collide +# with other users. +.if ${.OBJDIR} != ${.CURDIR} +# easy +TMPDIR:= ${.OBJDIR}/tmp +.elif defined(TMPDIR) +TMPDIR:= ${TMPDIR}/uid${.MAKE.UID} +.else +TMPDIR:= /tmp/uid${.MAKE.UID} +.endif +# make sure it exists +.if !exist(${TMPDIR}) +x!= echo; mkdir -p ${TMPDIR} +.endif + +MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc +MAKE_TEST_ENV+= TMPDIR=${TMPDIR} + +.if ${.MAKE.OS} == "NetBSD" +LIMIT_RESOURCES?= ulimit -v 200000 +.endif +LIMIT_RESOURCES?= : + +# Each test is run in a sub-make, to keep the tests for interfering with +# each other, and because they use different environment variables and +# command line options. .SUFFIXES: .mk .rawout .out .mk.rawout: - @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} - -@cd ${.OBJDIR} && \ - { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \ - 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp + @${_MKMSG_TEST:Uecho '# test '} ${.PREFIX} + @set -eu; \ + ${LIMIT_RESOURCES}; \ + cd ${.OBJDIR}; \ + env -i PATH="$$PATH" ${MAKE_TEST_ENV} ${ENV.${.PREFIX:T}} \ + ${TEST_MAKE} \ + -r -C ${.CURDIR} -f ${.IMPSRC} \ + ${FLAGS.${.PREFIX:T}:U-k} \ + > ${.TARGET}.tmp 2>&1 \ + && status=$$? || status=$$?; \ + echo $$status > ${.TARGET:R}.status @mv ${.TARGET}.tmp ${.TARGET} -# We always pretend .MAKE was called 'make' -# and strip ${.CURDIR}/ from the output -# and replace anything after 'stopped in' with unit-tests -# so the results can be compared. +# Postprocess the test output so that the results can be compared. +# +# always pretend .MAKE was called 'make' +_SED_CMDS+= -e 's,^${TEST_MAKE:T:S,.,\\.,g}[][0-9]*:,make:,' +_SED_CMDS+= -e 's,${TEST_MAKE:S,.,\\.,g},make,' +_SED_CMDS+= -e 's,${TEST_MAKE:T:S,.,\\.,g}[][0-9]* warning,make warning,' +_SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,' +# replace anything after 'stopped in' with unit-tests +_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,' +_SED_CMDS+= -e 's,${TMPDIR},TMPDIR,g' +# strip ${.CURDIR}/ from the output +_SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g' +_SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g' + .rawout.out: - @echo postprocess ${.TARGET} - @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \ - -e 's,${TEST_MAKE:C/\./\\\./g},make,' \ - -e '/stopped/s, /.*, unit-tests,' \ - -e 's,${.CURDIR:C/\./\\\./g}/,,g' \ - -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' \ - < ${.IMPSRC} > ${.TARGET}.tmp - @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp - @mv ${.TARGET}.tmp ${.TARGET} + @${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \ + < ${.IMPSRC} > ${.TARGET}.tmp1 + @${POSTPROC.${.PREFIX:T}:Ucat} < ${.TARGET}.tmp1 > ${.TARGET}.tmp2 + @rm ${.TARGET}.tmp1 + @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp2 + @mv ${.TARGET}.tmp2 ${.TARGET} # Compare all output files test: ${OUTFILES} .PHONY @failed= ; \ - for test in ${TESTNAMES}; do \ + for test in ${TESTS}; do \ diff -u ${UNIT_TESTS}/$${test}.exp $${test}.out \ || failed="$${failed}$${failed:+ }$${test}" ; \ done ; \ @@ -125,14 +701,34 @@ test: ${OUTFILES} .PHONY fi accept: - @for test in ${TESTNAMES}; do \ + @for test in ${TESTS}; do \ cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \ || { echo "Replacing $${test}.exp" ; \ cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \ done +# Note: only works for adding tests. +# To remove a test, the $$mi file must be edited manually. +sync-mi: + @set -eu; \ + cd "${MAKEFILE:tA:H}/../../.."; \ + mi="distrib/sets/lists/tests/mi"; \ + cvs update "$$mi"; \ + testsdir="usr.bin/make/unit-tests"; \ + fmt="./usr/tests/$$testsdir/%s\ttests-usr.bin-tests\tcompattestfile,atf\\n"; \ + cat "$$mi" > "$$mi.tmp"; \ + (cd "$$testsdir" && ls *.exp *.mk) | xargs printf "$$fmt" >> "$$mi.tmp"; \ + distrib/sets/fmt-list "$$mi.tmp"; \ + mv "$$mi.tmp" "$$mi"; \ + cvs diff "$$mi" || true + .if exists(${TEST_MAKE}) -${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE} +${TESTS:=.rawout}: ${TEST_MAKE} +# in meta mode, we *know* if a target script is impacted +# by a makefile change. +.if ${.MAKE.MODE:Unormal:Mmeta} == "" +${TESTS:=.rawout}: ${.PARSEDIR}/Makefile +.endif .endif .-include diff --git a/usr.bin/make/unit-tests/archive-suffix.exp b/usr.bin/make/unit-tests/archive-suffix.exp new file mode 100644 index 000000000..05f2ac662 --- /dev/null +++ b/usr.bin/make/unit-tests/archive-suffix.exp @@ -0,0 +1,2 @@ +`all' is up to date. +exit status 0 diff --git a/usr.bin/make/unit-tests/archive-suffix.mk b/usr.bin/make/unit-tests/archive-suffix.mk new file mode 100644 index 000000000..a216fd2d3 --- /dev/null +++ b/usr.bin/make/unit-tests/archive-suffix.mk @@ -0,0 +1,23 @@ +# $NetBSD: archive-suffix.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ +# +# Between 2020-08-23 and 2020-08-30, the below code produced an assertion +# failure in Var_SetWithFlags, triggered by Compat_Make, when setting the +# .IMPSRC of an archive node to its .TARGET. +# +# The code assumed that the .TARGET variable of every node would be set, but +# that is not guaranteed. +# +# Between 2016-03-15 and 2016-03-16 the behavior of the below code changed. +# Until 2016-03-15, it remade the target, starting with 2016-03-16 it says +# "`all' is up to date". + +.SUFFIXES: +.SUFFIXES: .c .o + +all: lib.a(obj1.o) + +.c.o: + : making $@ + +obj1.c: + : $@ diff --git a/usr.bin/make/unit-tests/archive.exp b/usr.bin/make/unit-tests/archive.exp new file mode 100644 index 000000000..645add4f5 --- /dev/null +++ b/usr.bin/make/unit-tests/archive.exp @@ -0,0 +1,28 @@ +Making remove-archive +rm -f libprog.a + +Making libprog.a out-of-date archive.mk modmisc.mk varmisc.mk +ar cru libprog.a archive.mk modmisc.mk varmisc.mk +ranlib libprog.a + +Making create-archive out-of-date libprog.a + +Making list-archive out-of-date libprog.a +ar t libprog.a +archive.mk +modmisc.mk +varmisc.mk + +Making list-archive-wildcard out-of-date archive-suffix.mk archive.mk ternary.mk +list-archive-wildcard: archive-suffix.mk +list-archive-wildcard: archive.mk +list-archive-wildcard: ternary.mk + +Making depend-on-existing-member out-of-date archive.mk +depend-on-existing-member + +`depend-on-nonexistent-member' is up to date. +Making remove-archive +rm -f libprog.a + +exit status 0 diff --git a/usr.bin/make/unit-tests/archive.mk b/usr.bin/make/unit-tests/archive.mk new file mode 100644 index 000000000..f8815cf40 --- /dev/null +++ b/usr.bin/make/unit-tests/archive.mk @@ -0,0 +1,60 @@ +# $NetBSD: archive.mk,v 1.11 2020/11/15 14:07:53 rillig Exp $ +# +# Very basic demonstration of handling archives, based on the description +# in PSD.doc/tutorial.ms. +# +# This test aims at covering the code, not at being an introduction to +# archive handling. That's why it deviates from the tutorial style of +# several other tests. + +ARCHIVE= libprog.a +FILES= archive.mk modmisc.mk varmisc.mk + +all: +.if ${.PARSEDIR:tA} != ${.CURDIR:tA} + @cd ${MAKEFILE:H} && cp ${FILES} [at]*.mk ${.CURDIR} +.endif +# The following targets create and remove files. The filesystem cache in +# dir.c would probably not handle this correctly, therefore each of the +# targets is run in its separate sub-make. + @${MAKE} -f ${MAKEFILE} remove-archive + @${MAKE} -f ${MAKEFILE} create-archive + @${MAKE} -f ${MAKEFILE} list-archive + @${MAKE} -f ${MAKEFILE} list-archive-wildcard + @${MAKE} -f ${MAKEFILE} depend-on-existing-member + @${MAKE} -f ${MAKEFILE} depend-on-nonexistent-member + @${MAKE} -f ${MAKEFILE} remove-archive + +create-archive: ${ARCHIVE} pre post + +# The indirect references with the $$ cover the code in Arch_ParseArchive +# that calls Var_Parse. It's an esoteric scenario since at the point where +# Arch_ParseArchive is called, the dependency line is already fully expanded. +# +${ARCHIVE}: $${:Ulibprog.a}(archive.mk modmisc.mk $${:Uvarmisc.mk}) pre post + ar cru ${.TARGET} ${.OODATE:O} + ranlib ${.TARGET} + +list-archive: ${ARCHIVE} pre post + ar t ${.ALLSRC} + +# XXX: I had expected that this dependency would select all *.mk files from +# the archive. Instead, the globbing is done in the current directory. +# +# To prevent an overly long file list, the pattern is restricted to [at]*.mk. +list-archive-wildcard: ${ARCHIVE}([at]*.mk) pre post + @printf '%s\n' ${.ALLSRC:O:@member@${.TARGET:Q}': '${member:Q}@} + +depend-on-existing-member: ${ARCHIVE}(archive.mk) pre post + @echo $@ + +depend-on-nonexistent-member: ${ARCHIVE}(nonexistent.mk) pre post + @echo $@ + +remove-archive: pre post + rm -f ${ARCHIVE} + +pre: .USEBEFORE + @echo Making ${.TARGET} ${.OODATE:C,.+,out-of-date,W} ${.OODATE:O} +post: .USE + @echo diff --git a/usr.bin/make/unit-tests/cmd-errors-jobs.exp b/usr.bin/make/unit-tests/cmd-errors-jobs.exp new file mode 100644 index 000000000..9ed055797 --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-errors-jobs.exp @@ -0,0 +1,9 @@ +: undefined eol +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier "Z" +: unknown-modifier eol +: end eol +exit status 0 diff --git a/usr.bin/make/unit-tests/cmd-errors-jobs.mk b/usr.bin/make/unit-tests/cmd-errors-jobs.mk new file mode 100644 index 000000000..8462a2e34 --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-errors-jobs.mk @@ -0,0 +1,32 @@ +# $NetBSD: cmd-errors-jobs.mk,v 1.1 2020/12/27 05:11:40 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed in jobs mode. + +.MAKEFLAGS: -j1 + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} eol + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} eol + +end: + : $@ eol + +# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0. diff --git a/usr.bin/make/unit-tests/cmd-errors-lint.exp b/usr.bin/make/unit-tests/cmd-errors-lint.exp new file mode 100644 index 000000000..90b63bbcb --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-errors-lint.exp @@ -0,0 +1,9 @@ +: undefined +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier "Z" +: unknown-modifier +: end +exit status 2 diff --git a/usr.bin/make/unit-tests/cmd-errors-lint.mk b/usr.bin/make/unit-tests/cmd-errors-lint.mk new file mode 100644 index 000000000..371e12af0 --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-errors-lint.mk @@ -0,0 +1,32 @@ +# $NetBSD: cmd-errors-lint.mk,v 1.1 2020/11/02 20:43:27 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed. + +.MAKEFLAGS: -dL + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} + +# XXX: As of 2020-11-01, this obvious syntax error is not detected. +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this obvious syntax error is not detected. +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} + +end: + : $@ diff --git a/usr.bin/make/unit-tests/cmd-errors.exp b/usr.bin/make/unit-tests/cmd-errors.exp new file mode 100644 index 000000000..9ed055797 --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-errors.exp @@ -0,0 +1,9 @@ +: undefined eol +make: Unclosed variable "UNCLOSED" +: unclosed-variable +make: Unclosed variable expression (expecting '}') for "UNCLOSED" +: unclosed-modifier +make: Unknown modifier "Z" +: unknown-modifier eol +: end eol +exit status 0 diff --git a/usr.bin/make/unit-tests/cmd-errors.mk b/usr.bin/make/unit-tests/cmd-errors.mk new file mode 100644 index 000000000..356fe1a3e --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-errors.mk @@ -0,0 +1,30 @@ +# $NetBSD: cmd-errors.mk,v 1.4 2020/12/27 05:11:40 rillig Exp $ +# +# Demonstrate how errors in variable expansions affect whether the commands +# are actually executed in compat mode. + +all: undefined unclosed-variable unclosed-modifier unknown-modifier end + +# Undefined variables are not an error. They expand to empty strings. +undefined: + : $@ ${UNDEFINED} eol + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-variable: + : $@ ${UNCLOSED + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unclosed-modifier: + : $@ ${UNCLOSED: + +# XXX: As of 2020-11-01, this command is executed even though it contains +# parse errors. +unknown-modifier: + : $@ ${UNKNOWN:Z} eol + +end: + : $@ eol + +# XXX: As of 2020-11-02, despite the parse errors, the exit status is 0. diff --git a/usr.bin/make/unit-tests/cmd-interrupt.exp b/usr.bin/make/unit-tests/cmd-interrupt.exp new file mode 100644 index 000000000..91f4439e7 --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-interrupt.exp @@ -0,0 +1,9 @@ +> cmd-interrupt-ordinary +make: *** cmd-interrupt-ordinary removed +interrupt-ordinary: ok +> cmd-interrupt-phony +make: *** cmd-interrupt-phony removed +interrupt-phony: ok +> cmd-interrupt-precious +interrupt-precious: ok +exit status 0 diff --git a/usr.bin/make/unit-tests/cmd-interrupt.mk b/usr.bin/make/unit-tests/cmd-interrupt.mk new file mode 100644 index 000000000..fa0d85fc9 --- /dev/null +++ b/usr.bin/make/unit-tests/cmd-interrupt.mk @@ -0,0 +1,50 @@ +# $NetBSD: cmd-interrupt.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ +# +# Tests for interrupting a command. +# +# If a command is interrupted (usually by the user, here by itself), the +# target is removed. This is to avoid having an unfinished target that +# would be newer than all of its sources and would therefore not be +# tried again in the next run. +# +# This happens for ordinary targets as well as for .PHONY targets, even +# though the .PHONY targets usually do not correspond to a file. +# +# To protect the target from being removed, the target has to be marked with +# the special source .PRECIOUS. These targets need to ensure for themselves +# that interrupting them does not leave an inconsistent state behind. +# +# See also: +# CompatDeleteTarget + +all: clean-before interrupt-ordinary interrupt-phony interrupt-precious clean-after + +clean-before clean-after: .PHONY + @rm -f cmd-interrupt-ordinary cmd-interrupt-phony cmd-interrupt-precious + +interrupt-ordinary: + @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-ordinary || true + # The ././ is necessary to work around the file cache. + @echo ${.TARGET}: ${exists(././cmd-interrupt-ordinary) :? error : ok } + +interrupt-phony: .PHONY + @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-phony || true + # The ././ is necessary to work around the file cache. + @echo ${.TARGET}: ${exists(././cmd-interrupt-phony) :? error : ok } + +interrupt-precious: .PRECIOUS + @${.MAKE} ${MAKEFLAGS} -f ${MAKEFILE} cmd-interrupt-precious || true + # The ././ is necessary to work around the file cache. + @echo ${.TARGET}: ${exists(././cmd-interrupt-precious) :? ok : error } + +cmd-interrupt-ordinary: + > ${.TARGET} + @kill -INT ${.MAKE.PID} + +cmd-interrupt-phony: .PHONY + > ${.TARGET} + @kill -INT ${.MAKE.PID} + +cmd-interrupt-precious: .PRECIOUS + > ${.TARGET} + @kill -INT ${.MAKE.PID} diff --git a/usr.bin/make/unit-tests/misc.exp b/usr.bin/make/unit-tests/cmdline-redirect-stdin.exp similarity index 100% rename from usr.bin/make/unit-tests/misc.exp rename to usr.bin/make/unit-tests/cmdline-redirect-stdin.exp diff --git a/usr.bin/make/unit-tests/cmdline-redirect-stdin.mk b/usr.bin/make/unit-tests/cmdline-redirect-stdin.mk new file mode 100644 index 000000000..994016b5e --- /dev/null +++ b/usr.bin/make/unit-tests/cmdline-redirect-stdin.mk @@ -0,0 +1,34 @@ +# $NetBSD: cmdline-redirect-stdin.mk,v 1.1 2021/02/01 20:31:41 rillig Exp $ +# +# Demonstrate that the '!=' assignment operator can read individual lines +# from make's stdin. +# +# This edge case is an implementation detail that has no practical +# application. + +all: .PHONY + @printf '%s\n' "first line" "second line" \ + | ${MAKE} -f ${MAKEFILE} read-lines + +.if make(read-lines) +line1!= read line; echo "$$line" +line2!= read line; echo "$$line" + +.if ${line1} != "first line" +. error line1="${line1}" + +.elif ${line2} == "" +# If this branch is ever reached, the shell from the assignment to line1 +# probably buffers its input. Most shells use unbuffered stdin, and this +# is actually specified by POSIX, which says that "The read utility shall +# read a single line from standard input". This is the reason why the shell +# reads its input byte by byte, which makes it terribly slow for practical +# applications. +. error The shell's read command does not read a single line. + +.elif ${line2} != "second line" +. error line2="${line2}" +.endif + +read-lines: .PHONY +.endif diff --git a/usr.bin/make/unit-tests/cmdline-undefined.exp b/usr.bin/make/unit-tests/cmdline-undefined.exp new file mode 100644 index 000000000..977ceee6d --- /dev/null +++ b/usr.bin/make/unit-tests/cmdline-undefined.exp @@ -0,0 +1,17 @@ +The = assignment operator +make: "cmdline-undefined.mk" line 29: From the command line: Undefined is . +make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is . +make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is . +make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined. +make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined. +make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined. + +The := assignment operator +make: "cmdline-undefined.mk" line 29: From the command line: Undefined is . +make: "cmdline-undefined.mk" line 30: From .MAKEFLAGS '=': Undefined is . +make: "cmdline-undefined.mk" line 31: From .MAKEFLAGS ':=': Undefined is . +make: "cmdline-undefined.mk" line 35: From the command line: Undefined is now defined. +make: "cmdline-undefined.mk" line 36: From .MAKEFLAGS '=': Undefined is now defined. +make: "cmdline-undefined.mk" line 37: From .MAKEFLAGS ':=': Undefined is now defined. + +exit status 0 diff --git a/usr.bin/make/unit-tests/cmdline-undefined.mk b/usr.bin/make/unit-tests/cmdline-undefined.mk new file mode 100644 index 000000000..5a3375cbb --- /dev/null +++ b/usr.bin/make/unit-tests/cmdline-undefined.mk @@ -0,0 +1,40 @@ +# $NetBSD: cmdline-undefined.mk,v 1.2 2020/11/04 04:49:33 rillig Exp $ +# +# Tests for undefined variable expressions in the command line. + +all: + # When the command line is parsed, variable assignments using the + # '=' assignment operator do get their variable name expanded + # (which probably occurs rarely in practice, if at all), but their + # variable value is not expanded, as usual. + # + @echo 'The = assignment operator' + @${.MAKE} -f ${MAKEFILE} print-undefined \ + CMDLINE='Undefined is $${UNDEFINED}.' + @echo + + # The interesting case is using the ':=' assignment operator, which + # expands its right-hand side. But only those variables that are + # defined. + @echo 'The := assignment operator' + @${.MAKE} -f ${MAKEFILE} print-undefined \ + CMDLINE:='Undefined is $${UNDEFINED}.' + @echo + +.if make(print-undefined) + +.MAKEFLAGS: MAKEFLAGS_ASSIGN='Undefined is $${UNDEFINED}.' +.MAKEFLAGS: MAKEFLAGS_SUBST:='Undefined is $${UNDEFINED}.' + +.info From the command line: ${CMDLINE} +.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN} +.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST} + +UNDEFINED?= now defined + +.info From the command line: ${CMDLINE} +.info From .MAKEFLAGS '=': ${MAKEFLAGS_ASSIGN} +.info From .MAKEFLAGS ':=': ${MAKEFLAGS_SUBST} + +print-undefined: +.endif diff --git a/usr.bin/make/unit-tests/cmdline.exp b/usr.bin/make/unit-tests/cmdline.exp new file mode 100644 index 000000000..596281ab0 --- /dev/null +++ b/usr.bin/make/unit-tests/cmdline.exp @@ -0,0 +1,5 @@ +makeobjdir-direct: +show-objdir: TMPDIR/6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 +makeobjdir-indirect: +show-objdir: TMPDIR/a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45/ +exit status 0 diff --git a/usr.bin/make/unit-tests/cmdline.mk b/usr.bin/make/unit-tests/cmdline.mk new file mode 100644 index 000000000..f82e7f967 --- /dev/null +++ b/usr.bin/make/unit-tests/cmdline.mk @@ -0,0 +1,36 @@ +# $NetBSD: cmdline.mk,v 1.3 2021/02/06 18:26:03 sjg Exp $ +# +# Tests for command line parsing and related special variables. + +TMPBASE?= ${TMPDIR:U/tmp/uid${.MAKE.UID}} +SUB1= a7b41170-53f8-4cc2-bc5c-e4c3dd93ec45 # just a random UUID +SUB2= 6a8899d2-d227-4b55-9b6b-f3c8eeb83fd5 # just a random UUID +MAKE_CMD= env TMPBASE=${TMPBASE}/${SUB1} ${.MAKE} -f ${MAKEFILE} -r +DIR2= ${TMPBASE}/${SUB2} +DIR12= ${TMPBASE}/${SUB1}/${SUB2} + +all: prepare-dirs +all: makeobjdir-direct makeobjdir-indirect + +prepare-dirs: + @rm -rf ${DIR2} ${DIR12} + @mkdir -p ${DIR2} ${DIR12} + +# The .OBJDIR can be set via the MAKEOBJDIR command line variable. +# It must be a command line variable; an environment variable would not work. +makeobjdir-direct: + @echo $@: + @${MAKE_CMD} MAKEOBJDIR=${DIR2} show-objdir + +# The .OBJDIR can be set via the MAKEOBJDIR command line variable, +# and that variable could even contain the usual modifiers. +# Since the .OBJDIR=MAKEOBJDIR assignment happens very early, +# the SUB2 variable in the modifier is not defined yet and is therefore empty. +# The SUB1 in the resulting path comes from the environment variable TMPBASE, +# see MAKE_CMD. +makeobjdir-indirect: + @echo $@: + @${MAKE_CMD} MAKEOBJDIR='$${TMPBASE}/$${SUB2}' show-objdir + +show-objdir: + @echo $@: ${.OBJDIR:Q} diff --git a/usr.bin/make/unit-tests/comment.exp b/usr.bin/make/unit-tests/comment.exp index 9a97df0b7..f4d31d01b 100644 --- a/usr.bin/make/unit-tests/comment.exp +++ b/usr.bin/make/unit-tests/comment.exp @@ -1,5 +1,6 @@ -comment testing start -this is foo -This is how a comment looks: # comment -comment testing done +echo This is a shell comment: # comment +This is a shell comment: +echo This is not a shell comment: '# comment' +This is not a shell comment: # comment +A shell comment can#not start in the middle of a word. exit status 0 diff --git a/usr.bin/make/unit-tests/comment.mk b/usr.bin/make/unit-tests/comment.mk index 7dd7dbbe2..d4fb04110 100644 --- a/usr.bin/make/unit-tests/comment.mk +++ b/usr.bin/make/unit-tests/comment.mk @@ -1,31 +1,78 @@ -# This is a comment -.if ${MACHINE_ARCH} == something -FOO=bar -.endif +# $NetBSD: comment.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ +# +# Demonstrate how comments are written in makefiles. + +# This is a comment. #\ - Multiline comment +This is a multiline comment. -BAR=# defined -FOOBAR= # defined - -# This is an escaped comment \ -that keeps going until the end of this line - -# Another escaped comment \ +# Another multiline comment \ that \ goes \ -on +on and on. -# This is NOT an escaped comment due to the double backslashes \\ -all: hi foo bar - @echo comment testing done + # Comments can be indented with spaces, but that is rather unusual. -hi: - @echo comment testing start + # Comments can be indented with a tab. + # These are not shell commands, they are just makefile comments. -foo: - @echo this is $@ +.if 1 # There can be comments after conditions. +.endif # And after the closing directive. -bar: - @echo This is how a comment looks: '# comment' +VAR= # This comment makes the variable value empty. + # ParseGetLine removes any whitespace before the + # comment. +.if ${VAR} != "" +. error +.endif + +# The comment does not need to start at the beginning of a word (as in the +# shell), it can start anywhere. +VAR=# defined but empty + +# The space before the comment is always trimmed. +VAR= value +.if ${VAR} != "value" +. error +.endif + +# This comment ends with 2 backslashes. An even number of backslashes does +# not count as a line continuation, therefore the variable assignment that +# follows is actively interpreted. \\ +VAR= not part of the comment +.if ${VAR} != "not part of the comment" +. error +.endif + +# To escape a comment sign, precede it with a backslash. +VAR= \# # Both in the assignment. +.if ${VAR} != "\#" # And in the comparison. +. error +.endif + +# 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. +WORDS= ${VAR:[#]} [# +.if ${WORDS} != "1 [#" +. error +.endif + +# An odd number of backslashes makes a line continuation, \\\ +no matter if it is 3 or 5 \\\\\ +or 9 backslashes. \\\\\\\\\ +This is the last line of the comment. +VAR= no comment anymore +.if ${VAR} != "no comment anymore" +. error +.endif + +all: +# In the commands associated with a target, the '#' does not start a makefile +# comment. The '#' is just passed to the shell, like any ordinary character. + echo This is a shell comment: # comment +# If the '#' were to start a makefile comment, the following shell command +# would have unbalanced quotes. + echo This is not a shell comment: '# comment' + @echo A shell comment can#not start in the middle of a word. diff --git a/usr.bin/make/unit-tests/compat-error.exp b/usr.bin/make/unit-tests/compat-error.exp new file mode 100644 index 000000000..256cb6d43 --- /dev/null +++ b/usr.bin/make/unit-tests/compat-error.exp @@ -0,0 +1,15 @@ +: Making success1 out of nothing. +: Making fail1 out of nothing. +false 'fail1' '${.TARGET}' '$${.TARGET}' +*** Error code 1 (continuing) +: Making success2 out of nothing. +: Making fail2 out of nothing. +false 'fail2' '${.TARGET}' '$${.TARGET}' +*** Error code 1 (continuing) +: Making success3 out of nothing. + +Stop. +make: stopped in unit-tests +.ERROR target: +.ERROR command: <> +exit status 1 diff --git a/usr.bin/make/unit-tests/compat-error.mk b/usr.bin/make/unit-tests/compat-error.mk new file mode 100644 index 000000000..4cbc48d4b --- /dev/null +++ b/usr.bin/make/unit-tests/compat-error.mk @@ -0,0 +1,37 @@ +# $NetBSD: compat-error.mk,v 1.3 2020/12/13 19:33:53 rillig Exp $ +# +# Test detailed error handling in compat mode. +# +# Until 2020-12-13, .ERROR_TARGET was success3, which was wrong. +# Since compat.c 1.215 from 2020-12-13, it is 'fail1', which is the first +# failed top-level target. XXX: Even better would be if .ERROR_TARGET were +# the smallest target that caused the build to fail, even if it were a +# sub-sub-sub-dependency of a top-level target. +# +# XXX: As of 2020-12-13, .ERROR_CMD is empty, which is wrong. +# +# See also: +# Compat_Run +# +# The commit that added the NULL command to gn->commands: +# CVS: 1994.06.06.22.45.?? +# Git: 26a8972fd7f982502c5fbfdabd34578b99d77ca5 +# 1994: Lst_Replace (cmdNode, (ClientData) NULL); +# 2020: LstNode_SetNull(cmdNode); +# +# The commit that skipped NULL commands for .ERROR_CMD: +# CVS: 2016.08.11.19.53.?? +# Git: 58b23478b7353d46457089e726b07a49197388e4 + +.MAKEFLAGS: success1 fail1 success2 fail2 success3 + +success1 success2 success3: + : Making ${.TARGET} out of nothing. + +fail1 fail2: + : Making ${.TARGET} out of nothing. + false '${.TARGET}' '$${.TARGET}' '$$$${.TARGET}' + +.ERROR: + @echo ${.TARGET} target: '<'${.ERROR_TARGET:Q}'>' + @echo ${.TARGET} command: '<'${.ERROR_CMD:Q}'>' diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-eq.exp b/usr.bin/make/unit-tests/cond-cmp-numeric-eq.exp new file mode 100644 index 000000000..72d3d9357 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-eq.exp @@ -0,0 +1,5 @@ +make: "cond-cmp-numeric-eq.mk" line 67: Malformed conditional (!(12345 = 12345)) +make: "cond-cmp-numeric-eq.mk" line 74: Malformed conditional (!(12345 === 12345)) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-eq.mk b/usr.bin/make/unit-tests/cond-cmp-numeric-eq.mk new file mode 100644 index 000000000..c6b39876e --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-eq.mk @@ -0,0 +1,81 @@ +# $NetBSD: cond-cmp-numeric-eq.mk,v 1.5 2020/11/08 21:47:59 rillig Exp $ +# +# Tests for numeric comparisons with the == operator in .if conditions. + +# This comparison yields the same result, whether numeric or character-based. +.if 1 == 1 +.else +. error +.endif + +# This comparison yields the same result, whether numeric or character-based. +.if 1 == 2 +. error +.endif + +.if 2 == 1 +. error +.endif + +# Scientific notation is supported, as per strtod. +.if 2e7 == 2000e4 +.else +. error +.endif + +.if 2000e4 == 2e7 +.else +. error +.endif + +# Trailing zeroes after the decimal point are irrelevant for the numeric +# value. +.if 3.30000 == 3.3 +.else +. error +.endif + +.if 3.3 == 3.30000 +.else +. error +.endif + +# As of 2020-08-23, numeric comparison is implemented as parsing both sides +# as double, and then performing a normal comparison. The range of double is +# typically 16 or 17 significant digits, therefore these two numbers seem to +# be equal. +.if 1.000000000000000001 == 1.000000000000000002 +.else +. error +.endif + +# Because an IEEE 754 double can only hold integers with a mantissa of 53 +# bits, these two numbers are considered the same. The 993 is rounded down +# to the 992. +.if 9007199254740993 == 9007199254740992 +.else +. error +.endif +# The 995 is rounded up, the 997 is rounded down. +.if 9007199254740995 == 9007199254740997 +.else +. error Probably a misconfiguration in the floating point environment, \ + or maybe a machine without IEEE 754 floating point support. +.endif + +# There is no = operator for numbers. +.if !(12345 = 12345) +. error +.else +. error +.endif + +# There is no === operator for numbers either. +.if !(12345 === 12345) +. error +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/qequals.exp b/usr.bin/make/unit-tests/cond-cmp-numeric-ge.exp similarity index 51% rename from usr.bin/make/unit-tests/qequals.exp rename to usr.bin/make/unit-tests/cond-cmp-numeric-ge.exp index 6b2f4dce6..39a938395 100644 --- a/usr.bin/make/unit-tests/qequals.exp +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-ge.exp @@ -1,2 +1 @@ -V.i386 ?= OK exit status 0 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-ge.mk b/usr.bin/make/unit-tests/cond-cmp-numeric-ge.mk new file mode 100644 index 000000000..e64be7f0c --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-ge.mk @@ -0,0 +1,75 @@ +# $NetBSD: cond-cmp-numeric-ge.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for numeric comparisons with the >= operator in .if conditions. + +# When both sides are equal, the >= operator always yields true. +.if 1 >= 1 +.else +. error +.endif + +# This comparison yields the same result, whether numeric or character-based. +.if 1 >= 2 +. error +.endif + +.if 2 >= 1 +.else +. error +.endif + +# If this comparison were character-based instead of numerical, the +# 5 would be >= 14 since its first digit is greater. +.if 5 >= 14 +. error +.endif + +.if 14 >= 5 +.else +. error +.endif + +# Scientific notation is supported, as per strtod. +.if 2e7 >= 1e8 +. error +.endif + +.if 1e8 >= 2e7 +.else +. error +.endif + +# Floating pointer numbers can be compared as well. +# This might be tempting to use for version numbers, but there are a few pitfalls. +.if 3.141 >= 111.222 +. error +.endif + +.if 111.222 >= 3.141 +.else +. error +.endif + +# When parsed as a version number, 3.30 is greater than 3.7. +# Since make parses numbers as plain numbers, that leads to wrong results. +# Numeric comparisons are not suited for comparing version number. +.if 3.30 >= 3.7 +. error +.endif + +.if 3.7 >= 3.30 +.else +. error +.endif + +# As of 2020-08-23, numeric comparison is implemented as parsing both sides +# as double, and then performing a normal comparison. The range of double is +# typically 16 or 17 significant digits, therefore these two numbers seem to +# be equal. +.if 1.000000000000000001 >= 1.000000000000000002 +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-gt.exp b/usr.bin/make/unit-tests/cond-cmp-numeric-gt.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-gt.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-gt.mk b/usr.bin/make/unit-tests/cond-cmp-numeric-gt.mk new file mode 100644 index 000000000..1cdcc9891 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-gt.mk @@ -0,0 +1,73 @@ +# $NetBSD: cond-cmp-numeric-gt.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for numeric comparisons with the > operator in .if conditions. + +# When both sides are equal, the > operator always yields false. +.if 1 > 1 +. error +.endif + +# This comparison yields the same result, whether numeric or character-based. +.if 1 > 2 +. error +.endif + +.if 2 > 1 +.else +. error +.endif + +# If this comparison were character-based instead of numerical, the +# 5 would be > 14 since its first digit is greater. +.if 5 > 14 +. error +.endif + +.if 14 > 5 +.else +. error +.endif + +# Scientific notation is supported, as per strtod. +.if 2e7 > 1e8 +. error +.endif + +.if 1e8 > 2e7 +.else +. error +.endif + +# Floating pointer numbers can be compared as well. +# This might be tempting to use for version numbers, but there are a few pitfalls. +.if 3.141 > 111.222 +. error +.endif + +.if 111.222 > 3.141 +.else +. error +.endif + +# When parsed as a version number, 3.30 is greater than 3.7. +# Since make parses numbers as plain numbers, that leads to wrong results. +# Numeric comparisons are not suited for comparing version number. +.if 3.30 > 3.7 +. error +.endif + +.if 3.7 > 3.30 +.else +. error +.endif + +# As of 2020-08-23, numeric comparison is implemented as parsing both sides +# as double, and then performing a normal comparison. The range of double is +# typically 16 or 17 significant digits, therefore these two numbers seem to +# be equal. +.if 1.000000000000000001 > 1.000000000000000002 +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-le.exp b/usr.bin/make/unit-tests/cond-cmp-numeric-le.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-le.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-le.mk b/usr.bin/make/unit-tests/cond-cmp-numeric-le.mk new file mode 100644 index 000000000..05f5e8dba --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-le.mk @@ -0,0 +1,75 @@ +# $NetBSD: cond-cmp-numeric-le.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for numeric comparisons with the <= operator in .if conditions. + +# When both sides are equal, the <= operator always yields true. +.if 1 <= 1 +.else +. error +.endif + +# This comparison yields the same result, whether numeric or character-based. +.if 1 <= 2 +.else +. error +.endif + +.if 2 <= 1 +. error +.endif + +# If this comparison were character-based instead of numerical, the +# 5 would be >= 14 since its first digit is greater. +.if 5 <= 14 +.else +. error +.endif + +.if 14 <= 5 +. error +.endif + +# Scientific notation is supported, as per strtod. +.if 2e7 <= 1e8 +.else +. error +.endif + +.if 1e8 <= 2e7 +. error +.endif + +# Floating pointer numbers can be compared as well. +# This might be tempting to use for version numbers, but there are a few pitfalls. +.if 3.141 <= 111.222 +.else +. error +.endif + +.if 111.222 <= 3.141 +. error +.endif + +# When parsed as a version number, 3.30 is greater than 3.7. +# Since make parses numbers as plain numbers, that leads to wrong results. +# Numeric comparisons are not suited for comparing version number. +.if 3.30 <= 3.7 +.else +. error +.endif + +.if 3.7 <= 3.30 +. error +.endif + +# As of 2020-08-23, numeric comparison is implemented as parsing both sides +# as double, and then performing a normal comparison. The range of double is +# typically 16 or 17 significant digits, therefore these two numbers seem to +# be equal. +.if 1.000000000000000001 <= 1.000000000000000002 +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-lt.exp b/usr.bin/make/unit-tests/cond-cmp-numeric-lt.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-lt.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-lt.mk b/usr.bin/make/unit-tests/cond-cmp-numeric-lt.mk new file mode 100644 index 000000000..b0dddd591 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-lt.mk @@ -0,0 +1,73 @@ +# $NetBSD: cond-cmp-numeric-lt.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for numeric comparisons with the < operator in .if conditions. + +# When both sides are equal, the < operator always yields false. +.if 1 < 1 +. error +.endif + +# This comparison yields the same result, whether numeric or character-based. +.if 1 < 2 +.else +. error +.endif + +.if 2 < 1 +. error +.endif + +# If this comparison were character-based instead of numerical, the +# 5 would be > 14 since its first digit is greater. +.if 5 < 14 +.else +. error +.endif + +.if 14 < 5 +. error +.endif + +# Scientific notation is supported, as per strtod. +.if 2e7 < 1e8 +.else +. error +.endif + +.if 1e8 < 2e7 +. error +.endif + +# Floating pointer numbers can be compared as well. +# This might be tempting to use for version numbers, but there are a few pitfalls. +.if 3.141 < 111.222 +.else +. error +.endif + +.if 111.222 < 3.141 +. error +.endif + +# When parsed as a version number, 3.30 is greater than 3.7. +# Since make parses numbers as plain numbers, that leads to wrong results. +# Numeric comparisons are not suited for comparing version number. +.if 3.30 < 3.7 +.else +. error +.endif + +.if 3.7 < 3.30 +. error +.endif + +# As of 2020-08-23, numeric comparison is implemented as parsing both sides +# as double, and then performing a normal comparison. The range of double is +# typically 16 or 17 significant digits, therefore these two numbers seem to +# be equal. +.if 1.000000000000000001 < 1.000000000000000002 +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-ne.exp b/usr.bin/make/unit-tests/cond-cmp-numeric-ne.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-ne.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric-ne.mk b/usr.bin/make/unit-tests/cond-cmp-numeric-ne.mk new file mode 100644 index 000000000..0a366a905 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric-ne.mk @@ -0,0 +1,49 @@ +# $NetBSD: cond-cmp-numeric-ne.mk,v 1.2 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for numeric comparisons with the != operator in .if conditions. + +# When both sides are equal, the != operator always yields false. +.if 1 != 1 +. error +.endif + +# This comparison yields the same result, whether numeric or character-based. +.if 1 != 2 +.else +. error +.endif + +.if 2 != 1 +.else +. error +.endif + +# Scientific notation is supported, as per strtod. +.if 2e7 != 2000e4 +. error +.endif + +.if 2000e4 != 2e7 +. error +.endif + +# Trailing zeroes after the decimal point are irrelevant for the numeric +# value. +.if 3.30000 != 3.3 +. error +.endif + +.if 3.3 != 3.30000 +. error +.endif + +# As of 2020-08-23, numeric comparison is implemented as parsing both sides +# as double, and then performing a normal comparison. The range of double is +# typically 16 or 17 significant digits, therefore these two numbers seem to +# be equal. +.if 1.000000000000000001 != 1.000000000000000002 +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric.exp b/usr.bin/make/unit-tests/cond-cmp-numeric.exp new file mode 100644 index 000000000..4a97b6879 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric.exp @@ -0,0 +1,11 @@ +CondParser_Eval: !(${:UINF} > 1e100) +make: "cond-cmp-numeric.mk" line 11: String comparison operator must be either == or != +CondParser_Eval: ${:UNaN} > NaN +make: "cond-cmp-numeric.mk" line 16: String comparison operator must be either == or != +CondParser_Eval: !(${:UNaN} == NaN) +lhs = "NaN", rhs = "NaN", op = == +CondParser_Eval: 123 ! 123 +make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-cmp-numeric.mk b/usr.bin/make/unit-tests/cond-cmp-numeric.mk new file mode 100644 index 000000000..b1ec3e719 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-numeric.mk @@ -0,0 +1,41 @@ +# $NetBSD: cond-cmp-numeric.mk,v 1.4 2020/11/08 22:56:16 rillig Exp $ +# +# Tests for numeric comparisons in .if conditions. + +.MAKEFLAGS: -dc + +# The ${:U...} on the left-hand side is necessary for the parser. + +# Even if strtod(3) parses "INF" as +Infinity, make does not accept this +# since it is not really a number; see TryParseNumber. +.if !(${:UINF} > 1e100) +. error +.endif + +# Neither is NaN a number; see TryParseNumber. +.if ${:UNaN} > NaN +. error +.endif + +# Since NaN is not parsed as a number, both operands are interpreted +# as strings and are therefore equal. If they were parsed as numbers, +# they would compare unequal, since NaN is unequal to any and everything, +# including itself. +.if !(${:UNaN} == NaN) +. error +.endif + +# The parsing code in CondParser_Comparison only performs a light check on +# whether the operator is valid, leaving the rest of the work to the +# evaluation functions EvalCompareNum and EvalCompareStr. Ensure that this +# parse error is properly reported. +# +# XXX: The warning message does not mention the actual operator. +.if 123 ! 123 +. error +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-cmp-string.exp b/usr.bin/make/unit-tests/cond-cmp-string.exp new file mode 100644 index 000000000..a10341ed2 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-string.exp @@ -0,0 +1,11 @@ +make: "cond-cmp-string.mk" line 18: Malformed conditional (str != str) +make: "cond-cmp-string.mk" line 42: Malformed conditional ("string" != "str""ing") +make: "cond-cmp-string.mk" line 49: Malformed conditional (!("value" = "value")) +make: "cond-cmp-string.mk" line 56: Malformed conditional (!("value" === "value")) +make: "cond-cmp-string.mk" line 113: String comparison operator must be either == or != +make: "cond-cmp-string.mk" line 120: String comparison operator must be either == or != +make: "cond-cmp-string.mk" line 127: String comparison operator must be either == or != +make: "cond-cmp-string.mk" line 134: String comparison operator must be either == or != +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-cmp-string.mk b/usr.bin/make/unit-tests/cond-cmp-string.mk new file mode 100644 index 000000000..9f3e731b2 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-string.mk @@ -0,0 +1,138 @@ +# $NetBSD: cond-cmp-string.mk,v 1.14 2021/01/19 19:54:57 rillig Exp $ +# +# Tests for string comparisons in .if conditions. + +# This is a simple comparison of string literals. +# Nothing surprising here. +.if "str" != "str" +. error +.endif + +# The right-hand side of the comparison may be written without quotes. +.if "str" != str +. error +.endif + +# The left-hand side of the comparison must be enclosed in quotes. +# This one is not enclosed in quotes and thus generates an error message. +.if str != str +. error +.endif + +# The left-hand side of the comparison requires that any variable 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 +# an undefined expression turns it into a defined expression. +# +# See ApplyModifier_Defined and VEF_DEF. +.if ${:Ustr} != "str" +. error +.endif + +# Any character in a string literal may be escaped using a backslash. +# This means that "\n" does not mean a newline but a simple "n". +.if "string" != "\s\t\r\i\n\g" +. error +.endif + +# It is not possible to concatenate two string literals to form a single +# string. In C, Python and the shell this is possible, but not in make. +.if "string" != "str""ing" +. error +.else +. error +.endif + +# There is no = operator for strings. +.if !("value" = "value") +. error +.else +. error +.endif + +# There is no === operator for strings either. +.if !("value" === "value") +. error +.else +. error +.endif + +# A variable 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 +# "Malformed conditional", even though the string was well-formed. +.if ${:Uword } != "${:Uword} " +. error +.endif +# Some other characters worked though, and some didn't. +# Those that are mentioned in is_separator didn't work. +.if ${:Uword0} != "${:Uword}0" +. error +.endif +.if ${:Uword&} != "${:Uword}&" +. error +.endif +.if ${:Uword!} != "${:Uword}!" +. error +.endif +.if ${:Uword<} != "${:Uword}<" +. error +.endif + +# Adding another variable 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 +# though. +.if ${:U word } != " ${:Uword} " +. error +.endif + +# If at least one side of the comparison is a string literal, the string +# comparison is performed. +.if 12345 != "12345" +. error +.endif + +# If at least one side of the comparison is a string literal, the string +# comparison is performed. The ".0" in the left-hand side makes the two +# sides of the equation unequal. +.if 12345.0 == "12345" +. error +.endif + +# Strings cannot be compared relationally, only for equality. +.if "string" < "string" +. error +.else +. error +.endif + +# Strings cannot be compared relationally, only for equality. +.if "string" <= "string" +. error +.else +. error +.endif + +# Strings cannot be compared relationally, only for equality. +.if "string" > "string" +. error +.else +. error +.endif + +# Strings cannot be compared relationally, only for equality. +.if "string" >= "string" +. error +.else +. error +.endif diff --git a/usr.bin/make/unit-tests/cond-cmp-unary.exp b/usr.bin/make/unit-tests/cond-cmp-unary.exp new file mode 100644 index 000000000..89f90dc16 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-unary.exp @@ -0,0 +1,2 @@ +make: "cond-cmp-unary.mk" line 53: This is only reached because of a bug in EvalNotEmpty. +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-cmp-unary.mk b/usr.bin/make/unit-tests/cond-cmp-unary.mk new file mode 100644 index 000000000..168de0f30 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-cmp-unary.mk @@ -0,0 +1,58 @@ +# $NetBSD: cond-cmp-unary.mk,v 1.2 2020/11/11 07:30:11 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, +# if it is a string, it is tested for emptiness. + +# The number 0 evaluates to false. +.if 0 +. error +.endif + +# Any other number evaluates to true. +.if !12345 +. error +.endif + +# The empty string evaluates to false. +.if "" +. error +.endif + +# Any other string evaluates to true. +.if !"0" +. error +.endif + +# The empty string may come from a variable expression. +# +# XXX: As of 2020-11-11, this empty string is interpreted "as a number" in +# EvalNotEmpty, which is plain wrong. The bug is in TryParseNumber. +.if ${:U} +. error +.endif + +# A variable 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. +.if !${:U12345} +. error +.endif + +# A string of whitespace should evaluate to false. +# +# XXX: As of 2020-11-11, the implementation in EvalNotEmpty does not skip +# whitespace before testing for the end. This was probably an oversight in +# a commit from 1992-04-15 saying "A variable is empty when it just contains +# spaces". +.if ${:U } +. info This is only reached because of a bug in EvalNotEmpty. +.else +. error +.endif + +all: # nothing diff --git a/usr.bin/make/unit-tests/cond-eof.exp b/usr.bin/make/unit-tests/cond-eof.exp new file mode 100644 index 000000000..3b1e6eb1f --- /dev/null +++ b/usr.bin/make/unit-tests/cond-eof.exp @@ -0,0 +1,9 @@ +side effect +make: "cond-eof.mk" line 15: Malformed conditional (0 ${SIDE_EFFECT} ${SIDE_EFFECT2}) +side effect +make: "cond-eof.mk" line 17: Malformed conditional (1 ${SIDE_EFFECT} ${SIDE_EFFECT2}) +side effect +make: "cond-eof.mk" line 19: Malformed conditional ((0) ${SIDE_EFFECT} ${SIDE_EFFECT2}) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-eof.mk b/usr.bin/make/unit-tests/cond-eof.mk new file mode 100644 index 000000000..08f432bc4 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-eof.mk @@ -0,0 +1,20 @@ +# $NetBSD: cond-eof.mk,v 1.2 2020/12/14 20:28:09 rillig Exp $ +# +# Tests for parsing conditions, especially the end of such conditions, which +# are represented as the token TOK_EOF. + +SIDE_EFFECT= ${:!echo 'side effect' 1>&2!} +SIDE_EFFECT2= ${:!echo 'side effect 2' 1>&2!} + +# In the following conditions, ${SIDE_EFFECT} is the position of the first +# parse error. It is always fully evaluated, even if it were not necessary +# to expand the variable expression. This is because these syntax errors are +# an edge case that does not occur during normal operation, therefore there +# is no need to optimize for this case, and it would slow down the common +# case as well. +.if 0 ${SIDE_EFFECT} ${SIDE_EFFECT2} +.endif +.if 1 ${SIDE_EFFECT} ${SIDE_EFFECT2} +.endif +.if (0) ${SIDE_EFFECT} ${SIDE_EFFECT2} +.endif diff --git a/usr.bin/make/unit-tests/cond-func-commands.exp b/usr.bin/make/unit-tests/cond-func-commands.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-commands.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-func-commands.mk b/usr.bin/make/unit-tests/cond-func-commands.mk new file mode 100644 index 000000000..e127a8ebd --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-commands.mk @@ -0,0 +1,37 @@ +# $NetBSD: cond-func-commands.mk,v 1.5 2020/11/15 14:07:53 rillig Exp $ +# +# Tests for the commands() function in .if conditions. + +.MAIN: all + +# At this point, the target 'target' does not exist yet, therefore it cannot +# have commands. Sounds obvious, but good to know that it is really so. +.if commands(target) +. error +.endif + +target: + +# Now the target exists, but it still has no commands. +.if commands(target) +. error +.endif + +target: + # not a command + +# Even after the comment, the target still has no commands. +.if commands(target) +. error +.endif + +target: + @:; + +# Finally the target has commands. +.if !commands(target) +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-func-defined.exp b/usr.bin/make/unit-tests/cond-func-defined.exp new file mode 100644 index 000000000..878f56de2 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-defined.exp @@ -0,0 +1,8 @@ +make: "cond-func-defined.mk" line 23: Missing closing parenthesis for defined() +make: "cond-func-defined.mk" line 33: Missing closing parenthesis for defined() +make: "cond-func-defined.mk" line 45: In .for loops, variable expressions for the loop variables are +make: "cond-func-defined.mk" line 46: substituted at evaluation time. There is no actual variable +make: "cond-func-defined.mk" line 47: involved, even if it feels like it. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-func-defined.mk b/usr.bin/make/unit-tests/cond-func-defined.mk new file mode 100644 index 000000000..2aa49ccbf --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-defined.mk @@ -0,0 +1,52 @@ +# $NetBSD: cond-func-defined.mk,v 1.7 2020/11/15 14:07:53 rillig Exp $ +# +# Tests for the defined() function in .if conditions. + +DEF= defined +${:UA B}= variable name with spaces + +.if !defined(DEF) +. error +.endif + +# Horizontal whitespace (space tab) after the opening parenthesis is ignored. +.if !defined( DEF) +. error +.endif + +# Horizontal whitespace (space tab) before the closing parenthesis is ignored. +.if !defined(DEF ) +. error +.endif + +# The argument of a function must not directly contain whitespace. +.if !defined(A B) +. error +.endif + +# If necessary, the whitespace can be generated by a variable expression. +.if !defined(${:UA B}) +. error +.endif + +# Parse error: missing closing parenthesis; see ParseFuncArg. +.if defined(DEF +. error +.else +. error +.endif + +# Variables from .for loops are not defined. +# See directive-for.mk for more details. +.for var in value +. if defined(var) +. error +. else +. info In .for loops, variable expressions for the loop variables are +. info substituted at evaluation time. There is no actual variable +. info involved, even if it feels like it. +. endif +.endfor + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-func-empty.exp b/usr.bin/make/unit-tests/cond-func-empty.exp new file mode 100644 index 000000000..77a4edd47 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-empty.exp @@ -0,0 +1,5 @@ +make: "cond-func-empty.mk" line 152: Unclosed variable "WORD" +make: "cond-func-empty.mk" line 152: Malformed conditional (empty(WORD) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-func-empty.mk b/usr.bin/make/unit-tests/cond-func-empty.mk new file mode 100644 index 000000000..53d8557ce --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-empty.mk @@ -0,0 +1,184 @@ +# $NetBSD: cond-func-empty.mk,v 1.13 2021/03/15 12:15:03 rillig Exp $ +# +# Tests for the empty() function in .if conditions, which tests a variable +# expression for emptiness. +# +# Note that the argument in the parentheses is indeed a variable name, +# optionally followed by variable modifiers. +# + +.undef UNDEF +EMPTY= # empty +SPACE= ${:U } +WORD= word + +# An undefined variable is empty. +.if !empty(UNDEF) +. error +.endif + +# An undefined variable has the empty string as the value, and the :M +# variable modifier does not change that. +# +.if !empty(UNDEF:M*) +. error +.endif + +# The :S modifier replaces the empty value with an actual word. The +# expression is now no longer empty, but it is still possible to see whether +# the expression was based on an undefined variable. The expression has the +# flag VEF_UNDEF. +# +# The expression does not have the flag VEF_DEF though, therefore it is still +# considered undefined. Yes, indeed, undefined but not empty. There are a +# few variable modifiers that turn an undefined expression into a defined +# expression, among them :U and :D, but not :S. +# +# XXX: This is hard to explain to someone who doesn't know these +# implementation details. +# +.if !empty(UNDEF:S,^$,value,W) +. error +.endif + +# The :U modifier modifies expressions based on undefined variables +# (DEF_UNDEF) by adding the DEF_DEFINED flag, which marks the expression +# as "being interesting enough to be further processed". +# +.if empty(UNDEF:S,^$,value,W:Ufallback) +. error +.endif + +# And now to the surprising part. Applying the following :S modifier to the +# undefined expression makes it non-empty, but the marker VEF_UNDEF is +# preserved nevertheless. The :U modifier that follows only looks at the +# VEF_UNDEF flag to decide whether the variable is defined or not. This kind +# of makes sense since the :U modifier tests the _variable_, not the +# _expression_. +# +# But since the variable was undefined to begin with, the fallback value from +# the :U modifier is used in this expression. +# +.if ${UNDEF:S,^$,value,W:Ufallback} != "fallback" +. error +.endif + +# The variable EMPTY is completely empty (0 characters). +.if !empty(EMPTY) +. error +.endif + +# The variable SPACE has a single space, which counts as being empty. +.if !empty(SPACE) +. error +.endif + +# The variable .newline has a single newline, which counts as being empty. +.if !empty(.newline) +. error +.endif + +# The empty variable named "" gets a fallback value of " ", which counts as +# empty. +# +# Contrary to the other functions in conditionals, the trailing space is not +# stripped off, as can be seen in the -dv debug log. If the space had been +# stripped, it wouldn't make a difference in this case. +# +.if !empty(:U ) +. error +.endif + +# Now the variable named " " gets a non-empty value, which demonstrates that +# neither leading nor trailing spaces are trimmed in the argument of the +# function. If the spaces were trimmed, the variable name would be "" and +# that variable is indeed undefined. Since ParseEmptyArg calls Var_Parse +# without VarEvalFlags.undefErr, the value of the undefined variable is +# returned as an empty string. +${:U }= space +.if empty( ) +. error +.endif + +# The value of the following expression is " word", which is not empty. +.if empty(:U word) +. error +.endif + +# The :L modifier creates a variable expression that has the same value as +# its name, which both are "VAR" in this case. The value is therefore not +# empty. +.if empty(VAR:L) +. error +.endif + +# The variable WORD has the value "word", which does not count as empty. +.if empty(WORD) +. error +.endif + +# The expression ${} for a variable with the empty name always evaluates +# to an empty string (see Var_Parse, varUndefined). +.if !empty() +. error +.endif + +# Ensure that variable expressions that appear as part of the argument are +# properly parsed. Typical use cases for this are .for loops, which are +# expanded to exactly these ${:U} expressions. +# +# If everything goes well, the argument expands to "WORD", and that variable +# is defined at the beginning of this file. The surrounding 'W' and 'D' +# ensure that the parser in ParseEmptyArg has the correct position, both +# before and after the call to Var_Parse. +.if empty(W${:UOR}D) +. error +.endif + +# There may be spaces at the outside of the parentheses. +# Spaces inside the parentheses are interpreted as part of the variable name. +.if ! empty ( WORD ) +. error +.endif + +${:U WORD }= variable name with spaces + +# Now there is a variable named " WORD ", and it is not empty. +.if empty ( WORD ) +. error +.endif + +# Parse error: missing closing parenthesis. +.if empty(WORD +. error +.else +. error +.endif + +# Between 2020-06-28 and var.c 1.226 from 2020-07-02, this paragraph generated +# a wrong error message "Variable VARNAME is recursive". +# +# The bug was that the !empty() condition was evaluated, even though this was +# not necessary since the defined() condition already evaluated to false. +# +# When evaluating the !empty condition, the variable name was parsed as +# "VARNAME${:U2}", but without expanding any nested variable expression, in +# this case the ${:U2}. Therefore, the variable name came out as simply +# "VARNAME". Since this variable name should have been discarded quickly after +# parsing it, this unrealistic variable name should have done no harm. +# +# The variable expression was expanded though, and this was wrong. The +# expansion was done without VarEvalFlags.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 +# only if the flag VarEvalFlags.wantRes is given. +VARNAME= ${VARNAME${:U1}} +.if defined(VARNAME${:U2}) && !empty(VARNAME${:U2}) +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-func-exists.exp b/usr.bin/make/unit-tests/cond-func-exists.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-exists.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-func-exists.mk b/usr.bin/make/unit-tests/cond-func-exists.mk new file mode 100644 index 000000000..48d7e727d --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-exists.mk @@ -0,0 +1,51 @@ +# $NetBSD: cond-func-exists.mk,v 1.6 2020/11/30 20:12:29 rillig Exp $ +# +# Tests for the exists() function in .if conditions. + +.if !exists(.) +. error +.endif + +# The argument to the function must not be enclosed in quotes. +# Neither double quotes nor single quotes are allowed. +.if exists(".") +. error +.endif + +.if exists('.') +. error +.endif + +# The only way to escape characters that would otherwise influence the parser +# is to enclose them in a variable expression. For function arguments, +# neither the backslash nor the dollar sign act as escape character. +.if exists(\.) +. error +.endif + +.if !exists(${:U.}) +. error +.endif + +# The argument to the function can have several variable expressions. +# See cond-func.mk for the characters that cannot be used directly. +.if !exists(${.PARSEDIR}/${.PARSEFILE}) +. error +.endif + +# Whitespace is trimmed on both sides of the function argument. +.if !exists( . ) +. error +.endif + +# The exists function does not really look up the file in the file system, +# instead it uses a cache that is preloaded very early, before parsing the +# first makefile. At that time, the file did not exist yet. +_!= > cond-func-exists.just-created +.if exists(cond-func-exists.just-created) +. error +.endif +_!= rm cond-func-exists.just-created + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-func-make-main.exp b/usr.bin/make/unit-tests/cond-func-make-main.exp new file mode 100644 index 000000000..3266459f3 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-make-main.exp @@ -0,0 +1,3 @@ +: Making dot-main-target-1a. +: Making dot-main-target-1b. +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-func-make-main.mk b/usr.bin/make/unit-tests/cond-func-make-main.mk new file mode 100644 index 000000000..31b370afa --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-make-main.mk @@ -0,0 +1,62 @@ +# $NetBSD: cond-func-make-main.mk,v 1.1 2020/11/22 19:37:27 rillig Exp $ +# +# Test how accurately the make() function in .if conditions reflects +# what is actually made. +# +# There are several ways to specify what is being made: +# +# 1. The default main target is the first target in the given makefiles that +# is not one of the special targets. For example, .PHONY is special when +# it appears on the left-hand side of the ':'. It is not special on the +# right-hand side though. +# +# 2. Command line arguments that are neither options (-ds or -k) nor variable +# assignments (VAR=value) are interpreted as targets to be made. These +# override the default main target from above. +# +# 3. All sources of the first '.MAIN: sources' line. Any further .MAIN line +# is treated as if .MAIN were a regular name. +# +# This test only covers items 1 and 3. For item 2, see cond-func-make.mk. + +first-main-target: + : Making ${.TARGET}. + +# Even though the main-target would actually be made at this point, it is +# ignored by the make() function. +.if make(first-main-target) +. error +.endif + +# Declaring a target via the .MAIN dependency adds it to the targets to be +# created (opts.create), but only that list was empty at the beginning of +# the line. This implies that several main targets can be set at the name +# time, but they have to be in the same dependency group. +# +# See ParseDoDependencyTargetSpecial, branch SP_MAIN. +.MAIN: dot-main-target-1a dot-main-target-1b + +.if !make(dot-main-target-1a) +. error +.endif +.if !make(dot-main-target-1b) +. error +.endif + +dot-main-target-{1,2}{a,b}: + : Making ${.TARGET}. + +# At this point, the list of targets to be made (opts.create) is not empty +# anymore. ParseDoDependencyTargetSpecial therefore treats the .MAIN as if +# it were an ordinary target. Since .MAIN is not listed as a dependency +# anywhere, it is not made. +.if target(.MAIN) +. error +.endif +.MAIN: dot-main-target-2a dot-main-target-2b +.if !target(.MAIN) +. error +.endif +.if make(dot-main-target-2a) +. error +.endif diff --git a/usr.bin/make/unit-tests/cond-func-make.exp b/usr.bin/make/unit-tests/cond-func-make.exp new file mode 100644 index 000000000..922203b72 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-make.exp @@ -0,0 +1,3 @@ +: via-cmdline +: via-dot-makeflags +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-func-make.mk b/usr.bin/make/unit-tests/cond-func-make.mk new file mode 100644 index 000000000..d75b69bcf --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-make.mk @@ -0,0 +1,24 @@ +# $NetBSD: cond-func-make.mk,v 1.3 2020/09/25 20:11:06 rillig Exp $ +# +# Tests for the make() function in .if conditions, which tests whether +# the argument has been passed as a target via the command line or later +# via the .MAKEFLAGS special dependency target. + +.if !make(via-cmdline) +. error +.endif +.if make(via-dot-makeflags) +. error +.endif + +.MAKEFLAGS: via-dot-makeflags + +.if !make(via-cmdline) +. error +.endif +.if !make(via-dot-makeflags) +. error +.endif + +via-cmdline via-dot-makeflags: + : $@ diff --git a/usr.bin/make/unit-tests/cond-func-target.exp b/usr.bin/make/unit-tests/cond-func-target.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-target.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-func-target.mk b/usr.bin/make/unit-tests/cond-func-target.mk new file mode 100644 index 000000000..62266839d --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func-target.mk @@ -0,0 +1,38 @@ +# $NetBSD: cond-func-target.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for the target() function in .if conditions. + +.MAIN: all + +# The target "target" does not exist yet. +.if target(target) +. error +.endif + +target: + +# The target exists, even though it does not have any commands. +.if !target(target) +. error +.endif + +target: + # not a command + +# Adding a comment to an existing target does not change whether the target +# is defined or not. +.if !target(target) +. error +.endif + +target: + @:; + +# Adding a command to an existing target does not change whether the target +# is defined or not. +.if !target(target) +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-func.exp b/usr.bin/make/unit-tests/cond-func.exp new file mode 100644 index 000000000..855b9e521 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func.exp @@ -0,0 +1,12 @@ +make: "cond-func.mk" line 36: Missing closing parenthesis for defined() +make: "cond-func.mk" line 51: Missing closing parenthesis for defined() +make: "cond-func.mk" line 54: Missing closing parenthesis for defined() +make: "cond-func.mk" line 94: The empty variable is never defined. +make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...). +make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...). +make: "cond-func.mk" line 119: Symbols may start with a function name. +make: "cond-func.mk" line 124: Symbols may start with a function name. +make: "cond-func.mk" line 130: Malformed conditional (defined() +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-func.mk b/usr.bin/make/unit-tests/cond-func.mk new file mode 100644 index 000000000..4ff43b72e --- /dev/null +++ b/usr.bin/make/unit-tests/cond-func.mk @@ -0,0 +1,137 @@ +# $NetBSD: cond-func.mk,v 1.9 2020/11/15 14:07:53 rillig Exp $ +# +# Tests for those parts of the functions in .if conditions that are common +# among several functions. +# +# The below test uses the function defined(...) since it has no side-effects, +# the other functions (except empty(...)) would work equally well. The +# function empty is special because it uses a different parsing algorithm for +# its argument. + +DEF= defined +${:UA B}= variable name with spaces +${:UVAR(value)}= variable name with parentheses +${:UVAR{value}}= variable name with balanced braces + +# Really strange variable names must be given indirectly via another variable, +# so that no unbalanced braces appear in the top-level expression. +VARNAME_UNBALANCED_BRACES= VAR{{{value +${VARNAME_UNBALANCED_BRACES}= variable name with unbalanced braces + +.if !defined(DEF) +. error +.endif + +# Horizontal whitespace (space tab) after the opening parenthesis is ignored. +.if !defined( DEF) +. error +.endif + +# Horizontal whitespace (space tab) before the closing parenthesis is ignored. +.if !defined(DEF ) +. error +.endif + +# The argument of a function must not directly contain whitespace. +.if !defined(A B) +. error +.endif + +# If necessary, the whitespace can be generated by a variable expression. +.if !defined(${:UA B}) +. error +.endif + +# Characters that could be mistaken for operators must not appear directly +# in a function argument. As with whitespace, these can be generated +# indirectly. +# +# It's not entirely clear why these characters are forbidden. +# The most plausible reason seems to be typo detection. +.if !defined(A&B) +. error +.endif +.if !defined(A|B) +. error +.endif + +# Even parentheses may appear in variable names. +# They must be balanced though. +.if !defined(VAR(value)) +. error +.endif + +# Braces do not have any special meaning when parsing arguments. +.if !defined(VAR{value}) +. error +.endif + +# Braces do not have any special meaning when parsing arguments. +# They don't need to be balanced. +.if !defined(VAR{{{value) +. error +.endif + +# There may be spaces around the operators and parentheses, and even +# inside the parentheses. The spaces inside the parentheses are not +# allowed for the empty() function (see cond-func-empty.mk), therefore +# they are typically omitted for the other functions as well. +.if ! defined ( DEF ) +. error +.endif + +# The following condition is interpreted as defined(A) && defined(B). +# In lack of a function call expression, each kind of .if directive has a +# default function that is called when a bare word is parsed. For the plain +# .if directive, this function is defined(); see "struct If ifs" in cond.c. +.if A&B +. error +.endif + +.if defined() +. error +.else +. info The empty variable is never defined. +.endif + +# The plain word 'defined' is interpreted as '!empty(defined)'. +# That variable is not defined (yet). +.if defined +. error +.else +. info A plain function name is parsed as !empty(...). +.endif + +# If a variable named 'defined' is actually defined and not empty, the plain +# symbol 'defined' evaluates to true. +defined= non-empty +.if defined +. info A plain function name is parsed as !empty(...). +.else +. error +.endif + +# A plain symbol name may start with one of the function names, in this case +# 'defined'. +.if defined-var +. error +.else +. info Symbols may start with a function name. +.endif + +defined-var= non-empty +.if defined-var +. info Symbols may start with a function name. +.else +. error +.endif + +# Missing closing parenthesis when parsing the function argument. +.if defined( +. error +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-late.exp b/usr.bin/make/unit-tests/cond-late.exp new file mode 100644 index 000000000..46c4aa2f4 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-late.exp @@ -0,0 +1,4 @@ +make: Bad conditional expression ` != "no"' in != "no"?: +yes +no +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-late.mk b/usr.bin/make/unit-tests/cond-late.mk new file mode 100644 index 000000000..4df3df2cf --- /dev/null +++ b/usr.bin/make/unit-tests/cond-late.mk @@ -0,0 +1,31 @@ +# $NetBSD: cond-late.mk,v 1.3 2020/11/15 14:07:53 rillig Exp $ +# +# Using the :? modifier, variable expressions can contain conditional +# expressions that are evaluated late, at expansion time. +# +# Any variables appearing in these +# conditions are expanded before parsing the condition. This is +# different from many other places. +# +# Because of this, variables that are used in these lazy conditions +# should not contain double-quotes, or the parser will probably fail. +# +# They should also not contain operators like == or <, since these are +# actually interpreted as these operators. This is demonstrated below. +# + +all: cond-literal + +COND.true= "yes" == "yes" +COND.false= "yes" != "yes" + +# If the order of evaluation were to change to first parse the condition +# and then expand the variables, the output would change from the +# current "yes no" to "yes yes", since both variables are non-empty. +cond-literal: + @echo ${ ${COND.true} :?yes:no} + @echo ${ ${COND.false} :?yes:no} + +VAR+= ${${UNDEF} != "no":?:} +.if empty(VAR:Mpattern) +.endif diff --git a/usr.bin/make/unit-tests/cond-op-and-lint.exp b/usr.bin/make/unit-tests/cond-op-and-lint.exp new file mode 100644 index 000000000..8817fd0d6 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-and-lint.exp @@ -0,0 +1,4 @@ +make: "cond-op-and-lint.mk" line 9: Unknown operator '&' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op-and-lint.mk b/usr.bin/make/unit-tests/cond-op-and-lint.mk new file mode 100644 index 000000000..626233901 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-and-lint.mk @@ -0,0 +1,13 @@ +# $NetBSD: cond-op-and-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $ +# +# Tests for the && operator in .if conditions, in lint mode. + +.MAKEFLAGS: -dL + +# The '&' operator is not allowed in lint mode. +# It is not used in practice anyway. +.if 0 & 0 +. error +.else +. error +.endif diff --git a/usr.bin/make/unit-tests/cond-op-and.exp b/usr.bin/make/unit-tests/cond-op-and.exp new file mode 100644 index 000000000..173b6861a --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-and.exp @@ -0,0 +1,4 @@ +make: "cond-op-and.mk" line 43: Malformed conditional (0 &&& 0) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op-and.mk b/usr.bin/make/unit-tests/cond-op-and.mk new file mode 100644 index 000000000..83c694f15 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-and.mk @@ -0,0 +1,48 @@ +# $NetBSD: cond-op-and.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for the && operator in .if conditions. + +.if 0 && 0 +. error +.endif + +.if 1 && 0 +. error +.endif + +.if 0 && 1 +. error +.endif + +.if !(1 && 1) +. error +.endif + +# The right-hand side is not evaluated since the left-hand side is already +# false. +.if 0 && ${UNDEF} +.endif + +# The && operator may be abbreviated as &. This is not widely known though +# and is also not documented in the manual page. + +.if 0 & 0 +. error +.endif +.if 1 & 0 +. error +.endif +.if 0 & 1 +. error +.endif +.if !(1 & 1) +. error +.endif + +# There is no operator &&&. +.if 0 &&& 0 +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-op-not.exp b/usr.bin/make/unit-tests/cond-op-not.exp new file mode 100644 index 000000000..440670ca7 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-not.exp @@ -0,0 +1,9 @@ +make: "cond-op-not.mk" line 29: Not empty evaluates to true. +make: "cond-op-not.mk" line 37: Not space evaluates to false. +make: "cond-op-not.mk" line 41: Not 0 evaluates to true. +make: "cond-op-not.mk" line 49: Not 1 evaluates to false. +make: "cond-op-not.mk" line 55: Not word evaluates to false. +make: "cond-op-not.mk" line 59: Malformed conditional (!) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op-not.mk b/usr.bin/make/unit-tests/cond-op-not.mk new file mode 100644 index 000000000..ffd5bc89e --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-not.mk @@ -0,0 +1,66 @@ +# $NetBSD: cond-op-not.mk,v 1.7 2021/01/19 17:49:13 rillig Exp $ +# +# Tests for the ! operator in .if conditions, which negates its argument. + +# The exclamation mark negates its operand. +.if !1 +. error +.endif + +# Exclamation marks can be chained. +# This doesn't happen in practice though. +.if !!!1 +. error +.endif + +# The ! binds more tightly than the &&. +.if !!0 && 1 +. error +.endif + +# The operator '==' binds more tightly than '!'. +# This is unusual since most other programming languages define the precedence +# to be the other way round. +.if !${:Uexpression} == "expression" +. error +.endif + +.if !${:U} +. info Not empty evaluates to true. +.else +. info Not empty evaluates to false. +.endif + +.if !${:U } +. info Not space evaluates to true. +.else +. info Not space evaluates to false. +.endif + +.if !${:U0} +. info Not 0 evaluates to true. +.else +. info Not 0 evaluates to false. +.endif + +.if !${:U1} +. info Not 1 evaluates to true. +.else +. info Not 1 evaluates to false. +.endif + +.if !${:Uword} +. info Not word evaluates to true. +.else +. info Not word evaluates to false. +.endif + +# A single exclamation mark is a parse error. +.if ! +. error +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-op-or-lint.exp b/usr.bin/make/unit-tests/cond-op-or-lint.exp new file mode 100644 index 000000000..8abae99b6 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-or-lint.exp @@ -0,0 +1,4 @@ +make: "cond-op-or-lint.mk" line 9: Unknown operator '|' +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op-or-lint.mk b/usr.bin/make/unit-tests/cond-op-or-lint.mk new file mode 100644 index 000000000..aa29e9a6c --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-or-lint.mk @@ -0,0 +1,13 @@ +# $NetBSD: cond-op-or-lint.mk,v 1.1 2020/11/08 23:54:28 rillig Exp $ +# +# Tests for the || operator in .if conditions, in lint mode. + +.MAKEFLAGS: -dL + +# The '|' operator is not allowed in lint mode. +# It is not used in practice anyway. +.if 0 | 0 +. error +.else +. error +.endif diff --git a/usr.bin/make/unit-tests/cond-op-or.exp b/usr.bin/make/unit-tests/cond-op-or.exp new file mode 100644 index 000000000..7888a475e --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-or.exp @@ -0,0 +1,4 @@ +make: "cond-op-or.mk" line 43: Malformed conditional (0 ||| 0) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op-or.mk b/usr.bin/make/unit-tests/cond-op-or.mk new file mode 100644 index 000000000..c6993e7c2 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-or.mk @@ -0,0 +1,48 @@ +# $NetBSD: cond-op-or.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $ +# +# Tests for the || operator in .if conditions. + +.if 0 || 0 +. error +.endif + +.if !(1 || 0) +. error +.endif + +.if !(0 || 1) +. error +.endif + +.if !(1 || 1) +. error +.endif + +# The right-hand side is not evaluated since the left-hand side is already +# true. +.if 1 || ${UNDEF} +.endif + +# The || operator may be abbreviated as |. This is not widely known though +# and is also not documented in the manual page. + +.if 0 | 0 +. error +.endif +.if !(1 | 0) +. error +.endif +.if !(0 | 1) +. error +.endif +.if !(1 | 1) +. error +.endif + +# There is no operator |||. +.if 0 ||| 0 +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-op-parentheses.exp b/usr.bin/make/unit-tests/cond-op-parentheses.exp new file mode 100644 index 000000000..b44093304 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-parentheses.exp @@ -0,0 +1,6 @@ +make: "cond-op-parentheses.mk" line 13: Parentheses can be nested at least to depth 112. +make: "cond-op-parentheses.mk" line 19: Malformed conditional (() +make: "cond-op-parentheses.mk" line 29: Malformed conditional ()) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op-parentheses.mk b/usr.bin/make/unit-tests/cond-op-parentheses.mk new file mode 100644 index 000000000..ca288cad5 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op-parentheses.mk @@ -0,0 +1,36 @@ +# $NetBSD: cond-op-parentheses.mk,v 1.4 2021/01/19 17:49:13 rillig Exp $ +# +# Tests for parentheses in .if conditions. + +# TODO: Implementation + +# Test for deeply nested conditions. +.if (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ + (((((((((((((((((((((((((((((((((((((((((((((((((((((((( \ + 1 \ + )))))))))))))))))))))))))))))))))))))))))))))))))))))))) \ + )))))))))))))))))))))))))))))))))))))))))))))))))))))))) +. info Parentheses can be nested at least to depth 112. +.else +. error +.endif + +# An unbalanced opening parenthesis is a parse error. +.if ( +. error +.else +. error +.endif + +# An unbalanced closing parenthesis is a parse error. +# +# As of 2021-01-19, CondParser_Term returned TOK_RPAREN even though this +# function promised to only ever return TOK_TRUE, TOK_FALSE or TOK_ERROR. +.if ) +. error +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-op.exp b/usr.bin/make/unit-tests/cond-op.exp new file mode 100644 index 000000000..28e8d48e2 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op.exp @@ -0,0 +1,20 @@ +make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word) +make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated}) +make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated. +make: "cond-op.mk" line 83: Parsing continues until here. +make: "cond-op.mk" line 86: A B C => (A || B) && C A || B && C A || (B && C) +make: "cond-op.mk" line 93: 0 0 0 => 0 0 0 +make: "cond-op.mk" line 93: 0 0 1 => 0 0 0 +make: "cond-op.mk" line 93: 0 1 0 => 0 0 0 +make: "cond-op.mk" line 93: 0 1 1 => 1 1 1 +make: "cond-op.mk" line 93: 1 0 0 => 0 1 1 +make: "cond-op.mk" line 93: 1 0 1 => 1 1 1 +make: "cond-op.mk" line 93: 1 1 0 => 0 1 1 +make: "cond-op.mk" line 93: 1 1 1 => 1 1 1 +make: "cond-op.mk" line 104: Malformed conditional (1 &&) +make: "cond-op.mk" line 112: Malformed conditional (0 &&) +make: "cond-op.mk" line 120: Malformed conditional (1 ||) +make: "cond-op.mk" line 129: Malformed conditional (0 ||) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-op.mk b/usr.bin/make/unit-tests/cond-op.mk new file mode 100644 index 000000000..2ed451c90 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-op.mk @@ -0,0 +1,136 @@ +# $NetBSD: cond-op.mk,v 1.13 2021/01/19 18:20:30 rillig Exp $ +# +# Tests for operators like &&, ||, ! in .if conditions. +# +# See also: +# cond-op-and.mk +# cond-op-not.mk +# cond-op-or.mk +# cond-op-parentheses.mk + +# In make, && binds more tightly than ||, like in C. +# If make had the same precedence for both && and ||, like in the shell, +# the result would be different. +# If || were to bind more tightly than &&, the result would be different +# as well. +.if !(1 || 1 && 0) +. error +.endif + +# If make were to interpret the && and || operators like the shell, the +# previous condition would be interpreted as: +.if (1 || 1) && 0 +. error +.endif + +# The precedence of the ! operator is different from C though. It has a +# lower precedence than the comparison operators. Negating a condition +# does not need parentheses. +# +# This kind of condition looks so unfamiliar that it doesn't occur in +# practice. +.if !"word" == "word" +. error +.endif + +# This is how the above condition is actually interpreted. +.if !("word" == "word") +. error +.endif + +# TODO: Demonstrate that the precedence of the ! and == operators actually +# makes a difference. There is a simple example for sure, I just cannot +# wrap my head around it right now. See the truth table generator below +# for an example that doesn't require much thought. + +# This condition is malformed because the '!' on the right-hand side must not +# appear unquoted. If any, it must be enclosed in quotes. +# In any case, it is not interpreted as a negation of an unquoted string. +# See CondParser_String. +.if "!word" == !word +. error +.endif + +# Surprisingly, the ampersand and pipe are allowed in bare strings. +# That's another opportunity for writing confusing code. +# See CondParser_String, which only has '!' in the list of stop characters. +.if "a&&b||c" != a&&b||c +. error +.endif + +# As soon as the parser sees the '$', it knows that the condition will +# be malformed. Therefore there is no point in evaluating it. +# +# As of 2021-01-20, that part of the condition is evaluated nevertheless, +# since CondParser_Or just requests the next token, without restricting +# the token to the expected tokens. If the parser were to restrict the +# valid follow tokens for the token "0" to those that can actually produce +# a correct condition (which in this case would be comparison operators, +# TOK_AND, TOK_OR or TOK_RPAREN), the variable expression would not have +# to be evaluated. +# +# This would add a good deal of complexity to the code though, for almost +# no benefit, especially since most expressions and conditions are side +# effect free. +.if 0 ${ERR::=evaluated} +. error +.endif +.if ${ERR:Uundefined} == evaluated +. info After detecting a parse error, the rest is evaluated. +.endif + +# Just in case that parsing should ever stop on the first error. +.info Parsing continues until here. + +# Demonstration that '&&' has higher precedence than '||'. +.info A B C => (A || B) && C A || B && C A || (B && C) +.for a in 0 1 +. for b in 0 1 +. for c in 0 1 +. for r1 in ${ ($a || $b) && $c :?1:0} +. for r2 in ${ $a || $b && $c :?1:0} +. for r3 in ${ $a || ($b && $c) :?1:0} +. info $a $b $c => ${r1} ${r2} ${r3} +. endfor +. endfor +. endfor +. endfor +. endfor +.endfor + +# This condition is obviously malformed. It is properly detected and also +# was properly detected before 2021-01-19, but only because the left hand +# side of the '&&' evaluated to true. +.if 1 && +. error +.else +. error +.endif + +# This obviously malformed condition was not detected as such before cond.c +# 1.238 from 2021-01-19. +.if 0 && +. error +.else +. error +.endif + +# This obviously malformed condition was not detected as such before cond.c +# 1.238 from 2021-01-19. +.if 1 || +. error +.else +. error +.endif + +# This condition is obviously malformed. It is properly detected and also +# was properly detected before 2021-01-19, but only because the left hand +# side of the '||' evaluated to false. +.if 0 || +. error +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-short.exp b/usr.bin/make/unit-tests/cond-short.exp new file mode 100644 index 000000000..2865dcb6e --- /dev/null +++ b/usr.bin/make/unit-tests/cond-short.exp @@ -0,0 +1,16 @@ +expected and +expected and exists +expected and empty +expected U23 condition +expected VAR23 +expected M pattern +expected or +expected or exists +expected or empty +defined(V42) && ${V42} > 0: Ok +defined(V66) && ( "${iV2}" < ${V42} ): Ok +1 || ${iV1} < ${V42}: Ok +1 || ${iV2:U2} < ${V42}: Ok +0 || ${iV1} <= ${V42}: Ok +0 || ${iV2:U2} < ${V42}: Ok +exit status 0 diff --git a/usr.bin/make/unit-tests/cond-short.mk b/usr.bin/make/unit-tests/cond-short.mk new file mode 100644 index 000000000..113c3fd08 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-short.mk @@ -0,0 +1,214 @@ +# $NetBSD: cond-short.mk,v 1.16 2021/03/14 11:49:37 rillig Exp $ +# +# Demonstrates that in conditions, the right-hand side of an && or || +# is only evaluated if it can actually influence the result. +# This is called 'short-circuit evaluation' and is the usual evaluation +# mode in most programming languages. A notable exception is Ada, which +# distinguishes between the operators 'And', 'And Then', 'Or', 'Or Else'. +# +# 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 +# parse them. They were still evaluated though, the only difference to +# relevant variable expressions was that in the irrelevant variable +# expressions, undefined variables were allowed. +# +# See also: +# var-eval-short.mk, for short-circuited variable modifiers + +# The && operator: + +.if 0 && ${echo "unexpected and" 1>&2 :L:sh} +.endif + +.if 1 && ${echo "expected and" 1>&2 :L:sh} +.endif + +.if 0 && exists(nonexistent${echo "unexpected and exists" 1>&2 :L:sh}) +.endif + +.if 1 && exists(nonexistent${echo "expected and exists" 1>&2 :L:sh}) +.endif + +.if 0 && empty(${echo "unexpected and empty" 1>&2 :L:sh}) +.endif + +.if 1 && empty(${echo "expected and empty" 1>&2 :L:sh}) +.endif + +# "VAR U11" is not evaluated; it was evaluated before 2020-07-02. +# The whole !empty condition is only parsed and then discarded. +VAR= ${VAR${:U11${echo "unexpected VAR U11" 1>&2 :L:sh}}} +VAR13= ${VAR${:U12${echo "unexpected VAR13" 1>&2 :L:sh}}} +.if 0 && !empty(VAR${:U13${echo "unexpected U13 condition" 1>&2 :L:sh}}) +.endif + +VAR= ${VAR${:U21${echo "unexpected VAR U21" 1>&2 :L:sh}}} +VAR23= ${VAR${:U22${echo "expected VAR23" 1>&2 :L:sh}}} +.if 1 && !empty(VAR${:U23${echo "expected U23 condition" 1>&2 :L:sh}}) +.endif +VAR= # empty again, for the following tests + +# The :M modifier is only parsed, not evaluated. +# Before 2020-07-02, it was wrongly evaluated. +.if 0 && !empty(VAR:M${:U${echo "unexpected M pattern" 1>&2 :L:sh}}) +.endif + +.if 1 && !empty(VAR:M${:U${echo "expected M pattern" 1>&2 :L:sh}}) +.endif + +.if 0 && !empty(VAR:S,from,${:U${echo "unexpected S modifier" 1>&2 :L:sh}},) +.endif + +.if 0 && !empty(VAR:C,from,${:U${echo "unexpected C modifier" 1>&2 :L:sh}},) +.endif + +.if 0 && !empty("" == "" :? ${:U${echo "unexpected ? modifier" 1>&2 :L:sh}} :) +.endif + +.if 0 && !empty(VAR:old=${:U${echo "unexpected = modifier" 1>&2 :L:sh}}) +.endif + +.if 0 && !empty(1 2 3:L:@var@${:U${echo "unexpected @ modifier" 1>&2 :L:sh}}@) +.endif + +.if 0 && !empty(:U${:!echo "unexpected exclam modifier" 1>&2 !}) +.endif + +# Irrelevant assignment modifiers are skipped as well. +.if 0 && ${1 2 3:L:@i@${FIRST::?=$i}@} +.endif +.if 0 && ${1 2 3:L:@i@${LAST::=$i}@} +.endif +.if 0 && ${1 2 3:L:@i@${APPENDED::+=$i}@} +.endif +.if 0 && ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@} +.endif +.if defined(FIRST) || defined(LAST) || defined(APPENDED) || defined(RAN) +. warning first=${FIRST} last=${LAST} appended=${APPENDED} ran=${RAN} +.endif + +# The || operator: + +.if 1 || ${echo "unexpected or" 1>&2 :L:sh} +.endif + +.if 0 || ${echo "expected or" 1>&2 :L:sh} +.endif + +.if 1 || exists(nonexistent${echo "unexpected or exists" 1>&2 :L:sh}) +.endif + +.if 0 || exists(nonexistent${echo "expected or exists" 1>&2 :L:sh}) +.endif + +.if 1 || empty(${echo "unexpected or empty" 1>&2 :L:sh}) +.endif + +.if 0 || empty(${echo "expected or empty" 1>&2 :L:sh}) +.endif + +# Unreachable nested conditions are skipped completely as well. + +.if 0 +. if ${echo "unexpected nested and" 1>&2 :L:sh} +. endif +.endif + +.if 1 +.elif ${echo "unexpected nested or" 1>&2 :L:sh} +.endif + +# make sure these do not cause complaint +#.MAKEFLAGS: -dc + +# TODO: Rewrite this whole section and check all the conditions and variables. +# Several of the assumptions are probably wrong here. +# TODO: replace 'x=' with '.info' or '.error'. +V42= 42 +iV1= ${V42} +iV2= ${V66} + +.if defined(V42) && ${V42} > 0 +x= Ok +.else +x= Fail +.endif +x!= echo 'defined(V42) && $${V42} > 0: $x' >&2; echo + +# With cond.c 1.76 from 2020-07-03, the following condition triggered a +# warning: "String comparison operator should be either == or !=". +# This was because the variable expression ${iV2} was defined, but the +# contained variable V66 was undefined. The left-hand side of the comparison +# therefore evaluated to the string "${V66}", which is obviously not a number. +# +# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant +# comparisons. Instead, they are only parsed and then discarded. +# +# At that time, there was not enough debug logging to see the details in the +# -dA log. To actually see it, add debug logging at the beginning and end of +# Var_Parse. +.if defined(V66) && ( ${iV2} < ${V42} ) +x= Fail +.else +x= Ok +.endif +# XXX: This condition doesn't match the one above. The quotes are missing +# above. This is a crucial detail since without quotes, the variable +# expression ${iV2} evaluates to "${V66}", and with quotes, it evaluates to "" +# since undefined variables are allowed and expand to an empty string. +x!= echo 'defined(V66) && ( "$${iV2}" < $${V42} ): $x' >&2; echo + +.if 1 || ${iV1} < ${V42} +x= Ok +.else +x= Fail +.endif +x!= echo '1 || $${iV1} < $${V42}: $x' >&2; echo + +# With cond.c 1.76 from 2020-07-03, the following condition triggered a +# warning: "String comparison operator should be either == or !=". +# This was because the variable expression ${iV2} was defined, but the +# contained variable V66 was undefined. The left-hand side of the comparison +# therefore evaluated to the string "${V66}", which is obviously not a number. +# +# This was fixed in cond.c 1.79 from 2020-07-09 by not evaluating irrelevant +# comparisons. Instead, they are only parsed and then discarded. +# +# At that time, there was not enough debug logging to see the details in the +# -dA log. To actually see it, add debug logging at the beginning and end of +# Var_Parse. +.if 1 || ${iV2:U2} < ${V42} +x= Ok +.else +x= Fail +.endif +x!= echo '1 || $${iV2:U2} < $${V42}: $x' >&2; echo + +# the same expressions are fine when the lhs is expanded +# ${iV1} expands to 42 +.if 0 || ${iV1} <= ${V42} +x= Ok +.else +x= Fail +.endif +x!= echo '0 || $${iV1} <= $${V42}: $x' >&2; echo + +# ${iV2:U2} expands to 2 +.if 0 || ${iV2:U2} < ${V42} +x= Ok +.else +x= Fail +.endif +x!= echo '0 || $${iV2:U2} < $${V42}: $x' >&2; echo + +# The right-hand side of the '&&' is irrelevant since the left-hand side +# already evaluates to false. Before cond.c 1.79 from 2020-07-09, it was +# expanded nevertheless, although with a small modification: undefined +# variables may be used in these expressions without generating an error. +.if defined(UNDEF) && ${UNDEF} != "undefined" +. error +.endif + +all: diff --git a/usr.bin/make/unit-tests/cond-token-number.exp b/usr.bin/make/unit-tests/cond-token-number.exp new file mode 100644 index 000000000..b5bfaf95d --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-number.exp @@ -0,0 +1,8 @@ +make: "cond-token-number.mk" line 15: Malformed conditional (-0) +make: "cond-token-number.mk" line 25: Malformed conditional (+0) +make: "cond-token-number.mk" line 35: Malformed conditional (!-1) +make: "cond-token-number.mk" line 45: Malformed conditional (!+1) +make: "cond-token-number.mk" line 80: End of the tests. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-token-number.mk b/usr.bin/make/unit-tests/cond-token-number.mk new file mode 100644 index 000000000..93e2646a6 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-number.mk @@ -0,0 +1,82 @@ +# $NetBSD: cond-token-number.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ +# +# Tests for number tokens in .if conditions. +# +# TODO: Add introduction. + +.if 0 +. error +.endif + +# Even though -0 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if -0 +. error +.else +. error +.endif + +# Even though +0 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if +0 +. error +.else +. error +.endif + +# Even though -1 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if !-1 +. error +.else +. error +.endif + +# Even though +1 is a number and would be accepted by strtod, it is not +# accepted by the condition parser. +# +# See the ch_isdigit call in CondParser_String. +.if !+1 +. error +.else +. error +.endif + +# When the number comes from a variable 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. +# XXX: This is inconsistent. +.if !${:U+1} +. error +.endif + +# Hexadecimal numbers are accepted. +.if 0x0 +. error +.endif +.if 0x1 +.else +. error +.endif + +# This is not a hexadecimal number, even though it has an x. +# It is interpreted as a string instead, effectively meaning defined(3x4). +.if 3x4 +.else +. error +.endif + +# Ensure that parsing continues until here. +.info End of the tests. + +all: # nothing diff --git a/usr.bin/make/unit-tests/cond-token-plain.exp b/usr.bin/make/unit-tests/cond-token-plain.exp new file mode 100644 index 000000000..24cfa6bcb --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-plain.exp @@ -0,0 +1,54 @@ +CondParser_Eval: ${:Uvalue} != value +lhs = "value", rhs = "value", op = != +CondParser_Eval: ${:U} != " +lhs = "", rhs = "", op = != +CondParser_Eval: ${:U#hash} != "#hash" +lhs = "#hash", rhs = "#hash", op = != +CondParser_Eval: ${:U\\} != "\\ +lhs = "\", rhs = "\", op = != +CondParser_Eval: ${:U#hash} != #hash +lhs = "#hash", rhs = "#hash", op = != +CondParser_Eval: 0 # This is treated as a comment, but why? +CondParser_Eval: ${0 # comment :?yes:no} != no +CondParser_Eval: 0 # comment +lhs = "no", rhs = "no", op = != +CondParser_Eval: ${1 # comment :?yes:no} != yes +CondParser_Eval: 1 # comment +lhs = "yes", rhs = "yes", op = != +CondParser_Eval: ${UNDEF:Uundefined}!=undefined +lhs = "undefined", rhs = "undefined", op = != +CondParser_Eval: ${UNDEF:U12345}>12345 +lhs = 12345.000000, rhs = 12345.000000, op = > +CondParser_Eval: ${UNDEF:U12345}<12345 +lhs = 12345.000000, rhs = 12345.000000, op = < +CondParser_Eval: (${UNDEF:U0})||0 +CondParser_Eval: ${:Uvar}&&name != "var&&name" +lhs = "var&&name", rhs = "var&&name", op = != +CondParser_Eval: ${:Uvar}||name != "var||name" +lhs = "var||name", rhs = "var||name", op = != +CondParser_Eval: bare +make: "cond-token-plain.mk" line 102: A bare word is treated like defined(...), and the variable 'bare' is not defined. +CondParser_Eval: VAR +make: "cond-token-plain.mk" line 107: A bare word is treated like defined(...). +CondParser_Eval: V${:UA}R +make: "cond-token-plain.mk" line 114: ok +CondParser_Eval: V${UNDEF}AR +make: "cond-token-plain.mk" line 122: Undefined variables in bare words expand to an empty string. +CondParser_Eval: 0${:Ux00} +make: "cond-token-plain.mk" line 130: Numbers can be composed from literals and variable expressions. +CondParser_Eval: 0${:Ux01} +make: "cond-token-plain.mk" line 134: Numbers can be composed from literals and variable expressions. +CondParser_Eval: "" == +make: "cond-token-plain.mk" line 140: Missing right-hand-side of operator '==' +CondParser_Eval: == "" +make: "cond-token-plain.mk" line 148: Malformed conditional (== "") +CondParser_Eval: \\ +make: "cond-token-plain.mk" line 163: The variable '\\' is not defined. +CondParser_Eval: \\ +make: "cond-token-plain.mk" line 168: Now the variable '\\' is defined. +CondParser_Eval: "unquoted\"quoted" != unquoted"quoted +lhs = "unquoted"quoted", rhs = "unquoted"quoted", op = != +CondParser_Eval: $$$$$$$$ != "" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-token-plain.mk b/usr.bin/make/unit-tests/cond-token-plain.mk new file mode 100644 index 000000000..89f2247e0 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-plain.mk @@ -0,0 +1,192 @@ +# $NetBSD: cond-token-plain.mk,v 1.10 2021/01/21 14:08:09 rillig Exp $ +# +# Tests for plain tokens (that is, string literals without quotes) +# in .if conditions. + +.MAKEFLAGS: -dc + +.if ${:Uvalue} != value +. error +.endif + +# Malformed condition since comment parsing is done in an early phase +# and removes the '#' and everything behind it long before the condition +# parser gets to see it. +# +# XXX: The error message is missing for this malformed condition. +# The right-hand side of the comparison is just a '"', before unescaping. +.if ${:U} != "#hash" +. error +.endif + +# To get a '#' into a condition, it has to be escaped using a backslash. +# This prevents the comment parser from removing it, and in turn, it becomes +# visible to CondParser_String. +.if ${:U\#hash} != "\#hash" +. error +.endif + +# Since 2002-12-30, and still as of 2020-09-11, CondParser_Token handles +# the '#' specially, even though at this point, there should be no need for +# comment handling anymore. The comments are supposed to be stripped off +# in a very early parsing phase. +# +# See https://gnats.netbsd.org/19596 for example makefiles demonstrating the +# original problems. This workaround is probably not needed anymore. +# +# XXX: Missing error message for the malformed condition. The right-hand +# side before unescaping is double-quotes, backslash, backslash. +.if ${:U\\} != "\\#hash" +. error +.endif + +# The right-hand side of a comparison is not parsed as a token, therefore +# the code from CondParser_Token does not apply to it. +# TODO: Explain the consequences. +# TODO: Does this mean that more syntactic variants are allowed here? +.if ${:U\#hash} != \#hash +. error +.endif + +# XXX: What is the purpose of treating an escaped '#' in the following +# condition as a comment? And why only at the beginning of a token, +# just as in the shell? +.if 0 \# This is treated as a comment, but why? +. error +.endif + +# Ah, ok, this can be used to add an end-of-condition comment. But does +# anybody really use this? This is neither documented nor obvious since +# the '#' is escaped. It's much clearer to write a comment in the line +# above the condition. +.if ${0 \# comment :?yes:no} != no +. error +.endif +.if ${1 \# comment :?yes:no} != yes +. error +.endif + +# Usually there is whitespace around the comparison operator, but this is +# not required. +.if ${UNDEF:Uundefined}!=undefined +. error +.endif +.if ${UNDEF:U12345}>12345 +. error +.endif +.if ${UNDEF:U12345}<12345 +. error +.endif +.if (${UNDEF:U0})||0 +. error +.endif + +# Only the comparison operator terminates the comparison operand, and it's +# a coincidence that the '!' is both used in the '!=' comparison operator +# as well as for negating a comparison result. +# +# The boolean operators '&' and '|' don't terminate a comparison operand. +.if ${:Uvar}&&name != "var&&name" +. error +.endif +.if ${:Uvar}||name != "var||name" +. error +.endif + +# A bare word may appear alone in a condition, without any comparison +# operator. It is implicitly converted into defined(bare). +.if bare +. error +.else +. info A bare word is treated like defined(...), and the variable $\ + 'bare' is not defined. +.endif + +VAR= defined +.if VAR +. info A bare word is treated like defined(...). +.else +. error +.endif + +# Bare words may be intermixed with variable expressions. +.if V${:UA}R +. info ok +.else +. error +.endif + +# In bare words, even undefined variables are allowed. Without the bare +# words, undefined variables are not allowed. That feels inconsistent. +.if V${UNDEF}AR +. info Undefined variables in bare words expand to an empty string. +.else +. error +.endif + +.if 0${:Ux00} +. error +.else +. info Numbers can be composed from literals and variable expressions. +.endif + +.if 0${:Ux01} +. info Numbers can be composed from literals and variable expressions. +.else +. error +.endif + +# If the right-hand side is missing, it's a parse error. +.if "" == +. error +.else +. error +.endif + +# If the left-hand side is missing, it's a parse error as well, but without +# a specific error message. +.if == "" +. error +.else +. error +.endif + +# The '\\' is not a line continuation. Neither is it an unquoted string +# literal. Instead, it is parsed as a function argument (ParseFuncArg), +# and in that context, the backslash is just an ordinary character. The +# function argument thus stays '\\' (2 backslashes). This string is passed +# to FuncDefined, and since there is no variable named '\\', the condition +# evaluates to false. +.if \\ +. error +.else +. info The variable '\\' is not defined. +.endif + +${:U\\\\}= backslash +.if \\ +. info Now the variable '\\' is defined. +.else +. error +.endif + +# Anything that doesn't start with a double quote is considered a "bare word". +# Strangely, a bare word may contain double quotes inside. Nobody should ever +# depend on this since it may well be unintended. See CondParser_String. +.if "unquoted\"quoted" != unquoted"quoted +. error +.endif + +# FIXME: In CondParser_String, Var_Parse returns var_Error without a +# corresponding error message. +.if $$$$$$$$ != "" +. error +.else +. error +.endif + +# See cond-token-string.mk for similar tests where the condition is enclosed +# in "quotes". + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-token-string.exp b/usr.bin/make/unit-tests/cond-token-string.exp new file mode 100644 index 000000000..45f999345 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-string.exp @@ -0,0 +1,18 @@ +make: "cond-token-string.mk" line 13: Unknown modifier "Z" +make: "cond-token-string.mk" line 13: Malformed conditional ("" != "${:Uvalue:Z}") +make: "cond-token-string.mk" line 22: xvalue is not defined. +make: "cond-token-string.mk" line 28: Malformed conditional (x${:Uvalue} == "") +make: "cond-token-string.mk" line 37: Expected. +CondParser_Eval: "UNDEF" +make: "cond-token-string.mk" line 46: The string literal "UNDEF" is not empty. +CondParser_Eval: " " +make: "cond-token-string.mk" line 55: The string literal " " is not empty, even though it consists of whitespace only. +CondParser_Eval: "${UNDEF}" +make: "cond-token-string.mk" line 64: An undefined variable in quotes expands to an empty string, which then evaluates to false. +CondParser_Eval: "${:Uvalue}" +make: "cond-token-string.mk" line 68: A nonempty variable expression evaluates to true. +CondParser_Eval: "${:U}" +make: "cond-token-string.mk" line 76: An empty variable evaluates to false. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-token-string.mk b/usr.bin/make/unit-tests/cond-token-string.mk new file mode 100644 index 000000000..a92d3a896 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-string.mk @@ -0,0 +1,82 @@ +# $NetBSD: cond-token-string.mk,v 1.4 2021/01/21 00:38:28 rillig Exp $ +# +# Tests for quoted string literals in .if conditions. +# +# See also: +# cond-token-plain.mk +# Covers string literals without quotes (called "bare words"). + +# TODO: Implementation + +# Cover the code in CondParser_String that frees the memory after parsing +# a variable expression based on an undefined variable. +.if "" != "${:Uvalue:Z}" +. error +.else +. error +.endif + +.if x${:Uvalue} +. error +.else +. info xvalue is not defined. +.endif + +# The 'x' produces a "Malformed conditional" since the left-hand side of a +# comparison in an .if directive must be either a variable expression, a +# quoted string literal or a number that starts with a digit. +.if x${:Uvalue} == "" +. error +.else +. error +.endif + +# In plain words, a '\' can be used to escape any character, just as in +# double-quoted string literals. See CondParser_String. +.if \x${:Uvalue} == "xvalue" +. info Expected. +.else +. error +.endif + +.MAKEFLAGS: -dc + +# A string in quotes is checked whether it is not empty. +.if "UNDEF" +. info The string literal "UNDEF" is not empty. +.else +. error +.endif + +# A space is not empty as well. +# This differs from many other places where whitespace is trimmed. +.if " " +. info The string literal " " is not empty, even though it consists of $\ + whitespace only. +.else +. error +.endif + +.if "${UNDEF}" +. error +.else +. info An undefined variable in quotes expands to an empty string, which $\ + then evaluates to false. +.endif + +.if "${:Uvalue}" +. info A nonempty variable expression evaluates to true. +.else +. error +.endif + +.if "${:U}" +. error +.else +. info An empty variable evaluates to false. +.endif + +.MAKEFLAGS: -d0 + +all: + @:; diff --git a/usr.bin/make/unit-tests/cond-token-var.exp b/usr.bin/make/unit-tests/cond-token-var.exp new file mode 100644 index 000000000..fcd92d12a --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-var.exp @@ -0,0 +1,7 @@ +make: "cond-token-var.mk" line 20: ok +make: "cond-token-var.mk" line 27: Malformed conditional (${UNDEF} == ${DEF}) +make: "cond-token-var.mk" line 33: Malformed conditional (${DEF} == ${UNDEF}) +make: "cond-token-var.mk" line 42: Malformed conditional (${UNDEF}) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-token-var.mk b/usr.bin/make/unit-tests/cond-token-var.mk new file mode 100644 index 000000000..30eba87ad --- /dev/null +++ b/usr.bin/make/unit-tests/cond-token-var.mk @@ -0,0 +1,48 @@ +# $NetBSD: cond-token-var.mk,v 1.5 2020/11/15 14:58:14 rillig Exp $ +# +# Tests for variable 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 +# variable modifiers such as in ${VAR:Mpattern}. +# +# XXX: Strictly speaking, variable modifiers should be called expression +# modifiers instead since they only modify the expression, not the variable. +# Well, except for the assignment modifiers, these do indeed change the value +# of the variable. + +DEF= defined + +# A defined variable may appear on either side of the comparison. +.if ${DEF} == ${DEF} +. info ok +.else +. error +.endif + +# A variable that appears on the left-hand side must be defined. +# The following line thus generates a parse error. +.if ${UNDEF} == ${DEF} +. error +.endif + +# A variable that appears on the right-hand side must be defined. +# The following line thus generates a parse error. +.if ${DEF} == ${UNDEF} +. error +.endif + +# A defined variable may appear as an expression of its own. +.if ${DEF} +.endif + +# An undefined variable on its own generates a parse error. +.if ${UNDEF} +.endif + +# The :U modifier turns an undefined expression into a defined expression. +# Since the expression is defined now, it doesn't generate any parse error. +.if ${UNDEF:U} +.endif diff --git a/usr.bin/make/unit-tests/cond-undef-lint.exp b/usr.bin/make/unit-tests/cond-undef-lint.exp new file mode 100644 index 000000000..2c4feb037 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-undef-lint.exp @@ -0,0 +1,7 @@ +make: "cond-undef-lint.mk" line 23: Variable "UNDEF" is undefined +make: "cond-undef-lint.mk" line 38: Variable "UNDEF" is undefined +make: "cond-undef-lint.mk" line 38: Variable "VAR." is undefined +make: "cond-undef-lint.mk" line 49: Variable "VAR.defined" is undefined +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond-undef-lint.mk b/usr.bin/make/unit-tests/cond-undef-lint.mk new file mode 100644 index 000000000..9dfd1bd53 --- /dev/null +++ b/usr.bin/make/unit-tests/cond-undef-lint.mk @@ -0,0 +1,73 @@ +# $NetBSD: cond-undef-lint.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ +# +# Tests for defined and undefined variables in .if conditions, in lint mode. +# +# As of 2020-09-14, lint mode contains experimental code for printing +# accurate error messages in case of undefined variables, instead of the +# wrong "Malformed condition". +# +# See also: +# opt-debug-lint.mk + +.MAKEFLAGS: -dL + +# DEF is defined, UNDEF is not. +DEF= defined + +# An expression based on a defined variable is fine. +.if !${DEF} +. error +.endif + +# Since the condition fails to evaluate, neither of the branches is taken. +.if ${UNDEF} +. error +.else +. error +.endif + +# The variable name depends on the undefined variable, which is probably a +# mistake. The variable UNDEF, as used here, can be easily turned into +# an expression that is always defined, using the :U modifier. +# +# The outer expression does not generate an error message since there was +# already an error evaluating this variable's name. +# +# TODO: Suppress the error message "Variable VAR. is undefined". That part +# of the expression must not be evaluated at all. +.if ${VAR.${UNDEF}} +. error +.else +. error +.endif + +# The variable VAR.defined is not defined and thus generates an error message. +# +# TODO: This pattern looks a lot like CFLAGS.${OPSYS}, which is at least +# debatable. Or would any practical use of CFLAGS.${OPSYS} be via an indirect +# expression, as in the next example? +.if ${VAR.${DEF}} +. error +.else +. error +.endif + + +# Variables that are referenced indirectly may be undefined in a condition. +# +# A practical example for this is CFLAGS, which consists of CWARNS, COPTS +# and a few others. Just because these nested variables are not defined, +# this does not make the condition invalid. +# +# The crucial point is that at the point where the variable appears in the +# condition, there is no way to influence the definedness of the nested +# variables. In particular, there is no modifier that would turn undefined +# nested variables into empty strings, as an equivalent to the :U modifier. +INDIRECT= ${NESTED_UNDEF} ${NESTED_DEF} +NESTED_DEF= nested-defined + +# Since NESTED_UNDEF is not controllable at this point, it must not generate +# an error message, and it doesn't do so, since 2020-09-14. +.if !${INDIRECT} +. error +.endif diff --git a/usr.bin/make/unit-tests/cond1.exp b/usr.bin/make/unit-tests/cond1.exp index 701d504fc..0acd93578 100644 --- a/usr.bin/make/unit-tests/cond1.exp +++ b/usr.bin/make/unit-tests/cond1.exp @@ -1,5 +1,5 @@ -make: "cond1.mk" line 75: warning: extra else -make: "cond1.mk" line 85: warning: extra else +make: "cond1.mk" line 80: warning: extra else +make: "cond1.mk" line 90: warning: extra else 2 is prime A='other' B='unknown' C='clever' o='no,no' Passed: @@ -16,7 +16,7 @@ Passed: 4 is not prime 5 is prime -make: warning: String comparison operator should be either == or != +make: String comparison operator must be either == or != make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No OK diff --git a/usr.bin/make/unit-tests/cond1.mk b/usr.bin/make/unit-tests/cond1.mk index e3618327e..53908c2da 100644 --- a/usr.bin/make/unit-tests/cond1.mk +++ b/usr.bin/make/unit-tests/cond1.mk @@ -1,4 +1,9 @@ -# $Id: cond1.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: cond1.mk,v 1.3 2020/11/15 14:58:14 rillig Exp $ + +# TODO: Convert these tests into tutorial form. +# TODO: Split these tests by topic. +# TODO: Use better variable names and expression values that actually express +# the intended behavior. uname(1) has nothing to do with conditions. # hard code these! TEST_UNAME_S= NetBSD diff --git a/usr.bin/make/unit-tests/cond2.exp b/usr.bin/make/unit-tests/cond2.exp deleted file mode 100644 index 22e76a5b2..000000000 --- a/usr.bin/make/unit-tests/cond2.exp +++ /dev/null @@ -1,7 +0,0 @@ -make: Bad conditional expression ` == "empty"' in == "empty"?oops:ok -make: "cond2.mk" line 13: Malformed conditional ({TEST_TYPO} == "Ok") -TEST_NOT_SET is empty or not defined -make: "cond2.mk" line 20: Malformed conditional (${TEST_NOT_SET} == "empty") -make: Fatal errors encountered -- cannot continue -make: stopped in unit-tests -exit status 1 diff --git a/usr.bin/make/unit-tests/cond2.mk b/usr.bin/make/unit-tests/cond2.mk deleted file mode 100644 index cccc9d6f8..000000000 --- a/usr.bin/make/unit-tests/cond2.mk +++ /dev/null @@ -1,25 +0,0 @@ -# $Id: cond2.mk,v 1.1 2015/05/05 21:51:09 sjg Exp $ - -TEST_UNAME_S= NetBSD - -# this should be ok -X:= ${${TEST_UNAME_S} == "NetBSD":?Ok:fail} -.if $X == "Ok" -Y= good -.endif -# expect: Bad conditional expression ` == "empty"' in == "empty"?oops:ok -X:= ${${TEST_NOT_SET} == "empty":?oops:ok} -# expect: Malformed conditional ({TEST_TYPO} == "Ok") -.if {TEST_TYPO} == "Ok" -Y= oops -.endif -.if empty(TEST_NOT_SET) -Y!= echo TEST_NOT_SET is empty or not defined >&2; echo -.endif -# expect: Malformed conditional (${TEST_NOT_SET} == "empty") -.if ${TEST_NOT_SET} == "empty" -Y= oops -.endif - -all: - @echo $@ diff --git a/usr.bin/make/unit-tests/counter-append.exp b/usr.bin/make/unit-tests/counter-append.exp new file mode 100644 index 000000000..e2439fe08 --- /dev/null +++ b/usr.bin/make/unit-tests/counter-append.exp @@ -0,0 +1,2 @@ +A=1 B=2 C=3 COUNTER=3 +exit status 0 diff --git a/usr.bin/make/unit-tests/counter-append.mk b/usr.bin/make/unit-tests/counter-append.mk new file mode 100644 index 000000000..1c4e00d61 --- /dev/null +++ b/usr.bin/make/unit-tests/counter-append.mk @@ -0,0 +1,28 @@ +# $NetBSD: counter-append.mk,v 1.4 2020/10/17 16:57:17 rillig Exp $ +# +# Demonstrates how to let make count the number of times a variable +# is actually accessed, using the ::+= variable modifier. +# +# This works since 2020-09-23. Before that, the counter ended up at having +# 6 words, even though the NEXT variable was only accessed 3 times. +# The cause for this surprising behavior was that the ::= variable modifiers +# returned an error marker instead of a simple empty string. + +RELEVANT= yes (load-time part) # just to filter the output + +COUNTER= # zero + +NEXT= ${COUNTER::+=a}${COUNTER:[#]} + +# This variable is first set to empty and then expanded. +# See parse.c, function Parse_DoVar, keyword "!Var_Exists". +A:= ${NEXT} +B:= ${NEXT} +C:= ${NEXT} + +RELEVANT= no + +all: + @: ${RELEVANT::=yes (run-time part)} + @echo A=${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} + @: ${RELEVANT::=no} diff --git a/usr.bin/make/unit-tests/counter.exp b/usr.bin/make/unit-tests/counter.exp new file mode 100644 index 000000000..e2439fe08 --- /dev/null +++ b/usr.bin/make/unit-tests/counter.exp @@ -0,0 +1,2 @@ +A=1 B=2 C=3 COUNTER=3 +exit status 0 diff --git a/usr.bin/make/unit-tests/counter.mk b/usr.bin/make/unit-tests/counter.mk new file mode 100644 index 000000000..3c75d7a50 --- /dev/null +++ b/usr.bin/make/unit-tests/counter.mk @@ -0,0 +1,28 @@ +# $NetBSD: counter.mk,v 1.5 2020/10/17 16:57:17 rillig Exp $ +# +# Demonstrates how to let make count the number of times a variable +# is actually accessed, using the ::= variable modifier. +# +# This works since 2020-09-23. Before that, the counter ended up at having +# 4 words, even though the NEXT variable was only accessed 3 times. +# The cause for this surprising behavior was that the ::= variable modifiers +# returned an error marker instead of a simple empty string. + +RELEVANT= yes (load-time part) # just to filter the output + +COUNTER= # zero + +NEXT= ${COUNTER::=${COUNTER} a}${COUNTER:[#]} + +# This variable is first set to empty and then expanded. +# See parse.c, function Parse_DoVar, keyword "!Var_Exists". +A:= ${NEXT} +B:= ${NEXT} +C:= ${NEXT} + +RELEVANT= no + +all: + @: ${RELEVANT::=yes (run-time part)} + @echo A=${A:Q} B=${B:Q} C=${C:Q} COUNTER=${COUNTER:[#]:Q} + @: ${RELEVANT::=no} diff --git a/usr.bin/make/unit-tests/dep-colon-bug-cross-file.exp b/usr.bin/make/unit-tests/dep-colon-bug-cross-file.exp new file mode 100644 index 000000000..855b575c4 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-colon-bug-cross-file.exp @@ -0,0 +1,4 @@ +make: "dep-colon-bug-cross-file.mk" line 31: warning: duplicate script for target "all" ignored +make: "dep-colon-bug-cross-file.mk" line 40: warning: using previous script for "all" defined here +: pass 1 +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-colon-bug-cross-file.mk b/usr.bin/make/unit-tests/dep-colon-bug-cross-file.mk new file mode 100644 index 000000000..57fbf4781 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-colon-bug-cross-file.mk @@ -0,0 +1,41 @@ +# $NetBSD: dep-colon-bug-cross-file.mk,v 1.4 2020/09/27 09:53:41 rillig Exp $ +# +# Until 2020-09-25, the very last dependency group of a top-level makefile +# was not finished properly. This made it possible to add further commands +# to that target. +# +# In pass 1, there is a dependency group at the bottom of the file. +# This dependency group is not finished properly. Finishing the dependency +# group would add the OP_HAS_COMMANDS flag to the "all" target, thereby +# preventing any commands from being added later. +# +# After the file has been parsed completely, it is parsed again in pass 2. +# In this pass, another command is added to the "current dependency group", +# which was still the one from pass 1, which means it was possible to later +# add commands to an existing target, even across file boundaries. +# +# Oops, even worse. Running this test in a make from 2020-09-25 or earlier +# on NetBSD 8.0 x86_64 with MALLOC_OPTIONS=JA produces this or a similar +# output: +# +# make: cannot open ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ. +# +# The 'Z' means access to already freed memory; see jemalloc(3). The cause +# for this is that in MainParseArgs, the command line arguments were not +# properly copied before storing them in global variables. + +PASS?= 1 + +.if ${PASS} == 2 +all: + : pass 2 +.endif + +.if ${PASS} == 1 + +PASS= 2 +.MAKEFLAGS: -f ${.PARSEDIR:q}/${.PARSEFILE:q} + +all: + : pass 1 +.endif diff --git a/usr.bin/make/unit-tests/dep-colon.exp b/usr.bin/make/unit-tests/dep-colon.exp new file mode 100644 index 000000000..55330d95a --- /dev/null +++ b/usr.bin/make/unit-tests/dep-colon.exp @@ -0,0 +1,3 @@ +making target1 from source1 +making target2 from source2 +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-colon.mk b/usr.bin/make/unit-tests/dep-colon.mk new file mode 100644 index 000000000..e9931c9b2 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-colon.mk @@ -0,0 +1,20 @@ +# $NetBSD: dep-colon.mk,v 1.3 2020/10/17 20:10:04 rillig Exp $ +# +# Tests for the : operator in dependency declarations. + +# TODO: Implementation + +# In a dependency declaration line, there may be a shell command after the +# sources. It is separated by a semicolon. This "feature" is required by +# POSIX. It is seldom used, if at all. +all: target1 +target1: source1; @echo making ${.TARGET} from ${.ALLSRC} +source1: .PHONY + +# The semicolon for separating the sources from the creation commands must +# appear at the top-level. The semicolons inside the :S;1;2; modifier are +# skipped when looking for the semicolon that separates the sources from +# the commands. +all: target2 +target2: source${:U1:S;1;2;}; @echo making ${.TARGET} from ${.ALLSRC} +source2: .PHONY diff --git a/usr.bin/make/unit-tests/dep-double-colon-indep.exp b/usr.bin/make/unit-tests/dep-double-colon-indep.exp new file mode 100644 index 000000000..b42514475 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-double-colon-indep.exp @@ -0,0 +1,3 @@ +: 'Making 1400 dep-double-colon-1300 from dep-double-colon-1400 oodate dep-double-colon-1400' +: 'Making 1500 dep-double-colon-1300 from dep-double-colon-1500 oodate dep-double-colon-1500' +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-double-colon-indep.mk b/usr.bin/make/unit-tests/dep-double-colon-indep.mk new file mode 100644 index 000000000..9f004cf88 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-double-colon-indep.mk @@ -0,0 +1,32 @@ +# $NetBSD: dep-double-colon-indep.mk,v 1.1 2020/10/23 19:11:30 rillig Exp $ +# +# Tests for the :: operator in dependency declarations, which allows multiple +# dependency groups with the same target. Each group is evaluated on its own, +# independent of the other groups. +# +# This is useful for targets that are updatable, such as a database or a log +# file. Be careful with parallel mode though, to avoid lost updates and +# other inconsistencies. +# +# The target 1300 depends on 1200, 1400 and 1500. The target 1200 is older +# than 1300, therefore nothing is done for it. The other targets are newer +# than 1300, therefore each of them is made, independently from the other. + +.END: + @rm -f dep-double-colon-1??? + +_!= touch -t 202001011200 dep-double-colon-1200 +_!= touch -t 202001011300 dep-double-colon-1300 +_!= touch -t 202001011400 dep-double-colon-1400 +_!= touch -t 202001011500 dep-double-colon-1500 + +all: dep-double-colon-1300 + +dep-double-colon-1300:: dep-double-colon-1200 + : 'Making 1200 ${.TARGET} from ${.ALLSRC} oodate ${.OODATE}' + +dep-double-colon-1300:: dep-double-colon-1400 + : 'Making 1400 ${.TARGET} from ${.ALLSRC} oodate ${.OODATE}' + +dep-double-colon-1300:: dep-double-colon-1500 + : 'Making 1500 ${.TARGET} from ${.ALLSRC} oodate ${.OODATE}' diff --git a/usr.bin/make/unit-tests/dep-double-colon.exp b/usr.bin/make/unit-tests/dep-double-colon.exp new file mode 100644 index 000000000..5528abbab --- /dev/null +++ b/usr.bin/make/unit-tests/dep-double-colon.exp @@ -0,0 +1,5 @@ +command 1a +command 1b +command 2a +command 2b +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-double-colon.mk b/usr.bin/make/unit-tests/dep-double-colon.mk new file mode 100644 index 000000000..70fddbcf2 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-double-colon.mk @@ -0,0 +1,23 @@ +# $NetBSD: dep-double-colon.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the '::' operator in dependency declarations, which allows +# several dependency groups for a single node, each having its own attributes +# and dependencies. In the code, the additional dependency groups are called +# cohorts. + +all:: + @echo 'command 1a' + @echo 'command 1b' + +all:: + @echo 'command 2a' + @echo 'command 2b' + +# When there are multiple command groups for a '::' target, each of these +# groups is added separately to the .ALLTARGETS variable. +# +# XXX: What is this good for? +# XXX: Where does the leading space come from? +.if ${.ALLTARGETS} != " all all" +. error +.endif diff --git a/usr.bin/make/unit-tests/dep-exclam.exp b/usr.bin/make/unit-tests/dep-exclam.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-exclam.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-exclam.mk b/usr.bin/make/unit-tests/dep-exclam.mk new file mode 100644 index 000000000..5f6b72da3 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-exclam.mk @@ -0,0 +1,13 @@ +# $NetBSD: dep-exclam.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the ! operator in dependency declarations, which always re-creates +# the target, whether or not it is out of date. +# +# TODO: Is this related to OP_PHONY? +# TODO: Is this related to OP_EXEC? +# TODO: Is this related to OP_MAKE? + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/dep-none.exp b/usr.bin/make/unit-tests/dep-none.exp new file mode 100644 index 000000000..0d299fcca --- /dev/null +++ b/usr.bin/make/unit-tests/dep-none.exp @@ -0,0 +1,4 @@ +make: no target to make. + +make: stopped in unit-tests +exit status 2 diff --git a/usr.bin/make/unit-tests/dep-none.mk b/usr.bin/make/unit-tests/dep-none.mk new file mode 100644 index 000000000..74b50aa29 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-none.mk @@ -0,0 +1,3 @@ +# $NetBSD: dep-none.mk,v 1.1 2020/08/22 12:51:11 rillig Exp $ +# +# Test for a Makefile that declares no target at all. diff --git a/usr.bin/make/unit-tests/dep-percent.exp b/usr.bin/make/unit-tests/dep-percent.exp new file mode 100644 index 000000000..1e6c04d2e --- /dev/null +++ b/usr.bin/make/unit-tests/dep-percent.exp @@ -0,0 +1,6 @@ +make: don't know how to make dep-percent.o (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/dep-percent.mk b/usr.bin/make/unit-tests/dep-percent.mk new file mode 100644 index 000000000..3d6dbeebb --- /dev/null +++ b/usr.bin/make/unit-tests/dep-percent.mk @@ -0,0 +1,14 @@ +# $NetBSD: dep-percent.mk,v 1.1 2020/10/23 19:54:35 rillig Exp $ +# +# Test for transformation rules of the form '%.o: %.c', which are supported +# by GNU make but not this make. + +.SUFFIXES: .c .o + +all: dep-percent.o + +%.o: %.c + : 'Making ${.TARGET} from ${.IMPSRC} or ${.ALLSRC}.' + +dep-percent.c: + : 'Making ${.TARGET} out of nothing.' diff --git a/usr.bin/make/unit-tests/dep-var.exp b/usr.bin/make/unit-tests/dep-var.exp new file mode 100644 index 000000000..d32aca455 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-var.exp @@ -0,0 +1,6 @@ +make: Malformed variable expression at "$)" +def2 +a-def2-b +1-2-NDIRECT_2-2-1 +) +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-var.mk b/usr.bin/make/unit-tests/dep-var.mk new file mode 100644 index 000000000..438a8a84a --- /dev/null +++ b/usr.bin/make/unit-tests/dep-var.mk @@ -0,0 +1,88 @@ +# $NetBSD: dep-var.mk,v 1.5 2020/09/13 20:04:26 rillig Exp $ +# +# Tests for variable references in dependency declarations. +# +# Uh oh, this feels so strange that probably nobody uses it. But it seems to +# be the only way to reach the lower half of SuffExpandChildren. + +# XXX: The -dv log says: +# Var_Parse: ${UNDEF1} with VARE_UNDEFERR|VARE_WANTRES +# but no error message is generated for this line. +# The variable expression ${UNDEF1} simply expands to an empty string. +all: ${UNDEF1} + +# Using a double dollar in order to circumvent immediate variable expansion +# feels like unintended behavior. At least the manual page says nothing at +# all about defined or undefined variables in dependency lines. +# +# At the point where the expression ${DEF2} is expanded, the variable DEF2 +# is defined, so everything's fine. +all: $${DEF2} a-$${DEF2}-b + +# This variable is not defined at all. +# XXX: The -dv log says: +# Var_Parse: ${UNDEF3} with VARE_UNDEFERR|VARE_WANTRES +# but no error message is generated for this line, just like for UNDEF1. +# The variable expression ${UNDEF3} simply expands to an empty string. +all: $${UNDEF3} + +# Try out how many levels of indirection are really expanded in dependency +# lines. +# +# The first level of indirection is the $$ in the dependency line. +# When the dependency line is parsed, it is resolved to the string +# "${INDIRECT_1}". At this point, the dollar is just an ordinary character, +# waiting to be expanded at some later point. +# +# Later, in SuffExpandChildren, that expression is expanded again by calling +# Var_Parse, and this time, the result is the string "1-2-${INDIRECT_2}-2-1". +# +# This string is not expanded anymore by Var_Parse. But there is another +# effect. Now DirExpandCurly comes into play and expands the curly braces +# in this filename pattern, resulting in the string "1-2-$INDIRECT_2-2-1". +# As of 2020-09-03, the test dir.mk contains further details on this topic. +# +# Finally, this string is assigned to the local ${.TARGET} variable. This +# variable is expanded when the shell command is generated. At that point, +# the $I is expanded. Since the variable I is not defined, it expands to +# the empty string. This way, the final output is the string +# "1-2-NDIRECT_2-2-1", which differs from the actual name of the target. +# For exactly this reason, it is not recommended to use dollar signs in +# target names. +# +# The number of actual expansions is way more than one might expect, +# therefore this feature is probably not widely used. +# +all: 1-$${INDIRECT_1}-1 +INDIRECT_1= 2-$${INDIRECT_2}-2 +INDIRECT_2= 3-$${INDIRECT_3}-3 +INDIRECT_3= indirect + +UNDEF1= undef1 +DEF2= def2 + +# Cover the code in SuffExpandChildren that deals with malformed variable +# expressions. +# +# This seems to be an edge case that never happens in practice, and it would +# probably be appropriate to just error out in such a case. +# +# To trigger this piece of code, the variable name must contain "$)" or "$:" +# or "$)" or "$$". Using "$:" does not work since the dependency line is +# fully expanded before parsing, therefore any ':' in a target or source name +# would be interpreted as a dependency operator instead. +all: $$$$) + +# The $$INDIRECT in the following line is treated like the dependency of the +# "all" target, that is, the "$$I" is first expanded to "$I", and in a second +# round of expansion, the "$I" expands to nothing since the variable "I" is +# undefined. +# +# Since 2020-09-13, this generates a parse error in lint mode (-dL), but not +# in normal mode since ParseDoDependency does not handle any errors after +# calling Var_Parse. +undef1 def2 a-def2-b 1-2-$$INDIRECT_2-2-1 ${:U\$)}: + @echo ${.TARGET:Q} + +# XXX: Why is the exit status still 0, even though Parse_Error is called +# with PARSE_FATAL in SuffExpandChildren? diff --git a/usr.bin/make/unit-tests/dep-wildcards.exp b/usr.bin/make/unit-tests/dep-wildcards.exp new file mode 100644 index 000000000..fb8a44e2c --- /dev/null +++ b/usr.bin/make/unit-tests/dep-wildcards.exp @@ -0,0 +1,10 @@ +dep-colon-bug-cross-file.mk +dep-colon.mk +dep-double-colon-indep.mk +dep-double-colon.mk +dep-exclam.mk +dep-none.mk +dep-percent.mk +dep-var.mk +dep-wildcards.mk +exit status 0 diff --git a/usr.bin/make/unit-tests/dep-wildcards.mk b/usr.bin/make/unit-tests/dep-wildcards.mk new file mode 100644 index 000000000..781b149f5 --- /dev/null +++ b/usr.bin/make/unit-tests/dep-wildcards.mk @@ -0,0 +1,9 @@ +# $NetBSD: dep-wildcards.mk,v 1.3 2020/09/08 05:33:05 rillig Exp $ +# +# Tests for wildcards such as *.c in dependency declarations. + +all: ${.PARSEDIR}/dep-*.mk + # The :T is necessary to run this test from another directory. + # The :O is necessary since the result of the dependency resolution + # does not order the directory entries itself. + @printf '%s\n' ${.ALLSRC:T:O} diff --git a/usr.bin/make/unit-tests/dep.exp b/usr.bin/make/unit-tests/dep.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/dep.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/dep.mk b/usr.bin/make/unit-tests/dep.mk new file mode 100644 index 000000000..b2463dfc6 --- /dev/null +++ b/usr.bin/make/unit-tests/dep.mk @@ -0,0 +1,8 @@ +# $NetBSD: dep.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for dependency declarations, such as "target: sources". + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/depsrc-end.exp b/usr.bin/make/unit-tests/depsrc-end.exp new file mode 100644 index 000000000..de0815f2b --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-end.exp @@ -0,0 +1,4 @@ +: 'Making .END.' +: 'Making .BEGIN.' +: 'Making all.' +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-end.mk b/usr.bin/make/unit-tests/depsrc-end.mk new file mode 100644 index 000000000..eb7543d5d --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-end.mk @@ -0,0 +1,14 @@ +# $NetBSD: depsrc-end.mk,v 1.1 2020/10/23 19:23:01 rillig Exp $ +# +# Demonstrate the edge case that .BEGIN depends on .END, which sounds a bit +# paradox but works since these special nodes are not in the dependency +# hierarchy where the cycles are detected. + +.BEGIN: + : 'Making ${.TARGET}.' +.END: + : 'Making ${.TARGET}.' +all: + : 'Making ${.TARGET}.' + +.BEGIN: .END diff --git a/usr.bin/make/unit-tests/depsrc-exec.exp b/usr.bin/make/unit-tests/depsrc-exec.exp new file mode 100644 index 000000000..5106d5c2a --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-exec.exp @@ -0,0 +1,2 @@ +: depsrc-exec.mk: This is always executed. +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-exec.mk b/usr.bin/make/unit-tests/depsrc-exec.mk new file mode 100644 index 000000000..e2c2b98aa --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-exec.mk @@ -0,0 +1,16 @@ +# $NetBSD: depsrc-exec.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# +# Tests for the special source .EXEC in dependency declarations, +# which always executes the commands, even if the target is up to date. +# The target itself is considered up to date. +# +# TODO: Describe possible use cases for .EXEC. + +all: ${MAKEFILE} ${MAKEFILE:H}/depsrc.mk + +${MAKEFILE}: .EXEC + : ${.TARGET:T}: This is always executed. + +${MAKEFILE:H}/depsrc.mk: + : This is not executed. + +: ${.TARGET:T}: This is not executed as well. diff --git a/usr.bin/make/unit-tests/depsrc-ignore.exp b/usr.bin/make/unit-tests/depsrc-ignore.exp new file mode 100644 index 000000000..162f10ddc --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-ignore.exp @@ -0,0 +1,11 @@ +ignore-errors begin +false ignore-errors +ignore-errors end +all begin +*** Error code 1 (ignored) +false all +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/depsrc-ignore.mk b/usr.bin/make/unit-tests/depsrc-ignore.mk new file mode 100644 index 000000000..aea7d2494 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-ignore.mk @@ -0,0 +1,66 @@ +# $NetBSD: depsrc-ignore.mk,v 1.5 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special source .IGNORE in dependency declarations, +# which ignores any command failures for that target. +# +# Even though 'ignore-errors' fails, 'all' is still made. Since 'all' is +# not marked with .IGNORE, it stops at the first failing command. +# +# XXX: The ordering of the messages in the output is confusing. +# The "ignored" comes much too late to be related to the "false +# ignore-errors". This is due to stdout being buffered. +# +# The "continuing" message comes from the -k option. If there had been +# other targets independent of "all", these would be built as well. +# +# Enabling the debugging option -de changes the order in which the messages +# appear. Now the "ignored" message is issued in the correct position. +# The explanation for the output reordering is that the output is buffered. +# As the manual page says, in debugging mode stdout is line buffered. +# In these tests the output is redirected to a file, therefore stdout is +# fully buffered. +# +# This is what actually happens, as of 2020-08-29. To verify it, set the +# following breakpoints in CompatRunCommand: +# +# * the "!silent" line, to see all commands +# * the "fflush" line, to see stdout being flushed +# * the "status = WEXITSTATUS" line +# * the "(continuing)" line +# * the "(ignored)" line +# +# The breakpoints are visited in the following order: +# +# "ignore-errors begin" +# Goes directly to STDOUT_FILENO since it is run in a child process. +# "false ignore-errors" +# Goes to the stdout buffer (CompatRunCommand, keyword "!silent") and +# the immediate call to fflush(stdout) copies it to STDOUT_FILENO. +# "*** Error code 1 (ignored)" +# Goes to the stdout buffer but is not flushed (CompatRunCommand, near +# the end). +# "ignore-errors end" +# Goes directly to STDOUT_FILENO. +# "all begin" +# Goes directly to STDOUT_FILENO. +# "false all" +# Goes to the stdout buffer, where the "*** Error code 1 (ignored)" is +# still waiting to be flushed. These two lines are flushed now. +# "*** Error code 1 (continuing)" +# Goes to the stdout buffer. +# "Stop." +# Goes to the stdout buffer. +# exit(1) +# Flushes the stdout buffer to STDOUT_FILENO. + +all: ignore-errors + +ignore-errors: .IGNORE + @echo $@ begin + false $@ + @echo $@ end + +all: + @echo $@ begin + false $@ + @echo $@ end diff --git a/usr.bin/make/unit-tests/depsrc-made.exp b/usr.bin/make/unit-tests/depsrc-made.exp new file mode 100644 index 000000000..9d20ddec7 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-made.exp @@ -0,0 +1,3 @@ +: Making chapter21 +: Making chapter22 +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-made.mk b/usr.bin/make/unit-tests/depsrc-made.mk new file mode 100644 index 000000000..851593e38 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-made.mk @@ -0,0 +1,15 @@ +# $NetBSD: depsrc-made.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# +# Tests for the special source .MADE in dependency declarations, +# which marks all its dependencies as already made, so their commands +# don't need to be executed. +# +# TODO: Describe a possible use case for .MADE. + +all: part1 part2 + +part1: chapter11 chapter12 .MADE +part2: chapter21 chapter22 + +chapter11 chapter12 chapter21 chapter22: + : Making ${.TARGET} diff --git a/usr.bin/make/unit-tests/depsrc-make.exp b/usr.bin/make/unit-tests/depsrc-make.exp new file mode 100644 index 000000000..85c936ea4 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-make.exp @@ -0,0 +1,3 @@ +this-is-made is made. +echo this-is-not-made is just echoed. +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-make.mk b/usr.bin/make/unit-tests/depsrc-make.mk new file mode 100644 index 000000000..8069007e6 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-make.mk @@ -0,0 +1,18 @@ +# $NetBSD: depsrc-make.mk,v 1.4 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special source .MAKE in dependency declarations, which +# executes the commands of the target even if the -n or -t command line +# options are given. + +# TODO: Add a test for the -t command line option. + +.MAKEFLAGS: -n + +all: this-is-made +all: this-is-not-made + +this-is-made: .MAKE + @echo ${.TARGET} is made. + +this-is-not-made: + @echo ${.TARGET} is just echoed. diff --git a/usr.bin/make/unit-tests/depsrc-meta.exp b/usr.bin/make/unit-tests/depsrc-meta.exp new file mode 100644 index 000000000..77e27582f --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-meta.exp @@ -0,0 +1,5 @@ +Skipping meta for actual-test: no commands +Skipping meta for .END: .SPECIAL +Targets from meta mode: +| TARGET depsrc-meta-target +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-meta.mk b/usr.bin/make/unit-tests/depsrc-meta.mk new file mode 100644 index 000000000..d41aad9a9 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-meta.mk @@ -0,0 +1,31 @@ +# $NetBSD: depsrc-meta.mk,v 1.4 2020/11/27 08:39:07 rillig Exp $ +# +# Tests for the special source .META in dependency declarations. + +# TODO: Implementation +# TODO: Explanation + +.if make(actual-test) + +.MAKEFLAGS: -dM +.MAKE.MODE= meta curDirOk=true + +actual-test: depsrc-meta-target +depsrc-meta-target: .META + @> ${.TARGET}-file + @rm -f ${.TARGET}-file + +.elif make(check-results) + +check-results: + @echo 'Targets from meta mode:' + @awk '/^TARGET/ { print "| " $$0 }' depsrc-meta-target.meta + @rm depsrc-meta-target.meta + +.else + +all: + @${MAKE} -f ${MAKEFILE} actual-test + @${MAKE} -f ${MAKEFILE} check-results + +.endif diff --git a/usr.bin/make/unit-tests/depsrc-nometa.exp b/usr.bin/make/unit-tests/depsrc-nometa.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-nometa.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-nometa.mk b/usr.bin/make/unit-tests/depsrc-nometa.mk new file mode 100644 index 000000000..194238385 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-nometa.mk @@ -0,0 +1,8 @@ +# $NetBSD: depsrc-nometa.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special source .NOMETA in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/depsrc-nometa_cmp.exp b/usr.bin/make/unit-tests/depsrc-nometa_cmp.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-nometa_cmp.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-nometa_cmp.mk b/usr.bin/make/unit-tests/depsrc-nometa_cmp.mk new file mode 100644 index 000000000..a0032d2c8 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-nometa_cmp.mk @@ -0,0 +1,8 @@ +# $NetBSD: depsrc-nometa_cmp.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special source .NOMETA_CMP in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/depsrc-nopath.exp b/usr.bin/make/unit-tests/depsrc-nopath.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-nopath.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-nopath.mk b/usr.bin/make/unit-tests/depsrc-nopath.mk new file mode 100644 index 000000000..052c6f10d --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-nopath.mk @@ -0,0 +1,8 @@ +# $NetBSD: depsrc-nopath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special source .NOPATH in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/depsrc-notmain.exp b/usr.bin/make/unit-tests/depsrc-notmain.exp new file mode 100644 index 000000000..931cfc06f --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-notmain.exp @@ -0,0 +1,2 @@ +: all +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-notmain.mk b/usr.bin/make/unit-tests/depsrc-notmain.mk new file mode 100644 index 000000000..7ba572be7 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-notmain.mk @@ -0,0 +1,11 @@ +# $NetBSD: depsrc-notmain.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# +# Tests for the special source .NOTMAIN in dependency declarations, +# which prevents the associated target from becoming the default target +# to be made. + +ignored: .NOTMAIN + : ${.TARGET} + +all: + : ${.TARGET} diff --git a/usr.bin/make/unit-tests/depsrc-optional.exp b/usr.bin/make/unit-tests/depsrc-optional.exp new file mode 100644 index 000000000..8ecd28266 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-optional.exp @@ -0,0 +1,20 @@ +Make_ExpandUse: examine all +ExamineLater: need to examine "important" +Make_ExpandUse: examine important +ExamineLater: need to examine "optional" +ExamineLater: need to examine "optional-cohort" +Make_ExpandUse: examine optional +Make_ExpandUse: examine optional-cohort +Examining optional...nonexistent...up-to-date. +Examining optional-cohort...nonexistent...:: operator and no sources...out-of-date. +: A leaf node using '::' is considered out-of-date. + recheck(optional-cohort): update time from nonexistent to now +Examining important...nonexistent...modified before source "optional-cohort"...out-of-date. +: important is made. + recheck(important): update time from nonexistent to now +Examining all...nonexistent...modified before source "important"...out-of-date. +: all is made. + recheck(all): update time from nonexistent to now +Examining .END...nonexistent...nonexistent and no sources...out-of-date. + recheck(.END): update time from nonexistent to now +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-optional.mk b/usr.bin/make/unit-tests/depsrc-optional.mk new file mode 100644 index 000000000..f12eeca2b --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-optional.mk @@ -0,0 +1,21 @@ +# $NetBSD: depsrc-optional.mk,v 1.5 2020/11/08 10:33:47 rillig Exp $ +# +# Tests for the special source .OPTIONAL in dependency declarations, +# which ignores the target if make cannot find out how to create it. +# +# TODO: Describe practical use cases for this feature. + +all: important + : ${.TARGET} is made. + +important: optional optional-cohort + : ${.TARGET} is made. + +optional: .OPTIONAL + : An optional leaf node is not executed. + +# See IsOODateRegular. +optional-cohort:: .OPTIONAL + : A leaf node using '::' is considered out-of-date. + +.MAKEFLAGS: -dm diff --git a/usr.bin/make/unit-tests/depsrc-phony.exp b/usr.bin/make/unit-tests/depsrc-phony.exp new file mode 100644 index 000000000..fa062132b --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-phony.exp @@ -0,0 +1,2 @@ +: depsrc-phony.mk is made. +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-phony.mk b/usr.bin/make/unit-tests/depsrc-phony.mk new file mode 100644 index 000000000..c41efac36 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-phony.mk @@ -0,0 +1,9 @@ +# $NetBSD: depsrc-phony.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# +# Tests for the special source .PHONY in dependency declarations, +# which executes the commands for the target even if a file of the same +# name exists and would be considered up to date. + +# Without the .PHONY, this target would be "up to date". +${MAKEFILE}: .PHONY + : ${.TARGET:T} is made. diff --git a/usr.bin/make/unit-tests/depsrc-precious.exp b/usr.bin/make/unit-tests/depsrc-precious.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-precious.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-precious.mk b/usr.bin/make/unit-tests/depsrc-precious.mk new file mode 100644 index 000000000..e8522a300 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-precious.mk @@ -0,0 +1,16 @@ +# $NetBSD: depsrc-precious.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special source .PRECIOUS in dependency declarations, which +# is only relevant if the commands for the target fail or are interrupted. +# In such a case, the target file is usually removed, to avoid having +# half-finished files with a timestamp suggesting the file were up-to-date. +# +# For targets marked with .PRECIOUS, the target file is not removed. +# The author of the makefile is then responsible for avoiding the above +# situation, in which the target would be wrongly considered up-to-date, +# just because its timestamp says so. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/depsrc-recursive.exp b/usr.bin/make/unit-tests/depsrc-recursive.exp new file mode 100644 index 000000000..85c936ea4 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-recursive.exp @@ -0,0 +1,3 @@ +this-is-made is made. +echo this-is-not-made is just echoed. +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-recursive.mk b/usr.bin/make/unit-tests/depsrc-recursive.mk new file mode 100644 index 000000000..c1f153787 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-recursive.mk @@ -0,0 +1,16 @@ +# $NetBSD: depsrc-recursive.mk,v 1.3 2020/09/05 15:57:12 rillig Exp $ +# +# Tests for the special source .RECURSIVE in dependency declarations, +# which executes the commands of the target even if the -n or -t command +# line options are given. + +.MAKEFLAGS: -n + +all: this-is-made +all: this-is-not-made + +this-is-made: .RECURSIVE + @echo ${.TARGET} is made. + +this-is-not-made: + @echo ${.TARGET} is just echoed. diff --git a/usr.bin/make/unit-tests/depsrc-silent.exp b/usr.bin/make/unit-tests/depsrc-silent.exp new file mode 100644 index 000000000..1ea493702 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-silent.exp @@ -0,0 +1,4 @@ +one +two +three +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-silent.mk b/usr.bin/make/unit-tests/depsrc-silent.mk new file mode 100644 index 000000000..98da7b387 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-silent.mk @@ -0,0 +1,12 @@ +# $NetBSD: depsrc-silent.mk,v 1.3 2020/08/29 17:34:21 rillig Exp $ +# +# Tests for the special source .SILENT in dependency declarations, +# which hides the commands, no matter whether they are prefixed with +# '@' or not. + +# Without the .SILENT, the commands 'echo one' and 'echo two' would be +# written to stdout. +all: .SILENT + echo one + echo two + @echo three diff --git a/usr.bin/make/unit-tests/depsrc-use.exp b/usr.bin/make/unit-tests/depsrc-use.exp new file mode 100644 index 000000000..c9810bda4 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-use.exp @@ -0,0 +1,6 @@ +first 1 +first 2 +second 1 +second 2 +directly +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-use.mk b/usr.bin/make/unit-tests/depsrc-use.mk new file mode 100644 index 000000000..17836cd39 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-use.mk @@ -0,0 +1,24 @@ +# $NetBSD: depsrc-use.mk,v 1.4 2020/08/22 12:30:57 rillig Exp $ +# +# Tests for the special source .USE in dependency declarations, +# which allows to append common commands to other targets. + +all: action directly + +first: .USE + @echo first 1 # Using ${.TARGET} here would expand to "action" + @echo first 2 + +second: .USE + @echo second 1 + @echo second 2 + +# It's possible but uncommon to have a .USE target with no commands. +# This may happen as the result of expanding a .for loop. +empty: .USE + +# It's possible but uncommon to directly make a .USEBEFORE target. +directly: .USE + @echo directly + +action: first second empty diff --git a/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.exp b/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.exp new file mode 100644 index 000000000..8bbdef5ad --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.exp @@ -0,0 +1,2 @@ +double-colon early 1 +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.mk b/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.mk new file mode 100644 index 000000000..448214112 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-usebefore-double-colon.mk @@ -0,0 +1,30 @@ +# $NetBSD: depsrc-usebefore-double-colon.mk,v 1.1 2020/08/22 08:29:13 rillig Exp $ +# +# Tests for the special source .USEBEFORE in dependency declarations, +# combined with the double-colon dependency operator. + +all: action + +# The dependency operator :: allows commands to be added later to the same +# target. +double-colon:: .USEBEFORE + @echo double-colon early 1 + +# This command is ignored, which kind of makes sense since this dependency +# declaration has no .USEBEFORE source. +double-colon:: + @echo double-colon early 2 + +# XXX: This command is ignored even though it has a .USEBEFORE source. +# This is unexpected. +double-colon:: .USEBEFORE + @echo double-colon early 3 + +# At this point, the commands from the .USEBEFORE targets are copied to +# the "action" target. +action: double-colon + +# This command is not added to the "action" target since it comes too late. +# The commands had been copied in the previous line already. +double-colon:: + @echo double-colon late diff --git a/usr.bin/make/unit-tests/depsrc-usebefore.exp b/usr.bin/make/unit-tests/depsrc-usebefore.exp new file mode 100644 index 000000000..c9810bda4 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-usebefore.exp @@ -0,0 +1,6 @@ +first 1 +first 2 +second 1 +second 2 +directly +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-usebefore.mk b/usr.bin/make/unit-tests/depsrc-usebefore.mk new file mode 100644 index 000000000..001cfb0d7 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-usebefore.mk @@ -0,0 +1,28 @@ +# $NetBSD: depsrc-usebefore.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special source .USEBEFORE in dependency declarations, +# which allows to prepend common commands to other targets. +# +# See also: +# .USE +# depsrc-use.mk + +all: action directly + +first: .USEBEFORE + @echo first 1 # Using ${.TARGET} here would expand to "action" + @echo first 2 # Using ${.TARGET} here would expand to "action" + +second: .USEBEFORE + @echo second 1 + @echo second 2 + +# It is possible but uncommon to have a .USEBEFORE target with no commands. +# This may happen as the result of expanding a .for loop. +empty: .USEBEFORE + +# It is possible but uncommon to directly make a .USEBEFORE target. +directly: .USEBEFORE + @echo directly + +action: second first empty diff --git a/usr.bin/make/unit-tests/depsrc-wait.exp b/usr.bin/make/unit-tests/depsrc-wait.exp new file mode 100644 index 000000000..d1a60fbaa --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-wait.exp @@ -0,0 +1,13 @@ +--- a --- +echo a +a +--- b1 --- +echo b1 +b1 +--- b --- +echo b +b +--- x --- +echo x +x +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc-wait.mk b/usr.bin/make/unit-tests/depsrc-wait.mk new file mode 100644 index 000000000..95b0ea96e --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc-wait.mk @@ -0,0 +1,21 @@ +# $NetBSD: depsrc-wait.mk,v 1.3 2020/09/07 18:40:32 rillig Exp $ +# +# Tests for the special source .WAIT in dependency declarations, +# which adds a sequence point between the nodes to its left and the nodes +# to its right. + +# Even though the build could run massively parallel, the .WAIT imposes a +# strict ordering in this example, which forces the targets to be made in +# exactly this order. +.MAKEFLAGS: -j8 + +# This is the example from the manual page. +.PHONY: x a b b1 +x: a .WAIT b + echo x +a: + echo a +b: b1 + echo b +b1: + echo b1 diff --git a/usr.bin/make/unit-tests/depsrc.exp b/usr.bin/make/unit-tests/depsrc.exp new file mode 100644 index 000000000..06165e6f9 --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc.exp @@ -0,0 +1,4 @@ +: 'Undefined variables are expanded directly in the dependency' +: 'declaration. They are not preserved and maybe expanded later.' +: 'This is in contrast to local variables such as ${.TARGET}.' +exit status 0 diff --git a/usr.bin/make/unit-tests/depsrc.mk b/usr.bin/make/unit-tests/depsrc.mk new file mode 100644 index 000000000..ab9d04c1d --- /dev/null +++ b/usr.bin/make/unit-tests/depsrc.mk @@ -0,0 +1,26 @@ +# $NetBSD: depsrc.mk,v 1.4 2020/12/22 19:38:44 rillig Exp $ +# +# Tests for special sources (those starting with a dot, followed by +# uppercase letters) in dependency declarations, such as .PHONY. + +# TODO: Implementation + +# TODO: Test 'target: ${:U.SILENT}' + +# Demonstrate when exactly undefined variables are expanded in a dependency +# declaration. +target: .PHONY source-${DEFINED_LATER} +# +DEFINED_LATER= later +# +source-: .PHONY + : 'Undefined variables are expanded directly in the dependency' + : 'declaration. They are not preserved and maybe expanded later.' + : 'This is in contrast to local variables such as $${.TARGET}.' +source-later: .PHONY + : 'Undefined variables are tried to be expanded in a dependency' + : 'declaration. If that fails because the variable is undefined,' + : 'the expression is preserved and tried to be expanded later.' + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.exp b/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.exp new file mode 100644 index 000000000..59575e839 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.exp @@ -0,0 +1,6 @@ +false +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.mk b/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.mk new file mode 100644 index 000000000..e50b6207b --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-begin-fail-indirect.mk @@ -0,0 +1,16 @@ +# $NetBSD: deptgt-begin-fail-indirect.mk,v 1.1 2020/11/24 19:02:59 rillig Exp $ +# +# Test for a .BEGIN target whose dependency results in an error. +# This stops make immediately and does not build the main targets. +# +# Between 2005-05-08 and 2020-11-24, a failing dependency of the .BEGIN node +# would not stop make from running the main targets. In the end, the exit +# status was even 0. + +.BEGIN: failing + +failing: .PHONY .NOTMAIN + false + +all: + : This is not made. diff --git a/usr.bin/make/unit-tests/deptgt-begin-fail.exp b/usr.bin/make/unit-tests/deptgt-begin-fail.exp new file mode 100644 index 000000000..59575e839 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-begin-fail.exp @@ -0,0 +1,6 @@ +false +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/deptgt-begin-fail.mk b/usr.bin/make/unit-tests/deptgt-begin-fail.mk new file mode 100644 index 000000000..80cea2566 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-begin-fail.mk @@ -0,0 +1,10 @@ +# $NetBSD: deptgt-begin-fail.mk,v 1.1 2020/11/24 19:02:59 rillig Exp $ +# +# Test for a .BEGIN target whose command results in an error. +# This stops make immediately and does not build the main targets. + +.BEGIN: + false + +all: + : This is not made. diff --git a/usr.bin/make/unit-tests/deptgt-begin.exp b/usr.bin/make/unit-tests/deptgt-begin.exp new file mode 100644 index 000000000..abc80afe9 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-begin.exp @@ -0,0 +1,7 @@ +make: "deptgt-begin.mk" line 17: warning: duplicate script for target ".BEGIN" ignored +make: "deptgt-begin.mk" line 8: warning: using previous script for ".BEGIN" defined here +: parse time +: Making before-begin before .BEGIN. +: .BEGIN +: all +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-begin.mk b/usr.bin/make/unit-tests/deptgt-begin.mk new file mode 100644 index 000000000..b71d78f37 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-begin.mk @@ -0,0 +1,47 @@ +# $NetBSD: deptgt-begin.mk,v 1.5 2020/11/15 22:28:08 rillig Exp $ +# +# Tests for the special target .BEGIN in dependency declarations, +# which is a container for commands that are run before any other +# commands from the shell lines. + +.BEGIN: + : $@ + +# To register a custom action to be run at the beginning, the simplest way is +# to directly place some commands on the '.BEGIN' target. This doesn't scale +# though, since the ':' dependency operator prevents that any other place may +# add its commands after this. +# +# There are several ways to resolve this situation, which are detailed below. +.BEGIN: + : Making another $@. + +# One way to run commands at the beginning is to define a custom target and +# make the .BEGIN depend on that target. This way, the commands from the +# custom target are run even before the .BEGIN target. +.BEGIN: before-begin +before-begin: .PHONY .NOTMAIN + : Making $@ before .BEGIN. + +# Another way is to define a custom target and make that a .USE dependency. +# For the .BEGIN target, .USE dependencies do not work though, since in +# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the +# .BEGIN target has been run, which is too late. +.BEGIN: use +use: .USE .NOTMAIN + : Making $@ from a .USE dependency. + +# Same as with .USE, but run the commands before the main commands from the +# .BEGIN target. +# +# For the .BEGIN target, .USEBEFORE dependencies do not work though, since in +# Compat_Run, the .USE and .USEBEFORE nodes are expanded right after the +# .BEGIN target has been run, which is too late. +.BEGIN: use-before +use-before: .USEBEFORE .NOTMAIN + : Making $@ from a .USEBEFORE dependency. + +all: + : $@ + +_!= echo : parse time 1>&2 diff --git a/usr.bin/make/unit-tests/deptgt-default.exp b/usr.bin/make/unit-tests/deptgt-default.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-default.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-default.mk b/usr.bin/make/unit-tests/deptgt-default.mk new file mode 100644 index 000000000..814eaf72a --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-default.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-default.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .DEFAULT in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-delete_on_error.exp b/usr.bin/make/unit-tests/deptgt-delete_on_error.exp new file mode 100644 index 000000000..9d9f1dc3e --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-delete_on_error.exp @@ -0,0 +1,51 @@ +Compatibility mode +> deptgt-delete_on_error-regular; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-regular removed +> deptgt-delete_on_error-regular-delete; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-regular-delete removed +> deptgt-delete_on_error-phony; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-phony removed +> deptgt-delete_on_error-phony-delete; false +*** Error code 1 (continuing) +make: *** deptgt-delete_on_error-phony-delete removed +> deptgt-delete_on_error-precious; false +*** Error code 1 (continuing) +> deptgt-delete_on_error-precious-delete; false +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests + +Parallel mode +> deptgt-delete_on_error-regular; false +*** [deptgt-delete_on_error-regular] Error code 1 +make: *** deptgt-delete_on_error-regular removed + +make: stopped in unit-tests +> deptgt-delete_on_error-regular-delete; false +*** [deptgt-delete_on_error-regular-delete] Error code 1 +make: *** deptgt-delete_on_error-regular-delete removed + +make: stopped in unit-tests +> deptgt-delete_on_error-phony; false +*** [deptgt-delete_on_error-phony] Error code 1 + +make: stopped in unit-tests +> deptgt-delete_on_error-phony-delete; false +*** [deptgt-delete_on_error-phony-delete] Error code 1 + +make: stopped in unit-tests +> deptgt-delete_on_error-precious; false +*** [deptgt-delete_on_error-precious] Error code 1 + +make: stopped in unit-tests +> deptgt-delete_on_error-precious-delete; false +*** [deptgt-delete_on_error-precious-delete] Error code 1 + +make: stopped in unit-tests +*** Error code 1 (ignored) +*** Error code 1 (ignored) +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-delete_on_error.mk b/usr.bin/make/unit-tests/deptgt-delete_on_error.mk new file mode 100644 index 000000000..e6d0610f4 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-delete_on_error.mk @@ -0,0 +1,45 @@ + # $NetBSD: deptgt-delete_on_error.mk,v 1.3 2020/10/25 21:31:00 rillig Exp $ +# +# Tests for the special target .DELETE_ON_ERROR in dependency declarations, +# which controls whether the target is deleted if a shell command fails or +# is interrupted. +# +# In compatibility mode, regular and phony targets are deleted, but precious +# targets are preserved. +# +# In parallel mode, regular targets are deleted, while phony and precious +# targets are preserved. +# +# See also: +# CompatDeleteTarget +# JobDeleteTarget + +THIS= deptgt-delete_on_error +TARGETS= ${THIS}-regular ${THIS}-regular-delete +TARGETS+= ${THIS}-phony ${THIS}-phony-delete +TARGETS+= ${THIS}-precious ${THIS}-precious-delete + +all: + @rm -f ${TARGETS} + @echo 'Compatibility mode' + @-${.MAKE} -f ${MAKEFILE} -k ${TARGETS} + @rm -f ${TARGETS} + @echo + @echo 'Parallel mode' + @-${.MAKE} -f ${MAKEFILE} -k -j1 ${TARGETS} + @rm -f ${TARGETS} + +${THIS}-regular{,-delete}: + > ${.TARGET}; false + +${THIS}-phony{,-delete}: .PHONY + > ${.TARGET}; false + +${THIS}-precious{,-delete}: .PRECIOUS + > ${.TARGET}; false + +# The special target .DELETE_ON_ERROR is a global setting. +# It does not apply to single targets. +# The following line is therefore misleading but does not generate any +# warning or even an error message. +.DELETE_ON_ERROR: ${TARGETS:M*-delete} diff --git a/usr.bin/make/unit-tests/deptgt-end-fail-all.exp b/usr.bin/make/unit-tests/deptgt-end-fail-all.exp new file mode 100644 index 000000000..2e2ee11f4 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-fail-all.exp @@ -0,0 +1,7 @@ +: Making all out of nothing. +false +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/deptgt-end-fail-all.mk b/usr.bin/make/unit-tests/deptgt-end-fail-all.mk new file mode 100644 index 000000000..4bf160d48 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-fail-all.mk @@ -0,0 +1,19 @@ +# $NetBSD: deptgt-end-fail-all.mk,v 1.2 2020/12/07 01:04:07 rillig Exp $ +# +# Test whether the commands from the .END target are run even if there is +# an error before. The manual page says "after everything else is done", +# which leaves room for interpretation. +# +# Until 2020-12-07, the .END node was made even if the main nodes had failed. +# This was not intended since the .END node had already been skipped if a +# dependency of the main nodes had failed, just not if one of the main nodes +# themselves had failed. This inconsistency was not worth keeping. To run +# some commands on error, use the .ERROR target instead, see deptgt-error.mk. + +all: .PHONY + : Making ${.TARGET} out of nothing. + false + +.END: + : Making ${.TARGET} out of nothing. + false diff --git a/usr.bin/make/unit-tests/deptgt-end-fail-indirect.exp b/usr.bin/make/unit-tests/deptgt-end-fail-indirect.exp new file mode 100644 index 000000000..17e509600 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-fail-indirect.exp @@ -0,0 +1,7 @@ +: all +false +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/deptgt-end-fail-indirect.mk b/usr.bin/make/unit-tests/deptgt-end-fail-indirect.mk new file mode 100644 index 000000000..29346b832 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-fail-indirect.mk @@ -0,0 +1,16 @@ +# $NetBSD: deptgt-end-fail-indirect.mk,v 1.2 2020/12/06 21:22:04 rillig Exp $ +# +# Tests for an error in a dependency of the .END node. +# +# Before 2020-11-25, an error in the .END target did not print the "Stop." +# and exited with status 0. The cause for this was a missing condition in +# Compat_Run in the handling of the .END node. + +all: + : $@ + +.END: failing + : Making ${.TARGET} from ${.ALLSRC}. + +failing: .PHONY + false diff --git a/usr.bin/make/unit-tests/deptgt-end-fail.exp b/usr.bin/make/unit-tests/deptgt-end-fail.exp new file mode 100644 index 000000000..9db907c20 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-fail.exp @@ -0,0 +1,163 @@ +Test case all=ok all-dep=ok end=ok end-dep=ok. +: Making all-dep out of nothing. +: Making all from all-dep. +: Making end-dep out of nothing. +: Making .END from end-dep. +exit status 0 + + +Test case all=ok all-dep=ok end=ok end-dep=ERR. +: Making all-dep out of nothing. +: Making all from all-dep. +: Making end-dep out of nothing. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ok all-dep=ok end=ERR end-dep=ok. +: Making all-dep out of nothing. +: Making all from all-dep. +: Making end-dep out of nothing. +: Making .END from end-dep. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ok all-dep=ok end=ERR end-dep=ERR. +: Making all-dep out of nothing. +: Making all from all-dep. +: Making end-dep out of nothing. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ok all-dep=ERR end=ok end-dep=ok. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ok all-dep=ERR end=ok end-dep=ERR. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ok all-dep=ERR end=ERR end-dep=ok. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ok all-dep=ERR end=ERR end-dep=ERR. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ok end=ok end-dep=ok. +: Making all-dep out of nothing. +: Making all from all-dep. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ok end=ok end-dep=ERR. +: Making all-dep out of nothing. +: Making all from all-dep. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ok end=ERR end-dep=ok. +: Making all-dep out of nothing. +: Making all from all-dep. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ok end=ERR end-dep=ERR. +: Making all-dep out of nothing. +: Making all from all-dep. +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ERR end=ok end-dep=ok. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ERR end=ok end-dep=ERR. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ERR end=ERR end-dep=ok. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +Test case all=ERR all-dep=ERR end=ERR end-dep=ERR. +: Making all-dep out of nothing. +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 + + +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-end-fail.mk b/usr.bin/make/unit-tests/deptgt-end-fail.mk new file mode 100644 index 000000000..57cdc7da8 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-fail.mk @@ -0,0 +1,69 @@ +# $NetBSD: deptgt-end-fail.mk,v 1.6 2020/12/07 01:04:07 rillig Exp $ +# +# Tests for an errors in the main target, its dependencies, +# the .END node and its dependencies. +# +# Before 2020-11-25, an error in the .END target did not print the "Stop.", +# even though this was intended. The cause for this was a missing condition +# in Compat_Run, in the code handling the .END node. + +test: .PHONY + +# The default stop-on-error mode is not as interesting to test since it +# stops right after the first error. +.MAKEFLAGS: -k + +.for all in ok ERR +. for all-dep in ok ERR +. for end in ok ERR +. for end-dep in ok ERR +. for target in ${all}-${all-dep}-${end}-${end-dep} +test: ${target} +${target}: .PHONY .SILENT + echo Test case all=${all} all-dep=${all-dep} end=${end} end-dep=${end-dep}. + ${MAKE} -r -f ${MAKEFILE} \ + all=${all} all-dep=${all-dep} \ + end=${end} end-dep=${end-dep} \ + all; \ + echo "exit status $$?" + echo + echo +. endfor +. endfor +. endfor +. endfor +.endfor + +.if make(all) + +all all-dep end-dep: .PHONY + +CMD.ok= true +CMD.ERR= false + +all: all-dep + : Making ${.TARGET} from ${.ALLSRC}. + @${CMD.${all}} + +all-dep: + : Making ${.TARGET} out of nothing. + @${CMD.${all-dep}} + +.END: end-dep + : Making ${.TARGET} from ${.ALLSRC}. + @${CMD.${end}} + +end-dep: + : Making ${.TARGET} out of nothing. + @${CMD.${end-dep}} + +.endif + +# Until 2020-12-07, several of the test cases printed "`all' not remade +# because of errors.", followed by "exit status 0", which contradicted +# each other. + +# Until 2020-12-07, '.END' was even made if 'all' failed, but if a dependency +# of 'all' failed, it was skipped. This inconsistency was not needed for +# anything and thus has been dropped. To run some commands on error, use the +# .ERROR target instead, see deptgt-error.mk. diff --git a/usr.bin/make/unit-tests/deptgt-end-jobs.exp b/usr.bin/make/unit-tests/deptgt-end-jobs.exp new file mode 100644 index 000000000..8dd934b14 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-jobs.exp @@ -0,0 +1,8 @@ +: .BEGIN '${VAR}' +--- all --- +: all '${VAR}' +: .END '${VAR}' +: .END '${VAR}' deferred +: .BEGIN 'Should not be expanded.' deferred +: all 'Should not be expanded.' deferred +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-end-jobs.mk b/usr.bin/make/unit-tests/deptgt-end-jobs.mk new file mode 100644 index 000000000..1a5d5c33a --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end-jobs.mk @@ -0,0 +1,46 @@ +# $NetBSD: deptgt-end-jobs.mk,v 1.1 2020/09/23 03:06:38 rillig Exp $ +# +# Tests for the special target .END in dependency declarations, +# which is run after making the desired targets. +# +# This test is very similar to deptgt-end.mk, except for the -j option. +# This option enables parallel mode, in which the code from job.c partially +# replaces the code from compat.c. +# +# Before 2020-08-22, this test crashed with a null pointer dereference. +# Before 2020-09-23, this test crashed with an assertion failure. +.MAKEFLAGS: -j 8 + +VAR= Should not be expanded. + +.BEGIN: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +.END: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred + +all: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +# The deferred commands are run in the order '.END .BEGIN all'. +# This may be unexpected at first since the natural order would be +# '.BEGIN all .END', but it is implemented correctly. +# +# At the point where the commands of a node with deferred commands are run, +# the deferred commands are appended to the commands of the .END node. +# This happens in Compat_RunCommand, and to prevent an endless loop, the +# deferred commands of the .END node itself are not appended to itself. +# Instead, the deferred commands of the .END node are run as if they were +# immediate commands. diff --git a/usr.bin/make/unit-tests/deptgt-end.exp b/usr.bin/make/unit-tests/deptgt-end.exp new file mode 100644 index 000000000..144ee0071 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end.exp @@ -0,0 +1,9 @@ +: .BEGIN '${VAR}' +: all '${VAR}' +: end-action '${VAR}' +: .END '${VAR}' +: .END '${VAR}' deferred +: .BEGIN 'Should not be expanded.' deferred +: all 'Should not be expanded.' deferred +: end-action 'Should not be expanded.' deferred +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-end.mk b/usr.bin/make/unit-tests/deptgt-end.mk new file mode 100644 index 000000000..1240d6714 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-end.mk @@ -0,0 +1,45 @@ +# $NetBSD: deptgt-end.mk,v 1.6 2020/10/23 19:28:17 rillig Exp $ +# +# Tests for the special target .END in dependency declarations, +# which is run after making the desired targets. + +VAR= Should not be expanded. + +.BEGIN: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +.END: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred + +# The .END node can define dependencies, just like a regular target. +.END: end-action +end-action: .NOTMAIN + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred + +all: + : $@ '$${VAR}' + ... + : $@ '$${VAR}' deferred +# Oops: The deferred command must not be expanded twice. +# The Var_Subst in Compat_RunCommand looks suspicious. +# The Var_Subst in JobSaveCommand looks suspicious. + +# The deferred commands are run in the order '.END .BEGIN all'. +# This may be unexpected at first since the natural order would be +# '.BEGIN all .END', but it is implemented correctly. +# +# At the point where the commands of a node with deferred commands are run, +# the deferred commands are appended to the commands of the .END node. +# This happens in Compat_RunCommand, and to prevent an endless loop, the +# deferred commands of the .END node itself are not appended to itself. +# Instead, the deferred commands of the .END node are run as if they were +# immediate commands. diff --git a/usr.bin/make/unit-tests/deptgt-error.exp b/usr.bin/make/unit-tests/deptgt-error.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-error.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-error.mk b/usr.bin/make/unit-tests/deptgt-error.mk new file mode 100644 index 000000000..5d515b95a --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-error.mk @@ -0,0 +1,9 @@ +# $NetBSD: deptgt-error.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special target .ERROR in dependency declarations, which +# collects commands that are run when another target fails. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-ignore.exp b/usr.bin/make/unit-tests/deptgt-ignore.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-ignore.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-ignore.mk b/usr.bin/make/unit-tests/deptgt-ignore.mk new file mode 100644 index 000000000..49c14d2cf --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-ignore.mk @@ -0,0 +1,9 @@ +# $NetBSD: deptgt-ignore.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special target .IGNORE in dependency declarations, which +# does not stop if a command from this target exits with a non-zero status. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-interrupt.exp b/usr.bin/make/unit-tests/deptgt-interrupt.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-interrupt.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-interrupt.mk b/usr.bin/make/unit-tests/deptgt-interrupt.mk new file mode 100644 index 000000000..d94009a52 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-interrupt.mk @@ -0,0 +1,10 @@ +# $NetBSD: deptgt-interrupt.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special target .INTERRUPT in dependency declarations, which +# collects commands to be run when make is interrupted while building another +# target. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-main.exp b/usr.bin/make/unit-tests/deptgt-main.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-main.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-main.mk b/usr.bin/make/unit-tests/deptgt-main.mk new file mode 100644 index 000000000..84d05dc25 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-main.mk @@ -0,0 +1,10 @@ +# $NetBSD: deptgt-main.mk,v 1.3 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special target .MAIN in dependency declarations, which defines +# the main target. This main target is built if no target has been specified +# on the command line or via MAKEFLAGS. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-makeflags.exp b/usr.bin/make/unit-tests/deptgt-makeflags.exp new file mode 100644 index 000000000..f6f6f44e0 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-makeflags.exp @@ -0,0 +1,10 @@ +Global:delete DOLLAR (not found) +Command:DOLLAR = $$$$ +Global:.MAKEOVERRIDES = VAR DOLLAR +CondParser_Eval: ${DOLLAR} != "\$\$" +Var_Parse: ${DOLLAR} != "\$\$" (eval-defined) +lhs = "$$", rhs = "$$", op = != +Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d +Global:.MAKEFLAGS = -r -k -D VAR -D VAR -d cv -d 0 +make: Unterminated quoted string [make VAR=initial UNBALANCED='] +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-makeflags.mk b/usr.bin/make/unit-tests/deptgt-makeflags.mk new file mode 100644 index 000000000..0a0f410e1 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-makeflags.mk @@ -0,0 +1,93 @@ +# $NetBSD: deptgt-makeflags.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the special target .MAKEFLAGS in dependency declarations, +# which adds command line options later, at parse time. +# +# In these unit tests, it is often used to temporarily toggle the debug log +# during parsing. + +# The -D option sets a variable in the "Global" scope and thus can be +# undefined later. +.MAKEFLAGS: -D VAR +.if ${VAR} != 1 +. error +.endif + +# Variables that are set via the -D command line option are normal global +# variables and can thus be undefined later. +.undef VAR +.if defined(VAR) +. error +.endif + +# The -D command line option can define a variable again, after it has been +# undefined. +.MAKEFLAGS: -D VAR +.if ${VAR} != 1 +. error +.endif + +# The "dependency" for .MAKEFLAGS is split into words, interpreting the usual +# quotes and escape sequences from the backslash. +.MAKEFLAGS: VAR="value"' with'\ spaces +.if ${VAR} != "value with spaces" +. error +.endif + +# Variables set on the command line as VAR=value are placed in the +# "Command" scope and thus cannot be undefined. +.undef VAR +.if ${VAR} != "value with spaces" +. error +.endif + +# When parsing this line, each '$$' becomes '$', resulting in '$$$$'. +# This is assigned to the variable DOLLAR. +# In the condition, that variable is expanded, and at that point, each '$$' +# becomes '$' again, the final expression is thus '$$'. +.MAKEFLAGS: -dcv +.MAKEFLAGS: DOLLAR=$$$$$$$$ +.if ${DOLLAR} != "\$\$" +.endif +.MAKEFLAGS: -d0 + +# An empty command line is skipped. +.MAKEFLAGS: # none + +# Escape sequences like \n are interpreted. +# The following line looks as if it assigned a newline to nl, but it doesn't. +# Instead, the \n ends up as a line that is then interpreted as a variable +# assignment. At that point, the line is simply "nl=\n", and the \n is +# skipped since it is whitespace (see Parse_IsVar). +.MAKEFLAGS: nl="\n" +.if ${nl} != "" +. error +.endif + +# Next try at defining another newline variable. Since whitespace around the +# variable value is trimmed, two empty variable expressions surround the +# literal newline now. This prevents the newline from being skipped during +# parsing. The ':=' assignment operator expands the empty variable +# 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 +# newlines into a line that is being parsed. This may confuse the parser. +# For example, in cond.c the parser only expects horizontal whitespace (' ' +# and '\t'), but no newlines. +#.MAKEFLAGS: -dcpv +.MAKEFLAGS: nl:="$${:U}\n$${:U}" +.if ${nl} != ${.newline} +. error +.endif +#.MAKEFLAGS: -d0 + +# Unbalanced quotes produce an error message. If they occur anywhere in the +# command line, the whole command line is skipped. +.MAKEFLAGS: VAR=previous +.MAKEFLAGS: VAR=initial UNBALANCED=' +.if ${VAR} != "previous" +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-no_parallel.exp b/usr.bin/make/unit-tests/deptgt-no_parallel.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-no_parallel.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-no_parallel.mk b/usr.bin/make/unit-tests/deptgt-no_parallel.mk new file mode 100644 index 000000000..247f6a9ac --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-no_parallel.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-no_parallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .NO_PARALLEL in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-nopath.exp b/usr.bin/make/unit-tests/deptgt-nopath.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-nopath.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-nopath.mk b/usr.bin/make/unit-tests/deptgt-nopath.mk new file mode 100644 index 000000000..a8cde2657 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-nopath.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-nopath.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .NOPATH in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-notparallel.exp b/usr.bin/make/unit-tests/deptgt-notparallel.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-notparallel.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-notparallel.mk b/usr.bin/make/unit-tests/deptgt-notparallel.mk new file mode 100644 index 000000000..db08aa9d3 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-notparallel.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-notparallel.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .NOTPARALLEL in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-objdir.exp b/usr.bin/make/unit-tests/deptgt-objdir.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-objdir.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-objdir.mk b/usr.bin/make/unit-tests/deptgt-objdir.mk new file mode 100644 index 000000000..f5e9a15d8 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-objdir.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-objdir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .OBJDIR in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-order.exp b/usr.bin/make/unit-tests/deptgt-order.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-order.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-order.mk b/usr.bin/make/unit-tests/deptgt-order.mk new file mode 100644 index 000000000..003552f57 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-order.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-order.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .ORDER in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-path-suffix.exp b/usr.bin/make/unit-tests/deptgt-path-suffix.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-path-suffix.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-path-suffix.mk b/usr.bin/make/unit-tests/deptgt-path-suffix.mk new file mode 100644 index 000000000..3a7e697bc --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-path-suffix.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-path-suffix.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .PATH.suffix in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-path.exp b/usr.bin/make/unit-tests/deptgt-path.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-path.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-path.mk b/usr.bin/make/unit-tests/deptgt-path.mk new file mode 100644 index 000000000..f74d1a46d --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-path.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-path.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .PATH in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-phony.exp b/usr.bin/make/unit-tests/deptgt-phony.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-phony.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-phony.mk b/usr.bin/make/unit-tests/deptgt-phony.mk new file mode 100644 index 000000000..7f9909fa8 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-phony.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-phony.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .PHONY in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-precious.exp b/usr.bin/make/unit-tests/deptgt-precious.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-precious.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-precious.mk b/usr.bin/make/unit-tests/deptgt-precious.mk new file mode 100644 index 000000000..07b2bf719 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-precious.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-precious.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .PRECIOUS in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-shell.exp b/usr.bin/make/unit-tests/deptgt-shell.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-shell.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-shell.mk b/usr.bin/make/unit-tests/deptgt-shell.mk new file mode 100644 index 000000000..71b535d89 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-shell.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-shell.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .SHELL in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-silent.exp b/usr.bin/make/unit-tests/deptgt-silent.exp new file mode 100644 index 000000000..c9b89da0a --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-silent.exp @@ -0,0 +1,5 @@ +echo 'This is a loud command.' +This is a loud command. +This is not echoed because of the @. +This is not echoed because of the .SILENT. +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-silent.mk b/usr.bin/make/unit-tests/deptgt-silent.mk new file mode 100644 index 000000000..335a1e897 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-silent.mk @@ -0,0 +1,15 @@ +# $NetBSD: deptgt-silent.mk,v 1.4 2020/11/15 20:49:20 rillig Exp $ +# +# Tests for the special target .SILENT in dependency declarations. + +.SILENT: all + +all: loud + @echo 'This is not echoed because of the @.' + # Without the .SILENT, the following command would be echoed. + echo 'This is not echoed because of the .SILENT.' + +# Demonstrate that the .SILENT only applies to the particular target. +# This is unlike .DELETE_ON_ERROR, which is a global setting. +loud: .PHONY + echo 'This is a loud command.' diff --git a/usr.bin/make/unit-tests/deptgt-stale.exp b/usr.bin/make/unit-tests/deptgt-stale.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-stale.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-stale.mk b/usr.bin/make/unit-tests/deptgt-stale.mk new file mode 100644 index 000000000..cfde5a2bf --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-stale.mk @@ -0,0 +1,8 @@ +# $NetBSD: deptgt-stale.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the special target .STALE in dependency declarations. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/deptgt-suffixes.exp b/usr.bin/make/unit-tests/deptgt-suffixes.exp new file mode 100644 index 000000000..512e6d44a --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-suffixes.exp @@ -0,0 +1,33 @@ +#*** Suffixes: +# ".custom-null" (num 1, ref 1) (SUFF_NULL) +# To: +# From: +# Search Path: . .. +# ".src-left" (num 2, ref 2) +# To: .tgt-right +# From: +# Search Path: +# ".tgt-right" (num 3, ref 2) +# To: +# From: .src-left +# Search Path: +# ".tgt-left" (num 4, ref 2) +# To: +# From: .src-right +# Search Path: +# ".src-right" (num 5, ref 2) +# To: .tgt-left +# From: +# Search Path: +#*** Transformations: +.src-left.tgt-right: + : Making ${.TARGET} from ${.IMPSRC}. + +.src-right.tgt-left: + : Making ${.TARGET} from ${.IMPSRC}. + +: Making deptgt-suffixes.src-left out of nothing. +: Making deptgt-suffixes.tgt-right from deptgt-suffixes.src-left. +: Making deptgt-suffixes.src-right out of nothing. +: Making deptgt-suffixes.tgt-left from deptgt-suffixes.src-right. +exit status 0 diff --git a/usr.bin/make/unit-tests/deptgt-suffixes.mk b/usr.bin/make/unit-tests/deptgt-suffixes.mk new file mode 100644 index 000000000..15b4dc081 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt-suffixes.mk @@ -0,0 +1,35 @@ +# $NetBSD: deptgt-suffixes.mk,v 1.4 2020/11/21 21:54:42 rillig Exp $ +# +# Tests for the special target .SUFFIXES in dependency declarations. +# +# See also: +# varname-dot-includes.mk +# varname-dot-libs.mk + +.MAKEFLAGS: -dg1 + +.MAIN: all + +.SUFFIXES: .custom-null + +# TODO: What is the effect of this? How is it useful? +.NULL: .custom-null +.PATH.custom-null: . .. + +# The order in which the suffixes are listed doesn't matter. +# Here, they are listed from source to target, just like in the transformation +# rule below it. +.SUFFIXES: .src-left .tgt-right +deptgt-suffixes.src-left: + : Making ${.TARGET} out of nothing. +.src-left.tgt-right: + : Making ${.TARGET} from ${.IMPSRC}. +all: deptgt-suffixes.tgt-right + +# Here, the target is listed earlier than the source. +.SUFFIXES: .tgt-left .src-right +deptgt-suffixes.src-right: + : Making ${.TARGET} out of nothing. +.src-right.tgt-left: + : Making ${.TARGET} from ${.IMPSRC}. +all: deptgt-suffixes.tgt-left diff --git a/usr.bin/make/unit-tests/deptgt.exp b/usr.bin/make/unit-tests/deptgt.exp new file mode 100644 index 000000000..d3f0095d9 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt.exp @@ -0,0 +1,14 @@ +make: "deptgt.mk" line 10: warning: Extra target ignored +make: "deptgt.mk" line 28: Unassociated shell command ": command3 # parse error, since targets == NULL" +ParseReadLine (34): '${:U}: empty-source' +ParseDoDependency(: empty-source) +ParseReadLine (35): ' : command for empty targets list' +ParseReadLine (36): ': empty-source' +ParseDoDependency(: empty-source) +ParseReadLine (37): ' : command for empty targets list' +ParseReadLine (38): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) +make: "deptgt.mk" line 46: Unknown modifier "Z" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/deptgt.mk b/usr.bin/make/unit-tests/deptgt.mk new file mode 100644 index 000000000..09f381715 --- /dev/null +++ b/usr.bin/make/unit-tests/deptgt.mk @@ -0,0 +1,49 @@ +# $NetBSD: deptgt.mk,v 1.10 2020/12/27 18:20:26 rillig Exp $ +# +# Tests for special targets like .BEGIN or .SUFFIXES in dependency +# declarations. + +# TODO: Implementation + +# Just in case anyone tries to compile several special targets in a single +# dependency line: That doesn't work, and make immediately rejects it. +.SUFFIXES .PHONY: .c.o + +# The following lines demonstrate how 'targets' is set and reset during +# parsing of dependencies. To see it in action, set breakpoints in: +# +# ParseDoDependency at the beginning +# FinishDependencyGroup at "targets = NULL" +# Parse_File at "Lst_Free(targets)" +# Parse_File at "targets = Lst_New()" +# ParseLine_ShellCommand at "targets == NULL" +# +# Keywords: +# parse.c:targets + +target1 target2: sources # targets := [target1, target2] + : command1 # targets == [target1, target2] + : command2 # targets == [target1, target2] +VAR=value # targets := NULL + : 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 +# expression or whether it is just omitted. +.MAKEFLAGS: -dp +${:U}: empty-source + : command for empty targets list +: empty-source + : command for empty targets list +.MAKEFLAGS: -d0 + +# Just to show that a malformed expression is only expanded once in +# ParseDependencyTargetWord. The only way to produce an expression that +# is well-formed on the first expansion and ill-formed on the second +# expansion would be to use the variable modifier '::=' to modify the +# targets. This in turn would be such an extreme and unreliable edge case +# that nobody uses it. +$$$$$$$${:U:Z}: + +all: + @:; diff --git a/usr.bin/make/unit-tests/dir-expand-path.exp b/usr.bin/make/unit-tests/dir-expand-path.exp new file mode 100644 index 000000000..74de25529 --- /dev/null +++ b/usr.bin/make/unit-tests/dir-expand-path.exp @@ -0,0 +1,4 @@ +dir-expand-path.dir.1/file1.src +dir-expand-path.dir.1/file2.src +dir-expand-path.dir.2/file3.src +exit status 0 diff --git a/usr.bin/make/unit-tests/dir-expand-path.mk b/usr.bin/make/unit-tests/dir-expand-path.mk new file mode 100644 index 000000000..1ce349cce --- /dev/null +++ b/usr.bin/make/unit-tests/dir-expand-path.mk @@ -0,0 +1,19 @@ +# $NetBSD: dir-expand-path.mk,v 1.1 2020/08/22 21:55:54 rillig Exp $ +# +# Tests for filename expansion in the search path. + +_!= rm -rf dir-expand-path.dir.* +_!= mkdir dir-expand-path.dir.1 +_!= mkdir dir-expand-path.dir.2 +_!= touch dir-expand-path.dir.1/file1.src +_!= touch dir-expand-path.dir.1/file2.src +_!= touch dir-expand-path.dir.2/file3.src + +.PATH: dir-expand-path.dir.1 +.PATH: dir-expand-path.dir.2 + +all: *.src + @printf '%s\n' ${.ALLSRC:O} + +.END: + @rm -rf dir-expand-path.dir.* diff --git a/usr.bin/make/unit-tests/dir.exp b/usr.bin/make/unit-tests/dir.exp new file mode 100644 index 000000000..1adb42e6c --- /dev/null +++ b/usr.bin/make/unit-tests/dir.exp @@ -0,0 +1,50 @@ +Searching for .depend ... + failed. +Searching for .depend ... + / ... + failed. +Expanding "{one,two,three}"... one two three +Expanding "f{our,ive}"... four five +Expanding "{{thi,fou}r,fif}teen"... Expanding "{thi,fou}rteen"... thirteen fourteen +thirteen fourteen fifteen +Expanding "{pre-,}{patch,configure}"... Expanding "pre-{patch,configure}"... pre-patch pre-configure +Expanding "{patch,configure}"... pre-patch pre-configure patch configure +pre-patch pre-configure patch configure +Expanding "{fetch,extract}{,-post}"... Expanding "fetch{,-post}"... fetch fetch-post +Expanding "extract{,-post}"... fetch fetch-post extract extract-post +fetch fetch-post extract extract-post +Expanding "dup-{1,1,1,1,1,1,1}"... dup-1 dup-1 dup-1 dup-1 dup-1 dup-1 dup-1 +Expanding "{{{{{{{{{{single-word}}}}}}}}}}"... Expanding "{{{{{{{{{single-word}}}}}}}}}"... Expanding "{{{{{{{{single-word}}}}}}}}"... Expanding "{{{{{{{single-word}}}}}}}"... Expanding "{{{{{{single-word}}}}}}"... Expanding "{{{{{single-word}}}}}"... Expanding "{{{{single-word}}}}"... Expanding "{{{single-word}}}"... Expanding "{{single-word}}"... Expanding "{single-word}"... single-word +single-word +single-word +single-word +single-word +single-word +single-word +single-word +single-word +single-word +: 1 +: 2 +: 3 +: 4 +: 5 +: 13 +: 14 +: 15 +: pre-patch +: pre-configure +: patch +: configure +: fetch +: fetch-post +: extract +: extract-post +: dup-1 +: single-word +Searching for .END ... + failed. +Searching for .END ... + failed. +Found '.END' as '(not found)' +exit status 0 diff --git a/usr.bin/make/unit-tests/dir.mk b/usr.bin/make/unit-tests/dir.mk new file mode 100644 index 000000000..36fe2baf9 --- /dev/null +++ b/usr.bin/make/unit-tests/dir.mk @@ -0,0 +1,94 @@ +# $NetBSD: dir.mk,v 1.9 2021/01/23 10:48:49 rillig Exp $ +# +# Tests for dir.c. + +.MAKEFLAGS: -m / # hide /usr/share/mk from the debug log + +# Dependency lines may use braces for expansion. +# See DirExpandCurly for the implementation. +all: {one,two,three} + +# XXX: The above dependency line is parsed as a single node named +# "{one,two,three}". There are no individual targets "one", "two", "three" +# yet. The node exists but is not a target since it never appeared +# on the left-hand side of a dependency operator. However, it is listed +# in .ALLTARGETS (which lists all nodes, not only targets). +.if target(one) +. error +.endif +.if target({one,two,three}) +. error +.endif +.if ${.ALLTARGETS:M{one,two,three}} != "{one,two,three}" +. error +.endif + +one: + : 1 +two: + : 2 +three: + : 3 + +# The braces may start in the middle of a word. +all: f{our,ive} + +four: + : 4 +five: + : 5 +six: + : 6 + +# Nested braces work as expected since 2020-07-31 19:06 UTC. +# They had been broken at least since 2003-01-01, probably even longer. +all: {{thi,fou}r,fif}teen + +thirteen: + : 13 +fourteen: + : 14 +fifteen: + : 15 + +# There may be multiple brace groups side by side. +all: {pre-,}{patch,configure} + +pre-patch patch pre-configure configure: + : $@ + +# Empty pieces are allowed in the braces. +all: {fetch,extract}{,-post} + +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. +all: dup-{1,1,1,1,1,1,1} + +dup-1: + : $@ + +# Other than in Bash, the braces are also expanded if there is no comma. +all: {{{{{{{{{{single-word}}}}}}}}}} + +single-word: + : $@ + +# Demonstrate debug logging for filename expansion, especially curly braces. +.MAKEFLAGS: -dd +# The below line does not call SearchPath_Expand yet. +# It is expanded only when necessary, that is, when the 'debug' target is +# indeed made. +debug: {{thi,fou}r,fif}twen +# Therefore, keep the debug logging active. + +.PHONY: one two three four five six +.PHONY: thirteen fourteen fifteen +.PHONY: single-word +.PHONY: pre-patch patch pre-configure configure +.PHONY: fetch fetch-post extract extract-post +.PHONY: dup-1 single-word +.PHONY: all diff --git a/usr.bin/make/unit-tests/directive-dinclude.exp b/usr.bin/make/unit-tests/directive-dinclude.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-dinclude.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-dinclude.mk b/usr.bin/make/unit-tests/directive-dinclude.mk new file mode 100644 index 000000000..94fa5fb44 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-dinclude.mk @@ -0,0 +1,9 @@ +# $NetBSD: directive-dinclude.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# +# Tests for the .dinclude directive, which includes another file, +# typically named .depend. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-elif.exp b/usr.bin/make/unit-tests/directive-elif.exp new file mode 100644 index 000000000..685649402 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elif.exp @@ -0,0 +1,21 @@ +make: "directive-elif.mk" line 47: Unknown directive "elsif" +make: "directive-elif.mk" line 52: This branch is taken. +make: "directive-elif.mk" line 60: Unknown directive "elsif" +make: "directive-elif.mk" line 63: This branch is taken. +make: "directive-elif.mk" line 69: This branch is taken. +make: "directive-elif.mk" line 89: Unknown directive "elsif" +make: "directive-elif.mk" line 90: This misspelling is detected. +make: "directive-elif.mk" line 91: This branch is taken because of the .else. +make: "directive-elif.mk" line 109: What happens on misspelling in a skipped branch? +make: "directive-elif.mk" line 119: else +make: "directive-elif.mk" line 122: What happens on misspelling in a taken branch? +make: "directive-elif.mk" line 124: 1-then +make: "directive-elif.mk" line 125: Unknown directive "elsif" +make: "directive-elif.mk" line 126: 1-elsif +make: "directive-elif.mk" line 127: Unknown directive "elsif" +make: "directive-elif.mk" line 128: 2-elsif +make: "directive-elif.mk" line 134: if-less elif +make: "directive-elif.mk" line 139: warning: extra elif +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-elif.mk b/usr.bin/make/unit-tests/directive-elif.mk new file mode 100644 index 000000000..e7b89beec --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elif.mk @@ -0,0 +1,142 @@ +# $NetBSD: directive-elif.mk,v 1.7 2020/12/19 19:49:01 rillig Exp $ +# +# Tests for the .elif directive. +# +# Misspellings of the .elif directive are not always detected. They are only +# detected if the conditional branch directly above it is taken. In all other +# cases, make skips over the skipped branch as fast as possible, looking only +# at the initial '.' of the line and whether the directive is one of the known +# conditional directives. All other directives are silently ignored, as they +# could be variable assignments or dependency declarations as well, and +# deciding this would cost time. + + +# TODO: Implementation + + +# Misspelling '.elsif' below an .if branch that is not taken. +.if 0 +. info This branch is not taken. +# As of 2020-12-19, the misspelling is not recognized as a conditional +# directive and is thus silently skipped. +# +# Since the .if condition evaluated to false, this whole branch is not taken. +.elsif 0 +. info XXX: This misspelling is not detected. +. info This branch is not taken. +# Even if the misspelling were detected, the branch would not be taken +# since the condition of the '.elsif' evaluates to false as well. +.endif + + +# Misspelling '.elsif' below an .if branch that is not taken. +.if 0 +. info This branch is not taken. +# As of 2020-12-19, the misspelling is not recognized as a conditional +# directive and is thus silently skipped. Since the .if condition evaluated +# to false, this whole branch is not taken. +.elsif 1 +. info XXX: This misspelling is not detected. +# If the misspelling were detected, this branch would be taken. +.endif + + +# Misspelling '.elsif' below an .if branch that is taken. +.if 1 +# This misspelling is in an active branch and is therefore detected. +.elsif 0 +# The only thing that make detects here is a misspelled directive, make +# doesn't recognize that it was meant to be a conditional directive. +# Therefore the branch continues here, even though the '.elsif' condition +# evaluates to false. +. info This branch is taken. +.endif + + +# Misspelling '.elsif' below an .if branch that is taken. +.if 1 +# As of 2020-12-19, the misspelling is in an active branch and is therefore +# detected. +.elsif 1 +# Since both conditions evaluate to true, this branch is taken no matter +# whether make detects a misspelling or not. +. info This branch is taken. +.endif + + +# Misspelling '.elsif' in a skipped branch below a branch that was taken. +.if 1 +. info This branch is taken. +.elif 0 +. info This branch is not taken. +.elsif 1 +. info XXX: This misspelling is not detected. +.endif + + +# Misspelling '.elsif' in an .else branch that is not taken. +.if 1 +.else +. info This branch is not taken. +.elsif 1 +. info XXX: This misspelling is not detected. +.endif + + +# Misspelling '.elsif' in an .else branch that is taken. +.if 0 +.else +.elsif 1 +. info This misspelling is detected. +. info This branch is taken because of the .else. +.endif + + +# Misspellings for .elif in a .elif branch that is not taken. +.if 0 +. info This branch is not taken. +.elif 0 # ok +. info This branch is not taken. +.elsif 0 +. info XXX: This misspelling is not detected. +. info This branch is not taken. +.elseif 0 +. info XXX: This misspelling is not detected. +. info This branch is not taken. +.endif + + +.info What happens on misspelling in a skipped branch? +.if 0 +. info 0-then +.elsif 1 +. info XXX: This misspelling is not detected. +. info 1-elsif +.elsif 2 +. info XXX: This misspelling is not detected. +. info 2-elsif +.else +. info else +.endif + +.info What happens on misspelling in a taken branch? +.if 1 +. info 1-then +.elsif 1 +. info 1-elsif +.elsif 2 +. info 2-elsif +.else +. info else +.endif + +# Expect: "if-less elif" +.elif 0 + +.if 1 +.else +# Expect: "warning: extra elif" +.elif +.endif + +all: diff --git a/usr.bin/make/unit-tests/directive-elifdef.exp b/usr.bin/make/unit-tests/directive-elifdef.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifdef.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-elifdef.mk b/usr.bin/make/unit-tests/directive-elifdef.mk new file mode 100644 index 000000000..f960c1513 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifdef.mk @@ -0,0 +1,8 @@ +# $NetBSD: directive-elifdef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the .elifdef directive. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-elifmake.exp b/usr.bin/make/unit-tests/directive-elifmake.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifmake.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-elifmake.mk b/usr.bin/make/unit-tests/directive-elifmake.mk new file mode 100644 index 000000000..c78e3aa91 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifmake.mk @@ -0,0 +1,8 @@ +# $NetBSD: directive-elifmake.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the .elifmake directive. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-elifndef.exp b/usr.bin/make/unit-tests/directive-elifndef.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifndef.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-elifndef.mk b/usr.bin/make/unit-tests/directive-elifndef.mk new file mode 100644 index 000000000..19bb66c11 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifndef.mk @@ -0,0 +1,8 @@ +# $NetBSD: directive-elifndef.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the .elifndef directive. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-elifnmake.exp b/usr.bin/make/unit-tests/directive-elifnmake.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifnmake.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-elifnmake.mk b/usr.bin/make/unit-tests/directive-elifnmake.mk new file mode 100644 index 000000000..11c4b973c --- /dev/null +++ b/usr.bin/make/unit-tests/directive-elifnmake.mk @@ -0,0 +1,8 @@ +# $NetBSD: directive-elifnmake.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the .elifnmake directive. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-else.exp b/usr.bin/make/unit-tests/directive-else.exp new file mode 100644 index 000000000..138e893ff --- /dev/null +++ b/usr.bin/make/unit-tests/directive-else.exp @@ -0,0 +1,11 @@ +make: "directive-else.mk" line 14: The .else directive does not take arguments. +make: "directive-else.mk" line 15: ok +make: "directive-else.mk" line 19: ok +make: "directive-else.mk" line 21: The .else directive does not take arguments. +make: "directive-else.mk" line 26: if-less else +make: "directive-else.mk" line 32: ok +make: "directive-else.mk" line 33: warning: extra else +make: "directive-else.mk" line 45: The .else directive does not take arguments. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-else.mk b/usr.bin/make/unit-tests/directive-else.mk new file mode 100644 index 000000000..794057110 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-else.mk @@ -0,0 +1,49 @@ +# $NetBSD: directive-else.mk,v 1.7 2020/12/14 22:17:11 rillig Exp $ +# +# Tests for the .else directive. +# +# Since 2020-11-13, an '.else' followed by extraneous text generates a parse +# error in -dL (lint) mode. +# +# Since 2020-12-15, an '.else' followed by extraneous text always generates +# a parse error. + +.if 0 +. warning must not be reached +# The .else directive does not take any arguments. +.else 123 +. info ok +.endif + +.if 1 +. info ok +# The .else directive does not take any arguments. +.else 123 +. warning must not be reached +.endif + +# An .else without a corresponding .if is an error. +.else + +# Accidental extra .else directives are detected too. +.if 0 +. warning must not be reached +.else +. info ok +.else +. info After an extra .else, everything is skipped. +.endif + +# An .else may have a comment. This comment does not count as an argument, +# therefore no parse error. +.if 0 +.else # comment +.endif + +# A variable expression does count as an argument, even if it is empty. +.if 0 +.else ${:U} +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-endfor.exp b/usr.bin/make/unit-tests/directive-endfor.exp new file mode 100644 index 000000000..7e243a8f6 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-endfor.exp @@ -0,0 +1,4 @@ +make: "directive-endfor.mk" line 9: for-less endfor +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-endfor.mk b/usr.bin/make/unit-tests/directive-endfor.mk new file mode 100644 index 000000000..b0c37f388 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-endfor.mk @@ -0,0 +1,9 @@ +# $NetBSD: directive-endfor.mk,v 1.1 2020/12/30 14:50:08 rillig Exp $ +# +# Test for the directive .endfor, which ends a .for loop. +# +# See also: +# directive-for.mk + +# An .endfor without a corresponding .for is a parse error. +.endfor diff --git a/usr.bin/make/unit-tests/directive-endif.exp b/usr.bin/make/unit-tests/directive-endif.exp new file mode 100644 index 000000000..286d85244 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-endif.exp @@ -0,0 +1,8 @@ +make: "directive-endif.mk" line 18: The .endif directive does not take arguments. +make: "directive-endif.mk" line 23: The .endif directive does not take arguments. +make: "directive-endif.mk" line 33: The .endif directive does not take arguments. +make: "directive-endif.mk" line 39: The .endif directive does not take arguments. +make: "directive-endif.mk" line 45: Unknown directive "endifx" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-endif.mk b/usr.bin/make/unit-tests/directive-endif.mk new file mode 100644 index 000000000..10dd6ce22 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-endif.mk @@ -0,0 +1,49 @@ +# $NetBSD: directive-endif.mk,v 1.5 2020/12/14 21:56:17 rillig Exp $ +# +# Tests for the .endif directive. +# +# Since 2020-12-15, the .endif directive no longer accepts arguments. +# The manual page had never allowed that, but the code didn't check it. +# +# See also: +# Cond_EvalLine + +# TODO: Implementation + +.MAKEFLAGS: -dL + +# Error: .endif does not take arguments +.if 0 +# Since 2020-12-15, complain about the extra text after the 'endif'. +.endif 0 + +# Error: .endif does not take arguments +.if 1 +# Since 2020-12-15, complain about the extra text after the 'endif'. +.endif 1 + +# Comments are allowed after an '.endif'. +.if 2 +.endif # comment + +# Only whitespace and comments are allowed after an '.endif', but nothing +# else. +.if 1 +# Since 2020-12-15, complain about the extra text after the 'endif'. +.endif0 + +# Only whitespace and comments are allowed after an '.endif', but nothing +# else. +.if 1 +# Since 2020-12-15, complain about the extra text after the 'endif'. +.endif/ + +# After an '.endif', no other letter must occur. This 'endifx' is not +# parsed as an 'endif', therefore another '.endif' must follow to balance +# the directives. +.if 1 +.endifx +.endif # to close the preceding '.if' + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-error.exp b/usr.bin/make/unit-tests/directive-error.exp new file mode 100644 index 000000000..bad12326a --- /dev/null +++ b/usr.bin/make/unit-tests/directive-error.exp @@ -0,0 +1,4 @@ +make: "directive-error.mk" line 13: message + +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-error.mk b/usr.bin/make/unit-tests/directive-error.mk new file mode 100644 index 000000000..135db2159 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-error.mk @@ -0,0 +1,13 @@ +# $NetBSD: directive-error.mk,v 1.5 2021/01/27 00:02:38 rillig Exp $ +# +# Tests for the .error directive, which prints an error message and exits +# immediately, unlike other "fatal" parse errors, which continue to parse +# until the end of the current top-level makefile. +# +# See also: +# opt-warnings-as-errors.mk + +# Before parse.c 1.532 from 2021-01-27, the ".error" issued an irrelevant +# message saying "parsing warnings being treated as errors". +.MAKEFLAGS: -W +.error message diff --git a/usr.bin/make/unit-tests/directive-export-env.exp b/usr.bin/make/unit-tests/directive-export-env.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-env.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-export-env.mk b/usr.bin/make/unit-tests/directive-export-env.mk new file mode 100644 index 000000000..2ef2ceaf7 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-env.mk @@ -0,0 +1,10 @@ +# $NetBSD: directive-export-env.mk,v 1.4 2020/12/13 01:07:54 rillig Exp $ +# +# Tests for the .export-env directive. + +# TODO: Implementation + +.export-env + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-export-gmake.exp b/usr.bin/make/unit-tests/directive-export-gmake.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-gmake.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-export-gmake.mk b/usr.bin/make/unit-tests/directive-export-gmake.mk new file mode 100644 index 000000000..d94cd9deb --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-gmake.mk @@ -0,0 +1,64 @@ +# $NetBSD: directive-export-gmake.mk,v 1.3 2020/11/17 20:16:44 rillig Exp $ +# +# Tests for the export directive (without leading dot), as in GNU make. + +# The "export" directive only affects the environment of the make process +# and its child processes. It does not affect the global variables or any +# other variables. +VAR= before +export VAR=exported +.if ${VAR} != "before" +. error +.endif + +# Ensure that the name-value pair is actually exported. +.if ${:!echo "\$VAR"!} != "exported" +. error +.endif + +# This line looks like it would export 2 variables, but it doesn't. +# It only exports VAR and appends everything else as the variable value. +export VAR=exported VAR2=exported-as-well +.if ${:!echo "\$VAR"!} != "exported VAR2=exported-as-well" +. error ${:!echo "\$VAR"!} +.endif + +# Contrary to the usual variable assignments, spaces are significant +# after the '=' sign and are prepended to the value of the environment +# variable. +export VAR= leading spaces +.if ${:!echo "\$VAR"!} != " leading spaces" +. error +.endif + +# Contrary to the usual variable assignments, spaces are significant +# before the '=' sign and are appended to the name of the environment +# variable. +# +# Depending on the shell, environment variables with such exotic names +# may be silently discarded. One such shell is dash, which is the default +# shell on Ubuntu and Debian. +export VAR =trailing space in varname +.if ${:!env | grep trailing || true!} != "VAR =trailing space in varname" +. if ${:!env | grep trailing || true!} != "" # for dash +. error +. endif +.endif + +# The right-hand side of the exported variable is expanded exactly once. +TWICE= expanded twice +ONCE= expanded once, leaving $${TWICE} as-is +export VAR=${ONCE} +.if ${:!echo "\$VAR"!} != "expanded once, leaving \${TWICE} as-is" +. error +.endif + +# Undefined variables are allowed on the right-hand side, they expand +# to an empty string, as usual. +export VAR=an ${UNDEF} variable +.if ${:!echo "\$VAR"!} != "an variable" +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-export-impl.exp b/usr.bin/make/unit-tests/directive-export-impl.exp new file mode 100644 index 000000000..5f7744d0a --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-impl.exp @@ -0,0 +1,56 @@ +ParseReadLine (21): 'UT_VAR= <${REF}>' +Global:UT_VAR = <${REF}> +ParseReadLine (28): '.export UT_VAR' +Global:.MAKE.EXPORTED = UT_VAR +ParseReadLine (32): ': ${UT_VAR:N*}' +Var_Parse: ${UT_VAR:N*} (eval-defined) +Var_Parse: ${REF}> (eval-defined) +Applying ${UT_VAR:N...} to "<>" (eval-defined, VFL_EXPORTED|VFL_REEXPORT, regular) +Pattern[UT_VAR] for [<>] is [*] +ModifyWords: split "<>" into 1 words +Result of ${UT_VAR:N*} is "" (eval-defined, VFL_EXPORTED|VFL_REEXPORT, regular) +ParseDoDependency(: ) +CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>" +Var_Parse: ${:!echo "\$UT_VAR"!} != "<>" (eval-defined) +Applying ${:!...} to "" (eval-defined, none, undefined) +Modifier part: "echo "$UT_VAR"" +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (eval, none, regular) +Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (eval, none, regular) +Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (eval, none, regular) +Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (eval, none, regular) +Var_Parse: ${UT_VAR} (eval) +Var_Parse: ${REF}> (eval) +Result of ${:!echo "\$UT_VAR"!} is "<>" (eval-defined, none, defined) +lhs = "<>", rhs = "<>", op = != +ParseReadLine (49): ': ${UT_VAR:N*}' +Var_Parse: ${UT_VAR:N*} (eval-defined) +Var_Parse: ${REF}> (eval-defined) +Applying ${UT_VAR:N...} to "<>" (eval-defined, VFL_EXPORTED|VFL_REEXPORT, regular) +Pattern[UT_VAR] for [<>] is [*] +ModifyWords: split "<>" into 1 words +Result of ${UT_VAR:N*} is "" (eval-defined, VFL_EXPORTED|VFL_REEXPORT, regular) +ParseDoDependency(: ) +ParseReadLine (53): 'REF= defined' +Global:REF = defined +CondParser_Eval: ${:!echo "\$UT_VAR"!} != "" +Var_Parse: ${:!echo "\$UT_VAR"!} != "" (eval-defined) +Applying ${:!...} to "" (eval-defined, none, undefined) +Modifier part: "echo "$UT_VAR"" +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Applying ${.MAKE.EXPORTED:O} to "UT_VAR" (eval, none, regular) +Result of ${.MAKE.EXPORTED:O} is "UT_VAR" (eval, none, regular) +Applying ${.MAKE.EXPORTED:u} to "UT_VAR" (eval, none, regular) +Result of ${.MAKE.EXPORTED:u} is "UT_VAR" (eval, none, regular) +Var_Parse: ${UT_VAR} (eval) +Var_Parse: ${REF}> (eval) +Result of ${:!echo "\$UT_VAR"!} is "" (eval-defined, none, defined) +lhs = "", rhs = "", op = != +ParseReadLine (61): 'all:' +ParseDoDependency(all:) +Global:.ALLTARGETS = all +ParseReadLine (62): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) +Global:.MAKEFLAGS = -r -k -d cpv -d +Global:.MAKEFLAGS = -r -k -d cpv -d 0 +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-export-impl.mk b/usr.bin/make/unit-tests/directive-export-impl.mk new file mode 100644 index 000000000..226ad3c4d --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-impl.mk @@ -0,0 +1,62 @@ +# $NetBSD: directive-export-impl.mk,v 1.2 2021/02/16 16:28:41 rillig Exp $ +# +# Test for the implementation of exporting variables to child processes. +# This involves marking variables for export, actually exporting them, +# or marking them for being re-exported. +# +# See also: +# Var_Export +# ExportVar +# VarExportedMode (global) +# VFL_EXPORTED (per variable) +# VFL_REEXPORT (per variable) +# VarExportMode (per call of Var_Export and ExportVar) + +: ${:U:sh} # side effect: initialize .SHELL + +.MAKEFLAGS: -dcpv + +# This is a variable that references another variable. At this point, the +# other variable is still undefined. +UT_VAR= <${REF}> + +# At this point, ExportVar("UT_VAR", VEM_PLAIN) is called. Since the +# variable value refers to another variable, ExportVar does not actually +# export the variable but only marks it as VFL_EXPORTED and VFL_REEXPORT. +# After that, ExportVars registers the variable name in .MAKE.EXPORTED. +# That's all for now. +.export UT_VAR + +# Evaluating this expression shows the variable flags in the debug log, +# which are VFL_EXPORTED|VFL_REEXPORT. +: ${UT_VAR:N*} + +# At the last moment before actually forking off the child process for the +# :!...! modifier, Cmd_Exec calls Var_ReexportVars to have all relevant +# variables exported. Since this variable has both of the above-mentioned +# flags set, it is actually exported to the environment. The variable flags +# are not modified though, since the next time the :!...! modifier is +# evaluated, the referenced variables could have changed, therefore the +# variable will be exported anew for each ':sh' modifier, ':!...!' modifier, +# '!=' variable assignment. +.if ${:!echo "\$UT_VAR"!} != "<>" +. error +.endif + +# Evaluating this expression shows the variable flags in the debug log, +# which are still VFL_EXPORTED|VFL_REEXPORT, which means that the variable +# is still marked as being re-exported for each child process. +: ${UT_VAR:N*} + +# Now the referenced variable gets defined. This does not influence anything +# in the process of exporting the variable value, though. +REF= defined + +# Nothing surprising here. The variable UT_VAR gets exported, and this time, +# REF is defined and gets expanded into the exported environment variable. +.if ${:!echo "\$UT_VAR"!} != "" +. error +.endif + +all: +.MAKEFLAGS: -d0 diff --git a/usr.bin/make/unit-tests/directive-export-literal.exp b/usr.bin/make/unit-tests/directive-export-literal.exp new file mode 100644 index 000000000..c5557e363 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-literal.exp @@ -0,0 +1,2 @@ +value with ${UNEXPANDED} expression +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-export-literal.mk b/usr.bin/make/unit-tests/directive-export-literal.mk new file mode 100644 index 000000000..5fafa4a72 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export-literal.mk @@ -0,0 +1,13 @@ +# $NetBSD: directive-export-literal.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $ +# +# Tests for the .export-literal directive, which exports a variable value +# without expanding it. + +UT_VAR= value with ${UNEXPANDED} expression + +.export-literal UT_VAR + +.export-literal # oops: missing argument + +all: + @echo "$$UT_VAR" diff --git a/usr.bin/make/unit-tests/directive-export.exp b/usr.bin/make/unit-tests/directive-export.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-export.mk b/usr.bin/make/unit-tests/directive-export.mk new file mode 100644 index 000000000..942d4b371 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-export.mk @@ -0,0 +1,44 @@ +# $NetBSD: directive-export.mk,v 1.8 2021/02/16 19:01:18 rillig Exp $ +# +# Tests for the .export directive. +# +# See also: +# directive-misspellings.mk + +# TODO: Implementation + +INDIRECT= indirect +VAR= value $$ ${INDIRECT} + +# Before 2020-12-13, this unusual expression invoked undefined behavior since +# it accessed out-of-bounds memory via Var_Export -> ExportVar -> MayExport. +.export ${:U } + +# A variable is exported using the .export directive. +# During that, its value is expanded, just like almost everywhere else. +.export VAR +.if ${:!env | grep '^VAR'!} != "VAR=value \$ indirect" +. error +.endif + +# Undefining a variable that has been exported implicitly removes it from +# the environment of all child processes. +.undef VAR +.if ${:!env | grep '^VAR' || true!} != "" +. error +.endif + +# No syntactical argument means to export all variables. +.export + +# An empty argument means no additional variables to export. +.export ${:U} + + +# Trigger the "This isn't going to end well" in ExportVarEnv. +EMPTY_SHELL= ${:sh} +.export EMPTY_SHELL # only marked for export at this point +_!= :;: # Force the variable to be actually exported. + + +all: diff --git a/usr.bin/make/unit-tests/directive-for-errors.exp b/usr.bin/make/unit-tests/directive-for-errors.exp new file mode 100644 index 000000000..da5eee473 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-errors.exp @@ -0,0 +1,22 @@ +make: "directive-for-errors.mk" line 7: Unknown directive "fori" +make: "directive-for-errors.mk" line 8: warning: +make: "directive-for-errors.mk" line 9: for-less endfor +make: "directive-for-errors.mk" line 19: Unknown directive "for" +make: "directive-for-errors.mk" line 20: warning: +make: "directive-for-errors.mk" line 21: for-less endfor +make: "directive-for-errors.mk" line 37: Dollar $ 1 1 and backslash 2 2 2. +make: "directive-for-errors.mk" line 37: Dollar $ 3 3 and backslash 4 4 4. +make: "directive-for-errors.mk" line 43: no iteration variables in for +make: "directive-for-errors.mk" line 47: warning: Should not be reached. +make: "directive-for-errors.mk" line 48: for-less endfor +make: "directive-for-errors.mk" line 53: Wrong number of words (5) in .for substitution list with 3 variables +make: "directive-for-errors.mk" line 64: missing `in' in for +make: "directive-for-errors.mk" line 66: warning: Should not be reached. +make: "directive-for-errors.mk" line 67: for-less endfor +make: "directive-for-errors.mk" line 73: Unknown modifier "Z" +make: "directive-for-errors.mk" line 74: warning: Should not be reached. +make: "directive-for-errors.mk" line 74: warning: Should not be reached. +make: "directive-for-errors.mk" line 74: warning: Should not be reached. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-for-errors.mk b/usr.bin/make/unit-tests/directive-for-errors.mk new file mode 100644 index 000000000..7890e2375 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-errors.mk @@ -0,0 +1,75 @@ +# $NetBSD: directive-for-errors.mk,v 1.1 2020/12/31 03:05:12 rillig Exp $ +# +# Tests for error handling in .for loops. + +# A .for directive must be followed by whitespace, everything else results +# in a parse error. +.fori in 1 2 3 +. warning ${i} +.endfor + +# A slash is not whitespace, therefore this is not parsed as a .for loop. +# +# XXX: The error message is misleading though. As of 2020-12-31, it says +# "Unknown directive "for"", but that directive is actually known. This is +# because ForEval does not detect the .for loop as such, so parsing +# continues in ParseLine > ParseDependency > ParseDoDependency > +# ParseDoDependencyTargets > ParseErrorNoDependency, and there the directive +# name is parsed a bit differently. +.for/i in 1 2 3 +. warning ${i} +.endfor + +# As of 2020-12-31, the variable name can be an arbitrary word, it just needs +# to be separated by whitespace. Even '$' and '\' are valid variable names, +# which is not useful in practice. +# +# The '$$' is not replaced with the values '1' or '3' from the .for loop, +# instead it is kept as-is, and when the .info directive expands its argument, +# each '$$' gets replaced with a single '$'. The "long variable expression" +# ${$} gets replaced though, even though this would be a parse error everywhere +# outside a .for loop. +# +# The '\' on the other hand is treated as a normal variable name. +${:U\$}= dollar # see whether the "variable" '$' is local +${:U\\}= backslash # see whether the "variable" '\' is local +.for $ \ in 1 2 3 4 +. info Dollar $$ ${$} $($) and backslash $\ ${\} $(\). +.endfor + +# If there are no variables, there is no point in expanding the .for loop +# since this would end up in an endless loop, each time consuming 0 of the +# 3 values. +.for in 1 2 3 +# XXX: This should not be reached. It should be skipped, as already done +# when the number of values is not a multiple of the number of variables, +# see below. +. warning Should not be reached. +.endfor + +# There are 3 variables and 5 values. These 5 values cannot be split evenly +# among the variables, therefore the loop is not expanded at all, it is +# rather skipped. +.for a b c in 1 2 3 4 5 +. warning Should not be reached. +.endfor + +# The list of values after the 'in' may be empty, no matter if this emptiness +# comes from an empty expansion or even from a syntactically empty line. +.for i in +. info Would be reached if there were items to loop over. +.endfor + +# A missing 'in' should parse the .for loop but skip the body. +.for i : k +# XXX: As of 2020-12-31, this line is reached once. +. warning Should not be reached. +.endfor + +# A malformed modifier should be detected and skip the body of the loop. +# +# XXX: As of 2020-12-31, Var_Subst doesn't report any errors, therefore +# the loop body is expanded as if no error had happened. +.for i in 1 2 ${:U3:Z} 4 +. warning Should not be reached. +.endfor diff --git a/usr.bin/make/unit-tests/directive-for-escape.exp b/usr.bin/make/unit-tests/directive-for-escape.exp new file mode 100644 index 000000000..59d4c2324 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-escape.exp @@ -0,0 +1,75 @@ +For: end for 1 +For: loop body: +. info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} +make: Unclosed variable expression, expecting '}' for modifier "U!"" of variable "" with value "!"" +make: "directive-for-escape.mk" line 19: !" +For: end for 1 +For: loop body: +. info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~} +make: Unclosed variable expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\" +make: "directive-for-escape.mk" line 29: !"\\ +For: end for 1 +For: loop body: +. info ${:U\$} +make: "directive-for-escape.mk" line 41: $ +For: loop body: +. info ${:U${V}} +make: "directive-for-escape.mk" line 41: value +For: loop body: +. info ${:U${V:=-with-modifier}} +make: "directive-for-escape.mk" line 41: value-with-modifier +For: loop body: +. info ${:U$(V)} +make: "directive-for-escape.mk" line 41: value +For: loop body: +. info ${:U$(V:=-with-modifier)} +make: "directive-for-escape.mk" line 41: value-with-modifier +For: end for 1 +For: loop body: +. info ${:U\${UNDEF\:U\\$\\$} +make: "directive-for-escape.mk" line 55: ${UNDEF:U\$ +For: loop body: +. info ${:U{{\}\}} +make: "directive-for-escape.mk" line 55: {{}} +For: loop body: +. info ${:Uend\}} +make: "directive-for-escape.mk" line 55: end} +For: end for 1 +For: loop body: +. info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end} +make: "directive-for-escape.mk" line 67: beginend +For: end for 1 +For: loop body: +. info ${:U\$} +make: "directive-for-escape.mk" line 75: $ +For: end for 1 +For: loop body: +. info ${NUMBERS} ${:Ureplaced} +make: "directive-for-escape.mk" line 83: one two three replaced +For: end for 1 +For: loop body: +. info ${:Ureplaced} +make: "directive-for-escape.mk" line 93: replaced +For: end for 1 +For: loop body: +. info . $$i: ${:Uinner} +. info . $${i}: ${:Uinner} +. info . $${i:M*}: ${:Uinner:M*} +. info . $$(i): $(:Uinner) +. info . $$(i:M*): $(:Uinner:M*) +. info . $${i$${:U}}: ${i${:U}} +. info . $${i\}}: ${:Uinner\}} # XXX: unclear why ForLoop_SubstVarLong needs this +. info . $${i2}: ${i2} +. info . $${i,}: ${i,} +. info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner} +make: "directive-for-escape.mk" line 101: . $i: inner +make: "directive-for-escape.mk" line 102: . ${i}: inner +make: "directive-for-escape.mk" line 103: . ${i:M*}: inner +make: "directive-for-escape.mk" line 104: . $(i): inner +make: "directive-for-escape.mk" line 105: . $(i:M*): inner +make: "directive-for-escape.mk" line 106: . ${i${:U}}: outer +make: "directive-for-escape.mk" line 107: . ${i\}}: inner} +make: "directive-for-escape.mk" line 108: . ${i2}: two +make: "directive-for-escape.mk" line 109: . ${i,}: comma +make: "directive-for-escape.mk" line 110: . adjacent: innerinnerinnerinner +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-for-escape.mk b/usr.bin/make/unit-tests/directive-for-escape.mk new file mode 100644 index 000000000..babc4b8c6 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-escape.mk @@ -0,0 +1,113 @@ +# $NetBSD: directive-for-escape.mk,v 1.7 2021/02/15 07:58:19 rillig Exp $ +# +# Test escaping of special characters in the iteration values of a .for loop. +# These values get expanded later using the :U variable modifier, and this +# escaping and unescaping must pass all characters and strings effectively +# unmodified. + +.MAKEFLAGS: -df + +# Even though the .for loops take quotes into account when splitting the +# string into words, the quotes don't need to be balanced, as of 2020-12-31. +# This could be considered a bug. +ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ + +# XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of +# the loop since it would not need only the escaping for the :U variable +# modifier but also the escaping for the line-end comment. +.for chars in ${ASCII} +. info ${chars} +.endfor + +# As of 2020-12-31, using 2 backslashes before be '#' would treat the '#' +# as comment character. Using 3 backslashes doesn't help either since +# then the situation is essentially the same as with 1 backslash. +# This means that a '#' sign cannot be passed in the value of a .for loop +# at all. +ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~ +.for chars in ${ASCII.2020-12-31} +. info ${chars} +.endfor + +# Cover the code in for_var_len. +# +# XXX: It is unexpected that the variable V gets expanded in the loop body. +# The double '$$' should prevent exactly this. Probably nobody was +# adventurous enough to use literal dollar signs in the values of a .for +# loop. +V= value +VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier) +.for i in ${VALUES} +. info $i +.endfor + +# Try to cover the code for nested '{}' in for_var_len, without success. +# +# The value of the variable VALUES is not meant to be a variable expression. +# Instead, it is meant to represent literal text, the only escaping mechanism +# being that each '$' is written as '$$'. +# +# The .for loop splits ${VALUES} into 3 words, at the space characters, since +# these are not escaped. +VALUES= $${UNDEF:U\$$\$$ {{}} end} +# XXX: Where in the code does the '\$\$' get converted into a single '\$'? +.for i in ${VALUES} +. info $i +.endfor + +# Second try to cover the code for nested '{}' in for_var_len. +# +# XXX: It is wrong that for_var_len requires the braces to be balanced. +# Each variable modifier has its own inconsistent way of parsing nested +# variable 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 +.for i in ${VALUES} +. info $i +.endfor + +# A single trailing dollar doesn't happen in practice. +# The dollar sign is correctly passed through to the body of the .for loop. +# There, it is expanded by the .info directive, but even there a trailing +# dollar sign is kept as-is. +.for i in ${:U\$} +. info ${i} +.endfor + +# As of 2020-12-31, the name of the iteration variable can even contain +# colons, which then affects variable expressions having this exact modifier. +# This is clearly an unintended side effect of the implementation. +NUMBERS= one two three +.for NUMBERS:M*e in replaced +. info ${NUMBERS} ${NUMBERS:M*e} +.endfor + +# As of 2020-12-31, the name of the iteration variable can contain braces, +# which gets even more surprising than colons, since it allows to replace +# sequences of variable expressions. There is no practical use case for +# this, though. +BASENAME= one +EXT= .c +.for BASENAME}${EXT in replaced +. info ${BASENAME}${EXT} +.endfor + +# Demonstrate the various ways to refer to the iteration variable. +i= outer +i2= two +i,= comma +.for i in inner +. info . $$i: $i +. info . $${i}: ${i} +. info . $${i:M*}: ${i:M*} +. info . $$(i): $(i) +. info . $$(i:M*): $(i:M*) +. info . $${i$${:U}}: ${i${:U}} +. info . $${i\}}: ${i\}} # XXX: unclear why ForLoop_SubstVarLong needs this +. info . $${i2}: ${i2} +. info . $${i,}: ${i,} +. info . adjacent: $i${i}${i:M*}$i +.endfor + +all: diff --git a/usr.bin/make/unit-tests/directive-for-generating-endif.exp b/usr.bin/make/unit-tests/directive-for-generating-endif.exp new file mode 100644 index 000000000..9e1301abf --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-generating-endif.exp @@ -0,0 +1,7 @@ +make: "directive-for-generating-endif.mk" line 21: if-less endif +make: "directive-for-generating-endif.mk" line 21: if-less endif +make: "directive-for-generating-endif.mk" line 21: if-less endif +make: "directive-for-generating-endif.mk" line 0: 3 open conditionals +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-for-generating-endif.mk b/usr.bin/make/unit-tests/directive-for-generating-endif.mk new file mode 100644 index 000000000..b4d709551 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-generating-endif.mk @@ -0,0 +1,25 @@ +# $NetBSD: directive-for-generating-endif.mk,v 1.1 2020/08/29 18:50:25 rillig Exp $ +# +# Test whether a .for loop can be used to generate multiple .endif +# directives to close nested .if directives. Depending on the exact +# implementation, this might have been possible. +# +# If it were possible, the 3 .if directives would perfectly match the +# 3 .endif directives generated by the .for loop. +# +# After the "included file" from the .for loop, the 3 .if directives +# are still open. +# +# See For_Run and ParseReadLine. Each .for loop is treated like a separately +# included file, and in each included file the .if/.endif directives must be +# balanced. + +.if 1 +. if 2 +. if 3 +.for i in 3 2 1 +.endif +.endfor + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-for-lines.exp b/usr.bin/make/unit-tests/directive-for-lines.exp new file mode 100644 index 000000000..7aeaaa4a7 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-lines.exp @@ -0,0 +1,10 @@ +make: "directive-for-lines.mk" line 23: expect 23 +make: "directive-for-lines.mk" line 23: expect 23 +make: "directive-for-lines.mk" line 30: expect 30 +make: "directive-for-lines.mk" line 23: expect 23 +make: "directive-for-lines.mk" line 23: expect 23 +make: "directive-for-lines.mk" line 30: expect 30 +make: no target to make. + +make: stopped in unit-tests +exit status 2 diff --git a/usr.bin/make/unit-tests/directive-for-lines.mk b/usr.bin/make/unit-tests/directive-for-lines.mk new file mode 100644 index 000000000..96d659426 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-lines.mk @@ -0,0 +1,32 @@ +# $NetBSD: directive-for-lines.mk,v 1.3 2020/12/19 12:40:00 rillig Exp $ +# +# Tests for the line numbers that are reported in .for loops. +# +# Between 2007-01-01 (git 4d3c468f96e1080e, parse.c 1.127) and 2020-12-19 +# (parse.c 1.494), the line numbers for the .info directives and error +# messages inside .for loops had been wrong since ParseGetLine skipped empty +# lines, even when collecting the lines for the .for loop body. + +.for outer in a b + +# comment \ +# continued comment + +.for inner in 1 2 + +# comment \ +# continued comment + +VAR= \ + multi-line + +.info expect 23 + +.endfor + +# comment \ +# continued comment + +.info expect 30 + +.endfor diff --git a/usr.bin/make/unit-tests/directive-for-null.exp b/usr.bin/make/unit-tests/directive-for-null.exp new file mode 100644 index 000000000..37a7d6892 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-null.exp @@ -0,0 +1,10 @@ +make: "(stdin)" line 2: Zero byte read from file +make: "(stdin)" line 2: Unexpected end of file in for loop. +make: "(stdin)" line 3: Zero byte read from file +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +*** Error code 1 (continuing) + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-for-null.mk b/usr.bin/make/unit-tests/directive-for-null.mk new file mode 100644 index 000000000..a374f508d --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for-null.mk @@ -0,0 +1,19 @@ +# $NetBSD: directive-for-null.mk,v 1.1 2020/12/19 16:00:17 rillig Exp $ +# +# Test for parsing a .for loop that accidentally contains a null byte. +# +# As of 2020-12-19, there are 3 error messages: +# +# make: "(stdin)" line 2: Zero byte read from file +# make: "(stdin)" line 2: Unexpected end of file in for loop. +# make: "(stdin)" line 3: Zero byte read from file +# +# The one about "end of file" might be misleading but is due to the +# implementation. On both errors and EOF, ParseGetLine returns NULL. +# +# The one about the "zero byte" in line 3 is surprising since the only +# line that contains a null byte is line 2. + +all: .PHONY + @printf '%s\n' '.for i in 1 2 3' 'VAR=value' '.endfor' | tr 'l' '\0' \ + | ${MAKE} -f - diff --git a/usr.bin/make/unit-tests/directive-for.exp b/usr.bin/make/unit-tests/directive-for.exp new file mode 100644 index 000000000..4e882aad7 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for.exp @@ -0,0 +1,24 @@ +make: "directive-for.mk" line 108: outer +make: "directive-for.mk" line 133: a:\ a:\file.txt +make: "directive-for.mk" line 133: d:\\ +make: "directive-for.mk" line 133: d:\\file.txt +make: "directive-for.mk" line 140: ( ( ( +make: "directive-for.mk" line 140: [ [ [ +make: "directive-for.mk" line 140: { { { +make: "directive-for.mk" line 140: ) ) ) +make: "directive-for.mk" line 140: ] ] ] +make: "directive-for.mk" line 140: } } } +make: "directive-for.mk" line 140: (()) (()) (()) +make: "directive-for.mk" line 140: [[]] [[]] [[]] +make: "directive-for.mk" line 140: {{}} {{}} {{}} +make: "directive-for.mk" line 140: )( )( )( +make: "directive-for.mk" line 140: ][ ][ ][ +make: "directive-for.mk" line 140: }{ }{ }{ +make: "directive-for.mk" line 148: outer value value +make: "directive-for.mk" line 148: outer "quoted" \"quoted\" +make: "directive-for.mk" line 154: Unknown modifier "Z" +make: "directive-for.mk" line 155: XXX: Not reached word1 +make: "directive-for.mk" line 155: XXX: Not reached word3 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-for.mk b/usr.bin/make/unit-tests/directive-for.mk new file mode 100644 index 000000000..153762509 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-for.mk @@ -0,0 +1,159 @@ +# $NetBSD: directive-for.mk,v 1.10 2020/12/27 09:58:35 rillig Exp $ +# +# Tests for the .for directive. +# +# TODO: Describe naming conventions for the loop variables. +# .for f in values +# .for file in values +# .for _FILE_ in values +# .for .FILE. in values +# .for _f_ in values + +# Using the .for loop, lists of values can be produced. +# In simple cases, the :@var@${var}@ variable modifier can be used to +# reach the same effects. +# +.undef NUMBERS +.for num in 1 2 3 +NUMBERS+= ${num} +.endfor +.if ${NUMBERS} != "1 2 3" +. error +.endif + +# The .for loop also works for multiple iteration variables. +# This is something that the variable modifier :@ cannot do. +.for name value in VARNAME value NAME2 value2 +${name}= ${value} +.endfor +.if ${VARNAME} != "value" || ${NAME2} != "value2" +. error +.endif + +# The .for loop splits the items at whitespace, taking quotes into account, +# just like the :M or :S variable modifiers. +# +# Until 2012-06-03, it had split the items exactly at whitespace, without +# taking the quotes into account. This had resulted in 10 words. +# +.undef WORDS +.for var in one t\ w\ o "three three" 'four four' `five six` +WORDS+= counted +.endfor +.if ${WORDS:[#]} != 6 +. error +.endif + +# In the body of the .for loop, the iteration variables can be accessed +# like normal variables, even though they are not really variables. +# +# Instead, the expression ${var} is transformed into ${:U1}, ${:U2} and so +# on, before the loop body is evaluated. +# +# A notable effect of this implementation technique is that the .for +# iteration variables and the normal global variables live in separate +# namespaces and do not influence each other. +# +var= value before +var2= value before +.for var var2 in 1 2 3 4 +.endfor +.if ${var} != "value before" +. warning After the .for loop, var must still have its original value. +.endif +.if ${var2} != "value before" +. warning After the .for loop, var2 must still have its original value. +.endif + +# Everything from the paragraph above also applies if the loop body is +# empty, even if there is no actual iteration since the loop items are +# also empty. +# +var= value before +var2= value before +.for var var2 in ${:U} +.endfor +.if ${var} != "value before" +. warning After the .for loop, var must still have its original value. +.endif +.if ${var2} != "value before" +. warning After the .for loop, var2 must still have its original value. +.endif + +# Until 2008-12-21, the values of the iteration variables were simply +# inserted as plain text and then parsed as usual, which made it possible +# to achieve all kinds of strange effects. +# +# Before that date, the .for loop expanded to: +# EXPANSION+= value +# Since that date, the .for loop expands to: +# EXPANSION${:U+}= value +# +EXPANSION= before +EXPANSION+ = before +.for plus in + +EXPANSION${plus}= value +.endfor +.if ${EXPANSION} != "before" +. error This must be a make from before 2009. +.endif +.if ${EXPANSION+} != "value" +. error This must be a make from before 2009. +.endif + +# When the outer .for loop is expanded, it sees the expression ${i} and +# expands it. The inner loop then has nothing more to expand. +.for i in outer +. for i in inner +. info ${i} +. endfor +.endfor + +# From https://gnats.netbsd.org/29985. +# +# Until 2008-12-21, the .for loop was expanded by replacing the variable +# value literally in the body. This could lead to situations where the +# characters from the variable value were interpreted as markup rather than +# plain text. +# +# Until 2012-06-03, the .for loop had split the words at whitespace, without +# taking quotes into account. This made it possible to have variable values +# like "a:\ a:\file.txt" that ended in a single backslash. Since then, the +# variable values have been replaced with expressions of the form ${:U...}, +# which are not interpreted as code anymore. +# +# As of 2020-09-22, a comment in for.c says that it may be possible to +# produce an "unwanted substitution", but there is no demonstration code yet. +# +# The above changes prevent a backslash at the end of a word from being +# interpreted as part of the code. Because of this, the trailingBackslash +# hack in Var_Subst is no longer needed and as of 2020-09-22, has been +# removed. +.for path in a:\ a:\file.txt d:\\ d:\\file.txt +. info ${path} +.endfor + +# Ensure that braces and parentheses are properly escaped by the .for loop. +# Each line must print the same word 3 times. +# See GetEscapes. +.for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{ +. info $v ${v} $(v) +.endfor + +# As of 2020-10-25, the variable names may contain arbitrary characters, +# except for whitespace. This allows for creative side effects. Hopefully +# nobody is misusing this "feature". +var= outer +.for var:Q in value "quoted" +. info ${var} ${var:Q} ${var:Q:Q} +.endfor + + +# XXX: A parse error or evaluation error in the items of the .for loop +# should skip the whole loop. As of 2020-12-27, the loop is expanded twice. +.for var in word1 ${:Uword2:Z} word3 +. info XXX: Not reached ${var} +.endfor + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-hyphen-include.exp b/usr.bin/make/unit-tests/directive-hyphen-include.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-hyphen-include.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-hyphen-include.mk b/usr.bin/make/unit-tests/directive-hyphen-include.mk new file mode 100644 index 000000000..8c851be43 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-hyphen-include.mk @@ -0,0 +1,9 @@ +# $NetBSD: directive-hyphen-include.mk,v 1.1 2020/09/13 09:20:23 rillig Exp $ +# +# Tests for the .-include directive, which includes another file, +# silently skipping it if it cannot be opened. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-if-nested.exp b/usr.bin/make/unit-tests/directive-if-nested.exp new file mode 100644 index 000000000..1a9ae02f0 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-if-nested.exp @@ -0,0 +1,2 @@ +make: "directive-if-nested.inc" line 1001: deeply nested .if directives +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-if-nested.mk b/usr.bin/make/unit-tests/directive-if-nested.mk new file mode 100644 index 000000000..19c8e9452 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-if-nested.mk @@ -0,0 +1,25 @@ +# $NetBSD: directive-if-nested.mk,v 1.1 2020/11/10 22:23:37 rillig Exp $ +# +# Tests for deeply nested .if directives. By default, memory for 128 nested +# .if directives is pre-allocated, any deeper nesting is reallocated. +# +# See also: +# Cond_EvalLine + +GEN= directive-if-nested.inc + +all: set-up test tear-down + +set-up: .PHONY + @{ printf '.if %s\n' ${:U:range=1000}; \ + printf '.info deeply nested .if directives\n'; \ + printf '.endif # %s\n' ${:U:range=1000}; \ + printf '\n'; \ + printf 'all:\n'; \ + } > ${GEN} + +test: .PHONY + @${MAKE} -f ${GEN} + +tear-down: .PHONY + @rm -f ${GEN} diff --git a/usr.bin/make/unit-tests/directive-if.exp b/usr.bin/make/unit-tests/directive-if.exp new file mode 100644 index 000000000..89a394fc0 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-if.exp @@ -0,0 +1,17 @@ +make: "directive-if.mk" line 13: 0 evaluates to false. +make: "directive-if.mk" line 17: 1 evaluates to true. +make: "directive-if.mk" line 40: Unknown directive "ifx" +make: "directive-if.mk" line 41: This is not conditional. +make: "directive-if.mk" line 42: if-less else +make: "directive-if.mk" line 43: This is not conditional. +make: "directive-if.mk" line 44: if-less endif +make: "directive-if.mk" line 47: Malformed conditional () +make: "directive-if.mk" line 57: Quotes in plain words are probably a mistake. +make: "directive-if.mk" line 66: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 70: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 76: Don't do this, always put a space around comparison operators. +make: "directive-if.mk" line 82: Don't do this, always put a space after a directive. +make: "directive-if.mk" line 86: Don't do this, always put a space after a directive. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-if.mk b/usr.bin/make/unit-tests/directive-if.mk new file mode 100644 index 000000000..b1ad2396b --- /dev/null +++ b/usr.bin/make/unit-tests/directive-if.mk @@ -0,0 +1,89 @@ +# $NetBSD: directive-if.mk,v 1.9 2020/12/19 22:33:11 rillig Exp $ +# +# Tests for the .if directive. +# +# See also: +# cond-*.mk + +# TODO: Implementation + +.if 0 +. error +.else +. info 0 evaluates to false. +.endif + +.if 1 +. info 1 evaluates to true. +.else +. error +.endif + +# There is no '.ifx'. +# +# The commit from 2005-05-01 intended to detect this situation, but it failed +# to do this since the call to is_token had its arguments switched. They were +# expected as (str, token, token_len) but were actually passed as (token, str, +# token_len). This made is_token return true even if the directive was +# directly followed by alphanumerical characters, which was wrong. The +# typical cases produced an error message such as "Malformed conditional +# (x 123)", while the intended error message was "Unknown directive". +# +# Back at that time, the commits only modified the main code but did not add +# the corresponding unit tests. This allowed the bug to hide for more than +# 15 years. +# +# Since 2020-11-10, the correct error message is produced. The '.ifx' is no +# longer interpreted as a variant of '.if', therefore the '.error' and '.else' +# are interpreted as ordinary directives, producing the error messages +# "if-less else" and "if-less endif". +.ifx 123 +.info This is not conditional. +.else +.info This is not conditional. +.endif + +# Missing condition. +.if +. error +.else +. error +.endif + +# A plain word must not start with a '"'. It may contain a embedded quotes +# though, which are kept. The quotes need not be balanced. The next space +# ends the word, and the remaining " || 1" is parsed as "or true". +.if ${:Uplain"""""} == plain""""" || 1 +. info Quotes in plain words are probably a mistake. +# XXX: Accepting quotes in plain words is probably a mistake as well. +.else +. error +.endif + +.if0 +. error +.else +. info Don't do this, always put a space after a directive. +.endif + +.if${:U-3} +. info Don't do this, always put a space after a directive. +.else +. error +.endif + +.if${:U-3}>-4 +. info Don't do this, always put a space around comparison operators. +.else +. error +.endif + +.if(1) +. info Don't do this, always put a space after a directive. +.endif + +.if!0 +. info Don't do this, always put a space after a directive. +.endif + +all: diff --git a/usr.bin/make/unit-tests/directive-ifdef.exp b/usr.bin/make/unit-tests/directive-ifdef.exp new file mode 100644 index 000000000..1a1358988 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifdef.exp @@ -0,0 +1,4 @@ +make: "directive-ifdef.mk" line 12: Function calls in .ifdef are possible. +make: "directive-ifdef.mk" line 23: String literals are tested for emptiness. +make: "directive-ifdef.mk" line 27: String literals are tested for emptiness. Whitespace is non-empty. +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-ifdef.mk b/usr.bin/make/unit-tests/directive-ifdef.mk new file mode 100644 index 000000000..12f3648e8 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifdef.mk @@ -0,0 +1,33 @@ +# $NetBSD: directive-ifdef.mk,v 1.4 2021/01/21 23:03:41 rillig Exp $ +# +# Tests for the .ifdef directive. + +# TODO: Implementation + +DEFINED= defined + +# It looks redundant to have a call to defined() in an .ifdef, but it's +# possible. The .ifdef only affects plain symbols, not function calls. +.ifdef defined(DEFINED) +. info Function calls in .ifdef are possible. +.else +. error +.endif + +# String literals are handled the same in all variants of the .if directive. +# They evaluate to true if they are not empty. Whitespace counts as non-empty +# as well. +.ifdef "" +. error +.else +. info String literals are tested for emptiness. +.endif + +.ifdef " " +. info String literals are tested for emptiness. Whitespace is non-empty. +.else +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-ifmake.exp b/usr.bin/make/unit-tests/directive-ifmake.exp new file mode 100644 index 000000000..fd4bcae15 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifmake.exp @@ -0,0 +1,11 @@ +make: "directive-ifmake.mk" line 13: ok: positive condition works +make: "directive-ifmake.mk" line 24: ok: negation works +make: "directive-ifmake.mk" line 33: ok: double negation works +make: "directive-ifmake.mk" line 40: ok: both mentioned +make: "directive-ifmake.mk" line 47: ok: only those mentioned +make: "directive-ifmake.mk" line 57: Targets can even be added at parse time. +make: "directive-ifmake.mk" line 75: ok +: first +: second +: late-target +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-ifmake.mk b/usr.bin/make/unit-tests/directive-ifmake.mk new file mode 100644 index 000000000..4d49add72 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifmake.mk @@ -0,0 +1,82 @@ +# $NetBSD: directive-ifmake.mk,v 1.8 2020/11/15 20:20:58 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. +# +# TODO: Describe why the shortcut may be useful (if it's useful at all), +# instead of sticking to the simple '.if' only. + +# The targets 'first' and 'second' are passed in on the command line. + +# This is the most basic form. +.ifmake first +. info ok: positive condition works +.else +. warning positive condition fails +.endif + +# The not operator works as expected. +# An alternative interpretation were that this condition is asking whether +# the target "!first" was requested. To distinguish this, see the next test. +.ifmake !first +. warning unexpected +.else +. info ok: negation works +.endif + +# See if the exclamation mark really means "not", or if it is just part of +# the target name. Since it means 'not', the two exclamation marks are +# effectively ignored, and 'first' is indeed a requested target. If the +# exclamation mark were part of the name instead, the name would be '!!first', +# and such a target was not requested to be made. +.ifmake !!first +. info ok: double negation works +.else +. warning double negation fails +.endif + +# Multiple targets can be combined using the && and || operators. +.ifmake first && second +. info ok: both mentioned +.else +. warning && does not work as expected +.endif + +# Negation also works in complex conditions. +.ifmake first && !unmentioned +. info ok: only those mentioned +.else +. warning && with ! does not work as expected +.endif + +# Using the .MAKEFLAGS special dependency target, arbitrary command +# line options can be added at parse time. This means that it is +# possible to extend the targets to be made. +.MAKEFLAGS: late-target +.ifmake late-target +. info Targets can even be added at parse time. +.else +. info No, targets cannot be added at parse time anymore. +.endif + +# Numbers are interpreted as numbers, no matter whether the directive is +# a plain .if or an .ifmake. +.ifmake 0 +. error +.endif +.ifmake 1 +.else +. error +.endif + +# A condition that consists of a variable expression only (without any +# comparison operator) can be used with .if and the other .ifxxx directives. +.ifmake ${:Ufirst} +. info ok +.else +. error +.endif + + +first second unmentioned late-target: + : $@ diff --git a/usr.bin/make/unit-tests/directive-ifndef.exp b/usr.bin/make/unit-tests/directive-ifndef.exp new file mode 100644 index 000000000..c653f6344 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifndef.exp @@ -0,0 +1,2 @@ +make: "directive-ifndef.mk" line 10: guarded section +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-ifndef.mk b/usr.bin/make/unit-tests/directive-ifndef.mk new file mode 100644 index 000000000..bf509ef80 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifndef.mk @@ -0,0 +1,24 @@ +# $NetBSD: directive-ifndef.mk,v 1.6 2020/10/24 08:46:08 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 +# macro name, there is no such syntax in make. Therefore, it is more +# common to use .if !defined(GUARD) instead. + +.ifndef GUARD +GUARD= # defined +. info guarded section +.endif + +.ifndef GUARD +GUARD= # defined +. info guarded section +.endif + +.if !defined(GUARD) +GUARD= # defined +. info guarded section +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-ifnmake.exp b/usr.bin/make/unit-tests/directive-ifnmake.exp new file mode 100644 index 000000000..31a5a8043 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifnmake.exp @@ -0,0 +1,3 @@ +Don't forget to run the tests (1) +Running the tests +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-ifnmake.mk b/usr.bin/make/unit-tests/directive-ifnmake.mk new file mode 100644 index 000000000..4bdb12a46 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-ifnmake.mk @@ -0,0 +1,22 @@ +# $NetBSD: directive-ifnmake.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the .ifnmake directive, which evaluates to true if its argument +# is _not_ listed in the command-line targets to be created. + +all: + @:; + +.ifnmake(test) +.BEGIN: + @echo "Don't forget to run the tests (1)" +.endif + +.MAKEFLAGS: test + +.ifnmake(test) +.BEGIN: + @echo "Don't forget to run the tests (2)" +.endif + +test: + @echo "Running the tests" diff --git a/usr.bin/make/unit-tests/directive-include-fatal.exp b/usr.bin/make/unit-tests/directive-include-fatal.exp new file mode 100644 index 000000000..c8ca97a0f --- /dev/null +++ b/usr.bin/make/unit-tests/directive-include-fatal.exp @@ -0,0 +1,4 @@ +make: "directive-include-fatal.mk" line 13: Malformed conditional (${UNDEF}) +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-include-fatal.mk b/usr.bin/make/unit-tests/directive-include-fatal.mk new file mode 100644 index 000000000..6744f9e80 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-include-fatal.mk @@ -0,0 +1,27 @@ +# $NetBSD: directive-include-fatal.mk,v 1.3 2021/02/01 22:16:57 rillig Exp $ +# +# Test for the .include directive combined with fatal errors. +# +# At 2020-09-13, the code in Parse_File that sets "fatals = 0" looked +# suspicious, as if it were possible to suppress fatal errors by including +# another file. It was a false alarm though, since Parse_File only handles +# the top-level makefiles from the command line. Any included files are +# handled by IncludeFile instead, and that function does not reset +# the "fatals" counter. + +# Using an undefined variable in a condition generates a fatal error. +.if ${UNDEF} +.endif + +# Including another file does not reset the global variable "fatals". +# The exit status will be 1. +.include "/dev/null" + +# Adding another file to be included has no effect either. +# When the command line is parsed, the additional file is only enqueued +# in the global "makefiles" variable, but not immediately run through +# Parse_File. +.MAKEFLAGS: -f "/dev/null" + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-include.exp b/usr.bin/make/unit-tests/directive-include.exp new file mode 100644 index 000000000..af56eefb2 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-include.exp @@ -0,0 +1,8 @@ +CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != +CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = != +make: "directive-include.mk" line 25: Could not find nonexistent.mk +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-include.mk b/usr.bin/make/unit-tests/directive-include.mk new file mode 100644 index 000000000..d36914b25 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-include.mk @@ -0,0 +1,34 @@ +# $NetBSD: directive-include.mk,v 1.5 2020/11/21 14:59:11 rillig Exp $ +# +# Tests for the .include directive, which includes another file. + +# TODO: Implementation + +.MAKEFLAGS: -dc + +# All included files are recorded in the variable .MAKE.MAKEFILES. +# In this test, only the basenames of the files are compared since +# the directories can differ. +.include "/dev/null" +.if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +. error +.endif + +# Each file is recorded only once in the variable .MAKE.MAKEFILES. +# Between 2015-11-26 and 2020-10-31, the very last file could be repeated, +# due to an off-by-one bug in ParseTrackInput. +.include "/dev/null" +.if ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null" +. error +.endif + +.include "nonexistent.mk" +.include "/dev/null" # size 0 +# including a directory technically succeeds, but shouldn't. +#.include "." # directory + +# As of 2020-11-21, anything after the delimiter '"' is ignored. +.include "/dev/null" and ignore anything in the rest of the line. + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-info.exp b/usr.bin/make/unit-tests/directive-info.exp new file mode 100644 index 000000000..2652c1914 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-info.exp @@ -0,0 +1,15 @@ +make: "directive-info.mk" line 11: begin .info tests +make: "directive-info.mk" line 12: Unknown directive "inf" +make: "directive-info.mk" line 13: Missing argument for ".info" +make: "directive-info.mk" line 14: message +make: "directive-info.mk" line 15: indented message +make: "directive-info.mk" line 16: Unknown directive "information" +make: "directive-info.mk" line 17: Unknown directive "information" +make: "directive-info.mk" line 22: Missing argument for ".info" +make: "directive-info.mk" line 23: Missing argument for ".info" +make: "directive-info.mk" line 26: Unknown directive "info-message" +make: "directive-info.mk" line 27: no-target: no-source +make: "directive-info.mk" line 36: expect line 30 for multi-line message +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-info.mk b/usr.bin/make/unit-tests/directive-info.mk new file mode 100644 index 000000000..5feea0cde --- /dev/null +++ b/usr.bin/make/unit-tests/directive-info.mk @@ -0,0 +1,39 @@ +# $NetBSD: directive-info.mk,v 1.8 2020/12/19 22:33:11 rillig Exp $ +# +# Tests for the .info directive. +# +# Until parse.c 1.502 from 2020-12-19, a missing argument to the directive +# produced the wrong error message "Unknown directive". Since parse.c 1.503 +# from 2020-12-19, the correct "Missing argument" is produced. + +# TODO: Implementation + +.info begin .info tests +.inf # misspelled +.info # "Missing argument" +.info message +.info indented message +.information +.information message # Accepted before 2020-12-13 01:07:54. +.info.man: # not a message, but possibly a suffix rule + +# Even if lines would have trailing whitespace, this would be trimmed by +# ParseGetLine. +.info +.info # comment + +.info: message # This is a dependency declaration. +.info-message # This is an unknown directive. +.info no-target: no-source # This is a .info directive, not a dependency. +# See directive.mk for more tests of this kind. + +# Since at least 2002-01-01, the line number that is used in error messages +# and the .info directives is the number of completely read lines. For the +# following multi-line directive, this means that the reported line number is +# the one of the last line, not the first line. +.info expect line 30 for\ + multi$\ + -line message + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-misspellings.exp b/usr.bin/make/unit-tests/directive-misspellings.exp new file mode 100644 index 000000000..e51d8473b --- /dev/null +++ b/usr.bin/make/unit-tests/directive-misspellings.exp @@ -0,0 +1,45 @@ +make: "directive-misspellings.mk" line 12: Unknown directive "dinclud" +make: "directive-misspellings.mk" line 14: Unknown directive "dincludx" +make: "directive-misspellings.mk" line 15: .include filename must be delimited by '"' or '<' +make: "directive-misspellings.mk" line 17: Unknown directive "erro" +make: "directive-misspellings.mk" line 18: Unknown directive "errox" +make: "directive-misspellings.mk" line 22: Unknown directive "expor" +make: "directive-misspellings.mk" line 24: Unknown directive "exporx" +make: "directive-misspellings.mk" line 25: Unknown directive "exports" +make: "directive-misspellings.mk" line 27: Unknown directive "export-en" +make: "directive-misspellings.mk" line 30: Unknown directive "export-environment" +make: "directive-misspellings.mk" line 32: Unknown directive "export-litera" +make: "directive-misspellings.mk" line 34: Unknown directive "export-literax" +make: "directive-misspellings.mk" line 35: Unknown directive "export-literally" +make: "directive-misspellings.mk" line 37: Unknown directive "-includ" +make: "directive-misspellings.mk" line 39: Unknown directive "-includx" +make: "directive-misspellings.mk" line 40: .include filename must be delimited by '"' or '<' +make: "directive-misspellings.mk" line 42: Unknown directive "includ" +make: "directive-misspellings.mk" line 43: Could not find file +make: "directive-misspellings.mk" line 44: Unknown directive "includx" +make: "directive-misspellings.mk" line 45: .include filename must be delimited by '"' or '<' +make: "directive-misspellings.mk" line 47: Unknown directive "inf" +make: "directive-misspellings.mk" line 48: msg +make: "directive-misspellings.mk" line 49: Unknown directive "infx" +make: "directive-misspellings.mk" line 50: Unknown directive "infos" +make: "directive-misspellings.mk" line 52: Unknown directive "sinclud" +make: "directive-misspellings.mk" line 54: Unknown directive "sincludx" +make: "directive-misspellings.mk" line 55: .include filename must be delimited by '"' or '<' +make: "directive-misspellings.mk" line 57: Unknown directive "unde" +make: "directive-misspellings.mk" line 59: Unknown directive "undex" +make: "directive-misspellings.mk" line 60: Unknown directive "undefs" +make: "directive-misspellings.mk" line 62: Unknown directive "unexpor" +make: "directive-misspellings.mk" line 64: Unknown directive "unexporx" +make: "directive-misspellings.mk" line 65: Unknown directive "unexports" +make: "directive-misspellings.mk" line 67: Unknown directive "unexport-en" +make: "directive-misspellings.mk" line 69: The directive .unexport-env does not take arguments +make: "directive-misspellings.mk" line 70: Unknown directive "unexport-enx" +make: "directive-misspellings.mk" line 71: Unknown directive "unexport-envs" +make: "directive-misspellings.mk" line 73: Unknown directive "warn" +make: "directive-misspellings.mk" line 74: Unknown directive "warnin" +make: "directive-misspellings.mk" line 75: warning: msg +make: "directive-misspellings.mk" line 76: Unknown directive "warninx" +make: "directive-misspellings.mk" line 77: Unknown directive "warnings" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-misspellings.mk b/usr.bin/make/unit-tests/directive-misspellings.mk new file mode 100644 index 000000000..5f479f03b --- /dev/null +++ b/usr.bin/make/unit-tests/directive-misspellings.mk @@ -0,0 +1,79 @@ +# $NetBSD: directive-misspellings.mk,v 1.3 2020/12/13 01:10:22 rillig Exp $ +# +# Tests for misspelled directives. +# +# Before 2020-12-12, make didn't catch most of these misspellings. For +# example, the directive ".exporting" was interpreted as if it were spelled +# ".export ing", which would export the variable named "ing" if that existed. +# Another misspelling, as improbable as the others, was that both ".infos" and +# ".information" were aliases to ".info" since the code for these diagnostic +# directives just skipped any letters following the "error", "warn" or "info". + +.dinclud "file" +.dinclude "file" +.dincludx "file" +.dincludes "file" # XXX: the 's' is not meant to be a filename + +.erro msg +.errox msg +# no .error since that would exit immediately +# no .errors since that would exit immediately, even with the typo + +.expor varname +.export varname +.exporx varname +.exports varname # Accepted before 2020-12-13 01:07:54. + +.export-en # Accepted before 2020-12-13 01:07:54. +.export-env +.export-env extra argument # XXX: undetected extra argument +.export-environment # Accepted before 2020-12-13 01:07:54. + +.export-litera varname # Accepted before 2020-12-13 01:07:54. +.export-literal varname +.export-literax varname # Accepted before 2020-12-13 01:07:54. +.export-literally varname # Accepted before 2020-12-13 01:07:54. + +.-includ "file" +.-include "file" +.-includx "file" +.-includes "file" # XXX: the 's' is not meant to be a filename + +.includ "file" +.include "file" +.includx "file" +.includex "file" # XXX: the 's' is not meant to be a filename + +.inf msg +.info msg +.infx msg +.infos msg # Accepted before 2020-12-13 01:07:54. + +.sinclud "file" +.sinclude "file" +.sincludx "file" +.sincludes "file" # XXX: the 's' is not meant to be a filename + +.unde varname +.undef varname +.undex varname +.undefs varname # Accepted before 2020-12-13 01:07:54. + +.unexpor varname +.unexport varname +.unexporx varname +.unexports varname # Accepted before 2020-12-12 18:00:18. + +.unexport-en # Accepted before 2020-12-12 18:11:42. +.unexport-env +.unexport-env extra argument # Accepted before 2020-12-12 18:00:18. +.unexport-enx # Accepted before 2020-12-12 18:00:18. +.unexport-envs # Accepted before 2020-12-12 18:00:18. + +.warn msg +.warnin msg +.warning msg +.warninx msg +.warnings msg # Accepted before 2020-12-13 01:07:54. + +all: diff --git a/usr.bin/make/unit-tests/directive-sinclude.exp b/usr.bin/make/unit-tests/directive-sinclude.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-sinclude.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-sinclude.mk b/usr.bin/make/unit-tests/directive-sinclude.mk new file mode 100644 index 000000000..1932e7b3b --- /dev/null +++ b/usr.bin/make/unit-tests/directive-sinclude.mk @@ -0,0 +1,13 @@ +# $NetBSD: directive-sinclude.mk,v 1.2 2020/11/15 20:20:58 rillig Exp $ +# +# Tests for the .sinclude directive, which includes another file, +# silently skipping it if it cannot be opened. +# +# The 'silently skipping' only applies to the case where the file cannot be +# opened. Parse errors and other errors are handled the same way as in the +# other .include directives. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-undef.exp b/usr.bin/make/unit-tests/directive-undef.exp new file mode 100644 index 000000000..56c871429 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-undef.exp @@ -0,0 +1,6 @@ +make: "directive-undef.mk" line 29: The .undef directive requires an argument +make: "directive-undef.mk" line 86: Unknown modifier "Z" +make: "directive-undef.mk" line 103: warning: UT_EXPORTED is still listed in .MAKE.EXPORTED even though spaceit is not exported anymore. +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-undef.mk b/usr.bin/make/unit-tests/directive-undef.mk new file mode 100644 index 000000000..41ea6b5bf --- /dev/null +++ b/usr.bin/make/unit-tests/directive-undef.mk @@ -0,0 +1,107 @@ +# $NetBSD: directive-undef.mk,v 1.10 2021/02/16 18:02:19 rillig Exp $ +# +# Tests for the .undef directive. +# +# See also: +# directive-misspellings.mk + +# Before var.c 1.737 from 2020-12-19, .undef only undefined the first +# variable, silently skipping all further variable names. +# +# Before var.c 1.761 from 2020-12-22, .undef complained about too many +# arguments. +# +# Since var.c 1.761 from 2020-12-22, .undef handles multiple variable names +# just like the .export directive. +1= 1 +2= 2 +3= 3 +.undef 1 2 3 +.if ${1:U_}${2:U_}${3:U_} != ___ +. warning $1$2$3 +.endif + + +# Without any arguments, until var.c 1.736 from 2020-12-19, .undef tried +# to delete the variable with the empty name, which never exists; see +# varname-empty.mk. Since var.c 1.737 from 2020-12-19, .undef complains +# about a missing argument. +.undef + + +# Trying to delete the variable with the empty name is ok, it just won't +# ever do anything since that variable is never defined. +.undef ${:U} + + +# The argument of .undef is first expanded exactly once and then split into +# words, just like everywhere else. This prevents variables whose names +# contain spaces or unbalanced 'single' or "double" quotes from being +# undefined, but these characters do not appear in variables names anyway. +1= 1 +2= 2 +3= 3 +${:U1 2 3}= one two three +VARNAMES= 1 2 3 +.undef ${VARNAMES} # undefines the variable "1 2 3" +.if !defined(${:U1 2 3}) +. error +.endif +.if ${1:U_}${2:U_}${3:U_} != "___" # these are still defined +. error +.endif + + +# A variable named " " cannot be undefined. There's no practical use case +# for such variables anyway. +SPACE= ${:U } +${SPACE}= space +.if !defined(${SPACE}) +. error +.endif +.undef ${SPACE} +.if !defined(${SPACE}) +. error +.endif + + +# A variable named "$" can be undefined since the argument to .undef is +# expanded exactly once, before being split into words. +DOLLAR= $$ +${DOLLAR}= dollar +.if !defined(${DOLLAR}) +. error +.endif +.undef ${DOLLAR} +.if defined(${DOLLAR}) +. error +.endif + + +# Since var.c 1.762 from 2020-12-22, parse errors in the argument should be +# properly detected and should stop the .undef directive from doing any work. +# +# As of var.c 1.762, this doesn't happen though because the error handling +# in Var_Parse and Var_Subst is not done properly. +.undef ${VARNAMES:L:Z} + + +UT_EXPORTED= exported-value +.export UT_EXPORTED +.if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "exported-value" +. error +.endif +.if !${.MAKE.EXPORTED:MUT_EXPORTED} +. error +.endif +.undef UT_EXPORTED # XXX: does not update .MAKE.EXPORTED +.if ${:!echo "\${UT_EXPORTED:-not-exported}"!} != "not-exported" +. error +.endif +.if ${.MAKE.EXPORTED:MUT_EXPORTED} +. warning UT_EXPORTED is still listed in .MAKE.EXPORTED even though $\ + it is not exported anymore. +.endif + + +all: diff --git a/usr.bin/make/unit-tests/directive-unexport-env.exp b/usr.bin/make/unit-tests/directive-unexport-env.exp new file mode 100644 index 000000000..18d6e92f0 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-unexport-env.exp @@ -0,0 +1,18 @@ +make: "directive-unexport-env.mk" line 13: Unknown directive "unexport-en" +make: "directive-unexport-env.mk" line 15: Unknown directive "unexport-environment" +Global:UT_EXPORTED = value +Global:UT_UNEXPORTED = value +Global:.MAKE.EXPORTED = UT_EXPORTED +make: "directive-unexport-env.mk" line 21: The directive .unexport-env does not take arguments +Var_Parse: ${.MAKE.EXPORTED:O:u} (eval) +Applying ${.MAKE.EXPORTED:O} to "UT_EXPORTED" (eval, none, regular) +Result of ${.MAKE.EXPORTED:O} is "UT_EXPORTED" (eval, none, regular) +Applying ${.MAKE.EXPORTED:u} to "UT_EXPORTED" (eval, none, regular) +Result of ${.MAKE.EXPORTED:u} is "UT_EXPORTED" (eval, none, regular) +Unexporting "UT_EXPORTED" +Global:delete .MAKE.EXPORTED +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-unexport-env.mk b/usr.bin/make/unit-tests/directive-unexport-env.mk new file mode 100644 index 000000000..ef58ae732 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-unexport-env.mk @@ -0,0 +1,25 @@ +# $NetBSD: directive-unexport-env.mk,v 1.7 2020/12/12 18:11:42 rillig Exp $ +# +# Tests for the .unexport-env directive. +# +# Before 2020-12-13, the directive unexport-env wrongly accepted arguments +# and ignored them. +# +# Before 2020-12-13, misspelled directive names like "unexport-environment" +# were not properly detected. + +# TODO: Implementation + +.unexport-en # misspelled +.unexport-env # ok +.unexport-environment # misspelled + +.MAKEFLAGS: -dv +UT_EXPORTED= value +UT_UNEXPORTED= value +.export UT_EXPORTED +.unexport-env UT_EXPORTED UT_UNEXPORTED +.MAKEFLAGS: -d0 + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-unexport.exp b/usr.bin/make/unit-tests/directive-unexport.exp new file mode 100644 index 000000000..d59fb4713 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-unexport.exp @@ -0,0 +1,5 @@ +make: "directive-unexport.mk" line 18: UT_A=a UT_B=b UT_C=c +make: "directive-unexport.mk" line 19: UT_A UT_B UT_C +make: "directive-unexport.mk" line 27: UT_A=a UT_B=b UT_C=c +make: "directive-unexport.mk" line 28: +exit status 0 diff --git a/usr.bin/make/unit-tests/directive-unexport.mk b/usr.bin/make/unit-tests/directive-unexport.mk new file mode 100644 index 000000000..efc103efe --- /dev/null +++ b/usr.bin/make/unit-tests/directive-unexport.mk @@ -0,0 +1,33 @@ +# $NetBSD: directive-unexport.mk,v 1.7 2020/12/13 01:07:54 rillig Exp $ +# +# Tests for the .unexport directive. +# +# Before 2020-12-13, misspelled directives like ".unexporting" or +# ".unexport-en" had not been detected properly. +# +# See also: +# directive-misspellings.mk + +# First, export 3 variables. +UT_A= a +UT_B= b +UT_C= c +.export UT_A UT_B UT_C + +# Show the exported variables and their values. +.info ${:!env|sort|grep '^UT_'!} +.info ${.MAKE.EXPORTED} + +# XXX: Now try to unexport all of them. The variables are still exported +# but not mentioned in .MAKE.EXPORTED anymore. +# See the ":N" in Var_UnExport for the implementation. +*= asterisk +.unexport * + +.info ${:!env|sort|grep '^UT_'!} +.info ${.MAKE.EXPORTED} + +.unexport # oops: missing argument + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive-warning.exp b/usr.bin/make/unit-tests/directive-warning.exp new file mode 100644 index 000000000..b08b32073 --- /dev/null +++ b/usr.bin/make/unit-tests/directive-warning.exp @@ -0,0 +1,11 @@ +make: "directive-warning.mk" line 11: Unknown directive "warn" +make: "directive-warning.mk" line 12: Unknown directive "warn" +make: "directive-warning.mk" line 13: Unknown directive "warnin" +make: "directive-warning.mk" line 14: Unknown directive "warnin" +make: "directive-warning.mk" line 15: Missing argument for ".warning" +make: "directive-warning.mk" line 16: warning: message +make: "directive-warning.mk" line 17: Unknown directive "warnings" +make: "directive-warning.mk" line 18: Unknown directive "warnings" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive-warning.mk b/usr.bin/make/unit-tests/directive-warning.mk new file mode 100644 index 000000000..d586c9fed --- /dev/null +++ b/usr.bin/make/unit-tests/directive-warning.mk @@ -0,0 +1,21 @@ +# $NetBSD: directive-warning.mk,v 1.6 2020/12/19 22:33:11 rillig Exp $ +# +# Tests for the .warning directive. +# +# Until parse.c 1.502 from 2020-12-19, a missing argument to the directive +# produced the wrong error message "Unknown directive". Since parse.c 1.503 +# from 2020-12-19, the correct "Missing argument" is produced. + +# TODO: Implementation + +.warn # misspelled +.warn message # misspelled +.warnin # misspelled +.warnin message # misspelled +.warning # "Missing argument" +.warning message # ok +.warnings # misspelled +.warnings messages # Accepted before 2020-12-13 01:07:54. + +all: + @:; diff --git a/usr.bin/make/unit-tests/directive.exp b/usr.bin/make/unit-tests/directive.exp new file mode 100644 index 000000000..b93d76816 --- /dev/null +++ b/usr.bin/make/unit-tests/directive.exp @@ -0,0 +1,12 @@ +make: "directive.mk" line 9: Unknown directive "indented" +make: "directive.mk" line 10: Unknown directive "indented" +make: "directive.mk" line 11: Unknown directive "indented" +make: "directive.mk" line 15: Unknown directive "info" +Global:.info = +Global:.info = value +make: "directive.mk" line 26: := value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/directive.mk b/usr.bin/make/unit-tests/directive.mk new file mode 100644 index 000000000..d463ce4f0 --- /dev/null +++ b/usr.bin/make/unit-tests/directive.mk @@ -0,0 +1,35 @@ +# $NetBSD: directive.mk,v 1.4 2020/11/15 11:57:00 rillig Exp $ +# +# Tests for the preprocessing directives, such as .if or .info. + +# TODO: Implementation + +# Unknown directives are correctly named in the error messages, +# even if they are indented. +.indented none +. indented 2 spaces +. indented tab + +# Directives must be written directly, not indirectly via variable +# expressions. +.${:Uinfo} directives cannot be indirect + +# There is no directive called '.target', therefore this is parsed as a +# dependency declaration with 2 targets and 1 source. +.target target: source + +# This looks ambiguous. It could be either an .info message or a variable +# assignment. It is a variable assignment. +.MAKEFLAGS: -dv +.info:= value +.info?= value # This is a variable assignment as well. +.info := value # The space after the '.info' makes this + # a directive. +.MAKEFLAGS: -d0 + +# This is a dependency since directives must be given directly. +# Not even the space after the '.info' can change anything about this. +.${:Uinfo} : source + +all: + @:; diff --git a/usr.bin/make/unit-tests/dollar.exp b/usr.bin/make/unit-tests/dollar.exp new file mode 100644 index 000000000..9b8c9d638 --- /dev/null +++ b/usr.bin/make/unit-tests/dollar.exp @@ -0,0 +1,51 @@ + +Printing dollar from literals and variables + +To survive the parser, a dollar sign must be doubled. + 1 dollar literal => + 1 dollar literal eol => <> + 2 dollar literal => <$> + 4 dollar literal => <$$> +Some hungry part of make eats all the dollars after a :U modifier. + 1 dollar default => <> + 2 dollar default => <> + 4 dollar default => <> +This works as expected. + 1 dollar variable => <> + 2 dollar variable => <$> + 4 dollar variable => <$$> +Some hungry part of make eats all the dollars after a :U modifier. + 1 dollar var-default => <> + 2 dollar var-default => <$> + 4 dollar var-default => <$$> + +Dollar in :S pattern + + S,$,word, => <$XYword> + S,$X,word, => <$XY> + S,$$X,word, => <$XY> + S,$$$X,word, => <$XY> + S,$X,replaced, => + S,$$X,replaced, => + S,$$$X,replaced, => + +Dollar in :C character class + +The A is replaced because the $$ is reduced to a single $, +which is then resolved to the variable X with the value VAR_X. +The effective character class becomes [VAR_XY]. + C,[$$XY],<&>,g => <$> + +Dollar in :C pattern + +For some reason, multiple dollars are folded into one. + C,$,dollar,g => <> + C,$$,dollar,g => <> + +Dollar in :S replacement + +For some reason, multiple dollars are folded into one. + S,word,a$Xo, => + S,word,a$$Xo, => + S,word,a$$$Xo, => +exit status 0 diff --git a/usr.bin/make/unit-tests/dollar.mk b/usr.bin/make/unit-tests/dollar.mk new file mode 100644 index 000000000..8a06c5bf7 --- /dev/null +++ b/usr.bin/make/unit-tests/dollar.mk @@ -0,0 +1,81 @@ +# $NetBSD: dollar.mk,v 1.4 2020/11/03 18:21:36 rillig Exp $ +# +# Test the various places where a dollar sign can appear and +# see what happens. There are lots of surprises here. +# + +LIST= plain 'single' "double" 'mix'"ed" back\ slashed +WORD= word + +DOLLAR1= $ +DOLLAR2= $$ +DOLLAR4= $$$$ + +X= VAR_X +DOLLAR_XY= $$XY +DOLLAR_AXY= $$AXY + +H= @header() { printf '\n%s\n\n' "$$*"; }; header +T= @testcase() { printf '%23s => <%s>\n' "$$@"; }; testcase +C= @comment() { printf '%s\n' "$$*"; }; comment + +# These variable values are not accessed. +# The trailing dollar in the '1 dollar literal eol' test case accesses +# the empty variable instead, which is always guaranteed to be empty. +${:U }= space-var-value +${:U${.newline}}= newline-var-value +# But this one is accessed. +${:U'}= single-quote-var-value' + +all: + $H 'Printing dollar from literals and variables' + + $C 'To survive the parser, a dollar sign must be doubled.' + $T '1 dollar literal' '$' + $T '1 dollar literal eol' ''$ + $T '2 dollar literal' '$$' + $T '4 dollar literal' '$$$$' + + $C 'Some hungry part of make eats all the dollars after a :U modifier.' + $T '1 dollar default' ''${:U$:Q} + $T '2 dollar default' ''${:U$$:Q} + $T '4 dollar default' ''${:U$$$$:Q} + + $C 'This works as expected.' + $T '1 dollar variable' ''${DOLLAR1:Q} + $T '2 dollar variable' ''${DOLLAR2:Q} + $T '4 dollar variable' ''${DOLLAR4:Q} + + $C 'Some hungry part of make eats all the dollars after a :U modifier.' + $T '1 dollar var-default' ''${:U${DOLLAR1}:Q} + $T '2 dollar var-default' ''${:U${DOLLAR2}:Q} + $T '4 dollar var-default' ''${:U${DOLLAR4}:Q} + + $H 'Dollar in :S pattern' + + $T 'S,$$,word,' ''${DOLLAR_XY:S,$,word,:Q} + $T 'S,$$X,word,' ''${DOLLAR_XY:S,$X,word,:Q} + $T 'S,$$$$X,word,' ''${DOLLAR_XY:S,$$X,word,:Q} + $T 'S,$$$$$$X,word,' ''${DOLLAR_XY:S,$$$X,word,:Q} + + $T 'S,$$X,replaced,' ''${X:S,$X,replaced,:Q} + $T 'S,$$$$X,replaced,' ''${X:S,$$X,replaced,:Q} + $T 'S,$$$$$$X,replaced,' ''${X:S,$$$X,replaced,:Q} + + $H 'Dollar in :C character class' + + $C 'The A is replaced because the $$$$ is reduced to a single $$,' + $C 'which is then resolved to the variable X with the value VAR_X.' + $C 'The effective character class becomes [VAR_XY].' + $T 'C,[$$$$XY],<&>,g' ''${DOLLAR_AXY:C,[$$XY],<&>,g:Q} + + $H 'Dollar in :C pattern' + $C 'For some reason, multiple dollars are folded into one.' + $T 'C,$$,dollar,g' ''${DOLLAR:C,$,dollar,g:Q} + $T 'C,$$$$,dollar,g' ''${DOLLAR:C,$$,dollar,g:Q} + + $H 'Dollar in :S replacement' + $C 'For some reason, multiple dollars are folded into one.' + $T 'S,word,a$$Xo,' ''${WORD:S,word,a$Xo,:Q} + $T 'S,word,a$$$$Xo,' ''${WORD:S,word,a$$Xo,:Q} + $T 'S,word,a$$$$$$Xo,' ''${WORD:S,word,a$$$Xo,:Q} diff --git a/usr.bin/make/unit-tests/doterror.exp b/usr.bin/make/unit-tests/doterror.exp index 0447a5193..5655644c3 100644 --- a/usr.bin/make/unit-tests/doterror.exp +++ b/usr.bin/make/unit-tests/doterror.exp @@ -1,9 +1,9 @@ At first, I am happy and now: sad -.ERROR: Looks like 'sad' is upset. *** Error code 1 Stop. make: stopped in unit-tests +.ERROR: Looks like 'sad' is upset. exit status 1 diff --git a/usr.bin/make/unit-tests/doterror.mk b/usr.bin/make/unit-tests/doterror.mk index 7f1c78ff5..d46fb3581 100644 --- a/usr.bin/make/unit-tests/doterror.mk +++ b/usr.bin/make/unit-tests/doterror.mk @@ -1,4 +1,4 @@ -# $Id: doterror.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: doterror.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ .BEGIN: diff --git a/usr.bin/make/unit-tests/dotwait.exp b/usr.bin/make/unit-tests/dotwait.exp index 6bf96e301..bdc0a0eb1 100644 --- a/usr.bin/make/unit-tests/dotwait.exp +++ b/usr.bin/make/unit-tests/dotwait.exp @@ -22,9 +22,9 @@ shared.2.1 shared.2.1 shared.2.99 shared.2.99 +cycle.1.99 +cycle.1.99 make: Graph cycles through `cycle.2.99' make: Graph cycles through `cycle.2.98' make: Graph cycles through `cycle.2.97' -cycle.1.99 -cycle.1.99 exit status 0 diff --git a/usr.bin/make/unit-tests/dotwait.mk b/usr.bin/make/unit-tests/dotwait.mk index 9bdaaba39..dbc502651 100644 --- a/usr.bin/make/unit-tests/dotwait.mk +++ b/usr.bin/make/unit-tests/dotwait.mk @@ -1,9 +1,9 @@ -# $NetBSD: dotwait.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: dotwait.mk,v 1.3 2020/10/24 08:50:17 rillig Exp $ -THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} +THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} -TESTS= simple recursive shared cycle -PAUSE= sleep 1 +TESTS= simple recursive shared cycle +PAUSE= sleep 1 # Use a .for loop rather than dependencies here, to ensure # that the tests are run one by one, with parallelism @@ -11,7 +11,7 @@ PAUSE= sleep 1 # Ignore "--- target ---" lines printed by parallel make. all: .for t in ${TESTS} - @${.MAKE} -f ${THISMAKEFILE} -j4 $t | grep -v "^--- " + @${.MAKE} -f ${THISMAKEFILE} -j4 $t 2>&1 | grep -v "^--- " .endfor # diff --git a/usr.bin/make/unit-tests/envfirst.exp b/usr.bin/make/unit-tests/envfirst.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/envfirst.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/envfirst.mk b/usr.bin/make/unit-tests/envfirst.mk new file mode 100644 index 000000000..60a331a5d --- /dev/null +++ b/usr.bin/make/unit-tests/envfirst.mk @@ -0,0 +1,44 @@ +# $NetBSD: envfirst.mk,v 1.5 2021/02/04 21:42:47 rillig Exp $ +# +# The -e option makes environment variables stronger than global variables. + +.MAKEFLAGS: -e + +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Try to override the variable; this does not have any effect. +FROM_ENV= value-from-mk +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Try to append to the variable; this also doesn't have any effect. +FROM_ENV+= appended +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# The default assignment also cannot change the variable. +FROM_ENV?= default +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Neither can the assignment modifiers. +.if ${FROM_ENV::=from-condition} +.endif +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +# Even .undef doesn't work since it only affects the global scope, +# which is independent from the environment variables. +.undef FROM_ENV +.if ${FROM_ENV} != value-from-env +. error ${FROM_ENV} +.endif + +all: + @: nothing diff --git a/usr.bin/make/unit-tests/error.exp b/usr.bin/make/unit-tests/error.exp index a2bf71b71..3adc099a4 100644 --- a/usr.bin/make/unit-tests/error.exp +++ b/usr.bin/make/unit-tests/error.exp @@ -1,4 +1,6 @@ -make: "error.mk" line 3: just FYI -make: "error.mk" line 4: warning: this could be serious -make: "error.mk" line 5: this is fatal +make: "error.mk" line 6: just FYI +make: "error.mk" line 7: warning: this could be serious +make: "error.mk" line 8: this is fatal + +make: stopped in unit-tests exit status 1 diff --git a/usr.bin/make/unit-tests/error.mk b/usr.bin/make/unit-tests/error.mk index 721ed5052..0029b3bc6 100644 --- a/usr.bin/make/unit-tests/error.mk +++ b/usr.bin/make/unit-tests/error.mk @@ -1,10 +1,12 @@ -# $Id: error.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: error.mk,v 1.3 2020/11/03 17:38:45 rillig Exp $ +# +# Demonstrate that the .error directive exits immediately, without +# continuing parsing until the end of the file. .info just FYI .warning this could be serious .error this is fatal +.info this is not reached because of the .error above all: - -.info.html: - @echo this should be ignored + : this is not reached because of the .error diff --git a/usr.bin/make/unit-tests/escape.mk b/usr.bin/make/unit-tests/escape.mk index 829403d7a..8bdd3ad2a 100644 --- a/usr.bin/make/unit-tests/escape.mk +++ b/usr.bin/make/unit-tests/escape.mk @@ -1,4 +1,4 @@ -# $Id: escape.mk,v 1.10 2014/09/09 10:22:27 apb Exp $ +# $NetBSD: escape.mk,v 1.14 2020/11/03 17:38:45 rillig Exp $ # # Test backslash escaping. @@ -35,8 +35,8 @@ # Also, our practice is that an even number of backslashes before a # newline in a variable assignment simply stores the backslashes as part # of the value, and treats the newline as though it was not escaped. -# Similarly, ann even number of backslashes before a newline in a -# command simply uses the backslashes as part of the command test, but +# Similarly, an even number of backslashes before a newline in a +# command simply uses the backslashes as part of the command, but # does not escape the newline. This is compatible with GNU make. all: .PHONY @@ -44,8 +44,8 @@ all: .PHONY # Some variables to be expanded in tests # -a = aaa -A = ${a} +a= aaa +A= ${a} # Backslash at end of line in a comment\ should continue the comment. \ @@ -57,13 +57,13 @@ __printvars: .USE .MADE # Embedded backslash in variable should be taken literally. # -VAR1BS = 111\111 -VAR1BSa = 111\${a} -VAR1BSA = 111\${A} -VAR1BSda = 111\$${a} -VAR1BSdA = 111\$${A} -VAR1BSc = 111\# backslash escapes comment char, so this is part of the value -VAR1BSsc = 111\ # This is a comment. Value ends with +VAR1BS= 111\111 +VAR1BSa= 111\${a} +VAR1BSA= 111\${A} +VAR1BSda= 111\$${a} +VAR1BSdA= 111\$${A} +VAR1BSc= 111\# backslash escapes comment char, so this is part of the value +VAR1BSsc= 111\ # This is a comment. Value ends with all: var-1bs var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \ @@ -71,13 +71,13 @@ var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \ # Double backslash in variable should be taken as two literal backslashes. # -VAR2BS = 222\\222 -VAR2BSa = 222\\${a} -VAR2BSA = 222\\${A} -VAR2BSda = 222\\$${a} -VAR2BSdA = 222\\$${A} -VAR2BSc = 222\\# backslash does not escape comment char, so this is a comment -VAR2BSsc = 222\\ # This is a comment. Value ends with +VAR2BS= 222\\222 +VAR2BSa= 222\\${a} +VAR2BSA= 222\\${A} +VAR2BSda= 222\\$${a} +VAR2BSdA= 222\\$${A} +VAR2BSc= 222\\# backslash does not escape comment char, so this is a comment +VAR2BSsc= 222\\ # This is a comment. Value ends with all: var-2bs var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ @@ -85,19 +85,19 @@ var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ # Backslash-newline in a variable setting is replaced by a single space. # -VAR1BSNL = 111\ +VAR1BSNL= 111\ 111 -VAR1BSNLa = 111\ +VAR1BSNLa= 111\ ${a} -VAR1BSNLA = 111\ +VAR1BSNLA= 111\ ${A} -VAR1BSNLda = 111\ +VAR1BSNLda= 111\ $${a} -VAR1BSNLdA = 111\ +VAR1BSNLdA= 111\ $${A} -VAR1BSNLc = 111\ +VAR1BSNLc= 111\ # this should be processed as a comment -VAR1BSNLsc = 111\ +VAR1BSNLsc= 111\ # this should be processed as a comment all: var-1bsnl @@ -113,19 +113,19 @@ var-1bsnl: .PHONY __printvars \ # generate syntax errors regardless of whether or not they are # treated as part of the value. # -VAR2BSNL = 222\\ +VAR2BSNL= 222\\ 222= -VAR2BSNLa = 222\\ +VAR2BSNLa= 222\\ ${a}= -VAR2BSNLA = 222\\ +VAR2BSNLA= 222\\ ${A}= -VAR2BSNLda = 222\\ +VAR2BSNLda= 222\\ $${a}= -VAR2BSNLdA = 222\\ +VAR2BSNLdA= 222\\ $${A}= -VAR2BSNLc = 222\\ +VAR2BSNLc= 222\\ # this should be processed as a comment -VAR2BSNLsc = 222\\ +VAR2BSNLsc= 222\\ # this should be processed as a comment all: var-2bsnl @@ -140,19 +140,19 @@ var-2bsnl: .PHONY __printvars \ # generate syntax errors regardless of whether or not they are # treated as part of the value. # -VAR3BSNL = 333\\\ +VAR3BSNL= 333\\\ 333= -VAR3BSNLa = 333\\\ +VAR3BSNLa= 333\\\ ${a}= -VAR3BSNLA = 333\\\ +VAR3BSNLA= 333\\\ ${A}= -VAR3BSNLda = 333\\\ +VAR3BSNLda= 333\\\ $${a}= -VAR3BSNLdA = 333\\\ +VAR3BSNLdA= 333\\\ $${A}= -VAR3BSNLc = 333\\\ +VAR3BSNLc= 333\\\ # this should be processed as a comment -VAR3BSNLsc = 333\\\ +VAR3BSNLsc= 333\\\ # this should be processed as a comment all: var-3bsnl @@ -163,20 +163,20 @@ var-3bsnl: .PHONY __printvars \ # Backslash-newline in a variable setting, plus any amount of white space # on the next line, is replaced by a single space. # -VAR1BSNL00= first line\ +VAR1BSNL00= first line\ # above line is entirely empty, and this is a comment -VAR1BSNL0= first line\ +VAR1BSNL0= first line\ no space on second line -VAR1BSNLs= first line\ +VAR1BSNLs= first line\ one space on second line -VAR1BSNLss= first line\ +VAR1BSNLss= first line\ two spaces on second line -VAR1BSNLt= first line\ +VAR1BSNLt= first line\ one tab on second line -VAR1BSNLtt= first line\ +VAR1BSNLtt= first line\ two tabs on second line -VAR1BSNLxx= first line\ +VAR1BSNLxx= first line\ many spaces and tabs [ ] on second line all: var-1bsnl-space @@ -187,7 +187,7 @@ var-1bsnl-space: .PHONY __printvars \ # Backslash-newline in a command is retained. # # The "#" in "# second line without space" makes it a comment instead -# of a syntax error if the preceding line is parsed incorretly. +# of a syntax error if the preceding line is parsed incorrectly. # The ":" in "third line':" makes it look like the start of a # target instead of a syntax error if the first line is parsed incorrectly. # @@ -220,7 +220,7 @@ cmd-1bsnl-eof: # XXX: This may differ from POSIX, but matches gmake. # # When make passes two backslashes to the shell, the shell will pass one -# backslash to the echo commant. +# backslash to the echo command. # all: cmd-2bsnl cmd-2bsnl: .PHONY diff --git a/usr.bin/make/unit-tests/export-all.mk b/usr.bin/make/unit-tests/export-all.mk index 576487b53..0d7410834 100644 --- a/usr.bin/make/unit-tests/export-all.mk +++ b/usr.bin/make/unit-tests/export-all.mk @@ -1,23 +1,24 @@ -# $Id: export-all.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $ +# $NetBSD: export-all.mk,v 1.5 2020/10/24 08:50:17 rillig Exp $ -UT_OK=good -UT_F=fine +UT_OK= good +UT_F= fine # the old way to do :tA -M_tAbad = C,.*,cd & \&\& 'pwd',:sh +M_tAbad= C,.*,cd & \&\& 'pwd',:sh # the new -M_tA = tA +M_tA= tA here := ${.PARSEDIR} # this will cause trouble (recursing if we let it) -UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T} +UT_BADDIR= ${${here}/../${here:T}:L:${M_tAbad}:T} # this will be ok -UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T} +UT_OKDIR= ${${here}/../${here:T}:L:${M_tA}:T} .export +FILTER_CMD= grep ^UT_ .include "export.mk" -UT_TEST=export-all -UT_ALL=even this gets exported +UT_TEST= export-all +UT_ALL= even this gets exported diff --git a/usr.bin/make/unit-tests/export-env.exp b/usr.bin/make/unit-tests/export-env.exp index 6221232a2..8a779e6af 100644 --- a/usr.bin/make/unit-tests/export-env.exp +++ b/usr.bin/make/unit-tests/export-env.exp @@ -2,8 +2,10 @@ make: UT_TEST=export-env.mk UT_ENV=not-exported UT_EXP=not-exported +UT_LIT=literal export-env.mk env: UT_TEST=export-env.mk UT_ENV=exported UT_EXP=exported +UT_LIT=literal ${UT_TEST} exit status 0 diff --git a/usr.bin/make/unit-tests/export-env.mk b/usr.bin/make/unit-tests/export-env.mk index 6e9993b7b..1605b1a71 100644 --- a/usr.bin/make/unit-tests/export-env.mk +++ b/usr.bin/make/unit-tests/export-env.mk @@ -1,23 +1,26 @@ -# $Id: export-env.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: export-env.mk,v 1.4 2020/10/24 08:50:17 rillig Exp $ # our normal .export, subsequent changes affect the environment -UT_TEST=this +UT_TEST= this .export UT_TEST -UT_TEST:= ${.PARSEFILE} +UT_TEST:= ${.PARSEFILE} # not so with .export-env -UT_ENV=exported +UT_ENV= exported .export-env UT_ENV -UT_ENV=not-exported +UT_ENV= not-exported # gmake style export goes further; affects nothing but the environment UT_EXP=before-export export UT_EXP=exported UT_EXP=not-exported +UT_LIT= literal ${UT_TEST} +.export-literal UT_LIT + all: - @echo make:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=${$v};@} - @echo env:; ${UT_TEST UT_ENV UT_EXP:L:@v@echo $v=$${$v};@} + @echo make:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=${$v};@} + @echo env:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=$${$v};@} diff --git a/usr.bin/make/unit-tests/export-variants.exp b/usr.bin/make/unit-tests/export-variants.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/export-variants.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/export-variants.mk b/usr.bin/make/unit-tests/export-variants.mk new file mode 100644 index 000000000..fdbbccccc --- /dev/null +++ b/usr.bin/make/unit-tests/export-variants.mk @@ -0,0 +1,40 @@ +# $NetBSD: export-variants.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $ +# +# Test whether exported variables apply to each variant of running +# external commands: +# +# The != assignments. +# The :!cmd! modifier. +# The :sh modifier. + +SHVAR!= env | grep ^UT_ || true +.if ${SHVAR} != "" +. warning At this point, no variable should be exported. +.endif + +.if ${:!env | grep ^UT_ || true!} != "" +. warning At this point, no variable should be exported. +.endif + +.if ${env | grep ^UT_ || true:L:sh} != "" +. warning At this point, no variable should be exported. +.endif + +UT_VAR= value +.export UT_VAR + +SHVAR!= env | grep ^UT_ || true +.if ${SHVAR} != "UT_VAR=value" +. warning At this point, a single variable should be exported. +.endif + +.if ${:!env | grep ^UT_ || true!} != "UT_VAR=value" +. warning At this point, a single variable should be exported. +.endif + +.if ${env | grep ^UT_ || true:L:sh} != "UT_VAR=value" +. warning At this point, a single variable should be exported. +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/export.exp b/usr.bin/make/unit-tests/export.exp index 143771ce9..648d1283f 100644 --- a/usr.bin/make/unit-tests/export.exp +++ b/usr.bin/make/unit-tests/export.exp @@ -1,3 +1,5 @@ +MAKELEVEL=1 +TMPDIR=TMPDIR UT_DOLLAR=This is $UT_FU UT_FOO=foobar is fubar UT_FU=fubar diff --git a/usr.bin/make/unit-tests/export.mk b/usr.bin/make/unit-tests/export.mk index 1b4ee722c..94e3a862d 100644 --- a/usr.bin/make/unit-tests/export.mk +++ b/usr.bin/make/unit-tests/export.mk @@ -1,22 +1,46 @@ -# $Id: export.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: export.mk,v 1.10 2020/10/24 08:50:17 rillig Exp $ -UT_TEST=export -UT_FOO=foo${BAR} -UT_FU=fubar -UT_ZOO=hoopie -UT_NO=all -# belive it or not, we expect this one to come out with $UT_FU unexpanded. -UT_DOLLAR= This is $$UT_FU +UT_TEST= export +UT_FOO= foo${BAR} +UT_FU= fubar +UT_ZOO= hoopie +UT_NO= all +# believe it or not, we expect this one to come out with $UT_FU unexpanded. +UT_DOLLAR= This is $$UT_FU .export UT_FU UT_FOO .export UT_DOLLAR -# this one will be ignored + +.if !defined(.MAKE.PID) +. error .MAKE.PID must be defined +.endif +@= at +%= percent +*= asterisk +${:U!}= exclamation # A direct != would try to run "exclamation" + # as a shell command and assign its output + # to the empty variable. +&= ampersand + +# This is ignored because it is internal. .export .MAKE.PID +# These are ignored because they are local to the target. +.export @ +.export % +.export * +.export ! +# This is exported (see the .rawout file) but not displayed since the dash +# shell filters it out. To reach consistent output for each shell, the +# ampersand is filtered out already by FILTER_CMD. +.export & +# This is ignored because it is undefined. +.export UNDEFINED -BAR=bar is ${UT_FU} +BAR= bar is ${UT_FU} -.MAKE.EXPORTED+= UT_ZOO UT_TEST +.MAKE.EXPORTED+= UT_ZOO UT_TEST + +FILTER_CMD?= egrep -v '^(MAKEFLAGS|MALLOC_OPTIONS|PATH|PWD|SHLVL|_|&)=' all: - @env | grep '^UT_' | sort - + @env | ${FILTER_CMD} | sort diff --git a/usr.bin/make/unit-tests/forloop.exp b/usr.bin/make/unit-tests/forloop.exp index df14b7512..422711b41 100644 --- a/usr.bin/make/unit-tests/forloop.exp +++ b/usr.bin/make/unit-tests/forloop.exp @@ -1,18 +1,19 @@ -x=one -x="two and three" -x=four -x="five" -x=-I/this -x=-I"This or that" -x=-Ithat -x="-DTHIS=\"this and that\"" -cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" -a=one b="two and three" -a=four b="five" -a=ONE b="TWO AND THREE" -a=FOUR b="FIVE" +make: "forloop.mk" line 14: x=one +make: "forloop.mk" line 14: x="two and three" +make: "forloop.mk" line 14: x=four +make: "forloop.mk" line 14: x="five" +make: "forloop.mk" line 20: x=-I/this +make: "forloop.mk" line 20: x=-I"This or that" +make: "forloop.mk" line 20: x=-Ithat +make: "forloop.mk" line 20: x="-DTHIS=\"this and that\"" +make: "forloop.mk" line 27: cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +make: "forloop.mk" line 41: newline-item=(a) +make: "forloop.mk" line 47: a=one b="two and three" +make: "forloop.mk" line 47: a=four b="five" +make: "forloop.mk" line 47: a=ONE b="TWO AND THREE" +make: "forloop.mk" line 47: a=FOUR b="FIVE" We expect an error next: -make: "forloop.mk" line 38: Wrong number of words (9) in .for substitution list with 2 vars +make: "forloop.mk" line 46: Wrong number of words (9) in .for substitution list with 2 variables make: Fatal errors encountered -- cannot continue make: stopped in unit-tests OK diff --git a/usr.bin/make/unit-tests/forloop.mk b/usr.bin/make/unit-tests/forloop.mk index e0399f355..cef05cbe4 100644 --- a/usr.bin/make/unit-tests/forloop.mk +++ b/usr.bin/make/unit-tests/forloop.mk @@ -1,42 +1,50 @@ -# $Id: forloop.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: forloop.mk,v 1.7 2020/11/03 17:37:57 rillig Exp $ all: for-loop -LIST = one "two and three" four "five" +LIST= one "two and three" four "five" .if make(for-fail) for-fail: -XTRA_LIST = xtra +XTRA_LIST= xtra .else -.for x in ${LIST} -X!= echo 'x=$x' >&2; echo -.endfor +. for x in ${LIST} +. info x=$x +. endfor -CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +CFL= -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" cfl= -.for x in ${CFL} -X!= echo 'x=$x' >&2; echo -.if empty(cfl) -cfl= $x -.else -cfl+= $x -.endif -.endfor -X!= echo 'cfl=${cfl}' >&2; echo +. for x in ${CFL} +. info x=$x +. if empty(cfl) +cfl= $x +. else +cfl+= $x +. endif +. endfor +. info cfl=${cfl} -.if ${cfl} != ${CFL} -.error ${.newline}'${cfl}' != ${.newline}'${CFL}' -.endif +. if ${cfl} != ${CFL} +. error ${.newline}${cfl} != ${.newline}${CFL} +. endif -.for a b in ${EMPTY} -X!= echo 'a=$a b=$b' >&2; echo -.endfor -.endif +. for a b in ${EMPTY} +. info a=$a b=$b +. endfor + +# Since at least 1993, iteration stops at the first newline. +# Back then, the .newline variable didn't exist, therefore it was unlikely +# that a newline ever occurred. +. for var in a${.newline}b${.newline}c +. info newline-item=(${var}) +. endfor + +.endif # for-fail .for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} -X!= echo 'a=$a b=$b' >&2; echo +. info a=$a b=$b .endfor for-loop: diff --git a/usr.bin/make/unit-tests/forsubst.mk b/usr.bin/make/unit-tests/forsubst.mk index 00cd9b634..9f293ab7f 100644 --- a/usr.bin/make/unit-tests/forsubst.mk +++ b/usr.bin/make/unit-tests/forsubst.mk @@ -1,4 +1,16 @@ -# $Id: forsubst.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: forsubst.mk,v 1.3 2020/11/03 17:59:27 rillig Exp $ +# +# The parser used to break dependency lines at ';' without regard for +# substitution patterns. Back then, the first ';' was interpreted as the +# separator between the dependency and its commands. This (perhaps coupled +# with the new handling of .for variables in ${:U...) caused +# interesting results for lines like: +# +# .for file in ${LIST} +# for-subst: ${file:S;^;${here}/;g} +# .endfor +# +# See the commit to unit-tests/forsubst (without the .mk) from 2009-10-07. all: for-subst diff --git a/usr.bin/make/unit-tests/gnode-submake.exp b/usr.bin/make/unit-tests/gnode-submake.exp new file mode 100644 index 000000000..ea00e8d76 --- /dev/null +++ b/usr.bin/make/unit-tests/gnode-submake.exp @@ -0,0 +1,11 @@ +#*** Input graph: +# all, unmade, type OP_DEPENDS, flags none +# makeinfo, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# make-index, unmade, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-dot, unmade, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-no-dot, unmade, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# braces-no-dot-modifier, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# parentheses-dot, unmade, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none +# parentheses-no-dot, unmade, type OP_DEPENDS|OP_SUBMAKE|OP_HAS_COMMANDS, flags none + +exit status 0 diff --git a/usr.bin/make/unit-tests/gnode-submake.mk b/usr.bin/make/unit-tests/gnode-submake.mk new file mode 100644 index 000000000..40ff20276 --- /dev/null +++ b/usr.bin/make/unit-tests/gnode-submake.mk @@ -0,0 +1,42 @@ +# $NetBSD: gnode-submake.mk,v 1.1 2020/11/07 23:25:06 rillig Exp $ +# +# Test whether OP_SUBMAKE is determined correctly. If it is, this node's +# shell commands are connected to the make process via pipes, to coordinate +# the number of running jobs. +# +# Determining whether a node is a sub-make node happens when the node is +# parsed. This information is only used in parallel mode, but the result +# from parsing is available in compat mode as well. + +.MAKEFLAGS: -n -dg1 + +all: makeinfo make-index +all: braces-dot braces-no-dot +all: braces-no-dot-modifier +all: parentheses-dot parentheses-no-dot + +makeinfo: + # The command contains the substring "make", but not as a whole word. + : makeinfo submake + +make-index: + # The command contains the word "make", therefore it is considered a + # possible sub-make. It isn't really, but that doesn't hurt. + : make-index + +braces-dot: + : ${.MAKE} + +braces-no-dot: + : ${MAKE} + +braces-no-dot-modifier: + # The command refers to MAKE, but not in its pure form. Therefore it + # is not considered a sub-make. + : ${MAKE:T} + +parentheses-dot: + : $(.MAKE) + +parentheses-no-dot: + : $(MAKE) diff --git a/usr.bin/make/unit-tests/hanoi-include.exp b/usr.bin/make/unit-tests/hanoi-include.exp new file mode 100644 index 000000000..ead78b9eb --- /dev/null +++ b/usr.bin/make/unit-tests/hanoi-include.exp @@ -0,0 +1,32 @@ +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack C to stack A. +Move the upper disk from stack B to stack A. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack C to stack B. +Move the upper disk from stack C to stack A. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +Move the upper disk from stack A to stack B. +Move the upper disk from stack C to stack B. +Move the upper disk from stack A to stack C. +Move the upper disk from stack B to stack A. +Move the upper disk from stack B to stack C. +Move the upper disk from stack A to stack C. +exit status 0 diff --git a/usr.bin/make/unit-tests/hanoi-include.mk b/usr.bin/make/unit-tests/hanoi-include.mk new file mode 100644 index 000000000..3ad0a7518 --- /dev/null +++ b/usr.bin/make/unit-tests/hanoi-include.mk @@ -0,0 +1,41 @@ +# $NetBSD: hanoi-include.mk,v 1.1 2020/10/03 17:30:54 rillig Exp $ +# +# Implements the Towers of Hanoi puzzle, thereby demonstrating a bunch of +# useful programming techniques: +# +# * default assignment using the ?= assignment operator +# * including the same file recursively +# * 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 -f hanoi-include.mk +# endless loop: +# make -f hanoi-include.mk N=3 + +N?= 5 # Move this number of disks ... +FROM?= A # ... from this stack ... +VIA?= B # ... via this stack ... +TO?= C # ... to this stack. + +.if $N == 1 +. for from to in ${FROM} ${TO} +all:: + @echo "Move the upper disk from stack ${from} to stack ${to}." +. endfor +.else +_:= ${N::!=expr $N - 1} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}} +. include "${.PARSEDIR}/${.PARSEFILE}" +_:= ${N::!=expr $N + 1} ${TMP::=${VIA}} ${VIA::=${TO}} ${TO::=${TMP}} + +. for from to in ${FROM} ${TO} +all:: + @echo "Move the upper disk from stack ${from} to stack ${to}." +. endfor + +_:= ${N::!=expr $N - 1} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}} +. include "${.PARSEDIR}/${.PARSEFILE}" +_:= ${N::!=expr $N + 1} ${TMP::=${VIA}} ${VIA::=${FROM}} ${FROM::=${TMP}} +.endif diff --git a/usr.bin/make/unit-tests/hash.exp b/usr.bin/make/unit-tests/hash.exp deleted file mode 100644 index 0a2423436..000000000 --- a/usr.bin/make/unit-tests/hash.exp +++ /dev/null @@ -1,9 +0,0 @@ -b2af338b -3360ac65 -7747f046 -9ca87054 -880fe816 -208fcbd3 -d5d376eb -de41416c -exit status 0 diff --git a/usr.bin/make/unit-tests/hash.mk b/usr.bin/make/unit-tests/hash.mk deleted file mode 100644 index 1ed84e776..000000000 --- a/usr.bin/make/unit-tests/hash.mk +++ /dev/null @@ -1,18 +0,0 @@ -STR1= -STR2= a -STR3= ab -STR4= abc -STR5= abcd -STR6= abcde -STR7= abcdef -STR8= abcdefghijklmnopqrstuvwxyz - -all: - @echo ${STR1:hash} - @echo ${STR2:hash} - @echo ${STR3:hash} - @echo ${STR4:hash} - @echo ${STR5:hash} - @echo ${STR6:hash} - @echo ${STR7:hash} - @echo ${STR8:hash} diff --git a/usr.bin/make/unit-tests/impsrc.exp b/usr.bin/make/unit-tests/impsrc.exp index 23e8347d2..3eb050e08 100644 --- a/usr.bin/make/unit-tests/impsrc.exp +++ b/usr.bin/make/unit-tests/impsrc.exp @@ -1,13 +1,13 @@ -expected: source4 -actual: source4 +expected: +actual: expected: target1.x actual: target1.x expected: target1.y actual: target1.y -expected: source1 -actual: source1 -expected: source2 -actual: source2 -expected: source1 -actual: source1 +expected: +actual: +expected: +actual: +expected: +actual: exit status 0 diff --git a/usr.bin/make/unit-tests/impsrc.mk b/usr.bin/make/unit-tests/impsrc.mk index 95ae0c34f..6fb31adac 100644 --- a/usr.bin/make/unit-tests/impsrc.mk +++ b/usr.bin/make/unit-tests/impsrc.mk @@ -1,4 +1,4 @@ -# $NetBSD: impsrc.mk,v 1.2 2014/08/30 22:21:07 sjg Exp $ +# $NetBSD: impsrc.mk,v 1.3 2020/08/07 13:43:50 rillig Exp $ # Does ${.IMPSRC} work properly? # It should be set, in order of precedence, to ${.TARGET} of: @@ -6,6 +6,8 @@ # 2) the first prerequisite from the dependency line of an explicit rule, or # 3) the first prerequisite of an explicit rule. # +# Items 2 and 3 work in GNU make. +# Items 2 and 3 are not required by POSIX 2018. all: target1.z target2 target3 target4 @@ -19,25 +21,40 @@ all: target1.z target2 target3 target4 @echo 'expected: target1.y' @echo 'actual: $<' +# (3) Making target1.z out of target1.y is done because of an inference rule. +# Therefore $< is available here. + +# (2) This is an additional dependency on the inference rule .x.y. +# The dependency target1.x comes from the inference rule, +# therefore it is available as $<. target1.y: source3 +# (1) This is an explicit dependency, not an inference rule. +# Therefore POSIX does not specify that $< be available here. target1.x: source4 - @echo 'expected: source4' + @echo 'expected: ' # either 'source4' or '' @echo 'actual: $<' +# (4) This is an explicit dependency, independent of any inference rule. +# Therefore $< is not available here. target2: source1 source2 - @echo 'expected: source1' + @echo 'expected: ' @echo 'actual: $<' +# (5) These are two explicit dependency rules. +# The first doesn't have any dependencies, only the second has. +# If any, the value of $< would be 'source2'. target3: source1 target3: source2 source3 - @echo 'expected: source2' + @echo 'expected: ' @echo 'actual: $<' +# (6) The explicit rule does not have associated commands. +# The value of $< might come from that rule, +# but it's equally fine to leave $< undefined. target4: source1 target4: - @echo 'expected: source1' + @echo 'expected: ' @echo 'actual: $<' source1 source2 source3 source4: - diff --git a/usr.bin/make/unit-tests/include-main.exp b/usr.bin/make/unit-tests/include-main.exp new file mode 100644 index 000000000..61e716ad8 --- /dev/null +++ b/usr.bin/make/unit-tests/include-main.exp @@ -0,0 +1,17 @@ +make: "include-main.mk" line 14: main-before-ok +make: "include-main.mk" line 21: main-before-for-ok +make: "include-sub.mk" line 4: sub-before-ok +make: "include-sub.mk" line 14: sub-before-for-ok +ParseReadLine (5): '. info subsub-ok' +make: "include-subsub.mk" line 5: subsub-ok + in .for loop from include-sub.mk:31 + in .for loop from include-sub.mk:30 + in .for loop from include-sub.mk:29 + in .include from include-main.mk:27 +ParseReadLine (6): '.MAKEFLAGS: -d0' +ParseDoDependency(.MAKEFLAGS: -d0) +make: "include-sub.mk" line 38: sub-after-ok +make: "include-sub.mk" line 45: sub-after-for-ok +make: "include-main.mk" line 30: main-after-ok +make: "include-main.mk" line 37: main-after-for-ok +exit status 0 diff --git a/usr.bin/make/unit-tests/include-main.mk b/usr.bin/make/unit-tests/include-main.mk new file mode 100644 index 000000000..d3f122aef --- /dev/null +++ b/usr.bin/make/unit-tests/include-main.mk @@ -0,0 +1,43 @@ +# $NetBSD: include-main.mk,v 1.6 2021/01/22 00:44:55 rillig Exp $ +# +# Until 2020-09-05, the .INCLUDEDFROMFILE magic variable did not behave +# as described in the manual page. +# +# The manual page says that it is the "filename of the file this Makefile +# was included from", while before 2020-09-05 it was the "filename in which +# the latest .include happened". See parse.c, function ParseSetIncludeFile. +# +# Since 2020-09-05, the .INCLUDEDFROMDIR and .INCLUDEDFROMFILE variables +# properly handle nested includes and even .for loops. + +.if !defined(.INCLUDEDFROMFILE) +. info main-before-ok +.else +. warning main-before-fail(${.INCLUDEDFROMFILE}) +.endif + +.for i in once +. if !defined(.INCLUDEDFROMFILE) +. info main-before-for-ok +. else +. warning main-before-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor + +.include "include-sub.mk" + +.if !defined(.INCLUDEDFROMFILE) +. info main-after-ok +.else +. warning main-after-fail(${.INCLUDEDFROMFILE}) +.endif + +.for i in once +. if !defined(.INCLUDEDFROMFILE) +. info main-after-for-ok +. else +. warning main-after-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor + +all: # nothing diff --git a/usr.bin/make/unit-tests/include-sub.mk b/usr.bin/make/unit-tests/include-sub.mk new file mode 100644 index 000000000..0b8dc7739 --- /dev/null +++ b/usr.bin/make/unit-tests/include-sub.mk @@ -0,0 +1,49 @@ +# $NetBSD: include-sub.mk,v 1.7 2020/11/02 19:07:09 rillig Exp $ + +.if ${.INCLUDEDFROMFILE} == "include-main.mk" +. info sub-before-ok +.else +. warning sub-before-fail(${.INCLUDEDFROMFILE}) +.endif + +# As of 2020-09-05, the .for loop is implemented as "including a file" +# with a custom buffer. Therefore this loop has side effects on these +# variables. +.for i in once +. if ${.INCLUDEDFROMFILE} == "include-main.mk" +. info sub-before-for-ok +. else +. warning sub-before-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor + +# To see the variable 'includes' in action: +# +# Breakpoints: +# Parse_File at "Vector_Push(&includes)" +# ParseMessage at entry +# Watches: +# ((const IFile *[10])(*includes.items)) +# *curFile + +.for i in deeply +. for i in nested +. for i in include +.include "include-subsub.mk" +. endfor +. endfor +.endfor + +.if ${.INCLUDEDFROMFILE} == "include-main.mk" +. info sub-after-ok +.else +. warning sub-after-fail(${.INCLUDEDFROMFILE}) +.endif + +.for i in once +. if ${.INCLUDEDFROMFILE} == "include-main.mk" +. info sub-after-for-ok +. else +. warning sub-after-for-fail(${.INCLUDEDFROMFILE}) +. endif +.endfor diff --git a/usr.bin/make/unit-tests/include-subsub.mk b/usr.bin/make/unit-tests/include-subsub.mk new file mode 100644 index 000000000..476d75f79 --- /dev/null +++ b/usr.bin/make/unit-tests/include-subsub.mk @@ -0,0 +1,9 @@ +# $NetBSD: include-subsub.mk,v 1.4 2021/01/26 23:44:56 rillig Exp $ + +.if ${.INCLUDEDFROMFILE} == "include-sub.mk" +.MAKEFLAGS: -dp +. info subsub-ok +.MAKEFLAGS: -d0 +.else +. warning subsub-fail(${.INCLUDEDFROMFILE}) +.endif diff --git a/usr.bin/make/unit-tests/job-flags.exp b/usr.bin/make/unit-tests/job-flags.exp new file mode 100644 index 000000000..b0c81d8f7 --- /dev/null +++ b/usr.bin/make/unit-tests/job-flags.exp @@ -0,0 +1,12 @@ +.BEGIN +silent +ignore +true in ignore +false in ignore +*** [ignore] Error code 1 (ignored) +false without indentation +false space +false tab +*** [ignore-cmds] Error code 1 (ignored) +.END +exit status 0 diff --git a/usr.bin/make/unit-tests/job-flags.mk b/usr.bin/make/unit-tests/job-flags.mk new file mode 100644 index 000000000..d4c3b5d43 --- /dev/null +++ b/usr.bin/make/unit-tests/job-flags.mk @@ -0,0 +1,32 @@ +# $NetBSD: job-flags.mk,v 1.2 2020/11/14 13:17:47 rillig Exp $ +# +# Tests for Job.flags, which are controlled by special source dependencies +# like .SILENT or .IGNORE, as well as the command line options -s or -i. + +.MAKEFLAGS: -j1 + +all: silent .WAIT ignore .WAIT ignore-cmds + +.BEGIN: + @echo $@ + +silent: .SILENT .PHONY + echo $@ + +ignore: .IGNORE .PHONY + @echo $@ + true in $@ + false in $@ + @echo 'Still there in $@' + +ignore-cmds: .PHONY + # This node is not marked .IGNORE; individual commands can be switched + # to ignore mode by prefixing them with a '-'. + -false without indentation + # This also works if the '-' is indented by a space or a tab. + # Leading whitespace is stripped off by ParseLine_ShellCommand. + -false space + -false tab + +.END: + @echo $@ diff --git a/usr.bin/make/unit-tests/job-output-long-lines.exp b/usr.bin/make/unit-tests/job-output-long-lines.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/job-output-long-lines.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/job-output-long-lines.mk b/usr.bin/make/unit-tests/job-output-long-lines.mk new file mode 100644 index 000000000..5a83bde0d --- /dev/null +++ b/usr.bin/make/unit-tests/job-output-long-lines.mk @@ -0,0 +1,32 @@ +# $NetBSD: job-output-long-lines.mk,v 1.4 2020/11/01 17:29:13 rillig Exp $ +# +# The jobs may produce long lines of output. A practical case are the echoed +# command lines from compiler invocations, with their many -D options. +# +# Each of these lines must be written atomically to the actual output. +# The markers for switching jobs must always be written at the beginning of +# the line, to make them clearly visible in large log files. +# +# As of 2020-09-27, the default job buffer size is 1024. When a job produces +# output lines that are longer than this buffer size, these output pieces are +# not terminated by a newline. Because of this missing newline, the job +# markers "--- job-a ---" and "--- job-b ---" are not always written at the +# beginning of a line, even though this is expected by anyone reading the log +# files. + +.MAKEFLAGS: -j2 + +100:= ${:U1:S,1,2222222222,g:S,2,3333333333,g} +5000:= ${100:S,3,4444444444,g:S,4,xxxxx,g} + +all: job-a job-b + +job-a: +.for i in ${:U:range=20} + @echo ${5000:S,x,a,g} +.endfor + +job-b: +.for i in ${:U:range=20} + @echo ${5000:S,x,b,g} +.endfor diff --git a/usr.bin/make/unit-tests/jobs-empty-commands.exp b/usr.bin/make/unit-tests/jobs-empty-commands.exp new file mode 100644 index 000000000..7fcf60109 --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-empty-commands.exp @@ -0,0 +1,2 @@ +action +exit status 0 diff --git a/usr.bin/make/unit-tests/jobs-empty-commands.mk b/usr.bin/make/unit-tests/jobs-empty-commands.mk new file mode 100644 index 000000000..ec8b2a8c8 --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-empty-commands.mk @@ -0,0 +1,18 @@ +# $NetBSD: jobs-empty-commands.mk,v 1.2 2021/01/30 12:46:38 rillig Exp $ +# +# In jobs mode, the shell commands for creating a target are written to a +# temporary file first, which is then run by the shell. In chains of +# dependencies, these files would end up empty. Since job.c 1.399 from +# 2021-01-29, these empty files are no longer created. +# +# https://mail-index.netbsd.org/current-users/2021/01/26/msg040215.html + +.MAKEFLAGS: -j1 +#.MAKEFLAGS: -dn # to see the created temporary files + +all: .PHONY step-1 +.for i i_plus_1 in ${:U:range=100:@i@$i $i@:[2..199]} +step-$i: .PHONY step-${i_plus_1} +.endfor +step-100: .PHONY + @echo 'action' diff --git a/usr.bin/make/unit-tests/jobs-error-indirect.exp b/usr.bin/make/unit-tests/jobs-error-indirect.exp new file mode 100644 index 000000000..5c5a3801f --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-error-indirect.exp @@ -0,0 +1,8 @@ +false +*** [indirect] Error code 1 + +make: stopped in unit-tests +1 error + +make: stopped in unit-tests +exit status 2 diff --git a/usr.bin/make/unit-tests/jobs-error-indirect.mk b/usr.bin/make/unit-tests/jobs-error-indirect.mk new file mode 100644 index 000000000..55e193d9b --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-error-indirect.mk @@ -0,0 +1,21 @@ +# $NetBSD: jobs-error-indirect.mk,v 1.1 2020/12/01 17:50:04 rillig Exp $ +# +# Ensure that in jobs mode, when a command fails, the current directory is +# printed, to aid in debugging. +# +# XXX: This test is run without the -k flag, which prints "stopped in" twice. +# Why? +# +# This particular case is not the cause for the PRs, but it is very close. +# +# https://gnats.netbsd.org/55578 +# https://gnats.netbsd.org/55832 +# +# + +.MAKEFLAGS: -j1 + +all: .PHONY indirect + +indirect: .PHONY + false diff --git a/usr.bin/make/unit-tests/jobs-error-nested-make.exp b/usr.bin/make/unit-tests/jobs-error-nested-make.exp new file mode 100644 index 000000000..88c32ab8d --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-error-nested-make.exp @@ -0,0 +1,11 @@ +make -f jobs-error-nested-make.mk nested +false +*** [nested] Error code 1 + +make: stopped in unit-tests +1 error + +make: stopped in unit-tests + +make: stopped in unit-tests +exit status 2 diff --git a/usr.bin/make/unit-tests/jobs-error-nested-make.mk b/usr.bin/make/unit-tests/jobs-error-nested-make.mk new file mode 100644 index 000000000..8cccf7df6 --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-error-nested-make.mk @@ -0,0 +1,20 @@ +# $NetBSD: jobs-error-nested-make.mk,v 1.2 2021/01/07 18:11:23 sjg Exp $ +# +# Ensure that in jobs mode, when a command fails, the current directory is +# printed, to aid in debugging, even if the target is marked as .MAKE. +# This marker is typically used for targets like 'all' that descend into +# subdirectories. +# +# XXX: In case of .MAKE targets, the "stopped if" output has been suppressed +# since job.c 1.198 from 2020-06-19. +# +# https://gnats.netbsd.org/55578 +# https://gnats.netbsd.org/55832 + +.MAKEFLAGS: -j1 + +all: .PHONY .MAKE + ${MAKE} -f ${MAKEFILE} nested + +nested: .PHONY + false diff --git a/usr.bin/make/unit-tests/jobs-error-nested.exp b/usr.bin/make/unit-tests/jobs-error-nested.exp new file mode 100644 index 000000000..f96b5d016 --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-error-nested.exp @@ -0,0 +1,15 @@ +make -f jobs-error-nested.mk nested +false +*** [nested] Error code 1 + +make: stopped in unit-tests +1 error + +make: stopped in unit-tests +*** [all] Error code 2 + +make: stopped in unit-tests +1 error + +make: stopped in unit-tests +exit status 2 diff --git a/usr.bin/make/unit-tests/jobs-error-nested.mk b/usr.bin/make/unit-tests/jobs-error-nested.mk new file mode 100644 index 000000000..879bfff89 --- /dev/null +++ b/usr.bin/make/unit-tests/jobs-error-nested.mk @@ -0,0 +1,20 @@ +# $NetBSD: jobs-error-nested.mk,v 1.1 2020/12/01 17:50:04 rillig Exp $ +# +# Ensure that in jobs mode, when a command fails, the current directory is +# printed, to aid in debugging. +# +# XXX: This test is run without the -k flag, which prints "stopped in" 4 +# times. Why? +# +# This particular case is not the cause for the PRs, but it is very close. +# +# https://gnats.netbsd.org/55578 +# https://gnats.netbsd.org/55832 + +.MAKEFLAGS: -j1 + +all: .PHONY + ${MAKE} -f ${MAKEFILE} nested + +nested: .PHONY + false diff --git a/usr.bin/make/unit-tests/lint.exp b/usr.bin/make/unit-tests/lint.exp new file mode 100644 index 000000000..d7068b5e0 --- /dev/null +++ b/usr.bin/make/unit-tests/lint.exp @@ -0,0 +1,4 @@ +make: In the :@ modifier of "VAR", the variable name "${:Ubar:S,b,v,}" must not contain a dollar. +y@:Q} +xvaluey +exit status 2 diff --git a/usr.bin/make/unit-tests/lint.mk b/usr.bin/make/unit-tests/lint.mk new file mode 100644 index 000000000..5db417639 --- /dev/null +++ b/usr.bin/make/unit-tests/lint.mk @@ -0,0 +1,22 @@ +# $NetBSD: lint.mk,v 1.4 2021/01/30 13:50:18 rillig Exp $ +# +# Demonstrates stricter checks that are only enabled in lint mode, using the +# option -dL. + +# 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 +# in shell commands, the parse errors were not checked again. + +# Ouch: as of 2020-08-03, the variable is malformed and parsing stops +# for a moment, but is continued after the wrongly-guessed end of the +# variable, which echoes "y@:Q}". + +.MAKEFLAGS: -dL + +all: mod-loop-varname + +mod-loop-varname: + @echo ${VAR:Uvalue:@${:Ubar:S,b,v,}@x${var}y@:Q} + @echo ${VAR:Uvalue:@!@x$!y@:Q} # surprisingly allowed diff --git a/usr.bin/make/unit-tests/make-exported.exp b/usr.bin/make/unit-tests/make-exported.exp new file mode 100644 index 000000000..05a916760 --- /dev/null +++ b/usr.bin/make/unit-tests/make-exported.exp @@ -0,0 +1,2 @@ +UT_VAR= +exit status 0 diff --git a/usr.bin/make/unit-tests/make-exported.mk b/usr.bin/make/unit-tests/make-exported.mk new file mode 100644 index 000000000..db7f09dc4 --- /dev/null +++ b/usr.bin/make/unit-tests/make-exported.mk @@ -0,0 +1,25 @@ +# $NetBSD: make-exported.mk,v 1.6 2020/10/05 19:27:48 rillig Exp $ +# +# As of 2020-08-09, the code in Var_Export is shared between the .export +# directive and the .MAKE.EXPORTED variable. This leads to non-obvious +# behavior for certain variable assignments. + +-env= make-exported-value-env +-literal= make-exported-value-literal +UT_VAR= ${UNEXPANDED} + +# Before 2020-10-03, the following line took the code path of .export-env, +# which was surprising behavior. Since 2020-10-03 this line tries to +# export the variable named "-env", but that is rejected because the +# variable name starts with a hyphen. +.MAKE.EXPORTED= -env + +# Before 2020-10-03, if the value of .MAKE.EXPORTED started with "-literal", +# make behaved like a mixture of .export-literal and a regular .export. +# +# Since 2020-10-03, the "variable" named "-literal" is not exported anymore, +# it is just ignored since its name starts with '-'. +.MAKE.EXPORTED= -literal UT_VAR + +all: + @env | sort | grep -E '^UT_|make-exported-value' || true diff --git a/usr.bin/make/unit-tests/meta-cmd-cmp.exp b/usr.bin/make/unit-tests/meta-cmd-cmp.exp new file mode 100644 index 000000000..bfc52123e --- /dev/null +++ b/usr.bin/make/unit-tests/meta-cmd-cmp.exp @@ -0,0 +1,37 @@ +one: +Building .meta-cmd-cmp.cmp +Building .meta-cmd-cmp.nocmp +Building .meta-cmd-cmp.cmp2 +This line not compared FLAGS= +Skipping meta for .END: .SPECIAL +two: +`.meta-cmd-cmp.cmp' is up to date. +`.meta-cmd-cmp.nocmp' is up to date. +.meta-cmd-cmp.cmp2.meta: 3: cannot compare command using .OODATE +`.meta-cmd-cmp.cmp2' is up to date. +Skipping meta for .END: .SPECIAL +change1: +.meta-cmd-cmp.cmp.meta: 2: a build command has changed +@echo FLAGS= > .meta-cmd-cmp.cmp +vs +@echo FLAGS=changed > .meta-cmd-cmp.cmp +Building .meta-cmd-cmp.cmp +`.meta-cmd-cmp.nocmp' is up to date. +.meta-cmd-cmp.cmp2.meta: 3: cannot compare command using .OODATE +`.meta-cmd-cmp.cmp2' is up to date. +Skipping meta for .END: .SPECIAL +change2: +.meta-cmd-cmp.cmp.meta: 2: a build command has changed +@echo FLAGS=changed > .meta-cmd-cmp.cmp +vs +@echo FLAGS= > .meta-cmd-cmp.cmp +Building .meta-cmd-cmp.cmp +`.meta-cmd-cmp.nocmp' is up to date. +.meta-cmd-cmp.cmp2.meta: 2: a build command has changed +@echo FLAGS2= > .meta-cmd-cmp.cmp2 +vs +@echo FLAGS2=changed > .meta-cmd-cmp.cmp2 +Building .meta-cmd-cmp.cmp2 +This line not compared FLAGS= +Skipping meta for .END: .SPECIAL +exit status 0 diff --git a/usr.bin/make/unit-tests/meta-cmd-cmp.mk b/usr.bin/make/unit-tests/meta-cmd-cmp.mk new file mode 100644 index 000000000..a1c0f7c10 --- /dev/null +++ b/usr.bin/make/unit-tests/meta-cmd-cmp.mk @@ -0,0 +1,52 @@ +# $NetBSD: meta-cmd-cmp.mk,v 1.2 2020/12/05 22:51:34 sjg Exp $ +# +# Tests META_MODE command line comparison +# + +.MAIN: all + +.MAKE.MODE= meta verbose silent=yes curdirok=yes +tf:= .${.PARSEFILE:R} + +.if ${.TARGETS:Nall} == "" +all: prep one two change1 change2 post + +CLEANFILES= ${tf}* + +prep post: .PHONY + @rm -f ${CLEANFILES} + +.endif + +FLAGS?= +FLAGS2?= + +tests= ${tf}.cmp ${tf}.nocmp ${tf}.cmp2 + +${tf}.cmp: + @echo FLAGS=${FLAGS:Uempty} > $@ + +${tf}.nocmp: .NOMETA_CMP + @echo FLAGS=${FLAGS:Uempty} > $@ + +# a line containing ${.OODATE} will not be compared +# this allows the trick below +${tf}.cmp2: + @echo FLAGS2=${FLAGS2:Uempty} > $@ + @echo This line not compared FLAGS=${FLAGS:Uempty} ${.OODATE:MNOMETA_CMP} + +# these do the same +one two: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} ${tests} + +change1: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} FLAGS=changed ${tests} + +change2: .PHONY + @echo $@: + @${.MAKE} -dM -r -C ${.CURDIR} -f ${MAKEFILE} FLAGS2=changed ${tests} + +# don't let gcov mess up the results +.MAKE.META.IGNORE_PATTERNS+= *.gcda diff --git a/usr.bin/make/unit-tests/misc.mk b/usr.bin/make/unit-tests/misc.mk deleted file mode 100644 index 2773e304b..000000000 --- a/usr.bin/make/unit-tests/misc.mk +++ /dev/null @@ -1,16 +0,0 @@ -# $Id: misc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ - -.if !exists(${.CURDIR}/) -.warning ${.CURDIR}/ doesn't exist ? -.endif - -.if !exists(${.CURDIR}/.) -.warning ${.CURDIR}/. doesn't exist ? -.endif - -.if !exists(${.CURDIR}/..) -.warning ${.CURDIR}/.. doesn't exist ? -.endif - -all: - @: all is well diff --git a/usr.bin/make/unit-tests/moderrs.exp b/usr.bin/make/unit-tests/moderrs.exp index cb51aa09d..3166275a0 100644 --- a/usr.bin/make/unit-tests/moderrs.exp +++ b/usr.bin/make/unit-tests/moderrs.exp @@ -1,16 +1,146 @@ -Expect: Unknown modifier 'Z' -make: Unknown modifier 'Z' -VAR:Z= -Expect: Unknown modifier 'Z' -make: Unknown modifier 'Z' -VAR:Z= -Expect: Unclosed variable specification for VAR -make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S +mod-unknown-direct: +want: Unknown modifier 'Z' +make: Unknown modifier "Z" +VAR:Z=before--after + +mod-unknown-indirect: +want: Unknown modifier 'Z' +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" VAR:S,V,v,=Thevariable -Expect: Unclosed variable specification for VAR -make: Unclosed variable specification after complex modifier (expecting '}') for VAR + +unclosed-indirect: +want: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" +make: Unclosed variable expression after indirect modifier, expecting '}' for variable "VAR" VAR:S,V,v,=Thevariable -Expect: Unclosed substitution for VAR (, missing) -make: Unclosed substitution for VAR (, missing) + +unfinished-indirect: +want: Unfinished modifier for VAR (',' missing) +make: Unfinished modifier for "VAR" (',' missing) VAR:S,V,v= + +unfinished-loop: +want: Unfinished modifier for UNDEF ('@' missing) +make: Unfinished modifier for "UNDEF" ('@' missing) + +want: Unfinished modifier for UNDEF ('@' missing) +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}..." +1}... 2}... 3}... +1}... 2}... 3}... + +words: +want: Unfinished modifier for UNDEF (']' missing) +make: Unfinished modifier for "UNDEF" (']' missing) + +want: Unfinished modifier for UNDEF (']' missing) +make: Unfinished modifier for "UNDEF" (']' missing) + +13= +make: Bad modifier ":[123451234512345123451234512345]" for variable "UNDEF" +12345=S,^ok,:S,^3ok,} + +exclam: +want: Unfinished modifier for VARNAME ('!' missing) +make: Unfinished modifier for "VARNAME" ('!' missing) + +want: Unfinished modifier for ! ('!' missing) +make: Unfinished modifier for "!" ('!' missing) + + +mod-subst-delimiter: +make: Missing delimiter for modifier ':S' +1: +make: Unfinished modifier for "VAR" (',' missing) +2: +make: Unfinished modifier for "VAR" (',' missing) +3: +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" +6: TheVariable +7: TheVariable + +mod-regex-delimiter: +make: Missing delimiter for :C modifier +1: +make: Unfinished modifier for "VAR" (',' missing) +2: +make: Unfinished modifier for "VAR" (',' missing) +3: +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" +6: TheVariable +7: TheVariable + +mod-regex-undefined-subexpression: +one one 2 3 5 8 one3 2one 34 +make: No match for subexpression \2 +make: No match for subexpression \2 +make: No match for subexpression \1 +make: No match for subexpression \2 +make: No match for subexpression \1 +()+() ()+() ()+() 3 5 8 (3)+() ()+(1) 34 + +mod-ts-parse: +112358132134 +15152535558513521534 +make: Bad modifier ":ts\65oct" for variable "FIB" +65oct} +make: Bad modifier ":ts\65oct" for variable "" +65oct} +make: Bad modifier ":tsxy" for variable "FIB" +xy} + +mod-t-parse: +make: Bad modifier ":t" for variable "FIB" + +make: Bad modifier ":txy" for variable "FIB" +y} +make: Bad modifier ":t" for variable "FIB" + +make: Bad modifier ":t" for variable "FIB" +M*} + +mod-ifelse-parse: +make: Unfinished modifier for "FIB" (':' missing) + +make: Unfinished modifier for "FIB" (':' missing) + +make: Unfinished modifier for "FIB" ('}' missing) + +make: Unfinished modifier for "FIB" ('}' missing) + +then + +mod-remember-parse: +1 1 2 3 5 8 13 21 34 +make: Unknown modifier "__" + + +mod-sysv-parse: +make: Unknown modifier "3" +make: Unclosed variable 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: Unknown modifier "3=x3" +make: Unclosed variable expression, expecting '}' for modifier "3=x3" of variable "FIB" with value "" + +1 1 2 x3 5 8 1x3 21 34 + exit status 0 diff --git a/usr.bin/make/unit-tests/moderrs.mk b/usr.bin/make/unit-tests/moderrs.mk index 825e6c719..88fb063ff 100644 --- a/usr.bin/make/unit-tests/moderrs.mk +++ b/usr.bin/make/unit-tests/moderrs.mk @@ -1,31 +1,172 @@ -# $Id: moderrs.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: moderrs.mk,v 1.27 2021/02/23 16:04:16 rillig Exp $ # # various modifier error tests -VAR=TheVariable -# incase we have to change it ;-) -MOD_UNKN=Z -MOD_TERM=S,V,v -MOD_S:= ${MOD_TERM}, +'= '\'' +VAR= TheVariable +# in case we have to change it ;-) +MOD_UNKN= Z +MOD_TERM= S,V,v +MOD_S:= ${MOD_TERM}, -all: modunkn modunknV varterm vartermV modtermV +FIB= 1 1 2 3 5 8 13 21 34 -modunkn: - @echo "Expect: Unknown modifier 'Z'" - @echo "VAR:Z=${VAR:Z}" +all: mod-unknown-direct mod-unknown-indirect +all: unclosed-direct unclosed-indirect +all: unfinished-indirect unfinished-loop +all: loop-close +all: words +all: exclam +all: mod-subst-delimiter +all: mod-regex-delimiter +all: mod-regex-undefined-subexpression +all: mod-ts-parse +all: mod-t-parse +all: mod-ifelse-parse +all: mod-remember-parse +all: mod-sysv-parse -modunknV: - @echo "Expect: Unknown modifier 'Z'" - @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}" +mod-unknown-direct: print-header print-footer + @echo 'want: Unknown modifier $'Z$'' + @echo 'VAR:Z=before-${VAR:Z}-after' -varterm: - @echo "Expect: Unclosed variable specification for VAR" +mod-unknown-indirect: print-header print-footer + @echo 'want: Unknown modifier $'Z$'' + @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 VAR:S,V,v,=${VAR:S,V,v, -vartermV: - @echo "Expect: Unclosed variable specification for VAR" +unclosed-indirect: print-header print-footer + @echo 'want: Unclosed variable expression after indirect modifier, expecting $'}$' for variable "VAR"' @echo VAR:${MOD_TERM},=${VAR:${MOD_S} -modtermV: - @echo "Expect: Unclosed substitution for VAR (, missing)" +unfinished-indirect: print-header print-footer + @echo 'want: Unfinished modifier for VAR ($',$' missing)' -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" + +unfinished-loop: print-header print-footer + @echo 'want: Unfinished modifier for UNDEF ($'@$' missing)' + @echo ${UNDEF:U1 2 3:@var} + @echo 'want: Unfinished modifier for UNDEF ($'@$' missing)' + @echo ${UNDEF:U1 2 3:@var@...} + @echo ${UNDEF:U1 2 3:@var@${var}@} + +# The closing brace after the ${var} is part of the replacement string. +# In ParseModifierPart, braces and parentheses don't have to be balanced. +# This is contrary to the :M, :N modifiers, where both parentheses and +# braces must be balanced. +# This is also contrary to the SysV modifier, where only the actually +# used delimiter (either braces or parentheses) must be balanced. +loop-close: print-header print-footer + @echo ${UNDEF:U1 2 3:@var@${var}}...@ + @echo ${UNDEF:U1 2 3:@var@${var}}...@} + +words: print-header print-footer + @echo 'want: Unfinished modifier for UNDEF ($']$' missing)' + @echo ${UNDEF:U1 2 3:[} + @echo 'want: Unfinished modifier for UNDEF ($']$' missing)' + @echo ${UNDEF:U1 2 3:[#} + + # out of bounds => empty + @echo 13=${UNDEF:U1 2 3:[13]} + + # Word index out of bounds. + # + # Until 2020-11-01, the behavior in this case depended upon the size + # of unsigned long. + # + # On LP64I32, strtol returns LONG_MAX, which was then truncated to + # int (undefined behavior), typically resulting in -1. This -1 was + # interpreted as "the last word". + # + # On ILP32, strtol returns LONG_MAX, which is a large number. This + # resulted in a range from LONG_MAX - 1 to 3, which was empty. + # + # Since 2020-11-01, the numeric overflow is detected and generates an + # error. In the remainder of the text, the '$,' is no longer parsed + # as part of a variable modifier, where it would have been interpreted + # as an anchor to the :S modifier, but as a normal variable named ','. + # That variable is undefined, resulting in an empty string. + @echo 12345=${UNDEF:U1 2 3:[123451234512345123451234512345]:S,^$,ok,:S,^3$,ok,} + +exclam: print-header print-footer + @echo 'want: Unfinished modifier for VARNAME ($'!$' missing)' + @echo ${VARNAME:!echo} + # When the final exclamation mark is missing, there is no + # fallback to the SysV substitution modifier. + # If there were a fallback, the output would be "exclam", + # and the above would have produced an "Unknown modifier '!'". + @echo 'want: Unfinished modifier for ! ($'!$' missing)' + @echo ${!:L:!=exclam} + +mod-subst-delimiter: print-header print-footer + @echo 1: ${VAR:S + @echo 2: ${VAR:S, + @echo 3: ${VAR:S,from + @echo 4: ${VAR:S,from, + @echo 5: ${VAR:S,from,to + @echo 6: ${VAR:S,from,to, + @echo 7: ${VAR:S,from,to,} + +mod-regex-delimiter: print-header print-footer + @echo 1: ${VAR:C + @echo 2: ${VAR:C, + @echo 3: ${VAR:C,from + @echo 4: ${VAR:C,from, + @echo 5: ${VAR:C,from,to + @echo 6: ${VAR:C,from,to, + @echo 7: ${VAR:C,from,to,} + +# In regular expressions with alternatives, not all capturing groups are +# always set; some may be missing. Warn about these. +# +# Since there is no way to turn off this warning, the combination of +# alternative matches and capturing groups is seldom used, if at all. +# +# A newly added modifier 'U' such as in :C,(a.)|(b.),\1\2,U might be added +# for treating undefined capturing groups as empty, but that would create a +# syntactical ambiguity since the :S and :C modifiers are open-ended (see +# mod-subst-chain). Luckily the modifier :U does not make sense after :C, +# therefore this case does not happen in practice. +# The sub-modifier for the :S and :C modifiers would have to be chosen +# wisely, to not create ambiguities while parsing. +mod-regex-undefined-subexpression: print-header print-footer + @echo ${FIB:C,1(.*),one\1,} # all ok + @echo ${FIB:C,1(.*)|2(.*),(\1)+(\2),:Q} # no match for subexpression + +mod-ts-parse: print-header print-footer + @echo ${FIB:ts} + @echo ${FIB:ts\65} # octal 065 == U+0035 == '5' + @echo ${FIB:ts\65oct} # bad modifier + @echo ${:U${FIB}:ts\65oct} # bad modifier, variable name is "" + @echo ${FIB:tsxy} # modifier too long + +mod-t-parse: print-header print-footer + @echo ${FIB:t + @echo ${FIB:txy} + @echo ${FIB:t} + @echo ${FIB:t:M*} + +mod-ifelse-parse: print-header print-footer + @echo ${FIB:? + @echo ${FIB:?then + @echo ${FIB:?then: + @echo ${FIB:?then:else + @echo ${FIB:?then:else} + +mod-remember-parse: print-header print-footer + @echo ${FIB:_} # ok + @echo ${FIB:__} # modifier name too long + +mod-sysv-parse: print-header print-footer + @echo ${FIB:3 + @echo ${FIB:3= + @echo ${FIB:3=x3 + @echo ${FIB:3=x3} # ok + +print-header: .USEBEFORE + @echo $@: +print-footer: .USE + @echo diff --git a/usr.bin/make/unit-tests/modmatch.mk b/usr.bin/make/unit-tests/modmatch.mk index 48a1befb5..7dcacf09d 100644 --- a/usr.bin/make/unit-tests/modmatch.mk +++ b/usr.bin/make/unit-tests/modmatch.mk @@ -1,21 +1,26 @@ +# $NetBSD: modmatch.mk,v 1.9 2020/10/24 08:50:17 rillig Exp $ +# +# Tests for the :M and :S modifiers. -X=a b c d e +X= a b c d e .for x in $X -LIB${x:tu}=/tmp/lib$x.a +LIB${x:tu}= /tmp/lib$x.a .endfor -X_LIBS= ${LIBA} ${LIBD} ${LIBE} +X_LIBS= ${LIBA} ${LIBD} ${LIBE} -LIB?=a +LIB?= a -var = head -res = no +var= head +res= no .if !empty(var:M${:Uhead\:tail:C/:.*//}) -res = OK +res= OK .endif -all: +all: show-libs + +show-libs: @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done @echo "Mscanner=${res}" diff --git a/usr.bin/make/unit-tests/modmisc.exp b/usr.bin/make/unit-tests/modmisc.exp index e406647bc..10475e65e 100644 --- a/usr.bin/make/unit-tests/modmisc.exp +++ b/usr.bin/make/unit-tests/modmisc.exp @@ -7,4 +7,14 @@ path_/usr/xbin=/opt/xbin/ paths=/bin /tmp / /no/such/dir /opt/xbin PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN The answer is 42 +S: +C: +@: +S:empty +C:empty +@: +mod-quote: new + +line +mod-break-many-words: 500 exit status 0 diff --git a/usr.bin/make/unit-tests/modmisc.mk b/usr.bin/make/unit-tests/modmisc.mk index a292b9665..9ace35c15 100644 --- a/usr.bin/make/unit-tests/modmisc.mk +++ b/usr.bin/make/unit-tests/modmisc.mk @@ -1,25 +1,29 @@ -# $Id: modmisc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: modmisc.mk,v 1.52 2020/12/20 19:29:06 rillig Exp $ # # miscellaneous modifier tests # do not put any dirs in this list which exist on some # but not all target systems - an exists() check is below. -path=:/bin:/tmp::/:.:/no/such/dir:. +path= :/bin:/tmp::/:.:/no/such/dir:. # strip cwd from path. -MOD_NODOT=S/:/ /g:N.:ts: -# and decorate, note that $'s need to be doubled. Also note that +MOD_NODOT= S/:/ /g:N.:ts: +# and decorate, note that $'s need to be doubled. Also note that # the modifier_variable can be used with other modifiers. -MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@ +MOD_NODOTX= S/:/ /g:N.:@d@'$$d'@ # another mod - pretend it is more interesting -MOD_HOMES=S,/home/,/homes/, -MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ -MOD_SEP=S,:, ,g +MOD_HOMES= S,/home/,/homes/, +MOD_OPT= @d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ +MOD_SEP= S,:, ,g -all: modvar modvarloop modsysv +all: modvar modvarloop modsysv emptyvar undefvar +all: mod-quote +all: mod-break-many-words +# See also sysv.mk. modsysv: @echo "The answer is ${libfoo.a:L:libfoo.a=42}" +# Demonstrates modifiers that are given indirectly from a variable. modvar: @echo "path='${path}'" @echo "path='${path:${MOD_NODOT}}'" @@ -28,11 +32,34 @@ modvar: @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}" .for d in ${path:${MOD_SEP}:N.} /usr/xbin -path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ -paths+= ${d:${MOD_OPT}:${MOD_HOMES}} +path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ +paths+= ${d:${MOD_OPT}:${MOD_HOMES}} .endfor modvarloop: @echo "path_/usr/xbin=${path_/usr/xbin}" @echo "paths=${paths}" @echo "PATHS=${paths:tu}" + +# When a modifier is applied to the "" variable, the result is discarded. +emptyvar: + @echo S:${:S,^$,empty,} + @echo C:${:C,^$,empty,} + @echo @:${:@var@${var}@} + +# The :U modifier turns even the "" variable into something that has a value. +# The value of the resulting expression is empty, but is still considered to +# contain a single empty word. This word can be accessed by the :S and :C +# modifiers, but not by the :@ modifier since it explicitly skips empty words. +undefvar: + @echo S:${:U:S,^$,empty,} + @echo C:${:U:C,^$,empty,} + @echo @:${:U:@var@empty@} + + +mod-quote: + @echo $@: new${.newline:Q}${.newline:Q}line + +# Cover the bmake_realloc in Str_Words. +mod-break-many-words: + @echo $@: ${UNDEF:U:range=500:[#]} diff --git a/usr.bin/make/unit-tests/modorder.exp b/usr.bin/make/unit-tests/modorder.exp deleted file mode 100644 index 411742738..000000000 --- a/usr.bin/make/unit-tests/modorder.exp +++ /dev/null @@ -1,11 +0,0 @@ -LIST = one two three four five six seven eight nine ten -LIST:O = eight five four nine one seven six ten three two -LIST:Ox = Ok -LIST:O:Ox = Ok -LISTX = Ok -LISTSX = Ok -make: Bad modifier `:OX' for LIST -BADMOD 1 = } -make: Bad modifier `:OxXX' for LIST -BADMOD 2 = XX} -exit status 0 diff --git a/usr.bin/make/unit-tests/modorder.mk b/usr.bin/make/unit-tests/modorder.mk deleted file mode 100644 index bc24d339f..000000000 --- a/usr.bin/make/unit-tests/modorder.mk +++ /dev/null @@ -1,22 +0,0 @@ -# $NetBSD: modorder.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ - -LIST= one two three four five six seven eight nine ten -LISTX= ${LIST:Ox} -LISTSX:= ${LIST:Ox} -TEST_RESULT= && echo Ok || echo Failed - -# unit-tests have to produce the same results on each run -# so we cannot actually include :Ox output. -all: - @echo "LIST = ${LIST}" - @echo "LIST:O = ${LIST:O}" - # Note that 1 in every 10! trials two independently generated - # randomized orderings will be the same. The test framework doesn't - # support checking probabilistic output, so we accept that the test - # will incorrectly fail with probability 2.8E-7. - @echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`" - @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`" - @echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`" - @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`" - @echo "BADMOD 1 = ${LIST:OX}" - @echo "BADMOD 2 = ${LIST:OxXX}" diff --git a/usr.bin/make/unit-tests/modts.exp b/usr.bin/make/unit-tests/modts.exp index cf3c91d43..18837016a 100644 --- a/usr.bin/make/unit-tests/modts.exp +++ b/usr.bin/make/unit-tests/modts.exp @@ -1,33 +1,14 @@ -LIST="one two three four five six" -LIST:ts,="one,two,three,four,five,six" -LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX" -LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX" -LIST:ts:tu="ONETWOTHREEFOURFIVESIX" -LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX" -LIST:ts:="one:two:three:four:five:six" -LIST:ts="onetwothreefourfivesix" -LIST:ts:S/two/2/="one2threefourfivesix" -LIST:S/two/2/:ts="one2threefourfivesix" -LIST:ts/:S/two/2/="one/2/three/four/five/six" -Pretend the '/' in '/n' etc. below are back-slashes. -LIST:ts/n="one -two -three -four -five -six" -LIST:ts/t="one two three four five six" -LIST:ts/012:tu="ONE -TWO -THREE -FOUR -FIVE -SIX" -make: Bad modifier `:tx' for LIST +make: Bad modifier ":tx" for variable "LIST" LIST:tx="}" -make: Bad modifier `:ts\x' for LIST -LIST:ts/x:tu="\x:tu}" +make: Bad modifier ":ts\X" for variable "LIST" +LIST:ts/x:tu="\X:tu}" FU_mod-ts="a/b/cool" FU_mod-ts:ts:T="cool" == cool? B.${AAA:ts}="Baaa" == Baaa? +:ts :S => aaxBbxaaxbbxaaxbb +:ts :S space => axa a axc +:ts :S space :M => axaxaxaxc +:ts :S => axa a axc +:ts :S :@ => axa a axc +:ts :S :@ :M => axaxaxaxc exit status 0 diff --git a/usr.bin/make/unit-tests/modts.mk b/usr.bin/make/unit-tests/modts.mk index 616bd8944..e9c10f055 100644 --- a/usr.bin/make/unit-tests/modts.mk +++ b/usr.bin/make/unit-tests/modts.mk @@ -1,43 +1,47 @@ +# $NetBSD: modts.mk,v 1.8 2020/11/03 18:42:33 rillig Exp $ -LIST= one two three -LIST+= four five six +LIST= one two three four five six -FU_mod-ts = a / b / cool +FU_mod-ts= a / b / cool -AAA= a a a -B.aaa= Baaa +AAA= a a a +B.aaa= Baaa -all: mod-ts +all: mod-ts mod-ts-space # Use print or printf iff they are builtin. -# XXX note that this causes problems, when make decides +# XXX note that this causes problems, when make decides # there is no need to use a shell, so avoid where possible. .if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= print -r -- +PRINT= print -r -- .elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != "" -PRINT= printf '%s\n' +PRINT= printf '%s\n' .else -PRINT= echo +PRINT= echo .endif mod-ts: - @echo 'LIST="${LIST}"' - @echo 'LIST:ts,="${LIST:ts,}"' - @echo 'LIST:ts/:tu="${LIST:ts/:tu}"' - @echo 'LIST:ts::tu="${LIST:ts::tu}"' - @echo 'LIST:ts:tu="${LIST:ts:tu}"' - @echo 'LIST:tu:ts/="${LIST:tu:ts/}"' - @echo 'LIST:ts:="${LIST:ts:}"' - @echo 'LIST:ts="${LIST:ts}"' - @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"' - @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"' - @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"' - @echo "Pretend the '/' in '/n' etc. below are back-slashes." - @${PRINT} 'LIST:ts/n="${LIST:ts\n}"' - @${PRINT} 'LIST:ts/t="${LIST:ts\t}"' - @${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"' @${PRINT} 'LIST:tx="${LIST:tx}"' - @${PRINT} 'LIST:ts/x:tu="${LIST:ts\x:tu}"' + @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"' @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"' @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?' @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?' + +mod-ts-space: + # After the :ts modifier, the whole string is interpreted as a single + # word since all spaces have been replaced with x. + @${PRINT} ':ts :S => '${aa bb aa bb aa bb:L:tsx:S,b,B,:Q} + + # The :ts modifier also applies to word separators that are added + # afterwards. + @${PRINT} ':ts :S space => '${a ababa c:L:tsx:S,b, ,g:Q} + @${PRINT} ':ts :S space :M => '${a ababa c:L:tsx:S,b, ,g:M*:Q} + + # Not all modifiers behave this way though. Some of them always use + # a space as word separator instead of the :ts separator. + # This seems like an oversight during implementation. + @${PRINT} ':ts :S => '${a ababa c:L:tsx:S,b, ,g:Q} + @${PRINT} ':ts :S :@ => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:Q} + + # A final :M* modifier applies the :ts separator again, though. + @${PRINT} ':ts :S :@ :M => '${a ababa c:L:tsx:S,b, ,g:@v@${v}@:M*:Q} diff --git a/usr.bin/make/unit-tests/modword.exp b/usr.bin/make/unit-tests/modword.exp index 258d7eadd..02e9974c0 100644 --- a/usr.bin/make/unit-tests/modword.exp +++ b/usr.bin/make/unit-tests/modword.exp @@ -1,4 +1,4 @@ -make: Bad modifier `:[]' for LIST +make: Bad modifier ":[]" for variable "LIST" LIST:[]="" is an error LIST:[0]="one two three four five six" LIST:[0x0]="one two three four five six" @@ -37,17 +37,17 @@ REALLYSPACE=" " REALLYSPACE:[1]="" == "" ? REALLYSPACE:[*]:[1]=" " == " " ? LIST:[1]="one" -make: Bad modifier `:[1.]' for LIST +make: Bad modifier ":[1.]" for variable "LIST" LIST:[1.]="" is an error -make: Bad modifier `:[1].' for LIST +make: Bad modifier ":[1]." for variable "LIST" LIST:[1].="}" is an error LIST:[2]="two" LIST:[6]="six" LIST:[7]="" LIST:[999]="" -make: Bad modifier `:[-]' for LIST +make: Bad modifier ":[-]" for variable "LIST" LIST:[-]="" is an error -make: Bad modifier `:[--]' for LIST +make: Bad modifier ":[--]" for variable "LIST" LIST:[--]="" is an error LIST:[-1]="six" LIST:[-2]="five" @@ -66,20 +66,23 @@ LIST:[@]:[2]="two" LIST:[*]:C/ /,/:[2]="" LIST:[*]:C/ /,/:[*]:[2]="" LIST:[*]:C/ /,/:[@]:[2]="three" -make: Bad modifier `:[1.]' for LIST +LONGLIST:[012..0x12]="10 11 12 13 14 15 16 17 18" +make: Bad modifier ":[1.]" for variable "LIST" LIST:[1.]="" is an error -make: Bad modifier `:[1..]' for LIST +make: Bad modifier ":[1..]" for variable "LIST" LIST:[1..]="" is an error +make: Bad modifier ":[1.. ]" for variable "LIST" +LIST:[1.. ]="" is an error LIST:[1..1]="one" -make: Bad modifier `:[1..1.]' for LIST +make: Bad modifier ":[1..1.]" for variable "LIST" LIST:[1..1.]="" is an error LIST:[1..2]="one two" LIST:[2..1]="two one" LIST:[3..-2]="three four five" LIST:[-4..4]="three four" -make: Bad modifier `:[0..1]' for LIST +make: Bad modifier ":[0..1]" for variable "LIST" LIST:[0..1]="" is an error -make: Bad modifier `:[-1..0]' for LIST +make: Bad modifier ":[-1..0]" for variable "LIST" LIST:[-1..0]="" is an error LIST:[-1..1]="six five four three two one" LIST:[0..0]="one two three four five six" @@ -94,11 +97,12 @@ LIST:[${ONE}]="one" LIST:[${MINUSONE}]="six" LIST:[${STAR}]="one two three four five six" LIST:[${AT}]="one two three four five six" -make: Bad modifier `:[${EMPTY' for LIST +make: Bad modifier ":[${EMPTY" for variable "LIST" LIST:[${EMPTY}]="" is an error LIST:[${LONGLIST:[21]:S/2//}]="one" LIST:[${LIST:[#]}]="six" LIST:[${LIST:[${HASH}]}]="six" +LIST:[ -1.. +3]="six five four three" LIST:S/ /,/="one two three four five six" LIST:S/ /,/W="one,two three four five six" LIST:S/ /,/gW="one,two,three,four,five,six" diff --git a/usr.bin/make/unit-tests/modword.mk b/usr.bin/make/unit-tests/modword.mk index 00a56de19..95bb1fec7 100644 --- a/usr.bin/make/unit-tests/modword.mk +++ b/usr.bin/make/unit-tests/modword.mk @@ -1,28 +1,30 @@ -# $Id: modword.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: modword.mk,v 1.6 2021/03/14 16:00:07 rillig Exp $ # # Test behaviour of new :[] modifier +# TODO: When was this modifier new? all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw -LIST= one two three -LIST+= four five six -LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 +LIST= one two three +LIST+= four five six +LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 -EMPTY= # the space should be ignored -ESCAPEDSPACE=\ # escaped space before the '#' -REALLYSPACE:=${EMPTY:C/^/ /W} -HASH= \# -AT= @ -STAR= * -ZERO= 0 -ONE= 1 -MINUSONE= -1 +EMPTY= # the space should be ignored +ESCAPEDSPACE= \ # escaped space before the '#' +REALLYSPACE:= ${EMPTY:C/^/ /W} +HASH= \# +AT= @ +STAR= * +ZERO= 0 +ONE= 1 +MINUSONE= -1 mod-squarebrackets: mod-squarebrackets-0-star-at \ mod-squarebrackets-hash \ mod-squarebrackets-n \ mod-squarebrackets-start-end \ - mod-squarebrackets-nested + mod-squarebrackets-nested \ + mod-squarebrackets-space mod-squarebrackets-0-star-at: @echo 'LIST:[]="${LIST:[]}" is an error' @@ -92,10 +94,12 @@ mod-squarebrackets-n: @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' + @echo 'LONGLIST:[012..0x12]="${LONGLIST:[012..0x12]}"' mod-squarebrackets-start-end: @echo 'LIST:[1.]="${LIST:[1.]}" is an error' @echo 'LIST:[1..]="${LIST:[1..]}" is an error' + @echo 'LIST:[1.. ]="${LIST:[1.. ]}" is an error' @echo 'LIST:[1..1]="${LIST:[1..1]}"' @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' @echo 'LIST:[1..2]="${LIST:[1..2]}"' @@ -124,6 +128,12 @@ mod-squarebrackets-nested: @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' +mod-squarebrackets-space: + # As of 2020-11-01, it is possible to have spaces before the numbers + # but not after them. This is an unintended side-effect of using + # strtol for parsing the numbers. + @echo 'LIST:[ -1.. +3]="${LIST:[ -1.. +3]}"' + mod-C-W: @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' diff --git a/usr.bin/make/unit-tests/objdir-writable.exp b/usr.bin/make/unit-tests/objdir-writable.exp new file mode 100644 index 000000000..9c507f647 --- /dev/null +++ b/usr.bin/make/unit-tests/objdir-writable.exp @@ -0,0 +1,5 @@ +make warning: TMPDIR/roobj: Permission denied. +/tmp +TMPDIR/roobj +TMPDIR/roobj +exit status 0 diff --git a/usr.bin/make/unit-tests/objdir-writable.mk b/usr.bin/make/unit-tests/objdir-writable.mk new file mode 100644 index 000000000..9fc1c69af --- /dev/null +++ b/usr.bin/make/unit-tests/objdir-writable.mk @@ -0,0 +1,31 @@ +# $NetBSD: objdir-writable.mk,v 1.4 2020/11/14 07:36:00 sjg Exp $ + +# test checking for writable objdir + +RO_OBJDIR?= ${TMPDIR:U/tmp}/roobj + +.if make(do-objdir) +# this should succeed +.OBJDIR: ${RO_OBJDIR} + +do-objdir: +.else +all: no-objdir ro-objdir explicit-objdir + +# make it now +x!= echo; mkdir -p ${RO_OBJDIR}; chmod 555 ${RO_OBJDIR} + +.END: rm-objdir +rm-objdir: + @rmdir ${RO_OBJDIR} + +no-objdir: + @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR + +ro-objdir: + @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR MAKE_OBJDIR_CHECK_WRITABLE=no + +explicit-objdir: + @MAKEOBJDIR=/tmp ${.MAKE} -r -f ${MAKEFILE:tA} -C /tmp do-objdir -V .OBJDIR +.endif + diff --git a/usr.bin/make/unit-tests/opt-backwards.exp b/usr.bin/make/unit-tests/opt-backwards.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-backwards.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-backwards.mk b/usr.bin/make/unit-tests/opt-backwards.mk new file mode 100644 index 000000000..bc0b1eff8 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-backwards.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-backwards.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -B command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-chdir.exp b/usr.bin/make/unit-tests/opt-chdir.exp new file mode 100644 index 000000000..d9759cf9e --- /dev/null +++ b/usr.bin/make/unit-tests/opt-chdir.exp @@ -0,0 +1,6 @@ +make: chdir /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././: File name too long +*** Error code 2 (ignored) +cwd: / +make: chdir /nonexistent: No such file or directory +*** Error code 2 (ignored) +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-chdir.mk b/usr.bin/make/unit-tests/opt-chdir.mk new file mode 100644 index 000000000..20241f027 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-chdir.mk @@ -0,0 +1,27 @@ +# $NetBSD: opt-chdir.mk,v 1.5 2020/11/15 05:43:56 sjg Exp $ +# +# Tests for the -C command line option, which changes the directory at the +# beginning. +# +# This option has been available since 2009-08-27. + +.MAKEFLAGS: -d0 # switch stdout to line-buffered + +all: chdir-filename-too-long +all: chdir-root +all: chdir-nonexistent + +# Try to overflow the internal buffer for .CURDIR, which is curdir. +chdir-filename-too-long: .PHONY .IGNORE + # 5000 slashes, separated by dots: /./././.../././ + @${MAKE} -C ${:U:range=5000:@@/@:ts.} + +# Changing to another directory is possible via the command line. +# In this test, it is the root directory since almost any other directory +# is not guaranteed to exist on every platform. +chdir-root: .PHONY .IGNORE + @MAKE_OBJDIR_CHECK_WRITABLE=no ${MAKE} -C / -V 'cwd: $${.CURDIR}' + +# Trying to change to a nonexistent directory exits immediately. +chdir-nonexistent: .PHONY .IGNORE + @${MAKE} -C /nonexistent diff --git a/usr.bin/make/unit-tests/opt-debug-all.exp b/usr.bin/make/unit-tests/opt-debug-all.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-all.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-all.mk b/usr.bin/make/unit-tests/opt-debug-all.mk new file mode 100644 index 000000000..400189eb6 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-all.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-all.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dA command line option, which enables all debug options +# except for -dL (lint), since that option is not related to debug logging +# but to static analysis. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-archive.exp b/usr.bin/make/unit-tests/opt-debug-archive.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-archive.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-archive.mk b/usr.bin/make/unit-tests/opt-debug-archive.mk new file mode 100644 index 000000000..d70641f97 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-archive.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-archive.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -da command line option, which adds debug logging for the +# archive handling. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-cond.exp b/usr.bin/make/unit-tests/opt-debug-cond.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-cond.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-cond.mk b/usr.bin/make/unit-tests/opt-debug-cond.mk new file mode 100644 index 000000000..2b9d1029c --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-cond.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-cond.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dc command line option, which adds debug logging for the +# evaluation of conditional expressions, such as in .if directives and +# ${cond:?then:else} expressions. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-curdir.exp b/usr.bin/make/unit-tests/opt-debug-curdir.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-curdir.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-curdir.mk b/usr.bin/make/unit-tests/opt-debug-curdir.mk new file mode 100644 index 000000000..3c37d2988 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-curdir.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-debug-curdir.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dC command line option, which does nothing, as of 2020-09-05. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-dir.exp b/usr.bin/make/unit-tests/opt-debug-dir.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-dir.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-dir.mk b/usr.bin/make/unit-tests/opt-debug-dir.mk new file mode 100644 index 000000000..5036a361c --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-dir.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-dir.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dd command line option, which adds debug logging for +# directory searching and caching. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-errors.exp b/usr.bin/make/unit-tests/opt-debug-errors.exp new file mode 100644 index 000000000..859a431f2 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-errors.exp @@ -0,0 +1,37 @@ +echo '3 spaces'; false +3 spaces + +*** Failed target: fail-spaces +*** Failed command: echo '3 spaces'; false +*** Error code 1 (continuing) +echo \ indented; false + indented + +*** Failed target: fail-escaped-space +*** Failed command: echo \ indented; false +*** Error code 1 (continuing) +echo 'line1 +line2'; false +line1 +line2 + +*** Failed target: fail-newline +*** Failed command: echo 'line1 line2'; false +*** Error code 1 (continuing) +echo 'line1 line2'; false +line1 line2 + +*** Failed target: fail-multiline +*** Failed command: echo 'line1 line2'; false +*** Error code 1 (continuing) +echo 'word1' 'word2'; false +word1 word2 + +*** Failed target: fail-multiline-intention +*** Failed command: echo 'word1' 'word2'; false +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-debug-errors.mk b/usr.bin/make/unit-tests/opt-debug-errors.mk new file mode 100644 index 000000000..1658c6b3c --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-errors.mk @@ -0,0 +1,42 @@ +# $NetBSD: opt-debug-errors.mk,v 1.2 2020/09/06 04:35:03 rillig Exp $ +# +# Tests for the -de command line option, which adds debug logging for +# failed commands and targets. + +.MAKEFLAGS: -de + +all: fail-spaces +all: fail-escaped-space +all: fail-newline +all: fail-multiline +all: fail-multiline-intention + +# XXX: The debug output folds the spaces, showing '3 spaces' instead of +# the correct '3 spaces'. +fail-spaces: + echo '3 spaces'; false + +# XXX: The debug output folds the spaces, showing 'echo \ indented' instead +# of the correct 'echo \ indented'. +fail-escaped-space: + echo \ indented; false + +# XXX: A newline is turned into an ordinary space in the debug log. +fail-newline: + echo 'line1${.newline}line2'; false + +# The line continuations in multiline commands are turned into an ordinary +# space before the command is actually run. +fail-multiline: + echo 'line1\ + line2'; false + +# It is a common style to align the continuation backslashes at the right +# of the lines, usually at column 73. All spaces before the continuation +# backslash are preserved and are usually outside a shell word and thus +# irrelevant. Having these spaces collapsed makes sense to show the command +# in its condensed form. +# +fail-multiline-intention: + echo 'word1' \ + 'word2'; false diff --git a/usr.bin/make/unit-tests/opt-debug-file.exp b/usr.bin/make/unit-tests/opt-debug-file.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-file.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-file.mk b/usr.bin/make/unit-tests/opt-debug-file.mk new file mode 100644 index 000000000..1ed477ef3 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-file.mk @@ -0,0 +1,37 @@ +# $NetBSD: opt-debug-file.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -dF command line option, which redirects the debug log +# to a file instead of writing it to stderr. + +# Enable debug logging for variable assignments and evaluation (-dv) +# and redirect the debug logging to the given file. +.MAKEFLAGS: -dvFopt-debug-file.debuglog + +# This output goes to the debug log file. +VAR= value ${:Uexpanded} + +# Hide the logging output for the remaining actions. +# As of 2020-10-03, it is not possible to disable debug logging again. +.MAKEFLAGS: -dF/dev/null + +# 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 +# upon further processing. Therefore, don't read from untrusted input. +#.MAKEFLAGS: -dc -dFstderr +.if !${DEBUG_OUTPUT:tW:M*VAR = value expanded*} +. error ${DEBUG_OUTPUT} +.endif + +# To get the unexpanded text that was actually written to the debug log +# file, the content of that log file must not be stored in a variable. +# XXX: In the :M modifier, a dollar is escaped as '$$', not '\$'. +.if !${:!cat opt-debug-file.debuglog!:tW:M*VAR = value $${:Uexpanded}*} +. error +.endif + +_!= rm opt-debug-file.debuglog + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-for.exp b/usr.bin/make/unit-tests/opt-debug-for.exp new file mode 100644 index 000000000..ea811b9bf --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-for.exp @@ -0,0 +1,22 @@ +For: new loop 2 +For: end for 2 +For: end for 1 +For: loop body: +. for inner in 1 2 +VAR.${:Ua}${inner}= value +. endfor +For: end for 1 +For: loop body: +VAR.${:Ua}${:U1}= value +For: loop body: +VAR.${:Ua}${:U2}= value +For: loop body: +. for inner in 1 2 +VAR.${:Ub}${inner}= value +. endfor +For: end for 1 +For: loop body: +VAR.${:Ub}${:U1}= value +For: loop body: +VAR.${:Ub}${:U2}= value +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-for.mk b/usr.bin/make/unit-tests/opt-debug-for.mk new file mode 100644 index 000000000..1de770de4 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-for.mk @@ -0,0 +1,26 @@ +# $NetBSD: opt-debug-for.mk,v 1.4 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -df command line option, which adds debug logging for +# parsing and evaluating .for loops. + +.MAKEFLAGS: -df + +# XXX: In the debug log, the "new loop 2" appears out of context. +# There should be a "begin loop 1" before, and all these messages should +# contain line number information. +# +# XXX: The "loop body" should print the nesting level as well. +# +# XXX: It is hard to extract any information from the debug log since +# the "begin" and "end" events are not balanced and the nesting level +# is not printed consistently. It would also be helpful to mention the +# actual substitutions, such as "For 1: outer=b". +# +.for outer in a b +. for inner in 1 2 +VAR.${outer}${inner}= value +. endfor +.endfor + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-graph1.exp b/usr.bin/make/unit-tests/opt-debug-graph1.exp new file mode 100644 index 000000000..43be07096 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-graph1.exp @@ -0,0 +1,55 @@ +#*** Input graph: +# all, unmade, type OP_DEPENDS|OP_HAS_COMMANDS, flags none +# made-target, unmade, type OP_DEPENDS, flags none +# made-target-no-sources, unmade, type OP_DEPENDS, flags none +# made-source, unmade, type OP_DEPENDS, flags none +# unmade-target, unmade, type OP_DEPENDS, flags none +# unmade-sources, unmade, type none, flags none +# unmade-silent-source, unmade, type OP_SILENT, flags none +# unmade-target-no-sources, unmade, type OP_DEPENDS, flags none + + +# +# Files that are only sources: +# unmade-sources [unmade-sources] +# unmade-silent-source [unmade-silent-source] .SILENT +#*** Global Variables: +.ALLTARGETS = all made-target made-target-no-sources made-source unmade-target unmade-sources unmade-silent-source unmade-target-no-sources +.CURDIR = +.INCLUDES = +.LIBS = +.MAKE =
+.MAKE.DEPENDFILE =
+.MAKE.GID =
+.MAKE.LEVEL =
+.MAKE.MAKEFILES =
+.MAKE.MAKEFILE_PREFERENCE =
+.MAKE.OS =
+.MAKE.PATH_FILEMON =
+.MAKE.PID =
+.MAKE.PPID =
+.MAKE.UID =
+.MAKEFLAGS = -r -k -d g1 +.MAKEOVERRIDES = +.OBJDIR = +.PATH = . +.TARGETS = +.newline = + +MACHINE =
+MACHINE_ARCH =
+MAKE =
+MFLAGS = -r -k -d g1 +#*** Command-line Variables: +.MAKE.LEVEL.ENV = MAKELEVEL + +#*** Directory Cache: +# Stats: 0 hits 2 misses 0 near misses 0 losers (0%) +# refs hits directory +# 1 0 +# 1 0 . +# 1 0 + +#*** Suffixes: +#*** Transformations: +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-graph1.mk b/usr.bin/make/unit-tests/opt-debug-graph1.mk new file mode 100644 index 000000000..618d49102 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-graph1.mk @@ -0,0 +1,23 @@ +# $NetBSD: opt-debug-graph1.mk,v 1.3 2020/09/05 06:46:12 rillig Exp $ +# +# Tests for the -dg1 command line option, which prints the input +# graph before making anything. + +.MAKEFLAGS: -dg1 + +all: made-target made-target-no-sources + +made-target: made-source + +made-source: + +made-target-no-sources: + +unmade-target: unmade-sources unmade-silent-source + +.SILENT: unmade-silent-source + +unmade-target-no-sources: + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-graph2.exp b/usr.bin/make/unit-tests/opt-debug-graph2.exp new file mode 100644 index 000000000..208990b5a --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-graph2.exp @@ -0,0 +1,93 @@ +: 'Making made-target.' +false +*** Error code 1 (continuing) +false +*** Error code 1 (continuing) +`all' not remade because of errors. +#*** Input graph: +# made-target, made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC +# +# *** MAIN TARGET *** +# No unmade children +# last modified : made +# parents: all +made-target : + (null) + + +# error-target, error when made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC +# +# No unmade children +# nonexistent (maybe): error when made +# parents: all +error-target : + (null) + + +# aborted-target, aborted, type OP_DEPENDS|OP_PHONY|OP_DEPS_FOUND|OP_MARK, flags none +# aborted-target-dependency, error when made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC +# +# No unmade children +# nonexistent (maybe): error when made +# parents: aborted-target +aborted-target-dependency: + (null) + + +# all, aborted, type OP_DEPENDS|OP_DEPS_FOUND, flags CHILDMADE|FORCE +# +# 3 unmade children +# nonexistent (maybe): aborted +all : made-target error-target aborted-target + + +# .END, unmade, type OP_SPECIAL, flags none + + +# +# Files that are only sources: +# .END [.END] +#*** Global Variables: +.ALLTARGETS = made-target error-target aborted-target aborted-target-dependency all .END +.CURDIR = +.INCLUDES = +.LIBS = +.MAKE =
+.MAKE.DEPENDFILE =
+.MAKE.GID =
+.MAKE.LEVEL =
+.MAKE.MAKEFILES =
+.MAKE.MAKEFILE_PREFERENCE =
+.MAKE.OS =
+.MAKE.PATH_FILEMON =
+.MAKE.PID =
+.MAKE.PPID =
+.MAKE.UID =
+.MAKEFLAGS = -r -k -d g2 +.MAKEOVERRIDES = +.OBJDIR = +.PATH = . +.TARGETS = all +.newline = + +MACHINE =
+MACHINE_ARCH =
+MAKE =
+MFLAGS = -r -k -d g2 +#*** Command-line Variables: +.MAKE.LEVEL.ENV = MAKELEVEL +.SHELL =
+ +#*** Directory Cache: +# Stats: 0 hits 4 misses 0 near misses 0 losers (0%) +# refs hits directory +# 1 0 +# 1 0 . +# 1 0 + +#*** Suffixes: +#*** Transformations: + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-debug-graph2.mk b/usr.bin/make/unit-tests/opt-debug-graph2.mk new file mode 100644 index 000000000..49e27569a --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-graph2.mk @@ -0,0 +1,23 @@ +# $NetBSD: opt-debug-graph2.mk,v 1.3 2021/02/02 17:47:56 rillig Exp $ +# +# Tests for the -dg2 command line option, which prints the input +# graph after making everything, or before exiting on error. +# +# Before compat.c 1.222 from 2021-02-02, there was no debug output despite +# the error. + +.MAKEFLAGS: -dg2 + +.MAIN: all + +made-target: .PHONY + : 'Making $@.' + +error-target: .PHONY + false + +aborted-target: .PHONY aborted-target-dependency +aborted-target-dependency: .PHONY + false + +all: made-target error-target aborted-target diff --git a/usr.bin/make/unit-tests/opt-debug-graph3.exp b/usr.bin/make/unit-tests/opt-debug-graph3.exp new file mode 100644 index 000000000..a6b329f6b --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-graph3.exp @@ -0,0 +1,93 @@ +: 'Making made-target.' +false +*** Error code 1 (continuing) +false +*** Error code 1 (continuing) +`all' not remade because of errors. +#*** Input graph: +# made-target, made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC +# +# *** MAIN TARGET *** +# No unmade children +# last modified : made +# parents: all +made-target : + (null) + + +# error-target, error when made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC +# +# No unmade children +# nonexistent (maybe): error when made +# parents: all +error-target : + (null) + + +# aborted-target, aborted, type OP_DEPENDS|OP_PHONY|OP_DEPS_FOUND|OP_MARK, flags none +# aborted-target-dependency, error when made, type OP_DEPENDS|OP_PHONY|OP_HAS_COMMANDS|OP_DEPS_FOUND|OP_MARK, flags REMAKE|DONE_ALLSRC +# +# No unmade children +# nonexistent (maybe): error when made +# parents: aborted-target +aborted-target-dependency: + (null) + + +# all, aborted, type OP_DEPENDS|OP_DEPS_FOUND, flags CHILDMADE|FORCE +# +# 3 unmade children +# nonexistent (maybe): aborted +all : made-target error-target aborted-target + + +# .END, unmade, type OP_SPECIAL, flags none + + +# +# Files that are only sources: +# .END [.END] +#*** Global Variables: +.ALLTARGETS = made-target error-target aborted-target aborted-target-dependency all .END +.CURDIR = +.INCLUDES = +.LIBS = +.MAKE =
+.MAKE.DEPENDFILE =
+.MAKE.GID =
+.MAKE.LEVEL =
+.MAKE.MAKEFILES =
+.MAKE.MAKEFILE_PREFERENCE =
+.MAKE.OS =
+.MAKE.PATH_FILEMON =
+.MAKE.PID =
+.MAKE.PPID =
+.MAKE.UID =
+.MAKEFLAGS = -r -k -d g3 +.MAKEOVERRIDES = +.OBJDIR = +.PATH = . +.TARGETS = all +.newline = + +MACHINE =
+MACHINE_ARCH =
+MAKE =
+MFLAGS = -r -k -d g3 +#*** Command-line Variables: +.MAKE.LEVEL.ENV = MAKELEVEL +.SHELL =
+ +#*** Directory Cache: +# Stats: 0 hits 4 misses 0 near misses 0 losers (0%) +# refs hits directory +# 1 0 +# 1 0 . +# 1 0 + +#*** Suffixes: +#*** Transformations: + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-debug-graph3.mk b/usr.bin/make/unit-tests/opt-debug-graph3.mk new file mode 100644 index 000000000..8cb99c59b --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-graph3.mk @@ -0,0 +1,23 @@ +# $NetBSD: opt-debug-graph3.mk,v 1.3 2021/02/02 17:47:56 rillig Exp $ +# +# Tests for the -dg3 command line option, which prints the input +# graph before exiting on error. +# +# Before compat.c 1.222 from 2021-02-02, there was no debug output despite +# the error. + +.MAKEFLAGS: -dg3 + +.MAIN: all + +made-target: .PHONY + : 'Making $@.' + +error-target: .PHONY + false + +aborted-target: .PHONY aborted-target-dependency +aborted-target-dependency: .PHONY + false + +all: made-target error-target aborted-target diff --git a/usr.bin/make/unit-tests/opt-debug-hash.exp b/usr.bin/make/unit-tests/opt-debug-hash.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-hash.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-hash.mk b/usr.bin/make/unit-tests/opt-debug-hash.mk new file mode 100644 index 000000000..c8cb99acd --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-hash.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-hash.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dh command line option, which adds debug logging for +# hash tables. Even more detailed logging is available by compiling +# make with -DDEBUG_HASH_LOOKUP. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-jobs.exp b/usr.bin/make/unit-tests/opt-debug-jobs.exp new file mode 100644 index 000000000..e79d8e94a --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-jobs.exp @@ -0,0 +1,27 @@ +job_pipe -1 -1, maxjobs 1, tokens 1, compat 0 +Job_TokenWithdraw(): aborting 0, running 0 +() withdrew token +echo ": expanded expression" +{ : expanded expression +} || exit $? +echo ": variable" +{ : variable +} || exit $? +echo ": 'single' and \"double\" quotes" +{ : 'single' and "double" quotes +} || exit $? +{ sleep 1 +} || exit $? +Running all + Command: +JobExec(all): pid added to jobs table +job table @ job started +job 0, status 3, flags ---, pid +: expanded expression +: variable +: 'single' and "double" quotes +Process exited/stopped status 0. +JobFinish: [all], status 0 +Job_TokenWithdraw(): aborting 0, running 0 +() withdrew token +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-jobs.mk b/usr.bin/make/unit-tests/opt-debug-jobs.mk new file mode 100644 index 000000000..f3732df7e --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-jobs.mk @@ -0,0 +1,33 @@ +# $NetBSD: opt-debug-jobs.mk,v 1.5 2020/11/12 21:54:52 rillig Exp $ +# +# Tests for the -dj command line option, which adds debug logging about +# running jobs in multiple shells. + +.MAKEFLAGS: -dj + +# Run in parallel mode since the debug logging is more interesting there +# than in compat mode. +.MAKEFLAGS: -j1 + +all: + # Only the actual command is logged. + # To see the evaluation of the variable expressions, use -dv. + : ${:Uexpanded} expression + + # Undefined variables expand to empty strings. + # Multiple spaces are preserved in the command, as they might be + # significant. + : ${UNDEF} variable + + # In the debug output, single quotes are not escaped, even though + # the whole command is enclosed in single quotes as well. + # This allows to copy and paste the whole command, without having + # to unescape anything. + : 'single' and "double" quotes + + # Avoid a race condition in the debug output. Without sleeping, + # it is not guaranteed that the two lines "exited/stopped" and + # "JobFinish" are output earlier than the stdout of the actual shell + # commands. The '@' prefix avoids that this final command gets into + # another race condition with the "exited/stopped" line. + @sleep 1 diff --git a/usr.bin/make/unit-tests/opt-debug-lint.exp b/usr.bin/make/unit-tests/opt-debug-lint.exp new file mode 100644 index 000000000..05b341b30 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-lint.exp @@ -0,0 +1,8 @@ +make: "opt-debug-lint.mk" line 19: Variable "X" is undefined +make: "opt-debug-lint.mk" line 41: Variable "UNDEF" is undefined +make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "L" +make: "opt-debug-lint.mk" line 61: Missing delimiter ':' after modifier "P" +make: "opt-debug-lint.mk" line 69: Unknown modifier "${" +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-debug-lint.mk b/usr.bin/make/unit-tests/opt-debug-lint.mk new file mode 100644 index 000000000..155e1a3de --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-lint.mk @@ -0,0 +1,95 @@ +# $NetBSD: opt-debug-lint.mk,v 1.14 2021/03/14 10:57:12 rillig Exp $ +# +# Tests for the -dL command line option, which runs additional checks +# to catch common mistakes, such as unclosed variable expressions. + +.MAKEFLAGS: -dL + +# Since 2020-09-13, undefined variables that are used on the left-hand side +# of a condition at parse time get a proper error message. Before, the +# error message was "Malformed conditional" only, which was wrong and +# misleading. The form of the condition is totally fine, it's the evaluation +# that fails. +# +# Since 2020-09-13, the "Malformed conditional" error message is not printed +# anymore. +# +# See also: +# cond-undef-lint.mk +.if $X +. error +.endif + +# The dynamic variables like .TARGET are treated specially. It does not make +# sense to expand them in the global scope since they will never be defined +# there under normal circumstances. Therefore they expand to a string that +# will later be expanded correctly, when the variable is evaluated again in +# the scope of an actual target. +# +# Even though the "@" variable is not defined at this point, this is not an +# error. In all practical cases, this is no problem. This particular test +# case is made up and unrealistic. +.if $@ != "\$(.TARGET)" +. error +.endif + +# Since 2020-09-13, Var_Parse properly reports errors for undefined variables, +# but only in lint mode. Before, it had only silently returned var_Error, +# hoping for the caller to print an error message. This resulted in the +# well-known "Malformed conditional" error message, even though the +# conditional was well-formed and the only error was an undefined variable. +.if ${UNDEF} +. error +.endif + +# Since 2020-09-14, dependency lines may contain undefined variables. +# Before, undefined variables were forbidden, but this distinction was not +# observable from the outside of the function Var_Parse. +${UNDEF}: ${UNDEF} + +# In a condition that has a defined(UNDEF) guard, all guarded conditions +# may assume that the variable is defined since they will only be evaluated +# if the variable is indeed defined. Otherwise they are only parsed, and +# for parsing it doesn't make a difference whether the variable is defined +# or not. +.if defined(UNDEF) && exists(${UNDEF}) +. error +.endif + +# Since 2020-10-03, in lint mode the variable modifier must be separated +# by colons. See varparse-mod.mk. +.if ${value:LPL} != "value" +. error +.endif + +# Between 2020-10-03 and var.c 1.752 from 2020-12-20, in lint mode the +# variable modifier had to be separated by colons. This was wrong though +# since make always fell back trying to parse the indirect modifier as a +# SysV modifier. +.if ${value:${:UL}PL} != "LPL}" # FIXME: "LPL}" is unexpected here. +. error ${value:${:UL}PL} +.endif + +# Typically, an indirect modifier is followed by a colon or the closing +# brace. This one isn't, therefore make falls back to parsing it as the SysV +# modifier ":lue=lid". +.if ${value:L:${:Ulue}=${:Ulid}} != "valid" +. error +.endif + +# In lint mode, the whole variable text is evaluated to check for unclosed +# expressions and unknown operators. During this check, the subexpression +# '${:U2}' is not expanded, instead it is copied verbatim into the regular +# expression, leading to '.*=.{1,${:U2}}$'. +# +# Before var.c 1.856 from 2021-03-14, this regular expression was then +# compiled even though that was not necessary for checking the syntax at the +# level of variable expressions. The unexpanded '$' then resulted in a wrong +# error message. +# +# This only happened in lint mode since in default mode the early check for +# unclosed expressions and unknown modifiers is skipped. +# +# See VarCheckSyntax, ApplyModifier_Regex. +# +VARMOD_REGEX= ${:UA=111 B=222 C=33:C/.*=.{1,${:U2}}$//g} diff --git a/usr.bin/make/unit-tests/opt-debug-loud.exp b/usr.bin/make/unit-tests/opt-debug-loud.exp new file mode 100644 index 000000000..9e572fba2 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-loud.exp @@ -0,0 +1,3 @@ +echo all-word +all-word +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-loud.mk b/usr.bin/make/unit-tests/opt-debug-loud.mk new file mode 100644 index 000000000..38a3c7d7a --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-loud.mk @@ -0,0 +1,22 @@ +# $NetBSD: opt-debug-loud.mk,v 1.4 2020/10/05 19:27:48 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 +# well as the .SILENT special source and target, as well as the '@' prefix +# for shell commands. + +.MAKEFLAGS: -dl -s +.SILENT: + +# The -dl command line option does not affect commands that are run during +# variable expansion, such as :!cmd! or :sh. +.if ${:!echo word!} != "word" +. error +.endif + +all: .SILENT + # Even though the command line option -s is given, .SILENT is set + # for all targets and for this target in particular, the command + # is still printed. The -dl debugging option is stronger than all + # of these. + @echo all-word diff --git a/usr.bin/make/unit-tests/opt-debug-making.exp b/usr.bin/make/unit-tests/opt-debug-making.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-making.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-making.mk b/usr.bin/make/unit-tests/opt-debug-making.mk new file mode 100644 index 000000000..bb37a9932 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-making.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-making.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dm command line option, which adds debug logging about +# making targets, including modification dates. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-meta.exp b/usr.bin/make/unit-tests/opt-debug-meta.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-meta.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-meta.mk b/usr.bin/make/unit-tests/opt-debug-meta.mk new file mode 100644 index 000000000..fb4a0bf49 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-meta.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-meta.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dM command line option, which adds debug logging about +# "meta" mode decisions about targets. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-no-rm.exp b/usr.bin/make/unit-tests/opt-debug-no-rm.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-no-rm.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-no-rm.mk b/usr.bin/make/unit-tests/opt-debug-no-rm.mk new file mode 100644 index 000000000..fd185cca7 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-no-rm.mk @@ -0,0 +1,13 @@ +# $NetBSD: opt-debug-no-rm.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dn command line option, which prevents the temporary +# command scripts from being removed from the temporary directory. + +# TODO: Implementation + +# TODO: Does this apply to non-jobs mode? +# TODO: Does this apply to jobs mode? +# TODO: Are the generated filenames predictable? + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-parse.exp b/usr.bin/make/unit-tests/opt-debug-parse.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-parse.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-parse.mk b/usr.bin/make/unit-tests/opt-debug-parse.mk new file mode 100644 index 000000000..3427b68be --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-parse.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-parse.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dp command line option, which adds debug logging about +# makefile parsing. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-suff.exp b/usr.bin/make/unit-tests/opt-debug-suff.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-suff.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-suff.mk b/usr.bin/make/unit-tests/opt-debug-suff.mk new file mode 100644 index 000000000..6ec466c90 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-suff.mk @@ -0,0 +1,11 @@ +# $NetBSD: opt-debug-suff.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -ds command line option, which adds debug logging about +# suffix-transformation rules such as .c.o. + +# TODO: Implementation + +# TODO: What about %.o: %.c? + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-targets.exp b/usr.bin/make/unit-tests/opt-debug-targets.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-targets.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-targets.mk b/usr.bin/make/unit-tests/opt-debug-targets.mk new file mode 100644 index 000000000..c99a6eaf4 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-targets.mk @@ -0,0 +1,11 @@ +# $NetBSD: opt-debug-targets.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dt command line option, which adds debug logging about +# target list maintenance. +# +# TODO: What exactly does "maintenance" mean? + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-var.exp b/usr.bin/make/unit-tests/opt-debug-var.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-var.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-var.mk b/usr.bin/make/unit-tests/opt-debug-var.mk new file mode 100644 index 000000000..4d0ef9447 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-var.mk @@ -0,0 +1,9 @@ +# $NetBSD: opt-debug-var.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dv command line option, which adds debug logging about +# variable assignment and evaluation. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-varraw.exp b/usr.bin/make/unit-tests/opt-debug-varraw.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-varraw.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-varraw.mk b/usr.bin/make/unit-tests/opt-debug-varraw.mk new file mode 100644 index 000000000..b334a2a89 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-varraw.mk @@ -0,0 +1,12 @@ +# $NetBSD: opt-debug-varraw.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dV command line option, which forces the -V option to +# print the raw values of variables. + +# TODO: Does this make -V and -v equivalent, or are there any other subtle +# differences? + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug-x-trace.exp b/usr.bin/make/unit-tests/opt-debug-x-trace.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-x-trace.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug-x-trace.mk b/usr.bin/make/unit-tests/opt-debug-x-trace.mk new file mode 100644 index 000000000..0936ba506 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug-x-trace.mk @@ -0,0 +1,10 @@ +# $NetBSD: opt-debug-x-trace.mk,v 1.1 2020/09/05 06:20:51 rillig Exp $ +# +# Tests for the -dx command line option, which runs shell commands with +# the -x option, thereby printing the actual commands as they are +# executed. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-debug.exp b/usr.bin/make/unit-tests/opt-debug.exp new file mode 100644 index 000000000..52a36c71b --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug.exp @@ -0,0 +1,4 @@ +Global:VAR = value +Global:.MAKEFLAGS = -r -k -d v -d +Global:.MAKEFLAGS = -r -k -d v -d 0 +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-debug.mk b/usr.bin/make/unit-tests/opt-debug.mk new file mode 100644 index 000000000..973085ba2 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-debug.mk @@ -0,0 +1,14 @@ +# $NetBSD: opt-debug.mk,v 1.5 2020/10/05 19:27:48 rillig Exp $ +# +# Tests for the -d command line option, which controls debug logging. + +# Enable debug logging for the variables (var.c). +.MAKEFLAGS: -dv + +VAR= value + +# Disable all debug logging again. +.MAKEFLAGS: -d0 # -d0 is available since 2020-10-03 + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-define.exp b/usr.bin/make/unit-tests/opt-define.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-define.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-define.mk b/usr.bin/make/unit-tests/opt-define.mk new file mode 100644 index 000000000..ce0516ba4 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-define.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-define.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -D command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-env.exp b/usr.bin/make/unit-tests/opt-env.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-env.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-env.mk b/usr.bin/make/unit-tests/opt-env.mk new file mode 100644 index 000000000..32e95ef41 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-env.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-env.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -e command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-file.exp b/usr.bin/make/unit-tests/opt-file.exp new file mode 100644 index 000000000..76a832949 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-file.exp @@ -0,0 +1,12 @@ +value +value +line-with-trailing-whitespace +make: "(stdin)" line 1: Zero byte read from file +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-file.mk b/usr.bin/make/unit-tests/opt-file.mk new file mode 100644 index 000000000..3ab8ef4e3 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-file.mk @@ -0,0 +1,105 @@ +# $NetBSD: opt-file.mk,v 1.11 2020/12/22 08:57:23 rillig Exp $ +# +# Tests for the -f command line option. + +# TODO: Implementation + +all: .PHONY +all: file-ending-in-backslash +all: file-ending-in-backslash-mmap +all: line-with-trailing-whitespace +all: file-containing-null-byte + +# Passing '-' as the filename reads from stdin. This is unusual but possible. +# +# In the unlikely case where a file ends in a backslash instead of a newline, +# that backslash is trimmed. See ParseGetLine. +# +# make-2014.01.01.00.00.00 invoked undefined behavior, reading text from +# outside of the file buffer. +# +# printf '%s' 'VAR=value\' \ +# | MALLOC_OPTIONS=JA make-2014.01.01.00.00.00 -r -f - -V VAR -dA 2>&1 \ +# | less +# +# The debug output shows how make happily uses freshly allocated memory (the +# ) and already freed memory ('Z'). +# +# ParseReadLine (1): 'VAR=value\' +# Global:VAR = value\value\ +# ParseReadLine (2): 'alue\' +# ParseDoDependency(alue\) +# make-2014.01.01.00.00.00: "(stdin)" line 2: Need an operator +# ParseReadLine (3): 'ZZZZZZZZZZZZZZZZ' +# ParseDoDependency(ZZZZZZZZZZZZZZZZ) +# +file-ending-in-backslash: .PHONY + @printf '%s' 'VAR=value\' \ + | ${MAKE} -r -f - -V VAR + +# Between parse.c 1.170 from 2010-12-25 and parse.c 1.511 from 2020-12-22, +# there was an out-of-bounds write in ParseGetLine, where line_end pointed at +# the end of the allocated buffer, in the special case where loadedfile_mmap +# had not added the final newline character. +file-ending-in-backslash-mmap: .PHONY + @printf '%s' 'VAR=value\' > opt-file-backslash + @${MAKE} -r -f opt-file-backslash -V VAR + @rm opt-file-backslash + +# Since parse.c 1.511 from 2020-12-22, an assertion in ParseGetLine failed +# for lines that contained trailing whitespace. Worked around in parse.c +# 1.513, properly fixed in parse.c 1.514. +line-with-trailing-whitespace: .PHONY + @printf '%s' 'VAR=$@ ' > opt-file-trailing-whitespace + @${MAKE} -r -f opt-file-trailing-whitespace -V VAR + @rm opt-file-trailing-whitespace + +# If a makefile contains null bytes, it is an error. Throughout the history +# of make, the behavior has changed several times, sometimes intentionally, +# sometimes by accident. +# +# echo 'VAR=value' | tr 'l' '\0' > zero-byte.in +# printf '%s\n' 'all:' ': VAR=${VAR:Q}' >> zero-byte.in +# +# for year in $(seq 2003 2020); do +# echo $year: +# make-$year.01.01.00.00.00 -r -f zero-byte.in +# echo "exit status $?" +# echo +# done 2>&1 \ +# | sed "s,$PWD/,.," +# +# This program generated the following output: +# +# 2003 to 2007: +# exit status 0 +# +# 2008 to 2010: +# make: "zero-byte.in" line 1: Zero byte read from file +# make: Fatal errors encountered -- cannot continue +# +# make: stopped in . +# exit status 1 +# +# 2011 to 2013: +# make: no target to make. +# +# make: stopped in . +# exit status 2 +# +# 2014 to 2020-12-06: +# make: "zero-byte.in" line 1: warning: Zero byte read from file, skipping rest of line. +# exit status 0 +# +# Since 2020-12-07: +# make: "zero-byte.in" line 1: Zero byte read from file +# make: Fatal errors encountered -- cannot continue +# make: stopped in . +# exit status 1 +file-containing-null-byte: .PHONY + @printf '%s\n' 'VAR=value' 'VAR2=VALUE2' \ + | tr 'l' '\0' \ + | ${MAKE} -r -f - -V VAR -V VAR2 + +all: + : Making ${.TARGET} diff --git a/usr.bin/make/unit-tests/opt-ignore.exp b/usr.bin/make/unit-tests/opt-ignore.exp new file mode 100644 index 000000000..7d7c437ff --- /dev/null +++ b/usr.bin/make/unit-tests/opt-ignore.exp @@ -0,0 +1,12 @@ +dependency 1 +*** Error code 1 (ignored) +dependency 2 +*** Error code 7 (ignored) +dependency 3 +other 1 +*** Error code 1 (ignored) +other 2 +main 1 +*** Error code 1 (ignored) +main 2 +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-ignore.mk b/usr.bin/make/unit-tests/opt-ignore.mk new file mode 100644 index 000000000..f776e8a27 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-ignore.mk @@ -0,0 +1,33 @@ +# $NetBSD: opt-ignore.mk,v 1.5 2020/11/09 20:50:56 rillig Exp $ +# +# Tests for the -i command line option, which ignores the exit status of the +# shell commands, and just continues with the next command, even from the same +# target. +# +# Is there a situation in which this option is useful? +# +# Why are the "Error code" lines all collected at the bottom of the output +# file, where they cannot be related to the individual shell commands that +# failed? + +.MAKEFLAGS: -d0 # switch stdout to being line-buffered +.MAKEFLAGS: -i + +all: dependency other + +dependency: + @echo dependency 1 + @false + @echo dependency 2 + @:; exit 7 + @echo dependency 3 + +other: + @echo other 1 + @false + @echo other 2 + +all: + @echo main 1 + @false + @echo main 2 diff --git a/usr.bin/make/unit-tests/opt-include-dir.exp b/usr.bin/make/unit-tests/opt-include-dir.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-include-dir.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-include-dir.mk b/usr.bin/make/unit-tests/opt-include-dir.mk new file mode 100644 index 000000000..d61a6c979 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-include-dir.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-include-dir.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -I command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-jobs-internal.exp b/usr.bin/make/unit-tests/opt-jobs-internal.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-jobs-internal.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-jobs-internal.mk b/usr.bin/make/unit-tests/opt-jobs-internal.mk new file mode 100644 index 000000000..5426807ca --- /dev/null +++ b/usr.bin/make/unit-tests/opt-jobs-internal.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-jobs-internal.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the (intentionally undocumented) -J command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-jobs-no-action.exp b/usr.bin/make/unit-tests/opt-jobs-no-action.exp new file mode 100644 index 000000000..8556fa3bf --- /dev/null +++ b/usr.bin/make/unit-tests/opt-jobs-no-action.exp @@ -0,0 +1,61 @@ +begin explain +# .echoOff +# .echoTmpl +echo "false regular" +# .runChkTmpl +{ false regular +} || exit $? +# .echoOn +# .runChkTmpl +{ : silent +} || exit $? +# .echoOn +false ignore-errors +echo run despite the -n option +run despite the -n option +end explain + +begin combined + +silent=no always=no ignerr=no +# .echoOff +# .echoTmpl +echo "echo running" +# .runChkTmpl +{ echo running +} || exit $? +# .echoOn + +silent=no always=no ignerr=yes +echo running; false + +silent=no always=yes ignerr=no +echo running +running + +silent=no always=yes ignerr=yes +echo running; false +running +*** Error code 1 (ignored) + +silent=yes always=no ignerr=no +# .runChkTmpl +{ echo running +} || exit $? +# .echoOn + +silent=yes always=no ignerr=yes +echo running; false +# .echoOn + +silent=yes always=yes ignerr=no +echo running +running + +silent=yes always=yes ignerr=yes +echo running; false +running +*** Error code 1 (ignored) + +end combined +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-jobs-no-action.mk b/usr.bin/make/unit-tests/opt-jobs-no-action.mk new file mode 100644 index 000000000..a75fc38cf --- /dev/null +++ b/usr.bin/make/unit-tests/opt-jobs-no-action.mk @@ -0,0 +1,102 @@ +# $NetBSD: opt-jobs-no-action.mk,v 1.8 2020/12/10 23:54:41 rillig Exp $ +# +# Tests for the combination of the options -j and -n, which prints the +# commands instead of actually running them. +# +# The format of the output differs from the output of only the -n option, +# without the -j. This is because all this code is implemented twice, once +# in compat.c and once in job.c. +# +# See also: +# opt-jobs.mk +# The corresponding tests without the -n option +# opt-no-action-combined.mk +# The corresponding tests without the -j option + +.MAKEFLAGS: -j1 -n + +# Change the templates for running the commands in jobs mode, to make it +# easier to see what actually happens. +# +# The shell attributes are handled by Job_ParseShell. +# The shell attributes 'quiet' and 'echo' don't need a trailing newline, +# this is handled by the [0] != '\0' checks in Job_ParseShell. +# The '\#' is handled by ParseGetLine. +# The '\n' is handled by Str_Words in Job_ParseShell. +# The '$$' is handled by Var_Subst in ParseDependency. +.SHELL: \ + name=sh \ + path=${.SHELL} \ + quiet="\# .echoOff" \ + echo="\# .echoOn" \ + filter="\# .noPrint\n" \ + check="\# .echoTmpl\n""echo \"%s\"\n" \ + ignore="\# .runIgnTmpl\n""%s\n" \ + errout="\# .runChkTmpl\n""{ %s \n} || exit $$?\n" + +all: explained combined +.ORDER: explained combined + +# Explain the most basic cases in detail. +explained: .PHONY + @+echo hide-from-output 'begin explain' + + # The following command is regular, it is printed twice: + # - first using the template shell.echoTmpl, + # - then using the template shell.runChkTmpl. + false regular + + # The following command is silent, it is printed once, using the + # template shell.runChkTmpl. + @: silent + + # The following command ignores errors, it is printed once, using + # the default template for cmdTemplate, which is "%s\n". + # XXX: Why is it not printed using shell.echoTmpl as well? + # XXX: The '-' should not influence the echoing of the command. + -false ignore-errors + + # The following command ignores the -n command line option, it is + # not handled by the Job module but by the Compat module, see the + # '!silent' in Compat_RunCommand. + +echo run despite the -n option + + @+echo hide-from-output 'end explain' + @+echo hide-from-output + + +# Test all combinations of the 3 RunFlags. +# +# TODO: Closely inspect the output whether it makes sense. +# XXX: silent=no always=no ignerr={no,yes} should be almost the same. +# +SILENT.no= # none +SILENT.yes= @ +ALWAYS.no= # none +ALWAYS.yes= + +IGNERR.no= echo running +IGNERR.yes= -echo running; false +# +combined: combined-begin + +combined-begin: .PHONY + @+echo hide-from-output 'begin combined' + @+echo hide-from-output + +.for silent in no yes +. for always in no yes +. for ignerr in no yes +. for target in combined-silent-${silent}-always-${always}-ignerr-${ignerr} +combined: .WAIT ${target} .WAIT +${target}: .PHONY + @+echo hide-from-output silent=${silent} always=${always} ignerr=${ignerr} + ${SILENT.${silent}}${ALWAYS.${always}}${IGNERR.${ignerr}} + @+echo hide-from-output +. endfor +. endfor +. endfor +.endfor + +combined: combined-end +combined-end: .PHONY + @+echo hide-from-output 'end combined' diff --git a/usr.bin/make/unit-tests/opt-jobs.exp b/usr.bin/make/unit-tests/opt-jobs.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-jobs.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-jobs.mk b/usr.bin/make/unit-tests/opt-jobs.mk new file mode 100644 index 000000000..7d54d08a8 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-jobs.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-jobs.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -j command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-keep-going-multiple.exp b/usr.bin/make/unit-tests/opt-keep-going-multiple.exp new file mode 100644 index 000000000..6d1bec189 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-keep-going-multiple.exp @@ -0,0 +1,9 @@ +false fail1 +*** Error code 1 (continuing) +false fail2 +*** Error code 1 (continuing) +true succeed + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-keep-going-multiple.mk b/usr.bin/make/unit-tests/opt-keep-going-multiple.mk new file mode 100644 index 000000000..cc08ccfb8 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-keep-going-multiple.mk @@ -0,0 +1,21 @@ +# $NetBSD: opt-keep-going-multiple.mk,v 1.1 2020/12/07 01:32:04 rillig Exp $ +# +# Tests for the -k command line option, which stops building a target as soon +# as an error is detected, but continues building the other, independent +# targets, as far as possible. +# +# Until 2020-12-07, the exit status of make depended only on the last of the +# main targets. Even if the first few targets could not be made, make +# nevertheless exited with status 0. + +.MAKEFLAGS: -k +.MAKEFLAGS: fail1 fail2 succeed + +fail1 fail2: .PHONY + false ${.TARGET} + +succeed: .PHONY + true ${.TARGET} + +.END: + : The end. diff --git a/usr.bin/make/unit-tests/opt-keep-going.exp b/usr.bin/make/unit-tests/opt-keep-going.exp new file mode 100644 index 000000000..2dbeb9927 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-keep-going.exp @@ -0,0 +1,9 @@ +dependency 1 +*** Error code 1 (continuing) +other 1 +*** Error code 1 (continuing) +`all' not remade because of errors. + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-keep-going.mk b/usr.bin/make/unit-tests/opt-keep-going.mk new file mode 100644 index 000000000..8a5e079ef --- /dev/null +++ b/usr.bin/make/unit-tests/opt-keep-going.mk @@ -0,0 +1,31 @@ +# $NetBSD: opt-keep-going.mk,v 1.6 2020/12/07 00:53:30 rillig Exp $ +# +# Tests for the -k command line option, which stops building a target as soon +# as an error is detected, but continues building the other, independent +# targets, as far as possible. +# +# Until 2020-12-07, if a dependency of the main target failed, the exit +# status was nevertheless 0, which was wrong since the main targets could +# not be made. This was only wrong in -k mode combined with compat mode. + +.MAKEFLAGS: -d0 # switch stdout to being line-buffered +.MAKEFLAGS: -k + +all: dependency other + +dependency: + @echo dependency 1 + @false + @echo dependency 2 + @:; exit 7 + @echo dependency 3 + +other: + @echo other 1 + @false + @echo other 2 + +all: + @echo main 1 + @false + @echo main 2 diff --git a/usr.bin/make/unit-tests/opt-m-include-dir.exp b/usr.bin/make/unit-tests/opt-m-include-dir.exp new file mode 100644 index 000000000..bce0e4be0 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-m-include-dir.exp @@ -0,0 +1,2 @@ +ok +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-m-include-dir.mk b/usr.bin/make/unit-tests/opt-m-include-dir.mk new file mode 100644 index 000000000..6e0801390 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-m-include-dir.mk @@ -0,0 +1,61 @@ +# $NetBSD: opt-m-include-dir.mk,v 1.4 2020/09/01 20:14:34 rillig Exp $ +# +# Tests for the -m command line option, which adds a directory to the +# search path for the .include <...> directive. +# +# The .../canary.mk special argument starts searching in the current +# directory and walks towards the file system root, until it finds a +# directory that contains a file called canary.mk. +# +# To set up this scenario, the file step2.mk is created deep in a hierarchy +# of subdirectories. Another file called opt-m-step3.mk is created a few +# steps up in the directory hierarchy, serving as the canary file. +# +# Next to the canary file, there is opt-m-step3.mk. This file is found +# by mentioning its simple name in an .include directive. It defines the +# target "step2" that is needed by "step2.mk". + +.if ${.PARSEFILE:T} == "opt-m-include-dir.mk" + +# Set up the other files needed for this test. + +TEST_DIR:= ${.PARSEFILE:R}.tmp/sub/sub/sub/workdir +CANARY_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-canary.mk +ACTUAL_FILE:= ${.PARSEFILE:R}.tmp/sub/opt-m-step3.mk + +_!= mkdir -p ${TEST_DIR} +_!= > ${CANARY_FILE} +_!= cp ${MAKEFILE} ${TEST_DIR}/step2.mk +_!= cp ${MAKEFILE} ${ACTUAL_FILE} + +step1: + @${.MAKE} -C ${TEST_DIR} -f step2.mk step2 + +.END: + @rm -rf ${MAKEFILE:R}.tmp + +.elif ${.PARSEFILE:T} == "step2.mk" + +# This is the file deep in the directory hierarchy. It sets up the +# search path for the .include <...> directive and then includes a +# single file from that search path. + +# This option adds .tmp/sub to the search path for .include <...>. +.MAKEFLAGS: -m .../opt-m-canary.mk + +# This option does not add any directory to the search path since the +# canary file does not exist. +.MAKEFLAGS: -m .../does-not-exist + +.include + +.elif ${.PARSEFILE:T} == "opt-m-step3.mk" + +# This file is included by step2.mk. + +step2: + @echo ok + +.else +. error +.endif diff --git a/usr.bin/make/unit-tests/opt-no-action-at-all.exp b/usr.bin/make/unit-tests/opt-no-action-at-all.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action-at-all.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-no-action-at-all.mk b/usr.bin/make/unit-tests/opt-no-action-at-all.mk new file mode 100644 index 000000000..6ab385946 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action-at-all.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-no-action-at-all.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -N command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-no-action-runflags.exp b/usr.bin/make/unit-tests/opt-no-action-runflags.exp new file mode 100644 index 000000000..33b311228 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action-runflags.exp @@ -0,0 +1,34 @@ +begin combined + +silent=no always=no ignerr=no +echo running + +silent=no always=no ignerr=yes +echo running; false + +silent=no always=yes ignerr=no +echo running +running + +silent=no always=yes ignerr=yes +echo running; false +running +*** Error code 1 (ignored) + +silent=yes always=no ignerr=no +echo running + +silent=yes always=no ignerr=yes +echo running; false + +silent=yes always=yes ignerr=no +echo running +running + +silent=yes always=yes ignerr=yes +echo running; false +running +*** Error code 1 (ignored) + +end combined +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-no-action-runflags.mk b/usr.bin/make/unit-tests/opt-no-action-runflags.mk new file mode 100644 index 000000000..61ae7b2bf --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action-runflags.mk @@ -0,0 +1,32 @@ +# $NetBSD: opt-no-action-runflags.mk,v 1.1 2020/12/09 07:57:52 rillig Exp $ +# +# Tests for the -n command line option, which runs almost no commands, +# combined with the RunFlags '@', '-', '+' for individual commands. +# +# See also: +# opt-jobs-no-action.mk +# The corresponding test with the -j option + +.MAKEFLAGS: -n + +all: .PHONY combined + +SILENT.no= # none +SILENT.yes= @ +ALWAYS.no= # none +ALWAYS.yes= + +IGNERR.no= echo running +IGNERR.yes= -echo running; false +# +combined: .PHONY + @+echo hide-from-output 'begin $@'; echo +.for silent in no yes +. for always in no yes +. for ignerr in no yes + @+echo hide-from-output silent=${silent} always=${always} ignerr=${ignerr} + ${SILENT.${silent}}${ALWAYS.${always}}${IGNERR.${ignerr}} + @+echo hide-from-output +. endfor +. endfor +.endfor + @+echo hide-from-output 'end $@' diff --git a/usr.bin/make/unit-tests/opt-no-action-touch.exp b/usr.bin/make/unit-tests/opt-no-action-touch.exp new file mode 100644 index 000000000..247bd7fb4 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action-touch.exp @@ -0,0 +1,11 @@ +echo ": Making opt-touch-phony." +{ : Making opt-touch-phony. +} || exit $? +echo 'Making opt-touch-make.' +Making opt-touch-make. +echo ": Making opt-touch-regular." +{ : Making opt-touch-regular. +} || exit $? +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-no-action-touch.mk b/usr.bin/make/unit-tests/opt-no-action-touch.mk new file mode 100644 index 000000000..8bbdd9702 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action-touch.mk @@ -0,0 +1,48 @@ +# $NetBSD: opt-no-action-touch.mk,v 1.1 2021/01/30 12:46:38 rillig Exp $ +# +# Tests for combining the command line options -n (no action) and -t (touch). +# This combination is unusual and probably doesn't ever happen in practice, +# but still make needs to behave as expected. The option -n is stronger than +# -t, so instead of being touched, the commands of the targets are printed. +# +# See also: +# opt-touch-jobs.mk contains the same test without the option -n. + +.MAKEFLAGS: -j1 -n -t +.MAKEFLAGS: opt-touch-phony +.MAKEFLAGS: opt-touch-join +.MAKEFLAGS: opt-touch-use +.MAKEFLAGS: opt-touch-make +.MAKEFLAGS: opt-touch-regular + +# .PHONY targets are not touched since they do not represent actual files. +# See Job_Touch. +opt-touch-phony: .PHONY + : Making $@. + +# .JOIN targets are not touched since they do not represent actual files. +# See Job_Touch. +opt-touch-join: .JOIN + : Making $@. + +# .USE targets are not touched since they do not represent actual files. +# See Job_Touch. +opt-touch-use: .USE + : Making use of $@. + +# The attribute .MAKE is stronger than the command line option -n. Therefore +# this target is run as usual. It is not prefixed by '@', therefore it is +# printed before being run. +opt-touch-make: .MAKE + echo 'Making $@.' + +# Since the option -n is stronger than the option -t, this target is not +# touched either. Without the -n, it would be touched. +opt-touch-regular: + : Making $@. + +# Since none of the above targets are actually touched, the following command +# does not output anything. +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } diff --git a/usr.bin/make/unit-tests/opt-no-action.exp b/usr.bin/make/unit-tests/opt-no-action.exp new file mode 100644 index 000000000..381d68e2f --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action.exp @@ -0,0 +1,13 @@ +command during parsing +echo '.BEGIN: hidden command' +echo '.BEGIN: run always' +.BEGIN: run always +echo 'main: hidden command' +echo 'main: run always' +main: run always +run-always: hidden command +run-always: run always +echo '.END: hidden command' +echo '.END: run always' +.END: run always +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-no-action.mk b/usr.bin/make/unit-tests/opt-no-action.mk new file mode 100644 index 000000000..91c05b2dc --- /dev/null +++ b/usr.bin/make/unit-tests/opt-no-action.mk @@ -0,0 +1,35 @@ +# $NetBSD: opt-no-action.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ +# +# Tests for the -n command line option, which runs almost no commands. +# It just outputs them, to be inspected by human readers. +# Only commands that are in a .MAKE target or prefixed by '+' are run. + +.MAKEFLAGS: -n + +# This command cannot be prevented from being run since it is used at parse +# time, and any later variable assignments may depend on its result. +!= echo 'command during parsing' 1>&2; echo + +all: main +all: run-always + +# Both of these commands are printed, but only the '+' command is run. +.BEGIN: + @echo '$@: hidden command' + @+echo '$@: run always' + +# Both of these commands are printed, but only the '+' command is run. +main: + @echo '$@: hidden command' + @+echo '$@: run always' + +# None of these commands is printed, but both are run, because this target +# depends on the special source ".MAKE". +run-always: .MAKE + @echo '$@: hidden command' + @+echo '$@: run always' + +# Both of these commands are printed, but only the '+' command is run. +.END: + @echo '$@: hidden command' + @+echo '$@: run always' diff --git a/usr.bin/make/unit-tests/opt-query.exp b/usr.bin/make/unit-tests/opt-query.exp new file mode 100644 index 000000000..38025dcf4 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-query.exp @@ -0,0 +1,2 @@ +command during parsing +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-query.mk b/usr.bin/make/unit-tests/opt-query.mk new file mode 100644 index 000000000..0a7d5219a --- /dev/null +++ b/usr.bin/make/unit-tests/opt-query.mk @@ -0,0 +1,26 @@ +# $NetBSD: opt-query.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ +# +# Tests for the -q command line option. +# +# The -q option only looks at the dependencies between the targets. +# None of the commands in the targets are run, not even those that are +# prefixed with '+'. + +.MAKEFLAGS: -q + +# This command cannot be prevented from being run since it is used at parse +# time, and any later variable assignments may depend on its result. +!= echo 'command during parsing' 1>&2; echo + +# None of these commands are run. +.BEGIN: + @echo '$@: hidden command' + @+echo '$@: run always' + +# None of these commands are run. +all: + @echo '$@: hidden command' + @+echo '$@: run always' + +# The exit status 1 is because the "all" target has to be made, that is, +# it is not up-to-date. diff --git a/usr.bin/make/unit-tests/opt-raw.exp b/usr.bin/make/unit-tests/opt-raw.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-raw.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-raw.mk b/usr.bin/make/unit-tests/opt-raw.mk new file mode 100644 index 000000000..d3591bb99 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-raw.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-raw.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -r command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-silent.exp b/usr.bin/make/unit-tests/opt-silent.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-silent.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-silent.mk b/usr.bin/make/unit-tests/opt-silent.mk new file mode 100644 index 000000000..7822d46ac --- /dev/null +++ b/usr.bin/make/unit-tests/opt-silent.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-silent.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -s command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-touch-jobs.exp b/usr.bin/make/unit-tests/opt-touch-jobs.exp new file mode 100644 index 000000000..0cfde5135 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-touch-jobs.exp @@ -0,0 +1,4 @@ +: Making opt-touch-make. +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-touch-jobs.mk b/usr.bin/make/unit-tests/opt-touch-jobs.mk new file mode 100644 index 000000000..6005ab49d --- /dev/null +++ b/usr.bin/make/unit-tests/opt-touch-jobs.mk @@ -0,0 +1,36 @@ +# $NetBSD: opt-touch-jobs.mk,v 1.2 2021/01/30 12:14:08 rillig Exp $ +# +# Tests for the -t command line option in jobs mode. + +.MAKEFLAGS: -j1 +.MAKEFLAGS: -t +.MAKEFLAGS: opt-touch-phony +.MAKEFLAGS: opt-touch-join +.MAKEFLAGS: opt-touch-use +.MAKEFLAGS: opt-touch-make + +# .PHONY targets are not touched since they do not represent actual files. +# See Job_Touch. +opt-touch-phony: .PHONY + : Making $@. + +# .JOIN targets are not touched since they do not represent actual files. +# See Job_Touch. +opt-touch-join: .JOIN + : Making $@. + +# .USE targets are not touched since they do not represent actual files. +# See Job_Touch. +opt-touch-use: .USE + : Making use of $@. + +# Even though it is listed last, in the output it appears first. +# This is because it is the only node that actually needs to be run. +# The "is up to date" of the other nodes happens after all jobs have +# finished, by Make_Run > MakePrintStatusList > MakePrintStatus. +opt-touch-make: .MAKE + : Making $@. + +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } diff --git a/usr.bin/make/unit-tests/opt-touch.exp b/usr.bin/make/unit-tests/opt-touch.exp new file mode 100644 index 000000000..c9e6c7890 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-touch.exp @@ -0,0 +1,4 @@ +`opt-touch-join' is up to date. +`opt-touch-use' is up to date. +: Making opt-touch-make. +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-touch.mk b/usr.bin/make/unit-tests/opt-touch.mk new file mode 100644 index 000000000..defb7c59e --- /dev/null +++ b/usr.bin/make/unit-tests/opt-touch.mk @@ -0,0 +1,21 @@ +# $NetBSD: opt-touch.mk,v 1.4 2020/11/14 14:13:09 rillig Exp $ +# +# Tests for the -t command line option. + +.MAKEFLAGS: -t opt-touch-phony opt-touch-join opt-touch-use opt-touch-make + +opt-touch-phony: .PHONY + : Making $@. + +opt-touch-join: .JOIN + : Making $@. + +opt-touch-use: .USE + : Making use of $@. + +opt-touch-make: .MAKE + : Making $@. + +.END: + @files=$$(ls opt-touch-* 2>/dev/null | grep -v -e '\.' -e '\*'); \ + [ -z "$$files" ] || { echo "created files: $$files" 1>&2; exit 1; } diff --git a/usr.bin/make/unit-tests/opt-tracefile.exp b/usr.bin/make/unit-tests/opt-tracefile.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-tracefile.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-tracefile.mk b/usr.bin/make/unit-tests/opt-tracefile.mk new file mode 100644 index 000000000..b62392ca9 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-tracefile.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-tracefile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -T command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-var-expanded.exp b/usr.bin/make/unit-tests/opt-var-expanded.exp new file mode 100644 index 000000000..4fcc0b99a --- /dev/null +++ b/usr.bin/make/unit-tests/opt-var-expanded.exp @@ -0,0 +1,3 @@ +other value $$ +value +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-var-expanded.mk b/usr.bin/make/unit-tests/opt-var-expanded.mk new file mode 100644 index 000000000..f7203374b --- /dev/null +++ b/usr.bin/make/unit-tests/opt-var-expanded.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-var-expanded.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ +# +# Tests for the -v command line option. + +.MAKEFLAGS: -v VAR -v VALUE + +VAR= other ${VALUE} $$$$ +VALUE= value diff --git a/usr.bin/make/unit-tests/opt-var-literal.exp b/usr.bin/make/unit-tests/opt-var-literal.exp new file mode 100644 index 000000000..e7f653b76 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-var-literal.exp @@ -0,0 +1,3 @@ +other ${VALUE} $$$$ +value +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-var-literal.mk b/usr.bin/make/unit-tests/opt-var-literal.mk new file mode 100644 index 000000000..d236e389d --- /dev/null +++ b/usr.bin/make/unit-tests/opt-var-literal.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-var-literal.mk,v 1.4 2020/11/09 20:50:56 rillig Exp $ +# +# Tests for the -V command line option. + +.MAKEFLAGS: -V VAR -V VALUE + +VAR= other ${VALUE} $$$$ +VALUE= value diff --git a/usr.bin/make/unit-tests/opt-warnings-as-errors.exp b/usr.bin/make/unit-tests/opt-warnings-as-errors.exp new file mode 100644 index 000000000..1db56b753 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-warnings-as-errors.exp @@ -0,0 +1,7 @@ +make: "opt-warnings-as-errors.mk" line 12: warning: message 1 +make: parsing warnings being treated as errors +make: "opt-warnings-as-errors.mk" line 13: warning: message 2 +parsing continues +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/opt-warnings-as-errors.mk b/usr.bin/make/unit-tests/opt-warnings-as-errors.mk new file mode 100644 index 000000000..c29343f96 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-warnings-as-errors.mk @@ -0,0 +1,18 @@ +# $NetBSD: opt-warnings-as-errors.mk,v 1.5 2021/01/27 00:02:38 rillig Exp $ +# +# Tests for the -W command line option, which turns warnings into errors. +# +# Even in -W mode, a .warning is not completely equivalent to an .error. +# First, the word "warning" is still printed, and second, parsing continues +# after a failed warning, whereas it would stop immediately at the first +# .error. + +.MAKEFLAGS: -W + +.warning message 1 +.warning message 2 + +_!= echo 'parsing continues' 1>&2 + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-where-am-i.exp b/usr.bin/make/unit-tests/opt-where-am-i.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-where-am-i.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-where-am-i.mk b/usr.bin/make/unit-tests/opt-where-am-i.mk new file mode 100644 index 000000000..9158a5981 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-where-am-i.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-where-am-i.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -w command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt-x-reduce-exported.exp b/usr.bin/make/unit-tests/opt-x-reduce-exported.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/opt-x-reduce-exported.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/opt-x-reduce-exported.mk b/usr.bin/make/unit-tests/opt-x-reduce-exported.mk new file mode 100644 index 000000000..7ee8e7c7e --- /dev/null +++ b/usr.bin/make/unit-tests/opt-x-reduce-exported.mk @@ -0,0 +1,8 @@ +# $NetBSD: opt-x-reduce-exported.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $ +# +# Tests for the -x command line option. + +# TODO: Implementation + +all: + @:; diff --git a/usr.bin/make/unit-tests/opt.exp b/usr.bin/make/unit-tests/opt.exp new file mode 100644 index 000000000..3c96cf250 --- /dev/null +++ b/usr.bin/make/unit-tests/opt.exp @@ -0,0 +1,26 @@ +make -r -f /dev/null -V MAKEFLAGS + -r -k -d 0 + +make -: +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +*** Error code 2 (ignored) + +make -r -f /dev/null -- -VAR=value -f /dev/null +make: don't know how to make -f (continuing) +`/dev/null' is up to date. + +Stop. +make: stopped in unit-tests +*** Error code 1 (ignored) + +make -? +usage: make [-BeikNnqrSstWwX] + [-C directory] [-D variable] [-d flags] [-f makefile] + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file] + [-V variable] [-v variable] [variable=value] [target ...] +*** Error code 2 (ignored) + +exit status 0 diff --git a/usr.bin/make/unit-tests/opt.mk b/usr.bin/make/unit-tests/opt.mk new file mode 100644 index 000000000..0931a66d3 --- /dev/null +++ b/usr.bin/make/unit-tests/opt.mk @@ -0,0 +1,28 @@ +# $NetBSD: opt.mk,v 1.6 2020/11/18 01:06:59 sjg Exp $ +# +# Tests for the command line options. + +.MAKEFLAGS: -d0 # make stdout line-buffered + +all: .IGNORE + # The options from the top-level make are passed to the sub-makes via + # the environment variable MAKEFLAGS. This is where the " -r -k -d 0" + # comes from. See MainParseArg. + ${MAKE} -r -f /dev/null -V MAKEFLAGS + @echo + + # Just to see how the custom argument parsing code reacts to a syntax + # error. The colon is used in the options string, marking an option + # that takes arguments. It is not an option by itself, though. + ${MAKE} -: + @echo + + # See whether a '--' stops handling of command line options, like in + # standard getopt programs. Yes, it does, and it treats the + # second '-f' as a target to be created. + ${MAKE} -r -f /dev/null -- -VAR=value -f /dev/null + @echo + + # This is the normal way to print the usage of a command. + ${MAKE} -? + @echo diff --git a/usr.bin/make/unit-tests/order.mk b/usr.bin/make/unit-tests/order.mk index f90b627d9..9b94016c1 100644 --- a/usr.bin/make/unit-tests/order.mk +++ b/usr.bin/make/unit-tests/order.mk @@ -1,10 +1,12 @@ -# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: order.mk,v 1.2 2020/11/09 20:50:56 rillig Exp $ # Test that .ORDER is handled correctly. # The explicit dependency the.o: the.h will make us examine the.h # the .ORDER will prevent us building it immediately, # we should then examine the.c rather than stop. +.MAKEFLAGS: -j1 + all: the.o .ORDER: the.c the.h diff --git a/usr.bin/make/unit-tests/parse-var.exp b/usr.bin/make/unit-tests/parse-var.exp new file mode 100644 index 000000000..39a938395 --- /dev/null +++ b/usr.bin/make/unit-tests/parse-var.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/parse-var.mk b/usr.bin/make/unit-tests/parse-var.mk new file mode 100644 index 000000000..bd6c59f0e --- /dev/null +++ b/usr.bin/make/unit-tests/parse-var.mk @@ -0,0 +1,13 @@ +# $NetBSD: parse-var.mk,v 1.1 2020/10/04 06:53:15 rillig Exp $ + +.MAKEFLAGS: -dL + +# In variable assignments, there may be spaces on the left-hand side of the +# assignment, but only if they occur inside variable expressions. +VAR.${:U param }= value +.if ${VAR.${:U param }} != "value" +. error +.endif + +all: + @:; diff --git a/usr.bin/make/unit-tests/phony-end.exp b/usr.bin/make/unit-tests/phony-end.exp index c3c517ccc..f1bff4ef6 100644 --- a/usr.bin/make/unit-tests/phony-end.exp +++ b/usr.bin/make/unit-tests/phony-end.exp @@ -1,5 +1,5 @@ .TARGET="phony" .PREFIX="phony" .IMPSRC="" -.TARGET="all" .PREFIX="all" .IMPSRC="phony" +.TARGET="all" .PREFIX="all" .IMPSRC="" .TARGET="ok" .PREFIX="ok" .IMPSRC="" .TARGET="also.ok" .PREFIX="also.ok" .IMPSRC="" .TARGET="bug" .PREFIX="bug" .IMPSRC="" diff --git a/usr.bin/make/unit-tests/phony-end.mk b/usr.bin/make/unit-tests/phony-end.mk index 92cc0e6bb..2992f640e 100644 --- a/usr.bin/make/unit-tests/phony-end.mk +++ b/usr.bin/make/unit-tests/phony-end.mk @@ -1,4 +1,4 @@ -# $Id: phony-end.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: phony-end.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ all ok also.ok bug phony: @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}' diff --git a/usr.bin/make/unit-tests/posix.exp b/usr.bin/make/unit-tests/posix.exp index 7e74cabad..01961f363 100644 --- a/usr.bin/make/unit-tests/posix.exp +++ b/usr.bin/make/unit-tests/posix.exp @@ -20,4 +20,7 @@ a command prefixed by '+' executes even with -n Now we expect an error... *** Error code 1 (continuing) `all' not remade because of errors. -exit status 0 + +Stop. +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/posix.mk b/usr.bin/make/unit-tests/posix.mk index a73e2e574..fc4cbead3 100644 --- a/usr.bin/make/unit-tests/posix.mk +++ b/usr.bin/make/unit-tests/posix.mk @@ -1,4 +1,4 @@ -# $Id: posix.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# $NetBSD: posix.mk,v 1.2 2020/10/24 08:34:59 rillig Exp $ all: x plus subs err diff --git a/usr.bin/make/unit-tests/posix1.mk b/usr.bin/make/unit-tests/posix1.mk index 1bf6a5606..59a91d91e 100644 --- a/usr.bin/make/unit-tests/posix1.mk +++ b/usr.bin/make/unit-tests/posix1.mk @@ -1,4 +1,4 @@ -# $NetBSD: posix1.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $ +# $NetBSD: posix1.mk,v 1.6 2020/10/24 08:50:17 rillig Exp $ # Keep the default suffixes from interfering, just in case. .SUFFIXES: @@ -9,6 +9,8 @@ all: line-continuations suffix-substitution localvars .BEGIN: clean clean: @rm -f lib.a dir/* dummy obj* +.END: + @rm -f lib.a dir/* dummy obj* # # Line continuations @@ -53,14 +55,14 @@ suffix-substitution: # In the past substitutions did not work with the D/F forms and those # forms were not available for $?. (PR 49085) -ARFLAGS = -rcv +ARFLAGS= -rcv localvars: lib.a # $@ = target or archive name $< = implied source -# $* = target without suffix $? = sources newer than target +# $* = target without suffix $? = sources newer than target # $% = archive member name -LOCALS = \ +LOCALS= \ "Local variables\n\ \$${@}=\"${@}\" \$${<}=\"${<}\"\n\ \$${*}=\"${*}\" \$${?}=\"${?}\"\n\ @@ -68,7 +70,7 @@ LOCALS = \ # $XD = directory part of X $XF = file part of X # X is one of the local variables. -LOCAL_ALTERNATIVES = \ +LOCAL_ALTERNATIVES= \ "Directory and filename parts of local variables\n\ \$${@D}=\"${@D}\" \$${@F}=\"${@F}\"\n\ \$${